odoo-workflow について
odoo-workflowは、変更前にOdooのコードと参照関係を追跡する開発者向けスキルです。バージョン対応のヘルパーコマンドと根拠を示すコンテキスト要約を用い、Python、XML、JavaScript、データの変更に伴う誤りを抑えます。
主な機能
- 01バージョンと実行環境の特定
- 02モデル、フィールド、メソッドの追跡
- 03XML、JavaScript、参照箇所の検索
- 04根拠に基づくコンテキスト要約
- 05テストとマイグレーションの手順
活用シーン
- 01変更前にフィールドの存在を確認
- 02適切なメソッドオーバーライドの特定
- 03継承ビューのアンカー確認
- 04マイグレーション前の依存関係追跡
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ unclecatvn/ agent-skills --skill odoo-workflowインストール後、Claude Code で/odoo-workflowを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/definition-of-done.mdscripts/odoo_trace.pytemplates/CLAUDE.md詳細
Odoo ワークフロー - まず追跡し、それからコードを書く
Odoo の変更すべてに適用する手順です。コストのかかる失敗はいつも同じです。存在しないフィールドやメソッド、実際には存在しなかった xpath アンカー、誤ったメソッドにフックされたオーバーライド、マイグレーションせずに名前を変更した保存フィールド、監査できない sudo()、テストを一件も実行しなかったテスト実行などです。解決策は機械的です。根拠を示せないなら、コードを書かないこと。
適用される場合
- フルパス - Odoo の Python、XML、JS、またはデータに関わるものすべてです。新しいフィールド、オーバーライド、継承ビュー、OWL パッチ、ウィザード、cron、コントローラー、レポート、セキュリティ、マイグレーション、バグ修正、リファクタリング、「最適化」、「クリーンアップ」が該当します。貼り付けられたトレースバックもここから始めます。ワークスペース内のモジュールにある最も内側のフレームが、最初の根拠です。
- 概要のみ - Odoo のシンボルに関する質問(「X はどこで定義されていますか」、「
sale.orderに F はありますか」、「19 ではこのフィールドはどのモデルにありますか」など)では、ステップ 0~2 を実行し、概要を使ってコードなしで回答します。 - ファストパス - 変更で編集対象のモジュール外のシンボルに触れず、フィールド、オーバーライド、xmlid、xpath、グループ、
dependsのいずれも追加しない場合です。たとえば、モジュール自身のコードにあるラベル、ヘルプテキスト、誤字、マニフェストのバージョン更新、テストのみの変更が該当します。概要には編集した行を示す一行を記載します。ステップ 1、3、6 は省略し、ステップ 5 は引き続き適用します(文字列を変更した場合は i18n が必要です)。判断に迷った場合はフルパスを使います。 - ドキュメントのみ、または git のみのタスクは対象外です(
odoo-commitを使います)。 - ブレインストーミングと計画のスキルを使うのはステップ 2 の後で、ビジネス上の意図がまだ不明な場合に限ります。コンテキスト概要を入力として使います。
ステップ 0 - ランタイム、バージョン、パック
同梱のヘルパー scripts/odoo_trace.py(標準ライブラリのみを使う Python 3.8 以降、読み取り専用)は、ランタイムを特定し、Python 側のトレースに関する質問に回答します。呼び出すたびに、スキルのベースディレクトリをフルパスで指定してください。シェル変数はツール呼び出し間で保持されません。以下では、odoo_trace は python3 <skill_dir>/scripts/odoo_trace.py を表します。
odoo_trace env
最初に見つかったものを使用します。フラグ、.claude/odoo.json、現在の作業ディレクトリを対象とするアドオンを指定した .claude/launch.json 内の odoo-bin 設定(両ファイルとも現在の作業ディレクトリ、または最も近い親ディレクトリにあるもの)、次に addons_path が現在の作業ディレクトリを対象とする *.conf を親ディレクトリから上に向かって検索します(各階層とそのサブディレクトリを検索)。version(odoo/release.py から取得)、odoo_root、conf、python、dev_db、および Odoo が使用する順序でアドオンのルート(モジュール名を含む最初のルートが優先)を出力します。.claude/odoo.json の odoo_version または .odoo-version ファイルのバージョンは、release.py と一致する必要があります。
- 終了コード 2 の場合、
SETUP ERROR:と原因が出力されます。ランタイムを特定できない場合は、コアのチェックアウト先と conf の場所を一度だけ尋ね、毎回の呼び出しで(rootsを含めて)odoo_trace --odoo-root <core> --conf <conf> <command>に渡します。また、odoo_version、odoo_root、conf、python、dev_db(マシン固有のパス)を含む、gitignore 対象の.claude/odoo.jsonを作成するよう提案します。バージョンの情報源が一致しない場合、または現在の作業ディレクトリを対象とする conf が複数ある場合は、どれが正しいか尋ねます。カスタムルートだけを使ってトレースしてはいけません。 - ユーザーまたはプロジェクトの
CLAUDE.md/AGENTS.mdに明示された内容は、ヘルパーより優先されます。内容が食い違う場合は停止して確認します。バージョンを暗黙に決めつけてはいけません。 - そのメジャーバージョン用の Odoo リファレンスパックを読み込みます。skills CLI では
odoo-<major>、Claude Code プラグインでは<plugin>:odoo-<major>-0(例:agent-skills:odoo-18-0)を使います。references/api-highlights.md(このバージョンで削除または名前が変更された API)と、タスクに必要なガイドのセクションだけを読みます(grep -n '^## ' <guide>を実行してから、該当範囲を読みます)。パックが見つからない場合は、その旨を伝えて続行します。正しい情報源は実際のソースコードであり、記憶ではありません。
ステップ 1 - 呼び出し経路を追跡する
Python シンボル:ヘルパー。 ast でクラスを解析し、バージョンごとの _name ルールを適用し、_inherit の親(mail.thread、portal.mixin などのミックスイン)、_inherits による委譲、base の拡張、コアの BaseModel をたどります。また、モジュールがインポートしないファイル内のクラスに UNIMPORTED の印を付け、絶対パスの file:line を出力します。
| 質問 | コマンド |
|---|---|
MODEL、そのミックスイン、type、_auto、_log_access を定義/拡張するクラス | odoo_trace model MODEL |
FIELD は MODEL に存在するか(独自定義、親、_inherits、マジックフィールドを含む)。すべての定義と comodel。ドット区切りのパスは各段階で確認されます | odoo_trace field MODEL FIELD[.FIELD...] |
super() の順序での METHOD の全定義(#1 が最初に実行)、super() 呼び出し、フック、AMBIGUOUS の組み合わせ | odoo_trace method MODEL METHOD --module MODULE |
MODULE の推移的な depends(読み込み順) | odoo_trace depends MODULE |
| xmlid は存在するか(読み込まれる XML レコードまたは CSV 行、あるいはインストール時に生成されるもの) | odoo_trace xmlid module.name |
読み込まれない MODULE のファイル(data/assets に含まれず、インポートもされないもの) | odoo_trace unlisted MODULE |
最後の行は VERDICT です。終了コード 0 = 見つかった、1 = NOT FOUND(verdict には最も近い実在のシンボルが示されます)、2 = セットアップエラーまたは回答不明です。NOT FOUND のセルを使えるのは終了コード 1 の場合だけです。ハブモデル(sale.order、mail.thread)のリストは上限があります。model、method、unlisted の後ろに --all を付けると、リスト全体が出力されます。
XML、JS、コントローラー、使用箇所:同じルートを grep で検索します。 以下の ROOTS は、完全な形で記述した $(odoo_trace roots) を意味します。zsh と bash ではコマンド置換がルートごとに 1 つの引数に分割されますが、単なる $ROOTS 文字列は zsh では分割されません。--include の glob を囲む引用符は残してください。No such file or directory という警告が出る grep、または終了コード 2 の grep は、NOT FOUND ではなくコマンドの不具合です。odoo_trace env が終了コード 2 を返しているときに実行した grep も同様です(空の $(odoo_trace roots) により、grep -r はカレントディレクトリだけを検索します)。また、パスに空白を含むルートを対象にした grep も該当します。パスが分割され、macOS の grep はその断片を黙ってスキップするため、そのルートは引用符で囲んで個別に検索してください。
# V1. Views on MODEL
grep -rlE --include='*.xml' "<field name=\"model\">MODEL</field>" ROOTS
# V2. Every view and QWeb template inheriting VIEW; -A1 also catches inherit_id split over two lines
grep -rnE -A1 --include='*.xml' "name=['\"]inherit_id['\"]|inherit_id=['\"]" ROOTS | grep -E ":[0-9]+:.*(ref|inherit_id)=['\"](MODULE\.)?VIEW['\"]|-[0-9]+-\s*ref=['\"](MODULE\.)?VIEW['\"]"
# V3. ANCHOR nodes inside one view record, absolute lines. A = the id line, B = the next </record>: grep -nE 'id="VIEW"|</record>' VIEW_FILE
awk -v a=A -v b=B 'NR>=a && NR<=b && /name="ANCHOR"/ {print FILENAME":"NR": "$0}' VIEW_FILE
# W1. OWL / QWeb client template: definition and every t-inherit (extension or primary)
grep -rnE --include='*.xml' "t-name=['\"]TEMPLATE['\"]|t-inherit=['\"]TEMPLATE['\"]" ROOTS
# W2. JS class or component: its definition (= import path) and every patch of it
grep -rnE --include='*.js' "^export (default )?(class|function|const) NAME[^A-Za-z0-9_]|patch\(\s*NAME[^A-Za-z0-9_]" ROOTS
# C1. Route (the second pattern finds a decorator split over lines), then every subclass of
# its controller class (an `import CLASS as ALIAS` hit: run the class grep again with ALIAS)
grep -rnE --include='*.py' "@(http\.)?route\(.*['\"]URL['\"]|^\s*(route=)?\[?\s*['\"]URL['\"]" ROOTS
grep -rnE --include='*.py' "^class \w+\(([^)]*[ ,])?([A-Za-z_.]+\.)?CLASS[,)]|import.*[ ,]CLASS as " ROOTS
# S1. ACL rows and record rules on MODEL (dots as underscores)
grep -rn --include='ir.model.access.csv' "model_MODEL_UNDERSCORED," ROOTS
grep -rlE --include='*.xml' "model_MODEL_UNDERSCORED['\"]" ROOTS | grep security
# U1. Every use of NAME before renaming, removing, or changing the signature of a field or method
grep -rnwE --include='*.py' --include='*.xml' --include='*.js' --include='*.csv' --include='*.scss' "NAME" ROOTS
# T1. Tests to mirror
ls BASE_MODULE/tests/; grep -rnE "def test_.*METHOD" MODULE/tests
U1 では MODEL に関するヒットを残し、ビュー、QWeb、@api.depends、related=、ドメイン、レコードルール、cron とサーバーアクションの code、<function>、ボタンの name=、JS の orm.call 文字列、テスト、マイグレーションをそれぞれ列挙します。一般的な名前(name、state、partner_id)の場合は、odoo_trace model MODEL が出力したモジュールと V1/V2 のビューに絞ってください。それでもヒットが 50 件を超える場合は、モジュールごとの件数を示し、続行する前に確認してください。
grep のヒットとして数えるのは、そのファイルが読み込まれる場合(読み込まれないファイルは odoo_trace unlisted MODULE に一覧表示されます)で、Python の場合は MODEL 自身のクラス内にあるものに限ります。同じファイル内の別のクラスにあるものは含めません。
読み取りの予算 - ここでトークンを使います。
- ファイル全体ではなく、範囲を読み取ります。offset/limit を指定して読むか、
awk 'NR>=A && NR<=B'を使います。行番号が 1 から振り直され、引用できなくなるsed -n 'A,Bp' | grep -nは使わないでください。1 ホップにつき最大 120 行です。 - インラインでのホップは最大 3 回です。さらに深い処理やモジュールをまたぐ処理は、
odoo-code-tracerエージェントにodoo_version、pack_dir(読み込まれたパックのディレクトリ)、roots、helper(ヘルパーの完全パスと、必要なenvフラグ)を渡して依頼してください。インストールされている場合に限ります。それ以外の場合は、上記のコマンドを使って続けてください。 - Brief ですでに引用したファイルは、再度読み込まないでください。
ステップ 2 - コンテキストブリーフ(引用なし、コードなし)
コードを書く前にブリーフを作成します。変更が関わるシンボルごとに 1 行とします(Odoo 18 の例):
| レイヤー | シンボル | 出典(ファイル:行) | 基本の動作 | 変更 |
|---|---|---|---|---|
| モデル | purchase.order | addons/purchase/models/purchase_order.py:19 | _name、mail.thread を継承 | 継承 |
| メソッド+super | button_approve | purchase_order.py:528;purchase_stock/models/purchase_order.py:117 のオーバーライドが _create_picking を呼び出す | ピッキングが存在するのは purchase_stock がある場合のみ | purchase_stock に依存し、super を呼び出した後、X を実行 |
| パス | stock.picking partner_id.commercial_partner_id.vat | stock/models/stock_picking.py:630 -> base/models/res_partner.py:304 -> :247 | Many2one、Many2one、Char | 関連フィールド |
| ビュー+xpath | purchase.purchase_order_form | purchase/views/purchase_views.xml:130、アンカー partner_ref :185、フォーム内にノードが1つ | - | アンカーの後にフィールドを追加 |
レイヤー:モデル / フィールド / メソッド+super / パス / ビュー+xpath / クライアント / コントローラー / セキュリティ / 会社 / データ / 移行 / i18n / テスト。
出典セルには、次のいずれかを正確に記載します。
file:line- 引用した行自体が主張を示しています(定義、レコード、アンカーノード)。NOT FOUND- ヘルパーが1を返した場合、またはすべての ROOTS を対象に MODEL を検索して一致するものがなかった場合です。 実在する最も近いシンボルがある場合は、その名前を記載します。UNCERTAIN: <不足している情報>- 読んでいないファイル、実行時に構築されるドメイン、またはアンカーを提供する別モジュールのビューを記載します。上記のコマンドで答えられる質問には使いません。AMBIGUOUS: <modules>- 相互に依存していないモジュール内のオーバーライドを記載します。
file:line 以外の値を含むセルが1つでもある行はコードがないため、その行を削除するか、不足している depends を追加するか、質問してください。行数が15を超える場合はタスクを分割してください。行数を合わせるために行を削除してはいけません。
ルール:
- 再定義されたフィールド。
odoo_trace fieldは、読み込み順にすべての定義を一覧表示します。変更が依存する属性を持つ定義を、それぞれ引用してください。再指定されていない属性は以前の値を維持します(selection_addはリストを拡張します)。ただし、新しいクラスが以前のクラスをサブクラス化していない場合は、以前の属性は破棄され、ヘルパーはfinal classと表示します。 - パス(
related=、@api.depends、depends=、domain=、domain_force)。ドット区切りのパスに対してfieldを1回呼び出し、各階層を1つずつ引用してください。階層が見つからない場合、または終端より前の階層がリレーショナルでない場合は、その階層で NOT FOUND になります。 - 依存関係。
odoo_trace depends MODULEの対象外のモジュールでのみ定義されているシンボルは、現在のデータベースに存在していても、インストールされるとは限りません。依存関係を追加するか、その行を削除してください。 - オーバーライドの順序。 記憶や grep の順序ではなく、
odoo_trace method ... --module MODULEの結果を使用してください。最後に読み込まれたクラスが先に実行され、そのsuper()が以前のクラスに到達します。変更が依存するオーバーライドを持つ各モジュールに対して、dependsを宣言してください。AMBIGUOUS と表示されたペアは、AMBIGUOUS のままにしてください。 - Xmlid。
odoo_trace xmlidの結果を基準にしてください。対象は、マニフェストが読み込む XML または CSV ファイル内のレコードか、インストール時に生成されるレコード(model_*、field_*__*、selection__*、base.module_*、チャートテンプレート由来のaccount.<company id>_<name>)で、いずれもヘルパーが示すモジュールに紐付いています。マニフェストが読み込まないファイルにしかないレコードは NOT FOUND です(init フックが読み込む可能性がある場合は UNKNOWN)。xmlid を決して捏造せず、生成された xmlid を見つからないものとして報告しないでください。ビューのgroups=に指定されたグループが見つからなくても、インストールは停止しません。Odoo は WARNING を記録するだけで、存在しないグループに属するユーザーはいないため、そのノードは非表示になります。 - マジックフィールド。 存在を決めつけないでください。
odoo_trace fieldの結果を基準にしてください。create_uid、create_date、write_uid、write_dateが存在するのは_log_accessが true の場合だけです。デフォルト値は_autoなので、レポートモデルと SQL ビューモデル(_auto = False)にはこれらのフィールドはありません。 - Xpath アンカー。 アンカーは、ベースビュー、または
dependsの対象モジュールに属する継承ビューに存在する必要があります。そうでない場合は UNCERTAIN です。ロケーターは最初のノードに一致するため、統合されたビュー(V2 + V3)でその名前を持つノードの数を明記してください。 - 会社。 MODEL またはコムモデルに
company_idがある場合は、それを引用し、そのモデルで_check_company_autoが設定されているかどうかも示してください。会社に紐付くコムモデルへの新しいリレーショナルフィールドにはcheck_company=Trueを設定します。company_idを持つ新しいモデルには、コアが書き込む形式のレコードルールを設定します。[('company_id', 'in', company_ids)]、または会社が任意の場合は[('company_id', 'in', company_ids + [False])]です。 - スキーマ変更。 保存フィールドの名前変更、削除、型変更、選択肢キーの追加・変更・削除、
requiredの追加、データが入力済みのテーブルへの保存計算フィールドの追加、またはモデル名の変更には、migration行が必要です。新しいマニフェストのバージョンと、データを移行するmigrations/<version>/pre-*.py/post-*.pyスクリプト(def migrate(cr, version))を記載してください。スクリプトは、そのバージョンがインストール済みのバージョンより上で、マニフェストのバージョン以下の場合にのみ実行されます。保存フィールドを移行スクリプトなしで名前変更すると、-u時にその列とデータが警告なしに失われます。重要なデータベースにそのモジュールが一度もインストールされていないことをユーザーが確認した場合に限り、省略できます。手順はパックの移行ガイドにあります。 - sudo。 追加した
sudo()またはwith_user(SUPERUSER_ID)ごとに、どの ACL またはレコードルールをバイパスするか(S1)と、その前に行うチェック(18 以降ではcheck_access/has_access、アクセス トークン、またはグループチェック)を示すsecurity行が必要です。行がなければ sudo も使用できません。 - 新しく作るより再利用してください。フィールド、ヘルパー、計算フィールドを追加する前に、既存のものがないか確認してください。
- 「クリーンなコード」や「ベースに沿う」とは、最小限の差分でベース実装のパターンに従い、
super()がすでに行う処理を書き換えないことを意味します。「最適化」が速度を指す場合は、まず計測(名前を明記したケースでのクエリー数または実行時間)し、変更後にもう一度計測してください。
ステップ 3 - 最小限の計画
箇条書きは最大 10 項目です。変更するファイル、明示的に変更しないファイル、延期する内容を記載します。Brief に変更が 2 つのモジュールにまたがる根拠がない限り、1 つのモジュールに限定します。変更する Git リポジトリごとに、コミットと PR を 1 つずつ作成します。YAGNI:決して変わらない値の設定は追加しません。一度しか使わないヘルパーは作りません。実装が 1 つしかない抽象化は作りません。
ステップ 4 - 実装
- このバージョンのパックにある
Coding Conventionsとapi-highlights.mdの一覧を適用します。個人の好みよりも、既存の stable-addon のスタイルを優先します。 - 変更に必要なときに実行される最も限定的なフック(
odoo_trace methodが出力するフック)をオーバーライドし、それをラップする公開メソッドはオーバーライドしません。super()を呼び出して、最小限の差分を追加します。ベースの処理内容をコピーしてはいけません。 _inherit = 'model'(文字列)で拡張します。新しいモデルでは_nameを明示的に設定します。_nameのないリスト形式の_inheritは、クラス名に基づく名前で新しいモデルを作成します。19 では常に(警告が出ます)、18 ではリストに 2 項目以上ある場合に作成されます。- 新しい XML または CSV ファイルはすべて、読み込み順(グループ、
ir.model.access.csv、ビュー、メニュー)に従ってマニフェストのdataに追加します。新しい静的ファイルはすべて、適切なassetsバンドルのエントリーに対応させます。一覧にないファイルは、インストール時にエラーにはなりませんが、何も行いません。 sudo()の命名規則:sudo()で取得したレコードセットはすべて、_sudoで終わる名前にバインドします(partner_sudo = self.partner_id.sudo()、orders_sudo = self.env['sale.order'].sudo().search(domain))。1 回限りの呼び出し(record.sudo().write(vals))では変数は不要です。公開メソッドから_sudoレコードセットを返したり、selfに保持したりしてはいけません。- Odoo コードに
ponytail:などの「とりあえず簡略化」といったマーカーを入れてはいけません。コードを正しくするか、ステップ 3 の計画に延期内容を記載します。 - ユーザーに表示される新しい文字列は、抽出可能になるよう
_()/_t()/string=/help=を通します。
ステップ 5 - 完了の定義
完了を報告する前に references/definition-of-done.md を読んでください。バージョンごとの正確なコマンドが記載されています。要約:
- チェックリスト全体で使い捨てデータベース
scratch_<module>を 1 つ使用します。モジュールのテストを実行しながらインストールし、テスト概要で N > 0 件のテストがカウントされていることを確認します(概要がない場合やof 0 testsの場合は合格ではありません)。ERROR/CRITICALがなく、モジュールに関するすべてのWARNINGが説明されていることを確認します。ユーザーから依頼されない限り、名前付きデータベース(conf のdb_name、ステージング、開発環境)に対して-i/-uを実行してはいけません。 - Brief に
migration行がある場合は、以前のバージョンを使っているデータベースのコピーでアップグレードをテストします。 -
odoo_trace unlisted <module>で、すべてのファイルが読み込まれることを確認します。 - 新しいモデルごとに ACL 行があり、会社ルールが適用される場合は会社ルールがあり、すべての
sudo()に対応するsecurity行があることを確認します。 - 文字列を変更した場合は i18n を再生成します。出荷する
.poファイルの名前は変更しません。 - プロジェクト独自のリンターがある場合は、変更したファイルに対して合格することを確認します。
- 変更を加えたすべての Git リポジトリで、衛生チェックに合格することを確認します。
- スクラッチデータベースとそのフィルストアを削除します。
odoo-commitを使ってコミットします([TAG] module: description、AI のクレジット表記なし)。リポジトリごとにコミットと PR を 1 つずつ作成します。
ステップ 6 - 引き継ぎ
odoo-code-review エージェントがインストールされている場合は、odoo_version、pack_dir、roots、helper を指定して実行します。インストールされていない場合は、完了を報告する前に references/definition-of-done.md のセキュリティと衛生の一覧を自分で確認します。