Skip to content

fix: ローカルLLM 実行の安定化 — opencode の防護装置と Finding Contract の凍結解消 - #1017

Merged
nrslib merged 59 commits into
mainfrom
feat/log-opencode-tool-error-input
Jul 19, 2026
Merged

nrslib merged 59 commits into
mainfrom
feat/log-opencode-tool-error-input

Conversation

@nrslib

@nrslib nrslib commented Jul 10, 2026 •

Copy link
Copy Markdown
Owner

概要

takt-bench(qwen3-coder-next / gemma4:31b の実務タスク走行)で実測した障害群の恒久修正。13コミット。

opencode 系

  • ツール呼び出し失敗時の引数を debug ログへ記録(機密はマスク)
  • ローカルモデルのツール引数破壊(数値の文字列化・キー名 typo)を tool.execute.before プラグインで矯正
  • invalid 擬似ツール(status=completed で返る拒否)を3つの検出器と cycle budget に配線。実測で435件の空振りが全防護装置を素通りしていた死角の解消
  • 構造化出力の退化(native 失敗→formatless)を fresh session 化し、フェンス指示を注入。stale な StructuredOutput 呼び出しは1回だけ fresh session で回復。builtin 10本に session_key 分離(persona 既定によるセッション契約混線の予防)
  • opencode SDK を 1.17.16 へ更新

Finding Contract 系

  • 修正確認と残存指摘の衝突で manager 出力全体が破棄され台帳が凍結する問題の恒久修正(canonicalize + decision-rules への規則一本化 + flatten 往復での conflict 生存)
  • レビュアが再観測中の finding への waive はゲートを開けず conflict + disputeNote へ変換(manager はコードを照合できないため)
  • manager には1件ごとの判断だけを返させ、組み立てと不変条件はコードが強制(decisions 契約 v2)
  • 単独ステップ・サブワークフローでも指摘を台帳へ取り込み
  • preview / 継承契約の検証を実行時の挙動に整合
  • loop judge にトリガー元のモデルを引き継がせない
  • for-local-llm ワークフロー一族を takt-default と同等の流れに整備

検証

  • build / lint / unit(4シャード 7,785件)/ test:it / test:e2e:mock 全シャード 通過
  • 実走: takt-bench v3-r2 で 292分・49イテレーションを完走レベルで走行(台帳凍結なし、dispute→waive→conflict 変換の実動作、loop judge 2回、replan 経路使用、実装 +397/-34 進行)。終端は設計どおりの escalation(未調停 conflict → ABORT)
  • 各修正は codex による設計レビュー・実装検証(計7ラウンド)を通過

Summary by CodeRabbit

  • 新機能
    • 実装直後にAI固有の問題を検出・修正・再判断するループと、最終ゲートで並行監督を実行
    • Findings Manager の出力を rawDecisions/disputeDecisions/conflictDecisions に整理(invalidateDecisions/duplicateDecisions も対応)
    • Finding Contract を単独ステップ/ワークフロー呼び出しで扱えるよう拡張し、未裁定競合は裁定手順へ分岐
  • 改善
    • ツール引数の補正、構造化出力の復旧、機密情報マスキングを強化
  • ドキュメント
    • 送信プロンプト確認手順と運用ルールを更新

nrslib added 13 commits July 11, 2026 05:39
ツールエラーは error 文字列しか記録しておらず、モデルが何をどう間違えたか
(スキーマ違反の該当欄、幻覚パス、oldString の不一致)を後から特定できな
かった。state.error には input が同梱されているので、それをそのまま出す。

実測の動機: 9万行のリポジトリで qwen3-coder-next の implement が read ツールの
SchemaError を20回連発して停止したが、エラー文が途中で切れており原因不明だった。
- tool.execute.before プラグインで数値引数の型違い("290.0")と filePath の
  別名(filepaath / path)を矯正する。上流 anomalyco/opencode#1328・#26870・
  #29142 が未修正で、qwen3-coder-next は同じ呼び出しを繰り返して cycle budget
  を使い切る。実在する絶対パスのときだけ読み替え、推測はしない。
- サイクル予算を「ツール成功のない連続サイクル」で数える。総サイクル数で
  打ち切ると健全な作業も殺す(実測: 9万行のリポジトリで implement が 120
  サイクル・ツール成功 150 回の途中で abort された)。
- session.summarize() はジョブ投入で即返るため、完了と成功の両方を待つ。
  今回の summarize が作った summary だけを id で識別する。要約対象が無くても
  summary は必ず作られる(実測)。
- agent プロンプトを OpenCode 既定に戻し、#892 が必要とした ls / Task tool の
  2 箇所だけを取り除く。節ごと削ったせいで弱いモデルがツールの使い方を
  失っていた。client.ts は常に 'en' を読むため ja 版は削除する。
- 失敗したツール引数の debug ログで、機密キーの値をマスクする。
judge に provider 指定が無いと、ループを踏んだステップ(fix = coder)の
provider/model を runtime として強制していた。実装した本人が「自分のループは
健全か」を判定することになり、監視が機能しない(実測: qwen が 4 回とも
「健全」と判定し、reviewers → final-gate → fix が 56 周・9 時間続いた)。

優先順位を judge.provider/model → provider_routing / persona_providers.loop-judge
→ トリガー元、に正す。judge ステップに providerRoutingPersonaKey: 'loop-judge'
を持たせ、findings-manager と同じ仕組みで席を指定できるようにする。
provider だけ明示された場合はトリガー元の model を引き継がない。
取り込みは ParallelRunner からしか呼ばれておらず、単独ステップの final-gate が
出した指摘は台帳に入らず fix に届かなかった。final-gate が同じ指摘を出し続け、
fix は直すものが無く、reviewers → final-gate → fix が永久に回る(実測: 56 周)。

- output_contracts.report[].formatRef が *-finding-contract のステップは、
  単独でも parallel でも取り込む。取り込みはルール評価の前に行い、直後に
  findings 状態を再読込する。
- workflow_call の子エンジンへ親の findingContract / findingLedgerStore を
  継承する。子が自前の contract も持つ場合は設定エラー(ledger_path が
  ワークフロー名に紐づき、親の when(findings.*) が別の台帳を見るため)。
- 子の書き込みを親が見られるよう、workflow_call 完了後に findings を再読込。
- 台帳が無いのに finding-contract 形式で出力するステップは実行前に落とす。
  黙って指摘を捨てない。
- raw finding ID に呼び出し名前空間(親ステップ#反復)を含める。継承により
  複数の子が同じ台帳へ書くようになり、同じ子を並列で呼ぶと ID が衝突した。
- 台帳の更新を直列化し、保存直前に再読込してから適用する。
従来は 2,599 字のスキーマで 8 つの配列を LLM に組み立てさせ、台帳の不変条件も
守らせていた。gpt-5.5 でも破る(takt-bench v2 の 7 走行が検証失敗。
"Cannot resolve finding F-0006 because it is not open" など)。難しさはモデルの
賢さではなく仕事の渡し方にある。

- LLM は 1 件ごとの判断(same / new / resolved / reopened / conflict、
  waive / note、resolve / keep)だけを返す。
- 組み立てと不変条件の強制は decision-assembly.ts が行う。違反した決定は
  その 1 件だけ不採用にし、不採用項目だけを再問い合わせする。
- 決定が欠落した residual raw finding も不採用として扱い、再問い合わせに乗せる。
- 同一 familyTag・同一 location の new は機械的に 1 件へ畳む。既存の open
  finding と一致するものは same に倒す。LLM の判断に委ねない。
- resolution_confirmation は new の証跡にできないため new に倒さず記録に残す。

これにより弱いモデルでも manager が動く。
takt-default-for-local-llm は #974 で takt-default から派生させたのではなく、
レビュアー編成の実験ワークフローに Finding Contract を足したものだった。本流に
ある工程が 2 つ落ちている。

- implement の後に AI 自己レビュー(ai-antipattern-review-1st ⇄
  ai-antipattern-fix ⇄ ai-antipattern-no-fix)を戻す。本流の backend.yaml
  などが持つ工程で、弱いモデルほど必要になる。
- final-gate を merge-readiness-review + supervise の parallel にする。
  supervise が新規・継続・解消を突き合わせないと、merge-readiness が毎周
  同じ指摘を出し直して閉じない(実測: 56 周・9 時間)。

対象は finding_contract を持つ 5 本(takt-default / backend / backend-cqrs /
frontend / dual)の ja / en。

あわせて、OpenCode の送信内容を捕まえる計器(prompt-evals/)と、その観測で
判明した事実(既定プロンプトの取得方法、CLAUDE.md の注入、リクエストが 2 本
飛ぶこと、opencode run は計器に使えないこと)を CLAUDE.md に記録する。
OpenCode は存在しないツール名と実在ツールの引数不備を、どちらも
`invalid` という擬似ツールの status='completed' として返す。3つの検出器
(unavailable / invalid-argument / error-budget)は status==='error' で
しか observe されないため、拒否がまるごと死角に入っていた。さらに
completed 扱いなので「ツール成功」とみなされ、空転サイクルの計数まで
リセットしていた。

実測(takt-bench v3-r1、qwen3-coder-next の implement): invalid 435件
(read の引数不備 219 / 存在しない run 216 / ほか)、最長195回連続。
どの防護装置も発火せず、cycle budget を焼き切って abort した。

- extractOpenCodeToolRejection() で error と invalid/completed を正規化し、
  state.input.tool から本来のツール名を取り出して検出器へ渡す
- 空転リセットを else if にして、拒否ではリセットしない
- 引数欠落の実際の文言 "is missing or invalid" をパターンに追加

回帰テストは実測したイベント形状(tool='invalid', status='completed',
input={tool,error})で書いた。既存テストは status='error' を使っており、
実データに1件も存在しない形だった。単発の空振り後に成功する自己修正は
打ち切らないことも検証する。
同じ finding について、あるレビュアーが「修正済み」を確認し、別のレビュアーが
「同じ問題が別の行に残っている」と報告することがある。どちらも正当な観測で、
矛盾は現実の側にある(部分的にしか直っていない)。

このとき manager の決定は同じ finding id を matches と resolvedFindings の
両方に載せる。「1 finding = 1 決定」の不変条件がこれを違反として扱い、出力
全体を破棄して台帳を更新しなかった。矛盾を記録するための conflicts 自体も
同じ検査に含まれていたため、逃げ道が無かった。

結果、レビュアーは毎ラウンド同じ修正確認を出し続け、reviewers ↔ fix が
収束しない。実測(takt-bench v3-r2): F-0002 の修正確認と同 familyTag の
残存指摘(:137 と :149)が衝突し、12 イテレーション回っても台帳は
最初のラウンドの 4 件で凍ったままだった。

- same と resolved が同じ finding に付いた場合、未修正の証拠を優先して
  open に留め、修正確認を conflict として記録する(manager 指示の
  「迷ったら open を維持」と同じ向き)
- conflicts は他の決定について述べるメタ決定なので、検証・適用の双方で
  「1 finding = 1 決定」検査の対象から外す
- resolution_confirmation の raw を根拠にした "new" は組み立て時に 1 件
  だけ不採用にする。最終検証まで通すと 1 件の違反で出力全体が無効化される

flatten → 再組み立ての往復(保存直前の lost update 対策)が安定することも
検証する。既存テスト1件は matches と conflicts の併存を「拒否すべき」と
固定しており、逃げ道を塞いでいた張本人だったため差し替えた。
同じ finding への修正確認と残存指摘の衝突が「1 finding = 1 決定」違反として
出力全体を破棄し、台帳が更新されないまま reviewers ↔ fix が収束しない問題の
恒久修正。8e17d94d は組み立て側だけを直しており、resolution_confirmation を
機械分類が先に消費する本番経路(機械分類 → LLM → merge)では衝突が merge で
再生して同じ凍結が残っていた(codex が直接実行で再現)。

- canonicalize.ts: matches ∪ conflicts に現れる finding の resolvedFindings を
  取り下げて conflict へ畳む正規化を新設し、merge 直後と組み立て直後の両方に適用
- decision-rules.ts: 許可ペア(match+conflict / reopened+conflict)と
  「closed な finding を conflict が参照するなら同じ出力で reopen 必須」の
  status 不変条件を一本化。検証と適用が同じ規則を使い、順序非依存で全ペア検査
- waive の裁定を merge 後の正規化済み出力に対して行う。レビュアーが再観測して
  いる finding への waive はゲートを開けず、conflict + disputeNote へ変換して
  need_replan 側へ流す(manager は allowedTools: [] でコードを照合できないため、
  ゲート開放の権限を与えない)
- flatten の戻り値に finding-only conflict を持ち越す carriedFindingOnlyConflicts
  を追加(raw ID の無い conflict は raw decisions から復元できず、保存直前の
  再組み立てで消えていた)。統合前に fresh ledger での open を検査し、並列子が
  先に resolved にした finding への carried は理由つきで不採用にする
- 同一ラウンドで再生成される conflict の resolve を不採用にする(最終形の
  conflicts から formatConflictId で判定)
- reconciler の重複した決定組み合わせ検査を削除(入口の validate に一本化)

エンジン実経路(実 FindingLedgerStore の updateLedger 往復)で、保存後台帳に
open finding + active conflict + disputeNote が残り conflicts.count>0 ルールが
選ばれることを正方向テストとして固定。旧テストは凍結挙動を仕様として固定して
いたため、新仕様の正方向へ転用し、negative は別の不変条件違反へ差し替えた。
main 起点への積み直しで、#1008 が finding_contract.manager に入れた
provider/model 指定(直接指定を最優先、未指定時は workflow provider/model →
provider_routing.personas の順で解決)が、衝突解決時に4ファイルから
脱落していた。ブランチの現在構造(decisions-only manager、contract-intake
経由の並列/単独共通取り込み)に載せ直す。

- manager-step.ts を唯一の合成ステップビルダーに戻し、スキーマを
  ブランチの decisions 契約(takt.findings.manager.v2)へ更新。
  #1008 の provider/model ロジックは維持
- manager-runner.ts のローカル簡易ビルダーと重複スキーマ定義を削除し、
  workflowProvider/workflowModel を受けてビルダーへ渡す
- 単独ステップ取り込み(StepExecutor 経由)にも同じ解決を適用
  (#1008 は ParallelRunner のみだったが、ブランチは両経路で manager を起動する)
- WorkflowValidator の finding_contract.manager 検証を main と同内容で復元
…け回復する

FC レビュアステップの phase 1 が次の連鎖で abort していた(takt-bench v3-r2 実測):
セッションキーの既定が persona のため -1st レビュアと parallel FC レビュアが
セッションを共有し、native StructuredOutput の成功履歴が混入 → native 失敗後の
退化が同じセッションのままツールだけ外し、フェンス指示も注入せず再試行 →
モデルが記憶にあるツールを2回呼び、UnavailableToolLoopDetector(閾値2)で
ステップ失敗 → workflow abort。検出器は幻覚ツールと「TAKT がいま外した
ツール」を区別できない。

- structured-output-recovery.ts を新設し、attempt 計画(sessionMode ×
  structuredMode)・退化遷移・stale 回復遷移を純粋関数に集約
- 退化(native 失敗後の formatless)は fresh session で実行し、プロンプトを
  structured_json_schema_instruction で包む(従来は生プロンプトを指示ゼロで
  再送していた)。1回限り。新しい session ID を上位へ返す
- resumed かつ format 無しの attempt で StructuredOutput だけが閾値到達した
  場合、エラーにせず fresh session で1回だけ再実行。fresh で再発したら本物の
  失敗として従来どおり落とす。run 等の他ツールは従来どおり即検出
- テンプレート(en/ja)に「StructuredOutput ツールは呼ばないでください」を明記
- builtin 10本で -1st レビュアと parallel FC レビュアに別 session_key を付け、
  契約混線を入口で防ぐ
codex レビューの残ブロッカー2件の解消。

- findings-manager の起動判定を resolveFindingContractIntakeStep として
  contract-intake.ts へ切り出し、実行時(StepExecutor)と preview
  (workflowPreview)が同じ述語を使う。preview は単独 FC ステップの後にも
  manager を表示する。並列サブステップには付けない(実行時は並列全体で
  親レベル1回のため)。parallel / arpeggio / team_leader は
  isDelegatedWorkflowStep で除外(専用 Runner は manager を起動しない)
- validateFindingContractManagerProviderModel が inheritedFindingContract も
  検証するよう修正して export。WorkflowCallExecutor は子 engine 構築前に
  明示的に検証し、workflow_call で継承した契約の manager が子の
  provider/model 解決で成立しない場合に fail-fast する
@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Finding Contract v3の意思決定・台帳処理・競合調停、workflow_call継承、ローカルLLMワークフロー、OpenCodeのツール処理・構造化出力回復・評価プローブを追加・更新した。

Changes

Finding Contract と台帳

Layer / File(s) Summary
意思決定契約と台帳処理
src/core/models/*, src/core/workflow/findings/*, builtins/{en,ja}/facets/*, src/shared/prompts/*
rawDecisions、dispute/conflict/invalidate/duplicate判断、relation検証、decision assembly、台帳再照合、監査レポートを追加・更新。
実行・継承・競合調停
src/core/workflow/engine/*, src/infra/config/loaders/*
単独・並列ステップのintake、workflow_callの契約・台帳継承、raw ID namespace、合成競合調停ステップ、動的遷移、設定検証を追加。
検証と回帰テスト
src/__tests__/finding-*, src/__tests__/workflow-*
decision assembly、retry、競合調停、location admission、継承、validator、previewのテストを追加・更新。

ローカルLLMワークフロー

Layer / File(s) Summary
AIアンチパターン工程
builtins/{en,ja}/workflows/*-for-local-llm.yaml
実装後にAI固有レビュー、修正、裁定を実行する遷移と監視サイクルを追加。
最終ゲート
builtins/{en,ja}/workflows/*-for-local-llm.yaml, src/core/workflow/engine/*
merge-readiness-reviewとsuperviseを並列実行し、findingsの状態に応じてCOMPLETE、plan、fix、finding-conflict-adjudication、ABORTへ遷移するルールを更新。

OpenCodeと評価プローブ

Layer / File(s) Summary
ツール・構造化出力処理
src/infra/opencode/*, src/shared/prompts/*
ツール引数補正、拒否ループ検出、機密ログマスキング、StructuredOutputのformatless/stale recovery、summary完了待機を追加。
SDK・プラグイン評価
prompt-evals/*, src/__tests__/opencode-*
SDKのprompt/tool/summary capture、plugin hook probe、OpenCodeの回復・ログ・引数処理テストを追加。

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • nrslib/takt#826: Finding Contractとfindings-managerの出力契約・台帳処理に関連。
  • nrslib/takt#967: OpenCodeのStructuredOutput fallback処理に関連。
  • nrslib/takt#974: local-LLM workflowとFinding Contractの制御フローに関連。
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed ローカルLLMの安定化とOpenCodeの防護装置・Finding Contract修正という主変更を適切に要約しています。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/log-opencode-tool-error-input

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/workflow/findings/store.ts (1)

20-44: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

updateLedger の失敗経路テストを追加してください。
src/__tests__/finding-ledger-store.test.ts では成功系と並列更新のテストはあるので、mutator が throw した場合と saveLedger が失敗した場合に例外が伝播し、次の呼び出しが継続できることも押さえてください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/workflow/findings/store.ts` around lines 20 - 44, Add failure-path
tests in finding-ledger-store.test.ts for updateLedger: verify exceptions thrown
by the mutator and by saveLedger propagate to the caller, and verify a
subsequent updateLedger call still executes successfully after each failure. Use
the existing FindingLedgerStore/updateLedger test setup and assert the ledger
remains consistent.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@builtins/en/workflows/frontend-for-local-llm.yaml`:
- Around line 186-225: loop_monitors に ai-antipattern-no-fix
を含む監視対象サイクルを追加し、ai-antipattern-fix と ai-antipattern-no-fix
の判断が食い違う場合も無限往復を検出できるようにしてください。既存の ai-antipattern-review-1st と
ai-antipattern-fix の監視設定を確認し、3つのステップを含むサイクルとして定義してください。

In `@builtins/ja/workflows/backend-cqrs-for-local-llm.yaml`:
- Around line 186-225: ai-antipattern-fix と ai-antipattern-no-fix
間の裁定ループが監視対象になっていません。既存の loop_monitors 設定を確認し、ai-antipattern-fix →
ai-antipattern-no-fix → ai-antipattern-fix
サイクル専用の監視エントリを追加して、反復回数や停止条件を既存監視と同等に設定してください。

In `@builtins/ja/workflows/dual-for-local-llm.yaml`:
- Around line 189-229: 既存の loop_monitors 設定に、ai-antipattern-fix と
ai-antipattern-no-fix 間を往復するサイクルを追加してください。ai-antipattern-fix の「修正不要」遷移と
ai-antipattern-no-fix の「指摘が妥当」遷移を含め、判定のすれ違いで max_steps まで継続しないよう監視対象へ登録してください。

---

Outside diff comments:
In `@src/core/workflow/findings/store.ts`:
- Around line 20-44: Add failure-path tests in finding-ledger-store.test.ts for
updateLedger: verify exceptions thrown by the mutator and by saveLedger
propagate to the caller, and verify a subsequent updateLedger call still
executes successfully after each failure. Use the existing
FindingLedgerStore/updateLedger test setup and assert the ledger remains
consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b05608e0-1a00-4e3f-9c76-63ca03d1909d

📥 Commits

Reviewing files that changed from the base of the PR and between 1196d02 and 3d34ff5.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json, !package-lock.json
📒 Files selected for processing (80)
  • CLAUDE.md
  • builtins/en/facets/instructions/findings-manager.md
  • builtins/en/facets/output-contracts/findings-manager.md
  • builtins/en/workflows/backend-cqrs-for-local-llm.yaml
  • builtins/en/workflows/backend-for-local-llm.yaml
  • builtins/en/workflows/dual-for-local-llm.yaml
  • builtins/en/workflows/frontend-for-local-llm.yaml
  • builtins/en/workflows/takt-default-for-local-llm.yaml
  • builtins/ja/facets/instructions/findings-manager.md
  • builtins/ja/facets/output-contracts/findings-manager.md
  • builtins/ja/workflows/backend-cqrs-for-local-llm.yaml
  • builtins/ja/workflows/backend-for-local-llm.yaml
  • builtins/ja/workflows/dual-for-local-llm.yaml
  • builtins/ja/workflows/frontend-for-local-llm.yaml
  • builtins/ja/workflows/takt-default-for-local-llm.yaml
  • package.json
  • prompt-evals/plugin-probe.mjs
  • prompt-evals/prompts/opencode-base-1.17.16.md
  • prompt-evals/sdk-prompt-capture.mjs
  • prompt-evals/sdk-tool-eval.mjs
  • prompt-evals/summarize-probe.mjs
  • src/__tests__/builtin-takt-default-provider-options.test.ts
  • src/__tests__/engine-loop-monitors.test.ts
  • src/__tests__/engine-workflow-call.test.ts
  • src/__tests__/finding-decision-assembly.test.ts
  • src/__tests__/finding-ledger-store.test.ts
  • src/__tests__/finding-manager-mechanical-runner.test.ts
  • src/__tests__/finding-manager-output-validation.test.ts
  • src/__tests__/finding-reconciler.test.ts
  • src/__tests__/finding-schemas.test.ts
  • src/__tests__/for-local-llm-replan-wiring.test.ts
  • src/__tests__/opencode-client-cleanup.test.ts
  • src/__tests__/opencode-client-compaction.test.ts
  • src/__tests__/opencode-client-tool-error-log-sanitize.test.ts
  • src/__tests__/opencode-coerce-tool-args.test.ts
  • src/__tests__/workflow-engine-structured-caller.test.ts
  • src/__tests__/workflow-preview.test.ts
  • src/__tests__/workflow-validator.test.ts
  • src/__tests__/workflowCallExecutor.test.ts
  • src/core/models/finding-schemas.ts
  • src/core/models/finding-types.ts
  • src/core/models/workflow-types.ts
  • src/core/workflow/engine/LoopMonitorJudgeRunner.ts
  • src/core/workflow/engine/ParallelRunner.ts
  • src/core/workflow/engine/StepExecutor.ts
  • src/core/workflow/engine/WorkflowCallExecutor.ts
  • src/core/workflow/engine/WorkflowCallRunner.ts
  • src/core/workflow/engine/WorkflowEngine.ts
  • src/core/workflow/engine/WorkflowEngineSetup.ts
  • src/core/workflow/engine/WorkflowValidator.ts
  • src/core/workflow/findings/canonicalize.ts
  • src/core/workflow/findings/contract-intake.ts
  • src/core/workflow/findings/decision-assembly.ts
  • src/core/workflow/findings/decision-rules.ts
  • src/core/workflow/findings/manager-output-validation.ts
  • src/core/workflow/findings/manager-runner.ts
  • src/core/workflow/findings/manager-step.ts
  • src/core/workflow/findings/mechanical-classification.ts
  • src/core/workflow/findings/reconciler.ts
  • src/core/workflow/findings/schemas.ts
  • src/core/workflow/findings/store.ts
  • src/core/workflow/findings/types.ts
  • src/core/workflow/provider-resolution.ts
  • src/core/workflow/types.ts
  • src/infra/config/loaders/workflowPreview.ts
  • src/infra/config/loaders/workflowStepFeaturesNormalizer.ts
  • src/infra/opencode/client.ts
  • src/infra/opencode/plugins/coerce-tool-args.ts
  • src/infra/opencode/structured-output-recovery.ts
  • src/infra/opencode/unavailable-tool-loop.ts
  • src/shared/prompts/en/finding_manager_instruction.md
  • src/shared/prompts/en/opencode_agent_prompt.md
  • src/shared/prompts/en/parts/structured_json_schema_instruction.md
  • src/shared/prompts/index.ts
  • src/shared/prompts/ja/finding_manager_instruction.md
  • src/shared/prompts/ja/opencode_agent_prompt.md
  • src/shared/prompts/ja/opencode_report_agent_prompt.md
  • src/shared/prompts/ja/opencode_review_agent_prompt.md
  • src/shared/prompts/ja/parts/structured_json_schema_instruction.md
  • src/shared/utils/sensitiveText.ts
💤 Files with no reviewable changes (3)
  • src/shared/prompts/ja/opencode_review_agent_prompt.md
  • src/shared/prompts/ja/opencode_agent_prompt.md
  • src/shared/prompts/ja/opencode_report_agent_prompt.md

nrslib added 2 commits July 11, 2026 07:47
for-local-llm 系10本の loop_monitors は review-1st ⇄ fix しか監視しておらず、
fix と no-fix の判定がすれ違う往復(相互遷移が実在する)は max_steps まで
無監視で回れた。既存エントリと同じ threshold・judge 構成で cycle を追加する。
判定基準(同一 finding_id の残存と修正の着地)は既存の
loop-monitor-ai-antipattern-fix がそのまま適用できるため facet は新設しない。

PR #1017 の CodeRabbit 指摘への対応。
takt-bench v3-r2(292分・49イテレーション)の台帳解剖で確定した3つの
非収束要因への対応。

1) 幻覚 location: 実在しないファイルへの指摘4件(うち critical 1件)が
   台帳に入り、critical は waive 不可 + 証跡は file:line 必須のため永久 open
   だった。raw の受け入れ時に path と行範囲を決定的に検証し(末尾改行の
   境界と symlink 脱出も遮断)、不採用は監査記録に残す。既存 finding には
   invalidated 状態を新設 — エンジンが fs で候補を計算し、manager は候補から
   選ぶだけ(LLM の主張単独では無効化できない)。critical も無効化できる。

2) 同一性の誤り: familyTag + location の完全一致は、同一問題を分裂させ
   (タグはレビュアごとの語彙、行はコード変更でずれる。F-0011/17/18 が
   3重に積まれた)、別問題を誤統合もした(F-0016 の混成)。familyTag と
   行番号を同一性から降格し、機械分類は3ケース(完全同一 raw / relation +
   targetFindingId の明示参照 / resolution_confirmation)に縮小。同一性の
   最終判断は manager の意味判断へ。機械の畳み込み鍵は正規化 path + title +
   description の完全一致(大小文字保存)に統一。

3) 逃げ道の不足: raw findings に relation(new / persists /
   resolution_confirmation / reopened)を追加し、既存指摘への言及を構造化
   (旧形式は kind と targetFindingId から導出して後方互換)。manager には
   unsupported 判断を追加 — 根拠が成立しない再報告を new に落とさない。
   duplicateDecisions で既存の重複 finding を canonical へ統合し、重複側は
   superseded にする(open 限定・循環検証・履歴保存。resolved/waived とは
   別の意味)。invalidated / superseded はサマリとルールコンテキストに
   件数を出し、open が減った理由を監査可能にする。

台帳スキーマは追加的(version 1 のまま)。v3-r2 の実台帳をフィクスチャに、
F-0016 を生んだ実 raw 群のリプレイで再マージが起きないことを固定した。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/__tests__/finding-schemas.test.ts (1)

33-39: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

invalidateDecisions/duplicateDecisions の required/properties 整合性チェックが漏れている

このテストは rawDecisions・disputeDecisions・conflictDecisions の items.required しか検証しておらず、本PRで新設された invalidateDecisions・duplicateDecisions の items.required が items.properties のキーと一致するかを検証していません。strict JSON Schema の構造整合性を保証するのがこのテストの目的である以上、新設2配列も同様に検証すべきです。

✅ 追加すべき検証例
     expect(decisionsProperties.conflictDecisions.items.required).toEqual(Object.keys(decisionsProperties.conflictDecisions.items.properties));
+    expect(decisionsProperties.invalidateDecisions.items.required).toEqual(Object.keys(decisionsProperties.invalidateDecisions.items.properties));
+    expect(decisionsProperties.duplicateDecisions.items.required).toEqual(Object.keys(decisionsProperties.duplicateDecisions.items.properties));
   });

As per path instructions, builtins/ja/facets/policies/testing.md の「契約変更(...)は「新経路だけでなく既存同種分岐も同一契約を満たす」ことを検証(旧仕様の不在だけの否定テストはREJECT)」に基づき指摘しています。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/finding-schemas.test.ts` around lines 33 - 39, Extend the test
in `finding-schemas.test.ts` to also compare `items.required` with
`Object.keys(items.properties)` for both `invalidateDecisions` and
`duplicateDecisions`, alongside the existing decision arrays, so all manager
decision schema arrays satisfy the same strict-schema contract.

Source: Path instructions

src/__tests__/finding-decision-assembly.test.ts (1)

582-928: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

canonical と waive の同時決定を回帰テスト化してください。

duplicate canonical が同じラウンドで waive されるケースを追加し、waive が reject されることを固定してください。現状の assembly は canonical を遷移済み扱いしておらず、この矛盾を通します。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/finding-decision-assembly.test.ts` around lines 582 - 928,
追加の回帰テストとして、同一ラウンドで重複する canonical finding に対して一方を canonical、もう一方を waive とするケースを
assembleManagerOutput のテスト群に追加してください。waive 対象が既存または同一ラウンドで canonical として選択された
finding と衝突する場合、waive が rejectedRawDecisions に入り、受理された waive
や不正な状態遷移が出力されないことを検証してください。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/__tests__/fixtures/v3-r2-ledger.json`:
- Around line 2141-2151: Correct the malformed targetFindingId values in the
fixture entries near the resolution confirmations, including the entry
identified by the rawFindingId for the contract-lifecycle review and the
corresponding entry near the other reported location, setting both values to
F-0006.

In `@src/core/models/finding-schemas.ts`:
- Around line 243-254: FindingManagerOutputSchema の
duplicateFindings.duplicateFindingIds
が空配列を許可し、FindingManagerDuplicateDecisionSchema と不整合になっています。duplicateFindingIds の
z.array(nonEmptyString) に .min(1) を追加し、validateDuplicateFindings
でも空配列を拒否する非空検証を実装して、両方の検証経路を一貫させてください。

In `@src/core/models/finding-types.ts`:
- Around line 99-108: Update the comments near RAW_FINDING_RELATIONS to
reference the existing deriveRawFindingRelation or resolveRawFindingRelation
symbols instead of parseRawFindingRelation. Extract the shared
relation-derivation logic from mechanical-classification.ts's
effectiveRawFindingRelation into a common helper, then make both
effectiveRawFindingRelation and the schema-side derivation use it, preserving
relation-first, resolution_confirmation, and targetFindingId-based behavior.

In `@src/core/workflow/findings/decision-assembly.ts`:
- Around line 908-911: transitionedFindingIds の構築で duplicate 側の ID
だけでなく、duplicateResult.duplicateFindings に含まれる各 canonical の ID
も追加してください。canonical が open のまま残る契約に合わせ、後続の検証・reconcile で canonical が waive
対象にならないよう、withInvalidateTransitioned と重複側 ID をまとめる集合へ canonical ID を含めてください。
- Around line 153-165: Recompute invalidation candidates at save time using the
latest ledger and working directory instead of trusting
managerOutput.invalidatedFindings IDs. Update freshAssembly to call
computeInvalidLocationCandidates(cwd, freshLedger.findings) and pass the
resulting set as invalidLocationCandidateFindingIds, preserving only candidates
still invalid under the current state.

In `@src/shared/prompts/ja/parts/finding_contract_instruction.md`:
- Around line 11-19: isReviewer 向けの raw finding 指示に、relation だけでなく必須フィールド kind
の設定規則も明記してください。relation が "new" または "persists" の場合は kind を "issue" に、relation が
"resolution_confirmation" または "reopened" の場合は kind を "resolution_confirmation"
に設定するよう、既存の「kind ではなく relation を設定」する説明を補強してください。

---

Outside diff comments:
In `@src/__tests__/finding-decision-assembly.test.ts`:
- Around line 582-928: 追加の回帰テストとして、同一ラウンドで重複する canonical finding に対して一方を
canonical、もう一方を waive とするケースを assembleManagerOutput のテスト群に追加してください。waive
対象が既存または同一ラウンドで canonical として選択された finding と衝突する場合、waive が rejectedRawDecisions
に入り、受理された waive や不正な状態遷移が出力されないことを検証してください。

In `@src/__tests__/finding-schemas.test.ts`:
- Around line 33-39: Extend the test in `finding-schemas.test.ts` to also
compare `items.required` with `Object.keys(items.properties)` for both
`invalidateDecisions` and `duplicateDecisions`, alongside the existing decision
arrays, so all manager decision schema arrays satisfy the same strict-schema
contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 58dd2b42-3312-42f3-a403-d6ad73894089

📥 Commits

Reviewing files that changed from the base of the PR and between 3d34ff5 and 26b9ead.

📒 Files selected for processing (48)
  • builtins/en/facets/instructions/findings-manager.md
  • builtins/en/facets/output-contracts/findings-manager.md
  • builtins/en/workflows/backend-cqrs-for-local-llm.yaml
  • builtins/en/workflows/backend-for-local-llm.yaml
  • builtins/en/workflows/dual-for-local-llm.yaml
  • builtins/en/workflows/frontend-for-local-llm.yaml
  • builtins/en/workflows/takt-default-for-local-llm.yaml
  • builtins/ja/facets/instructions/findings-manager.md
  • builtins/ja/facets/output-contracts/findings-manager.md
  • builtins/ja/workflows/backend-cqrs-for-local-llm.yaml
  • builtins/ja/workflows/backend-for-local-llm.yaml
  • builtins/ja/workflows/dual-for-local-llm.yaml
  • builtins/ja/workflows/frontend-for-local-llm.yaml
  • builtins/ja/workflows/takt-default-for-local-llm.yaml
  • src/__tests__/finding-convergence.test.ts
  • src/__tests__/finding-decision-assembly.test.ts
  • src/__tests__/finding-ledger-store.test.ts
  • src/__tests__/finding-manager-mechanical-runner.test.ts
  • src/__tests__/finding-manager-output-validation.test.ts
  • src/__tests__/finding-mechanical-classification.test.ts
  • src/__tests__/finding-reconciler.test.ts
  • src/__tests__/finding-schemas.test.ts
  • src/__tests__/fixtures/v3-r2-ledger.json
  • src/__tests__/for-local-llm-replan-wiring.test.ts
  • src/__tests__/instruction-builder-dispute-guidance.test.ts
  • src/__tests__/it-finding-contract-flow.test.ts
  • src/__tests__/workflow-engine-structured-caller.test.ts
  • src/core/models/finding-schemas.ts
  • src/core/models/finding-types.ts
  • src/core/workflow/engine/ParallelRunner.ts
  • src/core/workflow/engine/StepExecutor.ts
  • src/core/workflow/findings/admission-validation.ts
  • src/core/workflow/findings/context.ts
  • src/core/workflow/findings/contract-intake.ts
  • src/core/workflow/findings/decision-assembly.ts
  • src/core/workflow/findings/decision-rules.ts
  • src/core/workflow/findings/location.ts
  • src/core/workflow/findings/manager-output-validation.ts
  • src/core/workflow/findings/manager-runner.ts
  • src/core/workflow/findings/manager-step.ts
  • src/core/workflow/findings/mechanical-classification.ts
  • src/core/workflow/findings/reconciler.ts
  • src/core/workflow/findings/store.ts
  • src/core/workflow/findings/types.ts
  • src/shared/prompts/en/finding_manager_instruction.md
  • src/shared/prompts/en/parts/finding_contract_instruction.md
  • src/shared/prompts/ja/finding_manager_instruction.md
  • src/shared/prompts/ja/parts/finding_contract_instruction.md

Comment thread src/core/models/finding-schemas.ts Resolved
Comment thread src/core/models/finding-types.ts Resolved
nrslib added 2 commits July 11, 2026 08:07
依存更新は package.json / package-lock.json / flake.nix の3点セット。
リベース時の lockfile 変更に flake の固定が追随していなかった。
v3-r2 実測の詰み — coder の反証とレビュアの再観測が衝突した conflict が、
裁定の場なく即 ABORT — への対応(収束設計 Phase B)。

- finding-conflict-adjudication をエンジン合成の実在ステップとして注入
  (イベント・履歴・resume・ループ検出は標準機構)。supervisor persona +
  読み取り専用ツールで conflict の全証拠と現在の diff を読み、構造化4択
  (finding_valid / finding_stale / evidence_invalid / undetermined)で裁定。
  outcome → 遷移はエンジンが照合し、LLM の自由記述に権限を与えない
- actionable(finding_valid + 修正案あり)は conflict を解消して fix へ、
  stale/evidence_invalid は resolved / invalidated へ(file:line 実在検証つき)、
  undetermined はゲートを開けず ABORT 側(安全側エスカレーション)
- 裁定は証拠集合につき1回: 内容ベースの evidenceHash(raw 本文 + disputes +
  conflict description)を LLM 呼び出し前に排他区間で記録し、適用直前に
  fresh hash と一致確認(不一致は破棄して監査)。過去の全裁定・全 attempt と
  照合して既出 hash への再裁定を禁止。同一 run の rate-limit fallback だけが
  pending 予約を引き継げる(別 run の resume は封鎖)
- 戻り先 origin は attempt 記録に永続化し、previousStep → pending attempt →
  一意な配線元 → ABORT の順で解決(推測遷移をしない)
- for-local-llm 10本 × reviewers/final-gate の conflict ルールを裁定経由に
  変更し、裁定を迂回していた ai() ルールを削除
- レビュア再生成: 既存 open finding と同 path+title(description は異なる)の
  relation=new raw は1回だけ明示を求めて再生成。契約検証(raw 集合の完全一致・
  非対象 raw 不変)に違反する再生成は破棄し、なお不整合なら unsupported として
  監査記録に落とす。例外時は元出力保持でステップを失敗させない

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/__tests__/finding-conflict-adjudication-engine.test.ts`:
- Around line 103-117: Rename finding-conflict-adjudication-engine.test.ts to
use the integration-test naming convention, such as
finding-conflict-adjudication-engine.integration.test.ts, so
scripts/run-npm-test.mjs routes it to the integration gate instead of the unit
shard; preserve the existing describe block and tests unchanged.

In `@src/core/workflow/engine/WorkflowEngine.ts`:
- Around line 246-257: Use the canonical workflow name from
this.findingLedgerStore.workflowName when constructing
findingConflictAdjudicationRunner, replacing this.config.name for the
workflowName option so inherited parent ledgers remain consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5396f50b-15c9-4dec-b898-3b6b98085f84

📥 Commits

Reviewing files that changed from the base of the PR and between 31daf8c and 0d53ecf.

📒 Files selected for processing (38)
  • builtins/en/workflows/backend-cqrs-for-local-llm.yaml
  • builtins/en/workflows/backend-for-local-llm.yaml
  • builtins/en/workflows/dual-for-local-llm.yaml
  • builtins/en/workflows/frontend-for-local-llm.yaml
  • builtins/en/workflows/takt-default-for-local-llm.yaml
  • builtins/ja/workflows/backend-cqrs-for-local-llm.yaml
  • builtins/ja/workflows/backend-for-local-llm.yaml
  • builtins/ja/workflows/dual-for-local-llm.yaml
  • builtins/ja/workflows/frontend-for-local-llm.yaml
  • builtins/ja/workflows/takt-default-for-local-llm.yaml
  • src/__tests__/builtin-takt-default-provider-options.test.ts
  • src/__tests__/finding-adjudication-persona-prompt.test.ts
  • src/__tests__/finding-conflict-adjudication-engine.test.ts
  • src/__tests__/finding-conflict-adjudication-runner.test.ts
  • src/__tests__/finding-conflict-adjudication.test.ts
  • src/__tests__/finding-relation-coherence.test.ts
  • src/core/models/finding-schemas.ts
  • src/core/models/finding-types.ts
  • src/core/models/workflow-types.ts
  • src/core/workflow/constants.ts
  • src/core/workflow/engine/ParallelRunner.ts
  • src/core/workflow/engine/StepExecutor.ts
  • src/core/workflow/engine/WorkflowEngine.ts
  • src/core/workflow/engine/WorkflowEngineStepCoordinator.ts
  • src/core/workflow/engine/WorkflowRunLoop.ts
  • src/core/workflow/engine/WorkflowValidator.ts
  • src/core/workflow/findings/adjudication-apply.ts
  • src/core/workflow/findings/adjudication-evidence.ts
  • src/core/workflow/findings/adjudication-runner.ts
  • src/core/workflow/findings/adjudication-step.ts
  • src/core/workflow/findings/context.ts
  • src/core/workflow/findings/manager-runner.ts
  • src/core/workflow/findings/relation-coherence.ts
  • src/core/workflow/findings/schemas.ts
  • src/core/workflow/findings/store.ts
  • src/core/workflow/findings/types.ts
  • src/infra/config/loaders/workflowParser.ts
  • src/shared/prompts/en/finding_conflict_adjudication_instruction.md

nrslib added 2 commits July 11, 2026 11:18
doctor のグラフ検査が COMPLETE/ABORT しか予約名を知らず、正当な配線
(新 builtin 10本を含む)に unknown next step の偽陽性を出していた。
予約合成名を認識させ、あわせて validator と同じ「finding_contract なしの
配線はエラー」判定を doctor 側にも追加(ステップ直下と loop monitor judge)。
- 保存直前の再組み立てで invalidation 候補を fresh 台帳・現 cwd で再計算し、
  判断時点と保存時点の間に location が有効化した finding への stale な
  invalidate を不採用にする(判断の鮮度保証を carried conflict と同水準に)
- duplicateDecisions の canonical を waive/note 併存禁止集合に追加
  (canonical は open 維持が契約)
- duplicateFindingIds の非空をスキーマと検証の両方で強制(決定側と対称)
- relation 導出ロジックを deriveRawFindingRelation に一本化
  (mechanical-classification 側の独立実装を委譲に変更)、実在しない
  関数名を指していたコメントを修正
- レビュア向けプロンプト(en/ja)に kind の設定規則を明示
- workflow_call 継承時、裁定 runner の workflowName を台帳ストアの
  正準名(親名)にし、saveLedger の検証不整合を解消

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/infra/config/loaders/workflowDoctorGraph.ts (1)

161-171: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

parallel substep ルールに findingContractConfigured チェックが欠落している

通常ステップ(Lines 145-151)と loop monitor(Lines 177-183)には findingContractConfigured が偽のとき FINDING_CONFLICT_ADJUDICATION_STEP へのルーティングを error 診断する分岐が追加されていますが、parallel substep(Lines 161-171)には同じチェックがありません。SPECIAL_NEXT に FINDING_CONFLICT_ADJUDICATION_STEP が含まれるため、parallel substep から同ステップへのルーティングは「unknown next step」として検出されず、finding_contract 未設定でもエラーが出ません。

🔧 parallel substep ルールに finding_contract チェックを追加
     for (const sub of step.parallel ?? []) {
       for (const rule of sub.rules ?? []) {
+        if (!findingContractConfigured && findingConflictAdjudicationTargetNeedsContract(rule)) {
+          diagnostics.push({
+            level: 'error',
+            message: `Step "${step.name}/${sub.name}" routes to "${FINDING_CONFLICT_ADJUDICATION_STEP}" but finding_contract is not configured`,
+          });
+        }
         if (!rule.next || SPECIAL_NEXT.has(rule.next) || stepNames.has(rule.next)) {
           continue;
         }
         diagnostics.push({
           level: 'error',
           message: `Step "${step.name}/${sub.name}" routes to unknown next step "${rule.next}"`,
         });
       }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/infra/config/loaders/workflowDoctorGraph.ts` around lines 161 - 171,
parallel substep のルール検証に、findingContractConfigured が偽で rule.next が
FINDING_CONFLICT_ADJUDICATION_STEP の場合は error 診断を追加してください。通常ステップおよび loop monitor
の既存チェックと同じ条件・診断内容を、parallel/sub.rules のループ内で SPECIAL_NEXT
の判定より適切に処理できるよう実装してください。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/infra/config/loaders/workflowDoctorGraph.ts`:
- Around line 113-134: Add finding_contract validation for rules nested in
parallel substeps, not only top-level step rules and loop monitors. Update
validateDoctorGraph and its rule traversal to apply
findingConflictAdjudicationTargetNeedsContract to each step.parallel[].rules,
and add positive and negative coverage in workflow-doctor.test.ts for
parallel-substep transitions.

---

Outside diff comments:
In `@src/infra/config/loaders/workflowDoctorGraph.ts`:
- Around line 161-171: parallel substep のルール検証に、findingContractConfigured が偽で
rule.next が FINDING_CONFLICT_ADJUDICATION_STEP の場合は error 診断を追加してください。通常ステップおよび
loop monitor の既存チェックと同じ条件・診断内容を、parallel/sub.rules のループ内で SPECIAL_NEXT
の判定より適切に処理できるよう実装してください。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2e2be500-cbc0-44a0-8591-89ee64206865

📥 Commits

Reviewing files that changed from the base of the PR and between 0d53ecf and 9bde97f.

📒 Files selected for processing (18)
  • src/__tests__/finding-conflict-adjudication-engine.test.ts
  • src/__tests__/finding-contract-instruction.test.ts
  • src/__tests__/finding-convergence.test.ts
  • src/__tests__/finding-manager-output-validation.test.ts
  • src/__tests__/finding-schemas.test.ts
  • src/__tests__/instruction-builder-dispute-guidance.test.ts
  • src/__tests__/workflow-doctor.test.ts
  • src/core/models/finding-schemas.ts
  • src/core/models/finding-types.ts
  • src/core/workflow/engine/WorkflowEngine.ts
  • src/core/workflow/findings/decision-assembly.ts
  • src/core/workflow/findings/manager-output-validation.ts
  • src/core/workflow/findings/manager-runner.ts
  • src/core/workflow/findings/mechanical-classification.ts
  • src/core/workflow/findings/schemas.ts
  • src/infra/config/loaders/workflowDoctorGraph.ts
  • src/shared/prompts/en/parts/finding_contract_instruction.md
  • src/shared/prompts/ja/parts/finding_contract_instruction.md

nrslib added 6 commits July 11, 2026 12:03
qwen のツール名幻覚(実在しない 'run' や 'todo_write')が閾値2の検出器に
当たると即ステップ失敗 → workflow abort になり、v3-r3 が5イテレーションで
死んだ。プラグインでのエイリアスツール登録は不採用(公開 plugin API に
組み込み bash への転送手段が無く、自前 spawn は権限・フック・timeout を
バイパスする偽物になる)。

- 検出器の発火を構造化型 {tool, message} で client に伝える(文字列再パース廃止)
- unavailable-tool-recovery.ts: StructuredOutput 以外の幻覚ツール名で発火した
  場合、fresh session + 再試行前置文で1回だけ再実行。再発なら即エラー(計2 attempt)
- 前置文: 幻覚名の安全な引用、有効ツール一覧の動的生成、既知エイリアス案内
  (run→bash / todo_write→todowrite。変換先が有効なステップのみ)、
  workspace 継続の警告(fresh session でも作業ツリーは残る。既存変更を確認して
  続行、盲目的な再実行と rollback の禁止)
- recovery 優先順位を明文化: native SO 退化 → stale StructuredOutput →
  一般 unavailable-tool → transient → error。予算は独立、各1回
- 閾値2は不変(v3-r1 の216連続を止める価値は維持)
サブステップの rules の next は並列集約で消費されない(condition 文字列しか
読まれない)のに、合成ステップの注入条件はサブステップ配線も数えるため、
「注入されるが到達不能」という黙った死に設定が作れた。doctor はこれを
警告し(親の rules に配線せよ)、finding_contract なしの配線はエラーにする。
WorkflowValidator も同じ境界に揃える。
…E ゲートで塞ぐ

レビュア出力の kind/relation 矛盾などの曖昧な raw finding を、parse 失敗による
run 死ではなく3段の梯子で処理する:

1. 機械正規化: 決定可能な矛盾はコードで正規化(kind は relation から導出、
   矛盾 target は wire から除外)。正規化前の元の主張は rawNormalizations として
   intake 直後に write-ahead 永続化(監査復元可能)
2. レビュア1回突き返し: relation 不整合はレビュアへ1度だけ訂正機会を返す
3. manager 解釈: なお曖昧な raw は findings-manager が解釈を提案(capability 格子で
   ambiguous 起源は new/provisional/conflict のみ発行可能、resolve/waive 系は不可)

解釈不能な raw は provisional finding(status=open + provisional metadata)として
台帳に着地し、gate-blocking として COMPLETE をエンジン不変条件で fail-fast させる。
builtin 10 ワークフローに findings.provisional ルーティングを追加し、
旧 invalid_manager_output 迂回路は削除(doctor 警告 + docs 移行手順あり)。

- 2層スキーマ(candidate/canonical の nominal brand + WeakSet 登録)と ambiguity taint
- CAS 前提条件(全 confirmation/reopen/waive/invalidate/supersede)
- interpretation WAL(started/completed/ledger_applied、applied 再来は着地種別で分岐)
- provisional の確定は完全 identity または保存済み lineage の一意対応のみ
- clean raw の location 不成立は drop せず invalid-location-evidence provisional 化
- 出力上限は schema 構造(maxItems/maxLength)で強制
- 攻撃回帰テスト35件(finding-ladder-attacks.test.ts)

codex 検証4巡(B1-B4 + 監査復元 + write-ahead)合格済み
…容に受ける

v3-r3 ベンチ実測で、reviewer(gemma) が legacy の kind を rawFindings に併記し、
strict な wire スキーマ(additionalProperties: false・kind なし)が 1回訂正後も
出力を拒否して run が abort した。intake 側は kind を受けて機械正規化・claimedKind
監査する実装済みで、wire スキーマだけが寛容経路を到達不能にしていた。

単純に kind を optional 追加すると OpenAI/Codex 系 native structured output の
全プロパティ required 制約に違反し、逆に native 経路が生成前に死ぬため、役割で分離する:

- RawFindingsOutputJsonSchema(provider 向け): strict のまま復元。native provider は
  スキーマで生成が拘束されるため kind を出力できず、寛容化は不要
- RawFindingsOutputValidationJsonSchema(post-hoc 検証用、新設): strict + kind optional。
  スキーマが生成を拘束しない formless/劣化経路(opencode+ollama 等)の出力検証専用
- WorkflowStructuredOutput.validationSchema を追加し「provider へは schema のみ /
  post-hoc は validationSchema ?? schema」を契約として明文化。StepExecutor の
  post-hoc 検証2箇所を validationSchema 優先に変更

codex 検証合格(native/post-hoc 両立・選択漏れなし・寛容版の provider 逆流なし)
… 監査添付・npm test 高速化

v3-r3 ベンチ実測で、コードは収束したのに provisional が9件蓄積して COMPLETE に
到達できない非収束が観測された。codex 診断は「安全性(観測を消さない)は証明済みだが
liveness(いつか収束する)が未証明」。原因別に4対策を入れる:

- A-1: resolution_confirmation の証拠不採用時の除外が clean 経路にしかなく、
  tainted 経路では一律 provisional 化されていた経路非対称バグを修正。
  さらに tainted confirmation は admission 成否に依らず ladder に載せず
  監査保存のみとし、着地規則を4象限で対称化(曖昧起源は capability 格子上
  resolve 権限を持たないため、blocker も作らず target も触らない)
- A-2: location の機械正規化 — 行範囲 path:start-end は path で実在検証、
  N/A と空文字は locationless。適用事実は rawNormalizations に監査記録
- A-3: 証跡不成立の persists 再観測は、reconcile 後の台帳で target が open なら
  rejectedObservations へ監査添付(独立 provisional を作らない。target が既に
  gate を塞いでいるため観測は消えずゲートも開かない)。reconcile 中に target が
  閉じた場合は provisional へフォールバック(gate 非減少)
- A-4: npm test の引数なし経路を4シャード同時×1ワーカーから test:unit:parallel
  単一実行(既定75%ワーカー)へ変更。実測 98.6s→67.2s。CI は ci.yml が
  shard を直接実行しており無影響

codex 検証2巡合格(v3-r3 実台帳データでの再現防止・攻撃回帰42件・gate 非減少)
v3-r4 ベンチ実測で、implement 中の qwen3-coder-next が幻覚 oldString による
edit 失敗を14分半に散発させ(間に build 完走・write 成功を挟む生産的走行)、
累積25エラーで ToolErrorBudgetDetector が発動して run が abort した。
旧検出器は成功ツールコールを考慮しない単純累積で、「プロバイダ劣化スピン」
(導入動機: 559ループ・26分空転)と「弱いモデルの edit 税」を区別できない。

codex 診断に基づき単一カウンタを3つの独立した保護へ分離する:

- tool_error_burst: 進捗感知型の退行検出。成功なし連続数(既定10 — 559型は
  旧25より速く停止)、直近ウィンドウのエラー率、非 edit 同一署名反復で判定。
  弱い進捗(read/glob/grep)は緩和のみ、強い進捗(edit/write/patch/bash 成功)
  は短期カウンタとウィンドウをリセット
- edit_conflict_loop: 幻覚 oldString の専用検出。署名 = sha256(filePath+oldString)。
  同一署名3反復 → 同一セッション correction(再読込を指示)→ 再発で
  fresh-session recovery 最大1回 → さらに再発で失敗。矯正済み署名を追跡し、
  別署名の新規 conflict は自前の correction 段階から開始
- absolute_cost_limit: recovery をまたいで引き継ぐ絶対台帳(総エラー上限
  既定60、同一署名絶対反復12)。fresh session でも短期カウンタのみリセット

処遇は型付き ToolGuardFailure として client 内で決定(engine の文字列再解析なし、
needs_fix/plan への自動迂回なし)。ToolHealthStats を debugInfo に構造化して
閾値校正の材料にする。

edit の oldString/newString 本文は全経路で {sha256:先頭12,length} にマスク:
tool_use(発火時点)/ tool_result / debug ログ / ToolGuardFailure.message /
AgentResponse.error / correction 文 / provider-events.jsonl。

codex 検証5巡合格(誤 burst 再現・署名状態機械・漏えい経路網羅)
@nrslib
nrslib merged commit e662ab1 into main Jul 19, 2026
11 checks passed
@nrslib
nrslib deleted the feat/log-opencode-tool-error-input branch July 19, 2026 23:27
nrslib added a commit that referenced this pull request Jul 19, 2026
* fix: stop budget の時間上限を opt-in にしレビュアーの品質ゲート証跡指摘を入場から外す

- stop_budget.max_minutes の既定値(90分)を廃止し、設定時のみ有効にする。
  守るべき病理(churn)はラウンド数に現れるのであって時間には現れず、時間の
  既定上限は健全な大型 run を誤停止させた実測がある(PR #1017 再走: 11/40
  ラウンドで収束中に 90 分側が発火し NEEDS_ADJUDICATION 落ち)。ラウンド上限
  (既定 40)は決定的な停止保証として残す
- 品質ゲートの実行・証跡への要求を raw issue にしない規定を、全レビュアー
  共通の finding contract 注入プロンプトとレビューポリシー(en/ja)に追加。
  検証結果の評価は final gate の職掌で、locationless の検証不能な暫定として
  完了ゲートを塞ぎ続ける実測があった(同一要求が5重に積まれ stop budget を
  消費した)。テスト不足の指摘は location + source_quote を要求する

* docs: stop_budget の既定値コメントを opt-in 仕様へ追随させる

* fix: 品質ゲート証跡の欠落を issue にしない規定へレビューポリシー全体を整合させる

証跡欠落の報告を課していた既存規定が locationless 指摘 5 連発の発生源
だった(CodeRabbit 指摘で発覚した矛盾)。欠落は未確認範囲として記録し、
評価は final gate に委ねる。
nrslib added a commit that referenced this pull request Aug 5, 2026
* fix(opencode): 実行ガードを再設計し健全実行の誤爆を解消する

健全な implement 実行(13分・ツール82回成功・可視テキスト65.5KB)が
text_bytes 64KiB 上限で強制終了する誤爆が発端。#1017 のローカルLLM
防護装置群を全数調査し、上限値が正常系の実測と同じ桁にあるガードと、
長時間実行で単調に溜まり必ず発火する累積型ガードを撤去・再設計した。

- 全11ガードを共通抽象 + レジストリ + Strategy に集約
  (guards/ 新設。追加は登録1エントリで完結、実行経路に profile 分岐なし)
- 撤去: 累積エラー総数60 / 同一署名反復12+8 / sensitive ソース数256 /
  検査全バイト課金
- 緩和: text 64KiB→1MiB / 追跡ID 1,024→8,192
- 置換: sensitive は発見候補だけに課金(1,024個/1MiB、fail-closed 維持)、
  反復検出は連続完全一致タプル12回のみ
- 追加: call 全体 wall-clock 60分(AbortSignal 全配線・side-effect fence)
  と reasoning 4MiB — 従来どのガードにも掛からず永久ハングになる経路を封鎖
- provider_options.opencode.guards(profile standard|minimal /
  model_profiles ワイルドカード / call_timeout_ms / 容量上書き)を新設。
  mandatory ガードはどの設定でも外せない
- 廃止 env 5種は使用時に警告。60分超の call は call_timeout_ms の明示が必要

* fix(opencode): CodeRabbit レビュー指摘11件に対応する

- ByteVolumeGuard: 累積 part.text の再送を二重課金しない(オフセット差分課金)
- reasoning 4MiB 上限を consumeReasoningDelta の主経路にも適用
- CycleBudgetGuard / ExactLoopGuard / ExactRepeatStreakGuard に
  attempt 境界の状態リセットを実装(retry への持ち越し防止)
- sensitive 予算の理由未設定時は source_scan と偽らず unknown を表示
- ツール入力ハッシュの重複判定を SensitiveBudgetGuard に一本化し上限付きで保持
- idle timeout メッセージ生成を describeOpenCodeIdleTimeout() に集約
- WallClockGuard のタイマーを unref() し正常終了を妨げない
- docs 見出しレベル修正、テスト名の現行仕様化、境界値テストの it.each 分割

* fix(opencode): delta 無し累積 reasoning 更新を未出力差分に変換してから課金する
nrslib added a commit that referenced this pull request Aug 15, 2026
* fix(engine): 移行できない旧形式成果物で requeue を殺さず警告して除外する

requeue 起動時、論理 call-site を一意に証明できない旧形式の成果物 namespace で
明示エラーを投げて run 全体が起動不能になっていた。resume の原則(再開位置は
ユーザーが決める・不整合は警告で続行・欠落は無いものとして扱う)に合わせる。

- 曖昧な旧形式 namespace はユーザー可視の警告(onWarning → OutputFns.warn)を
  出して index から除外し、run は続行する
- 現行形式の digest を occurrence 非依存化し、除外対象への巻き込みを防止
- fallback 除外済み namespace は完全 path 単位で予約し、新規生成時に再利用しない
  (名前衝突・上書きの防止)
- 実事故の namespace 形式と現行形式の両方で境界・採番・非衝突・可視警告をテスト

* fix: 警告を出さず黙って除外し、digest は祖先 occurrence 分離を維持する

- 曖昧な旧形式成果物は仕様(requeue は無いものとして扱う)どおり黙って index から除外
- digest の occurrence 非依存化を取り消し(#1359 の nested 分離の退行を解消)
- 衝突防止は除外済み namespace の完全 path 予約のみで成立

* fix(engine): report 参照を「在るなら見つかる・無いなら進む」へ直す

1. requeue でコピー済みの report は、元 run の namespace チェーンで一意に解決した
   対応を resume report snapshot(manifest v2)へ記録し、新 run から確実に参照できる
   ようにする(存在するのに見えないのは解決機構のバグ、の原則)。0 iteration で
   死亡した run を挟む連鎖 requeue でも snapshot を伝搬する
2. run の成果物・レポートの欠落は致命エラーにしない。参照箇所を欠落を示す一文へ
   置換して続行する(#1017 で入った欠落=throw を廃止)。instruction の {report:...}、
   judge report、dynamic selector、exec/interactive の report 読み出し、trace 生成の
   6経路を降格
3. 安全境界・永続データ破損・整合性違反・設定検証など「壊れている」系の fail-fast は維持

* fix: レビュー指摘対応 — snapshot exact mapping の優先・use_judge の応答維持・本番経路の統合テスト化

* fix: CodeRabbit指摘対応 — manifest v2 の後方互換と判定フェーズの共有 resolver 化

* test: exec /go の2段呼び出しへ旧テストを追従させる
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant