diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 95e1ac2..4165ea6 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -8,7 +8,7 @@ "source": "github", "repo": "sd0xdev/sd0x-dev-flow" }, - "description": "Harness engineering for Claude Code — hook-enforced dual review, state-machine gates, and fail-closed safety where it counts." + "description": "Harness engineering for Claude Code — hook-enforced review gates, state-machine gates, and fail-closed safety where it counts." } ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 4c40b80..2839137 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "sd0x-dev-flow", - "description": "Harness engineering for Claude Code — hook-enforced dual review, state-machine gates, and fail-closed safety where it counts.", - "version": "3.0.12", + "description": "Harness engineering for Claude Code — hook-enforced review gates, state-machine gates, and fail-closed safety where it counts.", + "version": "4.0.0", "author": { "name": "sd0xdev" }, diff --git a/.gitignore b/.gitignore index d2f547e..32a8885 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,9 @@ node_modules/ .env.* settings.local.json .claude_review_state.json +.claude_review_state.json.* .claude_nit_history.json +.claude_workflows/ .claude/ # Local-only skills (not published) diff --git a/.sd0x/install-state.json b/.sd0x/install-state.json index 63933f2..437540d 100644 --- a/.sd0x/install-state.json +++ b/.sd0x/install-state.json @@ -2,7 +2,7 @@ "schema_version": 1, "installed_at": "2026-04-02T08:45:00Z", "updated_at": "2026-04-04T00:00:00Z", - "plugin_version": "3.0.12", + "plugin_version": "4.0.0", "hook_scripts": { "pre-edit-guard.sh": { "hash": "1e3b3847f5ff3a6eac654840edc8c7152e0d3662" diff --git a/CLAUDE.md b/CLAUDE.md index 1a243ce..8bde864 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,6 +8,17 @@ | `.md` docs | `/codex-review-doc` | `/codex-review-fast` | | Comments only | - | All | +> **What the Stop Hook actually enforces**: that *a* precommit gate ran and passed — not *which* variant. `/precommit-fast` skips the build/typecheck step yet satisfies the gate by default. Two settings are needed to make the full variant above actually binding, and each alone is insufficient: +> +> | Setting | Without it | +> |---------|-----------| +> | `PRECOMMIT_REQUIRE_FULL=1` | a passing `mode: fast` (or an unrecorded mode) satisfies the gate | +> | `STOP_GUARD_MODE=strict` | the default `warn` mode prints the missing step to stderr and **still exits 0** | +> +> With both set, the flag is honoured in **both** of stop-guard's modes: from `precommit.mode` when a state file exists, and from the invoked command name (`/precommit` vs `/precommit-fast`) in the transcript fallback. The fallback arm ships with the same change as this note — before it, the flag was state-file-only, so a session without a readable state file (the degraded path where an unproven verdict is least affordable) silently accepted the fast gate. Verify rather than assume: `grep -c PRECOMMIT_REQUIRE_FULL hooks/stop-guard.sh` should report 4, and a checkout predating this change reports 0. +> +> Even with both, the flag gates the **command variant**, not the stages that ran: a repo with no build script records `full` with no typecheck behind it. See `docs/features/precommit-tiering/2-tech-spec.md` §6. + Before PR: `/pr-review` ## Workflow @@ -25,12 +36,17 @@ After editing code or docs, you **MUST** run the review command **in the same re |------------------|----------------|--------------| | code files | `/codex-review-fast` | `/precommit` | | `.md` docs | `/codex-review-doc` | (done) | -| Review found issues | Fix all → re-run same review | — | +| Review found **blocking** issues | Fix all → re-run same review | — | +| Review found **sub-threshold** issues | Log `[NIT_DEFERRED]`, do not re-review | continue to the "Then on pass" column | + +One reviewer — Codex. `/codex-review-fast` and `/codex-review-doc` do not launch a secondary; `/codex-review-branch --dual` is the only code-review entry point where two reviewers run, and it is off unless the flag is passed. (`/plan-review --dual` is the plan-mode equivalent, also off by default.) + +What counts as blocking comes from the **tier** (`fast` P0 · `standard` P0/P1 · `thorough` P0/P1/P2). Unset means `standard`. **80 is a passing grade** — reach for `thorough` when the change is security, data integrity, a release, or public API, not by default. **Declaring ≠ Executing**: Saying "should run review" without invoking the Skill tool is a violation. **Summary ≠ Completion**: Outputting a table then stopping is a violation. -Full spec: @rules/auto-loop.md +Full spec: @rules/auto-loop.md (§ Tiers, § Sub-Threshold Findings) ## Test Requirements @@ -51,6 +67,8 @@ Coverage: happy path + error handling + edge cases (null, empty, extremes) | `/feasibility-study` | Feasibility analysis | Requirements | | `/tech-spec` | Generate tech spec | Design | | `/review-spec` | Review tech spec | Design | +| `/plan-review` | Pre-ExitPlanMode adversarial plan review loop | Planning | +| `/orchestrate` | Agent-driven workflow planning + read-only fanout (report-only v1) | Planning | | `/deep-analyze` | Deep analysis + roadmap | Design | | `/architecture` | Architecture design + 3-architecture.md | Design | | `/project-brief` | PM/CTO executive summary | Design | @@ -139,7 +157,7 @@ Coverage: happy path + error handling + edge cases (null, empty, extremes) ## Development Rules 1. **Reference existing code** -- find similar files first, keep style consistent -2. **Test command** -- `node --test test/**/*.test.js` +2. **Test command** -- `npm test`(`node --test $(find test -name '*.test.js')` — npm scripts 走 `/bin/sh`,`**` glob 不展開巢狀目錄,勿用 `test/**/*.test.js`) 3. **Author attribution** -- use developer's GitHub username, never AI names (exception: `/smart-commit --ai-co-author`). Forbidden patterns in commit messages **and PR title/body** (canonical source: `scripts/commit-msg-guard.sh`): Co-Authored-By AI, Generated-by tags, emoji robot tags. Commits: install `commit-msg-guard.sh` via `/install-scripts`. PRs: `/create-pr` Step 4b enforces sanitization automatically. 4. **No auto-commit** -- Claude must not run `git add`, `git commit`, `git push` (exception: `/push-ci` may execute `git push` after user approval; `/smart-commit --execute` may execute `git add` + `git commit` after user approval) diff --git a/CLAUDE.template.md b/CLAUDE.template.md index ca48490..2ccd6bd 100644 --- a/CLAUDE.template.md +++ b/CLAUDE.template.md @@ -4,10 +4,21 @@ | Change Type | Must Run | Can Skip | |-------------|----------|----------| -| code files | `/codex-review-fast` -> `/precommit-fast` | - | +| code files | `/codex-review-fast` -> `/precommit` | - | | `.md` docs | `/codex-review-doc` | `/codex-review-fast` | | Comments only | - | All | +> **What the Stop Hook actually enforces**: that *a* precommit gate ran and passed — not *which* variant. `/precommit-fast` skips the build/typecheck step yet satisfies the gate by default. Two settings are needed to make the full variant above actually binding, and each alone is insufficient: +> +> | Setting | Without it | +> |---------|-----------| +> | `PRECOMMIT_REQUIRE_FULL=1` | a passing `mode: fast` (or an unrecorded mode) satisfies the gate | +> | `STOP_GUARD_MODE=strict` | the default `warn` mode prints the missing step to stderr and **still exits 0** | +> +> With both set, the flag is honoured in both of stop-guard's modes: from `precommit.mode` when `.claude_review_state.json` exists, and from the invoked command name (`/precommit` vs `/precommit-fast`) when it falls back to reading the transcript. +> +> Even with both, the flag gates the **command variant**, not the stages that ran: `/precommit` resolves lint / build / test from whatever your manifest actually defines, so a repo with no build script records `full` with no build behind it. The gate proves which command was invoked; it cannot prove which stages existed to run. `/precommit` prints the resolved stages — read them rather than assuming. + Before PR: `/pr-review` ## Workflow @@ -23,14 +34,19 @@ After editing code or docs, you **MUST** run the review command **in the same re | After editing... | Immediately run | Then on pass | |------------------|----------------|--------------| -| code files | `/codex-review-fast` | `/precommit-fast` | +| code files | `/codex-review-fast` | `/precommit` | | `.md` docs | `/codex-review-doc` | (done) | -| Review found issues | Fix all -> re-run same review | - | +| Review found **blocking** issues | Fix all -> re-run same review | - | +| Review found **sub-threshold** issues | Log `[NIT_DEFERRED]`, do not re-review | continue to the "Then on pass" column | + +One reviewer -- Codex. `/codex-review-fast` and `/codex-review-doc` do not launch a secondary; `/codex-review-branch --dual` is the only code-review entry point where two reviewers run, and it is off unless the flag is passed. (`/plan-review --dual` is the plan-mode equivalent, also off by default.) + +What counts as blocking comes from the **tier** (`fast` P0 / `standard` P0+P1 / `thorough` P0+P1+P2). Unset means `standard`. **80 is a passing grade** -- reach for `thorough` when the change is security, data integrity, a release, or public API, not by default. **Declaring != Executing**: Saying "should run review" without invoking the Skill tool is a violation. **Summary != Completion**: Outputting a table then stopping is a violation. -Full spec: @rules/auto-loop.md +Full spec: @rules/auto-loop.md (§ Tiers, § Sub-Threshold Findings) ## Test Requirements @@ -99,6 +115,8 @@ Coverage: happy path + error handling + edge cases (null, empty, extremes) | `/feasibility-study` | Feasibility analysis | Requirements | | `/tech-spec` | Generate tech spec | Design | | `/review-spec` | Review tech spec | Design | +| `/plan-review` | Pre-ExitPlanMode adversarial plan review loop | Planning | +| `/orchestrate` | Agent-driven workflow planning + read-only fanout (report-only v1) | Planning | | `/deep-analyze` | Deep analysis + roadmap | Design | | `/architecture` | Architecture design + 3-architecture.md | Design | | `/project-brief` | PM/CTO executive summary | Design | diff --git a/README.es.md b/README.es.md index a43105d..21f841f 100644 --- a/README.es.md +++ b/README.es.md @@ -6,7 +6,7 @@ > La capa harness para Claude Code. -**Gates de calidad que la IA no puede saltarse.** Una reference implementation de AI Agent Harness Engineering para [Claude Code](https://claude.com/claude-code) — dual review forzado por hooks, gates de state-machine que sobreviven a la compactación del contexto y seguridad fail-closed donde importa. +**Gates de calidad que la IA no puede saltarse.** Una reference implementation de AI Agent Harness Engineering para [Claude Code](https://claude.com/claude-code) — gates de review forzados por hooks, gates de state-machine que sobreviven a la compactación del contexto y seguridad fail-closed donde importa. 96 bundled · 96 public skills · 15 agents — ~4% de la ventana de context de Claude @@ -28,7 +28,7 @@ sd0x-dev-flow es una reference implementation. Cada fila de la tabla mapea un su | 4 | **Lifecycle interceptors** | 5 tipos de hook event despachados a 8 scripts: PreToolUse / PostToolUse / Stop / SessionStart / UserPromptSubmit | [`hooks/`](hooks/) (8 scripts) + [`.claude/settings.json`](.claude/settings.json) | | 5 | **Capability-based tool gating** | Frontmatter de skill `allowed-tools` — p. ej., `/ask` no tiene Edit/Write | 86 de 95 skills públicas declaran `allowed-tools` | | 6 | **Defense-in-depth safety** | 5 capas: pre-edit-guard → commit-msg-guard → pre-push-gate → stop-guard → sidecar fail-closed marker | [`scripts/pre-push-gate.sh`](scripts/pre-push-gate.sh) + [`scripts/commit-msg-guard.sh`](scripts/commit-msg-guard.sh) + [`hooks/stop-guard.sh`](hooks/stop-guard.sh) | -| 7 | **Generator-evaluator split** | Dual review: Codex (primario) + Claude (secundario) despachados en paralelo en cada ciclo de review | [`rules/codex-invocation.md`](rules/codex-invocation.md) + [`rules/auto-loop.md`](rules/auto-loop.md) (Dual Review Mode) | +| 7 | **Generator-evaluator split** | Codex revisa lo que escribió Claude e investiga el repositorio por su cuenta — nunca recibe una conclusión que confirmar | [`rules/codex-invocation.md`](rules/codex-invocation.md) + [`rules/auto-loop.md`](rules/auto-loop.md) (Review Dispatch) | | 8 | **Incremental progress tracking** | `iteration_history.current_round` + `max_rounds` + detección de convergence plateau | [`rules/auto-loop.md`](rules/auto-loop.md) (condiciones de salida + strategic reset) | | 9 | **Human-in-the-loop safety gates** | Confirmación por `/dev/tty` + `AskUserQuestion` para operaciones destructivas | [`scripts/pre-push-gate.sh`](scripts/pre-push-gate.sh) + [`skills/push-ci/SKILL.md`](skills/push-ci/SKILL.md) | | 10 | **Self-improvement loop** | Corrección → registrar lesson → promover a regla tras 3+ recurrencias | [`rules/self-improvement.md`](rules/self-improvement.md) | @@ -40,7 +40,7 @@ La mayoría de proyectos de harness cubren 2–4 de estos subproblemas. sd0x-dev | Sin barreras de seguridad | Con sd0x-dev-flow | |---|---| | La IA salta el review cuando el contexto es largo | **Forzado por Hook**: stop-guard bloquea reviews incompletos | -| Un solo reviewer pierde problemas | **Dual dispatch**: Codex + secundario en paralelo | +| La autorrevisión solo sella lo ya hecho | **Reviewer independiente**: Codex investiga el repositorio por su cuenta; `--dual` opcional cuando hace falta profundidad | | "Arreglado" sin re-verificación | **Auto-loop**: fix → re-review → pass → continuar | | Estado de review perdido tras compact | **Seguimiento de estado**: SessionStart hook re-inyecta | @@ -55,7 +55,7 @@ La mayoría de proyectos de harness cubren 2–4 de estos subproblemas. sd0x-dev /project-setup ``` -Un solo comando autodetecta framework, package manager, base de datos, entry points y scripts. Instala un subconjunto de rules y hooks; el plugin completo incluye 14 rules + 9 hooks. +Un solo comando autodetecta framework, package manager, base de datos, entry points y scripts. Instala un subconjunto de rules y hooks; el plugin completo incluye 14 rules + 8 hooks. Usa `--lite` para solo configurar CLAUDE.md (sin rules/hooks). @@ -73,34 +73,27 @@ flowchart LR S -.- S1["/smart-commit
/push-ci
/create-pr
/pr-review"] ``` -El **motor auto-loop** aplica quality gates automáticamente — tras ediciones de código, el comando de review despacha **dual review** (Codex MCP + reviewer secundario en paralelo) en la misma respuesta. Los hallazgos se deduplican, normalizan por severidad y agregan en un único gate. En modo strict, los hooks aplican semántica fail-closed: si el gate agregado está incompleto, stop-guard bloquea. Ver [docs/hooks.md](docs/hooks.md) para detalles. +El **motor auto-loop** aplica quality gates automáticamente — tras ediciones de código, el comando de review despacha **Codex** en la misma respuesta. Qué bloquea lo decide el tier (`fast` P0 · `standard` P0/P1 · `thorough` P0/P1/P2); los hallazgos por debajo de ese umbral se registran y el bucle continúa, sin abrir otra ronda. En modo strict, los hooks aplican semántica fail-closed: si el gate está incompleto, stop-guard bloquea. Un segundo reviewer está disponible vía `/codex-review-branch --dual` y viene desactivado. Ver [docs/hooks.md](docs/hooks.md) para detalles.
-Detalle: Diagrama de secuencia del dual-review +Detalle: Diagrama de secuencia del bucle de review ```mermaid sequenceDiagram participant D as Developer participant C as Claude participant X as Codex MCP - participant T as Secondary Reviewer participant H as Hooks D->>C: Edit code H->>H: Track file change - C->>H: emit-review-gate PENDING - par Dual Review - C->>X: Codex review (sandbox) - and - C->>T: Task(code-reviewer) - end - X-->>C: Findings (primary) - T-->>C: Findings (secondary) - C->>C: Aggregate + dedup + gate - C->>H: emit-review-gate READY/BLOCKED + C->>X: Codex review (sandbox, researches repo itself) + X-->>C: Findings + gate sentinel + H->>H: Parse sentinel into code_review.passed + C->>C: Gate on the tier's blocking severity - alt Issues found - C->>C: Fix all issues + alt Blocking findings + C->>C: Fix them (sub-threshold: log and move on) C->>X: --continue threadId X-->>C: Re-verify end @@ -113,16 +106,19 @@ sequenceDiagram
-## Funcionalidad destacada: Arquitectura Dual-Reviewer +## Funcionalidad destacada: Review por tiers + +Un solo reviewer — Codex — por defecto en todas partes. El **tier** decide cuánto rigor recibe un cambio, y qué tan grave debe ser un hallazgo para reabrir el bucle: -v2.0 despacha dos reviewers independientes en paralelo — Dual-review por defecto con modos de fallback degradado: +| Tier | Para | Bloquea en | Tope de rondas | +|------|------|-----------|----------------| +| `fast` | Documentación, configuración, ediciones pequeñas de bajo riesgo | P0 | 3 | +| `standard` **(por defecto)** | Funcionalidades y correcciones ordinarias | P0, P1 | 5 | +| `thorough` | Seguridad, integridad de datos, releases, API pública | P0, P1, P2 | 10 | -| Reviewer | Rol | Fallback | -|----------|-----|----------| -| Codex MCP | Primario (sandbox, diff completo) | Modo single-reviewer si no está disponible | -| Secundario (pr-review-toolkit) | Review con puntuación de confianza | strict-reviewer → modo single | +**80 es nota de aprobado.** Los hallazgos por debajo del umbral de bloqueo del tier se registran (`[NIT_DEFERRED]`, persistido con TTL para que no se vuelvan a plantear en la siguiente sesión) y el bucle avanza a `/precommit` — sin pasada extra de correcciones ni ronda extra de review. `/codex-review-branch` los retoma cuando el cambio se revise en profundidad. -Los hallazgos se **normalizan por severidad** (P0-Nit), **deduplican** (archivo + clave de issue, tolerancia ±5 líneas) y se **atribuyen por fuente** (`codex` | `toolkit` | `both`). +Un segundo reviewer está disponible vía `/codex-review-branch --dual` y está **desactivado salvo que se pase el flag** — duplica el coste en tokens y en tiempo de cada ronda, algo que vale la pena en un release o una revisión de seguridad, no en una corrección corriente. Bajo `--dual`, los hallazgos se normalizan por severidad, se deduplican (archivo + clave de issue, tolerancia ±5 líneas) y se atribuyen por fuente. Gate: `✅ Ready` o `⛔ Blocked` — en modo strict, gate incompleto = bloqueado. @@ -131,7 +127,7 @@ Gate: `✅ Ready` o `⛔ Blocked` — en modo strict, gate incompleto = bloquead | Capacidad | sd0x-dev-flow | gstack | Prompts genéricos | |---|---|---|---| | Gates de review forzados | Hook + capa de comportamiento | Solo sugerencia | Ninguno | -| Dual-reviewer | Codex + secundario (paralelo) | Un solo /review | Ninguno | +| Reviewer independiente | Codex, autoinvestiga; `--dual` opcional | Un solo /review | Ninguno | | Bucle de auto-fix | Fix → re-review → pass | Manual | Ninguno | | Investigación multi-agente | /deep-research (3 agentes) | Ninguno | Ninguno | | Validación adversarial | Debate equilibrio Nash | Ninguno | Ninguno | @@ -167,7 +163,7 @@ npx skills add sd0xdev/sd0x-dev-flow | `/codex-setup init` | Codex CLI | AGENTS.md kernel + git hooks | -**Requisitos**: Claude Code 2.1+ | [Codex MCP](https://github.com/openai/codex) (opcional — los skills `/codex-*` lo requieren; sin él, se usa modo single-reviewer) +**Requisitos**: Claude Code 2.1+ | [Codex MCP](https://github.com/openai/codex) (opcional para instalar el plugin, obligatorio para los gates de review `/codex-*` — Codex *es* el reviewer único, así que sin él la review emite `⛔ Blocked` + `⚠️ Need Human` en vez de degradarse) ## Tracks de workflow @@ -249,9 +245,9 @@ Escenarios reales que muestran qué habilidades combinar y en qué orden. |-----------|----------|----------| | Skills | 96 public (96 bundled) | `/project-setup`, `/codex-review-fast`, `/verify`, `/smart-commit`, `/deep-research` | | Agents | 15 | strict-reviewer, verify-app, coverage-analyst, architecture-designer | -| Hooks | 9 | pre-edit-guard, auto-format, review state tracking, stop guard, namespace hint, post-compact-auto-loop, post-skill-auto-loop, user-prompt-review-guard, session-init | +| Hooks | 8 | pre-edit-guard, auto-format, review state tracking, stop guard, post-compact-auto-loop, post-skill-auto-loop, user-prompt-review-guard, session-init | | Rules | 14 | auto-loop, auto-loop-project, codex-invocation, security, testing, git-workflow, self-improvement, context-management | -| Scripts | 13 | precommit runner, verify runner, dep audit, namespace hint, skill runner, commit-msg guard, pre-push gate, utils (shared lib), emit-review-gate, build-codex-artifacts, resolve-feature (CLI + shell), feature-resolver, readme-catalog | +| Scripts | 17 | precommit runner, verify runner, dep audit, namespace hint, skill runner, commit-msg guard, pre-push gate, emit-review-gate, emit-plan-gate, build-codex-artifacts, resolve-feature (CLI + shell), classify-docs, detect-scope, migration-audit, security-redact, readme-catalog | ### Mínimo consumo de context @@ -419,7 +415,7 @@ Los skills se cargan bajo demanda. Los skills inactivos no consumen tokens. ## Reglas & Hooks -14 reglas (convenciones siempre cargadas) + 9 hooks (guardrails automatizados). +14 reglas (convenciones siempre cargadas) + 8 hooks (guardrails automatizados). > **Personalización**: Edita `auto-loop-project.md` para sobrescribir el comportamiento de auto-loop por proyecto. Las actualizaciones del plugin no conflictuarán — ver [Rule Override Pattern](docs/features/rule-override-pattern/2-tech-spec.md). diff --git a/README.ja.md b/README.ja.md index 3bd127b..954d7ff 100644 --- a/README.ja.md +++ b/README.ja.md @@ -6,7 +6,7 @@ > Claude Code のための harness レイヤー。 -**AI がスキップできない品質ゲート。** [Claude Code](https://claude.com/claude-code) のための AI Agent Harness Engineering の reference implementation — Hook 強制のデュアルレビュー、context compaction を乗り越える state machine ゲート、そして本当に必要な箇所での fail-closed な安全性。 +**AI がスキップできない品質ゲート。** [Claude Code](https://claude.com/claude-code) のための AI Agent Harness Engineering の reference implementation — Hook 強制のレビューゲート、context compaction を乗り越える state machine ゲート、そして本当に必要な箇所での fail-closed な安全性。 96 bundled · 96 public skills · 15 agents — Claude の context window のわずか ~4% @@ -28,7 +28,7 @@ sd0x-dev-flow は reference implementation です。以下の各行は、harness | 4 | **Lifecycle interceptors** | 5 種類の hook event を 8 本のスクリプトへディスパッチ: PreToolUse / PostToolUse / Stop / SessionStart / UserPromptSubmit | [`hooks/`](hooks/) (8 scripts) + [`.claude/settings.json`](.claude/settings.json) | | 5 | **Capability-based tool gating** | Skill frontmatter の `allowed-tools` — 例: `/ask` には Edit/Write が無い | 95 個の公開 skill のうち 86 個が `allowed-tools` を宣言 | | 6 | **Defense-in-depth safety** | 5 層構成: pre-edit-guard → commit-msg-guard → pre-push-gate → stop-guard → sidecar fail-closed marker | [`scripts/pre-push-gate.sh`](scripts/pre-push-gate.sh) + [`scripts/commit-msg-guard.sh`](scripts/commit-msg-guard.sh) + [`hooks/stop-guard.sh`](hooks/stop-guard.sh) | -| 7 | **Generator-evaluator split** | デュアルレビュー: Codex (primary) + Claude (secondary) を各レビューサイクルで並列ディスパッチ | [`rules/codex-invocation.md`](rules/codex-invocation.md) + [`rules/auto-loop.md`](rules/auto-loop.md) (Dual Review Mode) | +| 7 | **Generator-evaluator split** | Codex が Claude の書いたコードをレビュー。リポジトリを自力で調査し、結論を渡されて追認することはない | [`rules/codex-invocation.md`](rules/codex-invocation.md) + [`rules/auto-loop.md`](rules/auto-loop.md) (Review Dispatch) | | 8 | **Incremental progress tracking** | `iteration_history.current_round` + `max_rounds` + 収束プラトー検出 | [`rules/auto-loop.md`](rules/auto-loop.md) (exit conditions + strategic reset) | | 9 | **Human-in-the-loop safety gates** | 破壊的操作に対する `/dev/tty` 確認 + `AskUserQuestion` | [`scripts/pre-push-gate.sh`](scripts/pre-push-gate.sh) + [`skills/push-ci/SKILL.md`](skills/push-ci/SKILL.md) | | 10 | **Self-improvement loop** | 是正 → lesson として記録 → 3 回以上の再発で rule に昇格 | [`rules/self-improvement.md`](rules/self-improvement.md) | @@ -40,7 +40,7 @@ sd0x-dev-flow は reference implementation です。以下の各行は、harness | ガードレールなし | sd0x-dev-flow あり | |---|---| | コンテキストが長いと AI がレビューをスキップ | **Hook 強制**: stop-guard が未完了レビューをブロック | -| 単一レビューアーが問題を見落とす | **デュアルディスパッチ**: Codex + セカンダリが並列実行 | +| 自己レビューは追認にしかならない | **独立レビューアー**: Codex がリポジトリを自力で調査。深く見たいときだけ `--dual` をオプトイン | | 「修正済み」なのに再検証なし | **Auto-loop**: 修正 → 再レビュー → パス → 続行 | | compact 後にレビュー状態が消失 | **状態追跡**: SessionStart hook が再注入 | @@ -55,7 +55,7 @@ sd0x-dev-flow は reference implementation です。以下の各行は、harness /project-setup ``` -1つのコマンドでフレームワーク、パッケージマネージャー、データベース、エントリポイント、スクリプトを自動検出します。ルールとフックのサブセットをインストールします。完全なプラグインには14ルール + 9フックが含まれます。 +1つのコマンドでフレームワーク、パッケージマネージャー、データベース、エントリポイント、スクリプトを自動検出します。ルールとフックのサブセットをインストールします。完全なプラグインには14ルール + 8フックが含まれます。 `--lite` で CLAUDE.md のみ設定(ルール/フックをスキップ)。 @@ -73,34 +73,27 @@ flowchart LR S -.- S1["/smart-commit
/push-ci
/create-pr
/pr-review"] ``` -**Auto-Loop エンジン**が品質ゲートを自動的に実行します。コード編集後、レビューコマンドが**デュアルレビュー**(Codex MCP + セカンダリレビューアーを並列実行)をディスパッチします。Findings は重複排除・重要度正規化後、単一ゲートに集約されます。strict モードでは、Hooks が fail-closed を強制:集約ゲートが未完了なら stop-guard がブロックします。詳細は [docs/hooks.md](docs/hooks.md) を参照。 +**Auto-Loop エンジン**が品質ゲートを自動的に実行します。コード編集後、レビューコマンドが同じ応答内で **Codex** をディスパッチします。何が blocking かは tier が決めます(`fast` P0 · `standard` P0/P1 · `thorough` P0/P1/P2)。その閾値を下回る Findings は記録するだけで、ループは次に進み、追加のラウンドは開きません。strict モードでは、Hooks が fail-closed を強制:ゲートが未完了なら stop-guard がブロックします。2 人目のレビューアーは `/codex-review-branch --dual` から利用でき、デフォルトでは無効です。詳細は [docs/hooks.md](docs/hooks.md) を参照。
-詳細:デュアルレビュー シーケンス図 +詳細:レビューループ シーケンス図 ```mermaid sequenceDiagram participant D as Developer participant C as Claude participant X as Codex MCP - participant T as Secondary Reviewer participant H as Hooks D->>C: Edit code H->>H: Track file change - C->>H: emit-review-gate PENDING - par Dual Review - C->>X: Codex review (sandbox) - and - C->>T: Task(code-reviewer) - end - X-->>C: Findings (primary) - T-->>C: Findings (secondary) - C->>C: Aggregate + dedup + gate - C->>H: emit-review-gate READY/BLOCKED + C->>X: Codex review (sandbox, researches repo itself) + X-->>C: Findings + gate sentinel + H->>H: Parse sentinel into code_review.passed + C->>C: Gate on the tier's blocking severity - alt Issues found - C->>C: Fix all issues + alt Blocking findings + C->>C: Fix them (sub-threshold: log and move on) C->>X: --continue threadId X-->>C: Re-verify end @@ -113,16 +106,19 @@ sequenceDiagram
-## 機能スポットライト:デュアルレビューアーキテクチャ +## 機能スポットライト:ティア別レビュー + +レビューアーはデフォルトで Codex 1 人だけです。**tier** が、その変更にどれだけの厳格さを与えるか、そして findings がどれだけ重ければループを再開するかを決めます: -v2.0 では2つの独立したレビューアーを並列でディスパッチします — 単一障害点ゼロ: +| Tier | 対象 | Blocking | ラウンド上限 | +|------|------|----------|-------------| +| `fast` | ドキュメント、設定、低リスクな小変更 | P0 | 3 | +| `standard` **(デフォルト)** | 通常の機能開発とバグ修正 | P0、P1 | 5 | +| `thorough` | セキュリティ、データ整合性、リリース、公開 API | P0、P1、P2 | 10 | -| レビューアー | 役割 | フォールバック | -|-------------|------|---------------| -| Codex MCP | デフォルトでデュアルレビュー、フォールバックモードをサポート | 利用不可時はシングルレビューモードにフォールバック | -| セカンダリ(pr-review-toolkit) | 信頼度スコアリングレビュー | strict-reviewer → シングルモード | +**80 点で合格です。** tier の blocking 閾値を下回る findings は記録され(`[NIT_DEFERRED]`。TTL 付きで永続化されるため、次のセッションで蒸し返されません)、ループはそのまま `/precommit` へ進みます — 追加の修正パスも、追加のレビューラウンドもありません。これらは次に `/codex-review-branch` で深くレビューする際に拾われます。 -Findings は**重要度正規化**(P0-Nit)、**重複排除**(ファイル + issue キー、±5 行許容)、**ソース帰属**(`codex` | `toolkit` | `both`)されます。 +2 人目のレビューアーは `/codex-review-branch --dual` から利用でき、**フラグを渡さない限り無効**です — 1 ラウンドあたりのトークンと実時間のコストが倍になるため、リリースやセキュリティレビューには見合っても、通常の修正には見合いません。`--dual` 使用時、findings は重要度正規化、重複排除(ファイル + issue キー、±5 行許容)、ソース帰属が行われます。 ゲート:`✅ Ready` または `⛔ Blocked` — strict モードでは、未完了ゲート = ブロック。 @@ -131,7 +127,7 @@ Findings は**重要度正規化**(P0-Nit)、**重複排除**(ファイル | 機能 | sd0x-dev-flow | gstack | 汎用プロンプト | |---|---|---|---| | 強制レビューゲート | Hook + 動作レイヤー | 提案のみ | なし | -| デュアルレビューアー | Codex + セカンダリ(並列) | 単一 /review | なし | +| 独立レビューアー | Codex が自力で調査。`--dual` はオプトイン | 単一 /review | なし | | 自動修正ループ | 修正 → 再レビュー → パス | 手動 | なし | | マルチエージェントリサーチ | /deep-research(3 エージェント) | なし | なし | | 敵対的検証 | ナッシュ均衡ディベート | なし | なし | @@ -167,7 +163,7 @@ npx skills add sd0xdev/sd0x-dev-flow | `/codex-setup init` | Codex CLI | AGENTS.md カーネル + git フック | -**必要環境**: Claude Code 2.1+ | [Codex MCP](https://github.com/openai/codex)(オプション — `/codex-*` スキルに必要;未インストール時はシングルレビューモードにフォールバック) +**必要環境**: Claude Code 2.1+ | [Codex MCP](https://github.com/openai/codex)(プラグインのインストール自体は不要ですが、`/codex-*` のレビューゲートには必須です — Codex がその唯一のレビューアーなので、未インストール時はフォールバックせず `⛔ Blocked` + `⚠️ Need Human` を出します) ## ワークフロートラック @@ -249,9 +245,9 @@ flowchart TD |----------|-----|-----| | スキル | 96 public (96 bundled) | `/project-setup`, `/codex-review-fast`, `/verify`, `/smart-commit`, `/deep-research` | | エージェント | 15 | strict-reviewer, verify-app, coverage-analyst, architecture-designer | -| フック | 9 | pre-edit-guard, auto-format, review state tracking, stop guard, namespace hint, post-compact-auto-loop, post-skill-auto-loop, user-prompt-review-guard, session-init | +| フック | 8 | pre-edit-guard, auto-format, review state tracking, stop guard, post-compact-auto-loop, post-skill-auto-loop, user-prompt-review-guard, session-init | | ルール | 14 | auto-loop, auto-loop-project, codex-invocation, security, testing, git-workflow, self-improvement, context-management | -| スクリプト | 13 | precommit runner, verify runner, dep audit, namespace hint, skill runner, commit-msg guard, pre-push gate, utils (shared lib), emit-review-gate, build-codex-artifacts, resolve-feature (CLI + shell), feature-resolver, readme-catalog | +| スクリプト | 17 | precommit runner, verify runner, dep audit, namespace hint, skill runner, commit-msg guard, pre-push gate, emit-review-gate, emit-plan-gate, build-codex-artifacts, resolve-feature (CLI + shell), classify-docs, detect-scope, migration-audit, security-redact, readme-catalog | ### 極小の Context 使用量 @@ -419,7 +415,7 @@ Claude の 200k context window のわずか ~4% — 96% はコードに使えま ## ルール & フック -14 ルール(常時読み込みの規約)+ 9 フック(自動ガードレール)。 +14 ルール(常時読み込みの規約)+ 8 フック(自動ガードレール)。 > **カスタマイズ**:`auto-loop-project.md` を編集してプロジェクトの auto-loop 動作をオーバーライドできます。プラグイン更新と競合しません — [Rule Override Pattern](docs/features/rule-override-pattern/2-tech-spec.md) 参照。 diff --git a/README.ko.md b/README.ko.md index bac336e..3d3b0e8 100644 --- a/README.ko.md +++ b/README.ko.md @@ -6,7 +6,7 @@ > Claude Code를 위한 harness 레이어. -**AI가 건너뛸 수 없는 품질 게이트.** [Claude Code](https://claude.com/claude-code)를 위한 AI Agent Harness Engineering의 reference implementation — hook 강제 듀얼 리뷰, context 압축 이후에도 유지되는 state machine 게이트, 그리고 정말 중요한 지점의 fail-closed 안전장치. +**AI가 건너뛸 수 없는 품질 게이트.** [Claude Code](https://claude.com/claude-code)를 위한 AI Agent Harness Engineering의 reference implementation — hook 강제 리뷰 게이트, context 압축 이후에도 유지되는 state machine 게이트, 그리고 정말 중요한 지점의 fail-closed 안전장치. 96 bundled · 96 public skills · 15 agents — Claude context window의 ~4%만 사용 @@ -28,7 +28,7 @@ sd0x-dev-flow는 그 reference implementation입니다. 아래 각 행은 harnes | 4 | **Lifecycle interceptor** | 5가지 hook event type을 8개 스크립트로 디스패치: PreToolUse / PostToolUse / Stop / SessionStart / UserPromptSubmit | [`hooks/`](hooks/) (8개 스크립트) + [`.claude/settings.json`](.claude/settings.json) | | 5 | **Capability 기반 tool gating** | Skill frontmatter의 `allowed-tools` — 예: `/ask`는 Edit/Write 없음 | 공개된 95개 skill 중 86개가 `allowed-tools`를 선언 | | 6 | **Defense-in-depth 안전장치** | 5개 레이어: pre-edit-guard → commit-msg-guard → pre-push-gate → stop-guard → sidecar fail-closed 마커 | [`scripts/pre-push-gate.sh`](scripts/pre-push-gate.sh) + [`scripts/commit-msg-guard.sh`](scripts/commit-msg-guard.sh) + [`hooks/stop-guard.sh`](hooks/stop-guard.sh) | -| 7 | **Generator-evaluator 분리** | 듀얼 리뷰: 모든 리뷰 사이클에서 Codex(주)와 Claude(보조)를 병렬로 디스패치 | [`rules/codex-invocation.md`](rules/codex-invocation.md) + [`rules/auto-loop.md`](rules/auto-loop.md) (Dual Review Mode) | +| 7 | **Generator-evaluator 분리** | Codex가 Claude의 결과물을 리뷰하며 저장소를 직접 조사 — 결론을 건네받아 승인만 하는 일은 없음 | [`rules/codex-invocation.md`](rules/codex-invocation.md) + [`rules/auto-loop.md`](rules/auto-loop.md) (Review Dispatch) | | 8 | **점진적 진행 추적** | `iteration_history.current_round` + `max_rounds` + 수렴 plateau 감지 | [`rules/auto-loop.md`](rules/auto-loop.md) (exit conditions + strategic reset) | | 9 | **Human-in-the-loop 안전 게이트** | 파괴적 작업에 대한 `/dev/tty` 확인 + `AskUserQuestion` | [`scripts/pre-push-gate.sh`](scripts/pre-push-gate.sh) + [`skills/push-ci/SKILL.md`](skills/push-ci/SKILL.md) | | 10 | **자기 개선 루프** | 지적 → lesson 기록 → 3회 이상 재발 시 rule로 승격 | [`rules/self-improvement.md`](rules/self-improvement.md) | @@ -40,7 +40,7 @@ sd0x-dev-flow는 그 reference implementation입니다. 아래 각 행은 harnes | 가드레일 없을 때 | sd0x-dev-flow 사용 시 | |---|---| | 컨텍스트가 길면 AI가 리뷰를 건너뜀 | **Hook 강제**: stop-guard가 미완료 리뷰를 차단 | -| 단일 리뷰어가 문제를 놓침 | **듀얼 디스패치**: Codex + 보조 리뷰어 병렬 실행 | +| 자체 리뷰는 거수기가 됨 | **독립 리뷰어**: Codex가 저장소를 직접 조사, 깊이가 필요할 때만 `--dual` 옵트인 | | "수정 완료"인데 재검증 없음 | **Auto-loop**: 수정 → 재리뷰 → 통과 → 계속 | | compact 후 리뷰 상태 소실 | **상태 추적**: SessionStart hook이 재주입 | @@ -55,7 +55,7 @@ sd0x-dev-flow는 그 reference implementation입니다. 아래 각 행은 harnes /project-setup ``` -하나의 명령어로 프레임워크, 패키지 매니저, 데이터베이스, 엔트리포인트, 스크립트를 자동 감지합니다. Rules와 Hooks의 서브셋을 설치합니다. 전체 플러그인에는 14개 Rules + 9개 Hooks가 포함됩니다. +하나의 명령어로 프레임워크, 패키지 매니저, 데이터베이스, 엔트리포인트, 스크립트를 자동 감지합니다. Rules와 Hooks의 서브셋을 설치합니다. 전체 플러그인에는 14개 Rules + 8개 Hooks가 포함됩니다. `--lite`로 CLAUDE.md만 설정 (Rules/Hooks 스킵). @@ -73,34 +73,27 @@ flowchart LR S -.- S1["/smart-commit
/push-ci
/create-pr
/pr-review"] ``` -**Auto-Loop 엔진**이 품질 Gate를 자동으로 적용합니다. 코드 편집 후 리뷰 명령어가 **듀얼 리뷰**(Codex MCP + 보조 리뷰어 병렬 실행)를 디스패치합니다. Findings는 중복 제거, 심각도 정규화 후 단일 gate로 집계됩니다. strict 모드에서 Hooks는 fail-closed를 강제합니다: 집계 gate가 미완료이면 stop-guard가 차단합니다. 자세한 내용은 [docs/hooks.md](docs/hooks.md) 참조. +**Auto-Loop 엔진**이 품질 Gate를 자동으로 적용합니다. 코드 편집 후 리뷰 명령어가 같은 응답 안에서 **Codex**를 디스패치합니다. 무엇이 blocking인지는 tier가 결정합니다(`fast` P0 · `standard` P0/P1 · `thorough` P0/P1/P2). 그 기준 아래의 findings는 기록만 하고 루프는 그대로 진행하며, 라운드를 새로 열지 않습니다. strict 모드에서 Hooks는 fail-closed를 강제합니다: gate가 미완료이면 stop-guard가 차단합니다. 두 번째 리뷰어는 `/codex-review-branch --dual`로 쓸 수 있고 기본값은 비활성입니다. 자세한 내용은 [docs/hooks.md](docs/hooks.md) 참조.
-상세: 듀얼 리뷰 시퀀스 다이어그램 +상세: 리뷰 루프 시퀀스 다이어그램 ```mermaid sequenceDiagram participant D as Developer participant C as Claude participant X as Codex MCP - participant T as Secondary Reviewer participant H as Hooks D->>C: Edit code H->>H: Track file change - C->>H: emit-review-gate PENDING - par Dual Review - C->>X: Codex review (sandbox) - and - C->>T: Task(code-reviewer) - end - X-->>C: Findings (primary) - T-->>C: Findings (secondary) - C->>C: Aggregate + dedup + gate - C->>H: emit-review-gate READY/BLOCKED + C->>X: Codex review (sandbox, researches repo itself) + X-->>C: Findings + gate sentinel + H->>H: Parse sentinel into code_review.passed + C->>C: Gate on the tier's blocking severity - alt Issues found - C->>C: Fix all issues + alt Blocking findings + C->>C: Fix them (sub-threshold: log and move on) C->>X: --continue threadId X-->>C: Re-verify end @@ -113,16 +106,19 @@ sequenceDiagram
-## 기능 하이라이트: 듀얼 리뷰어 아키텍처 +## 기능 하이라이트: 티어별 리뷰 + +기본 리뷰어는 Codex 하나뿐입니다. **tier**가 해당 변경에 얼마만큼의 엄격함을 적용할지, 그리고 finding이 얼마나 심각해야 루프를 다시 여는지를 결정합니다: -v2.0은 두 개의 독립적인 리뷰어를 병렬로 디스패치합니다 — 단일 장애점 제로: +| Tier | 대상 | Blocking | 라운드 상한 | +|------|------|----------|------------| +| `fast` | 문서, 설정, 위험이 낮은 소규모 편집 | P0 | 3 | +| `standard` **(기본값)** | 일반적인 기능 개발과 버그 수정 | P0, P1 | 5 | +| `thorough` | 보안, 데이터 무결성, 릴리스, 공개 API | P0, P1, P2 | 10 | -| 리뷰어 | 역할 | 폴백 | -|--------|------|------| -| Codex MCP | 기본적으로 듀얼 리뷰, 저하 폴백 모드 지원 | 사용 불가 시 싱글 리뷰어 모드로 폴백 | -| 보조 (pr-review-toolkit) | 신뢰도 스코어링 리뷰 | strict-reviewer → 싱글 모드 | +**80점이면 합격입니다.** tier의 blocking 기준 아래 findings는 기록되고(`[NIT_DEFERRED]` — TTL과 함께 저장되어 다음 세션에서 다시 제기되지 않습니다) 루프는 곧바로 `/precommit`으로 진행합니다. 추가 수정 패스도, 추가 리뷰 라운드도 없습니다. 이 항목들은 다음에 `/codex-review-branch`로 깊이 리뷰할 때 다시 다뤄집니다. -Findings는 **심각도 정규화** (P0-Nit), **중복 제거** (파일 + 이슈 키, ±5줄 허용), **소스 귀속** (`codex` | `toolkit` | `both`)됩니다. +두 번째 리뷰어는 `/codex-review-branch --dual`로 쓸 수 있고 **플래그를 넘기지 않으면 비활성**입니다 — 라운드당 토큰과 실제 소요 시간이 두 배가 되므로 릴리스나 보안 리뷰에는 값어치를 하지만 일상적인 수정에는 그렇지 않습니다. `--dual`에서는 findings에 심각도 정규화, 중복 제거(파일 + 이슈 키, ±5줄 허용), 소스 귀속이 적용됩니다. Gate: `✅ Ready` 또는 `⛔ Blocked` — strict 모드에서, 미완료 gate = blocked. @@ -131,7 +127,7 @@ Gate: `✅ Ready` 또는 `⛔ Blocked` — strict 모드에서, 미완료 gate = | 기능 | sd0x-dev-flow | gstack | 일반 프롬프트 | |---|---|---|---| | 강제 리뷰 게이트 | Hook + 동작 레이어 | 제안만 | 없음 | -| 듀얼 리뷰어 | Codex + 보조 (병렬) | 단일 /review | 없음 | +| 독립 리뷰어 | Codex가 직접 조사; `--dual` 옵트인 | 단일 /review | 없음 | | 자동 수정 루프 | 수정 → 재리뷰 → 통과 | 수동 | 없음 | | 멀티 에이전트 리서치 | /deep-research (3 에이전트) | 없음 | 없음 | | 적대적 검증 | 내시 균형 디베이트 | 없음 | 없음 | @@ -167,7 +163,7 @@ npx skills add sd0xdev/sd0x-dev-flow | `/codex-setup init` | Codex CLI | AGENTS.md 커널 + git hooks | -**요구 사항**: Claude Code 2.1+ | [Codex MCP](https://github.com/openai/codex)(선택 — `/codex-*` skill에 필요; 미설치 시 싱글 리뷰어 모드로 폴백) +**요구 사항**: Claude Code 2.1+ | [Codex MCP](https://github.com/openai/codex)(플러그인 설치 자체에는 선택이지만 `/codex-*` 리뷰 게이트에는 필수 — Codex가 바로 그 유일한 리뷰어이므로, 미설치 시 폴백하지 않고 `⛔ Blocked` + `⚠️ Need Human`을 냅니다) ## 워크플로 트랙 @@ -249,9 +245,9 @@ flowchart TD |----------|------|------| | Skills | 96 public (96 bundled) | `/project-setup`, `/codex-review-fast`, `/verify`, `/smart-commit`, `/deep-research` | | Agents | 15 | strict-reviewer, verify-app, coverage-analyst, architecture-designer | -| Hooks | 9 | pre-edit-guard, auto-format, review state tracking, stop guard, namespace hint, post-compact-auto-loop, post-skill-auto-loop, user-prompt-review-guard, session-init | +| Hooks | 8 | pre-edit-guard, auto-format, review state tracking, stop guard, post-compact-auto-loop, post-skill-auto-loop, user-prompt-review-guard, session-init | | Rules | 14 | auto-loop, auto-loop-project, codex-invocation, security, testing, git-workflow, self-improvement, context-management | -| Scripts | 13 | precommit runner, verify runner, dep audit, namespace hint, skill runner, commit-msg guard, pre-push gate, utils (shared lib), emit-review-gate, build-codex-artifacts, resolve-feature (CLI + shell), feature-resolver, readme-catalog | +| Scripts | 17 | precommit runner, verify runner, dep audit, namespace hint, skill runner, commit-msg guard, pre-push gate, emit-review-gate, emit-plan-gate, build-codex-artifacts, resolve-feature (CLI + shell), classify-docs, detect-scope, migration-audit, security-redact, readme-catalog | ### 최소한의 Context 사용량 @@ -419,7 +415,7 @@ Skills는 온디맨드로 로드됩니다. 미사용 Skills는 토큰을 소비 ## 규칙 & Hook -14개 규칙 (상시 로드 컨벤션) + 9개 Hook (자동 가드레일). +14개 규칙 (상시 로드 컨벤션) + 8개 Hook (자동 가드레일). > **커스터마이징**: `auto-loop-project.md`를 편집하여 프로젝트별 auto-loop 동작을 오버라이드할 수 있습니다. 플러그인 업데이트와 충돌하지 않습니다 — [Rule Override Pattern](docs/features/rule-override-pattern/2-tech-spec.md) 참조. diff --git a/README.md b/README.md index 8eedaab..3c9b8ab 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ > The harness layer for Claude Code. -**Quality gates that AI can't skip.** A reference implementation of AI Agent Harness Engineering for [Claude Code](https://claude.com/claude-code) — hook-enforced dual review, state-machine gates that survive context compaction, and fail-closed safety where it counts. +**Quality gates that AI can't skip.** A reference implementation of AI Agent Harness Engineering for [Claude Code](https://claude.com/claude-code) — hook-enforced review gates, state-machine gates that survive context compaction, and fail-closed safety where it counts. -96 bundled · 96 public skills · 15 agents — ~4% of Claude's context window +98 bundled · 98 public skills · 15 agents — ~4% of Claude's context window [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![npm](https://img.shields.io/badge/npx-skills%20add-blue)](https://www.npmjs.com/package/skills) @@ -26,9 +26,9 @@ sd0x-dev-flow is a reference implementation. Each row below maps a canonical har | 2 | **Sentinel-driven state machine** | `✅ Ready` / `⛔ Blocked` / `✅ All Pass` gate markers parsed into durable state | [`scripts/emit-review-gate.sh`](scripts/emit-review-gate.sh) (producer) + [`hooks/post-tool-review-state.sh`](hooks/post-tool-review-state.sh) (parser) | | 3 | **Context recovery across compaction** | `[AUTO_LOOP_RESUME]` stdout injection after SessionStart(compact) | [`hooks/post-compact-auto-loop.sh`](hooks/post-compact-auto-loop.sh) | | 4 | **Lifecycle interceptors** | 5 hook event types dispatched to 8 scripts: PreToolUse / PostToolUse / Stop / SessionStart / UserPromptSubmit | [`hooks/`](hooks/) (8 scripts) + [`.claude/settings.json`](.claude/settings.json) | -| 5 | **Capability-based tool gating** | Skill frontmatter `allowed-tools` — e.g., `/ask` has no Edit/Write | 86 of 95 public skills declare `allowed-tools` | +| 5 | **Capability-based tool gating** | Skill frontmatter `allowed-tools` — e.g., `/ask` has no Edit/Write | 89 of 98 public skills declare `allowed-tools` | | 6 | **Defense-in-depth safety** | 5 layers: pre-edit-guard → commit-msg-guard → pre-push-gate → stop-guard → sidecar fail-closed marker | [`scripts/pre-push-gate.sh`](scripts/pre-push-gate.sh) + [`scripts/commit-msg-guard.sh`](scripts/commit-msg-guard.sh) + [`hooks/stop-guard.sh`](hooks/stop-guard.sh) | -| 7 | **Generator-evaluator split** | Dual review: Codex (primary) + Claude (secondary) dispatched in parallel on every review cycle | [`rules/codex-invocation.md`](rules/codex-invocation.md) + [`rules/auto-loop.md`](rules/auto-loop.md) (Dual Review Mode) | +| 7 | **Generator-evaluator split** | Codex reviews what Claude wrote, researching the repo independently — never handed a conclusion to confirm | [`rules/codex-invocation.md`](rules/codex-invocation.md) + [`rules/auto-loop.md`](rules/auto-loop.md) (Review Dispatch) | | 8 | **Incremental progress tracking** | `iteration_history.current_round` + `max_rounds` + convergence plateau detection | [`rules/auto-loop.md`](rules/auto-loop.md) (exit conditions + strategic reset) | | 9 | **Human-in-the-loop safety gates** | `/dev/tty` confirmation + `AskUserQuestion` for destructive ops | [`scripts/pre-push-gate.sh`](scripts/pre-push-gate.sh) + [`skills/push-ci/SKILL.md`](skills/push-ci/SKILL.md) | | 10 | **Self-improvement loop** | Correction → record lesson → promote to rule after 3+ recurrences | [`rules/self-improvement.md`](rules/self-improvement.md) | @@ -40,7 +40,7 @@ Most harness projects cover 2–4 of these. sd0x-dev-flow covers all 10 — whic | Without guardrails | With sd0x-dev-flow | |---|---| | AI skips review when context is long | **Hook-enforced**: stop-guard blocks incomplete reviews | -| Single reviewer misses issues | **Dual dispatch**: Codex + secondary in parallel | +| Self-review rubber-stamps its own work | **Independent reviewer**: Codex researches the repo itself, opt-in `--dual` for depth | | "Fixed it" without re-verification | **Auto-loop**: fix → re-review → pass → continue | | Review state lost after compact | **State tracking**: SessionStart hook re-injects | @@ -55,7 +55,7 @@ Most harness projects cover 2–4 of these. sd0x-dev-flow covers all 10 — whic /project-setup ``` -One command auto-detects framework, package manager, database, entrypoints, and scripts. Installs a subset of rules and hooks; the full plugin bundles 14 rules + 9 hooks. +One command auto-detects framework, package manager, database, entrypoints, and scripts. Installs a subset of rules and hooks; the full plugin bundles 14 rules + 8 hooks. Use `--lite` to only configure CLAUDE.md (skip rules/hooks). @@ -73,34 +73,27 @@ flowchart LR S -.- S1["/smart-commit
/push-ci
/create-pr
/pr-review"] ``` -The **auto-loop engine** enforces quality gates automatically — after code edits, the review command dispatches **dual review** (Codex MCP + secondary reviewer in parallel) in the same reply. Findings are deduplicated, severity-normalized, and aggregated into a single gate. In strict mode, hooks enforce fail-closed semantics: if the aggregate gate is incomplete, stop-guard blocks. See [docs/hooks.md](docs/hooks.md) for mode and dependency details. +The **auto-loop engine** enforces quality gates automatically — after code edits, the review command dispatches **Codex** in the same reply. What blocks comes from the tier (`fast` P0 · `standard` P0/P1 · `thorough` P0/P1/P2); findings below that line are logged and the loop proceeds rather than opening another round. In strict mode, hooks enforce fail-closed semantics: if the gate is incomplete, stop-guard blocks. A second reviewer is available via `/codex-review-branch --dual` and is off by default. See [docs/hooks.md](docs/hooks.md) for mode and dependency details.
-Detailed: Dual-Review Sequence Diagram +Detailed: Review Loop Sequence Diagram ```mermaid sequenceDiagram participant D as Developer participant C as Claude participant X as Codex MCP - participant T as Secondary Reviewer participant H as Hooks D->>C: Edit code H->>H: Track file change - C->>H: emit-review-gate PENDING - par Dual Review - C->>X: Codex review (sandbox) - and - C->>T: Task(code-reviewer) - end - X-->>C: Findings (primary) - T-->>C: Findings (secondary) - C->>C: Aggregate + dedup + gate - C->>H: emit-review-gate READY/BLOCKED + C->>X: Codex review (sandbox, researches repo itself) + X-->>C: Findings + gate sentinel + H->>H: Parse sentinel into code_review.passed + C->>C: Gate on the tier's blocking severity - alt Issues found - C->>C: Fix all issues + alt Blocking findings + C->>C: Fix them (sub-threshold: log and move on) C->>X: --continue threadId X-->>C: Re-verify end @@ -113,16 +106,19 @@ sequenceDiagram
-## Feature Spotlight: Dual-Reviewer Architecture +## Feature Spotlight: Tiered Review + +One reviewer — Codex — runs everywhere by default. The **tier** decides how much rigour a change gets, and what a reviewer finding has to be before it re-opens the loop: -v2.0 dispatches two independent reviewers in parallel — dual-review by default with degraded fallback modes: +| Tier | Use for | Blocks on | Round cap | +|------|---------|-----------|-----------| +| `fast` | Docs, config, small low-risk edits | P0 | 3 | +| `standard` **(default)** | Ordinary features and bug fixes | P0, P1 | 5 | +| `thorough` | Security, data integrity, releases, public API | P0, P1, P2 | 10 | -| Reviewer | Role | Fallback | -|----------|------|----------| -| Codex MCP | Primary (sandbox, full diff) | Single-reviewer mode if unavailable | -| Secondary (pr-review-toolkit) | Confidence-scored review | strict-reviewer → single mode | +**80 is a passing grade.** Findings below the tier's blocking severity are logged (`[NIT_DEFERRED]`, persisted with a TTL so they are not re-raised next session) and the loop proceeds to `/precommit` — no extra fix pass, no extra review round. `/codex-review-branch` picks them up when the change is next reviewed at depth. -Findings are **severity-normalized** (P0-Nit), **deduplicated** (file + issue key, ±5 line tolerance), and **source-attributed** (`codex` | `toolkit` | `both`). +A second reviewer is available via `/codex-review-branch --dual` and is **off unless the flag is passed** — worth its doubled token and wall-clock cost on a release or a security review, not on a typical fix. Under `--dual`, findings are severity-normalized, deduplicated (file + issue key, ±5 line tolerance) and source-attributed. Gate: `✅ Ready` or `⛔ Blocked` — in strict mode, incomplete gate = blocked. @@ -131,7 +127,7 @@ Gate: `✅ Ready` or `⛔ Blocked` — in strict mode, incomplete gate = blocked | Capability | sd0x-dev-flow | gstack | Generic prompts | |---|---|---|---| | Enforced review gates | Hook + behavior layer | Suggestion only | None | -| Dual-reviewer | Codex + secondary (parallel) | Single /review | None | +| Independent reviewer | Codex, self-researching; `--dual` opt-in | Single /review | None | | Auto-fix loop | Fix → re-review → pass | Manual | None | | Multi-agent research | /deep-research (3 agents) | None | None | | Adversarial validation | Nash equilibrium debate | None | None | @@ -162,12 +158,12 @@ npx skills add sd0xdev/sd0x-dev-flow | Method | Tools | Coverage | |--------|-------|----------| -| Plugin install | Claude Code | Full (96 bundled skills, hooks, rules, auto-loop) | -| `npx skills add` | Codex CLI, Cursor, Windsurf, Aider | Skills only (96 public skills) | +| Plugin install | Claude Code | Full (98 bundled skills, hooks, rules, auto-loop) | +| `npx skills add` | Codex CLI, Cursor, Windsurf, Aider | Skills only (98 public skills) | | `/codex-setup init` | Codex CLI | AGENTS.md kernel + git hooks | -**Requirements**: Claude Code 2.1+ | [Codex MCP](https://github.com/openai/codex) (optional — `/codex-*` skills require it; without it, review falls back to single-reviewer mode) +**Requirements**: Claude Code 2.1+ | [Codex MCP](https://github.com/openai/codex) (optional to install the plugin, required for the `/codex-*` review gates — Codex *is* the single reviewer, so without it a review emits `⛔ Blocked` + `⚠️ Need Human` rather than degrading) ## Workflow Tracks @@ -247,11 +243,11 @@ Real-world scenarios showing which skills to combine and in what order. | Category | Count | Examples | |----------|-------|---------| -| Skills | 96 public (96 bundled) | `/project-setup`, `/codex-review-fast`, `/verify`, `/smart-commit`, `/deep-research` | +| Skills | 98 public (98 bundled) | `/project-setup`, `/codex-review-fast`, `/verify`, `/smart-commit`, `/deep-research` | | Agents | 15 | strict-reviewer, verify-app, coverage-analyst, architecture-designer | -| Hooks | 9 | pre-edit-guard, auto-format, review state tracking, stop guard, namespace hint, post-compact-auto-loop, post-skill-auto-loop, user-prompt-review-guard, session-init | +| Hooks | 8 | pre-edit-guard, auto-format, review state tracking, stop guard, post-compact-auto-loop, post-skill-auto-loop, user-prompt-review-guard, session-init | | Rules | 14 | auto-loop, auto-loop-project, codex-invocation, security, testing, git-workflow, self-improvement, context-management | -| Scripts | 13 | precommit runner, verify runner, dep audit, namespace hint, skill runner, commit-msg guard, pre-push gate, utils (shared lib), emit-review-gate, build-codex-artifacts, resolve-feature (CLI + shell), feature-resolver, readme-catalog | +| Scripts | 17 | precommit runner, verify runner, dep audit, namespace hint, skill runner, commit-msg guard, pre-push gate, emit-review-gate, emit-plan-gate, build-codex-artifacts, resolve-feature (CLI + shell), classify-docs, detect-scope, migration-audit, security-redact, readme-catalog | ### Minimal Context Footprint @@ -291,7 +287,7 @@ Skills load on-demand. Idle skills cost zero tokens.
-All 96 public skills +All 98 public skills ### Development (33) @@ -331,7 +327,7 @@ Skills load on-demand. Idle skills cost zero tokens. | `/smart-rebase` | Smart partial rebase for squash-merge repositories. | | `/watch-ci` | Monitor GitHub Actions CI runs until completion. | -### Review (Codex MCP) (14) +### Review (Codex MCP) (15) | Skill | Description | Loop Support | |-------|-------------|--------------| @@ -346,6 +342,7 @@ Skills load on-demand. Idle skills cost zero tokens. | `/codex-test-gen` | Generate unit tests for specified functions using Codex MCP | - | | `/codex-test-review` | Review test case sufficiency using Codex MCP, suggest additional edge cases. | `--continue ` | | `/doc-review` | Document review via Codex MCP. | - | +| `/plan-review` | Pre-ExitPlanMode adversarial plan review loop via Codex MCP. | - | | `/security-review` | Security review via Codex MCP. | - | | `/seek-verdict` | Independent second-opinion verification for any finding. | - | | `/test-review` | Test coverage review via Codex MCP. | - | @@ -368,7 +365,7 @@ Skills load on-demand. Idle skills cost zero tokens. | `/test-health` | Holistic test coverage measurement. | | `/verify` | Verification loop — lint -> typecheck -> unit -> integration -> e2e | -### Planning (16) +### Planning (17) | Skill | Description | |-------|-------------| @@ -378,6 +375,7 @@ Skills load on-demand. Idle skills cost zero tokens. | `/deep-research` | Universal multi-source research orchestration. | | `/feasibility-study` | Feasibility analysis from first principles. | | `/fp-brief` | First-principles briefing from technical documents. | +| `/orchestrate` | Agent-driven workflow orchestration (v1 report-only). | | `/post-dev-recap` | Post-development recap wrapper. | | `/project-brief` | Convert a technical spec into a PM/CTO-readable executive summary. | | `/recap-ask` | Interactive Q&A over an existing recap document. | @@ -419,7 +417,7 @@ Skills load on-demand. Idle skills cost zero tokens. ## Rules & Hooks -14 rules (always-loaded conventions) + 9 hooks (automated guardrails). +14 rules (always-loaded conventions) + 8 hooks (automated guardrails). > **Customization**: Edit `auto-loop-project.md` to override auto-loop behavior per project. Plugin updates won't conflict — see [Rule Override Pattern](docs/features/rule-override-pattern/2-tech-spec.md). diff --git a/README.zh-CN.md b/README.zh-CN.md index fd8ef90..e8c4809 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -6,7 +6,7 @@ > 给 Claude Code 的 harness 层。 -**AI 跳不过的质量关卡。** 一个面向 [Claude Code](https://claude.com/claude-code) 的 AI Agent Harness Engineering reference implementation — 具备 hook 强制双审查、可跨 context compaction 存活的 state-machine gates,以及在关键环节 fail-closed 的安全机制。 +**AI 跳不过的质量关卡。** 一个面向 [Claude Code](https://claude.com/claude-code) 的 AI Agent Harness Engineering reference implementation — 具备 hook 强制的 review gate、可跨 context compaction 存活的 state-machine gates,以及在关键环节 fail-closed 的安全机制。 96 bundled · 96 public skills · 15 agents — 仅占 Claude context window 的 ~4% @@ -28,7 +28,7 @@ sd0x-dev-flow 是一个 reference implementation。下表的每一行都把一 | 4 | **Lifecycle interceptors** | 5 类 hook 事件分派到 8 个脚本:PreToolUse / PostToolUse / Stop / SessionStart / UserPromptSubmit | [`hooks/`](hooks/) (8 个脚本) + [`.claude/settings.json`](.claude/settings.json) | | 5 | **Capability-based tool gating** | Skill frontmatter 的 `allowed-tools` — 例如 `/ask` 不具备 Edit/Write 权限 | 95 个公开 skills 中有 86 个声明了 `allowed-tools` | | 6 | **Defense-in-depth safety** | 5 层防护:pre-edit-guard → commit-msg-guard → pre-push-gate → stop-guard → sidecar fail-closed 标记 | [`scripts/pre-push-gate.sh`](scripts/pre-push-gate.sh) + [`scripts/commit-msg-guard.sh`](scripts/commit-msg-guard.sh) + [`hooks/stop-guard.sh`](hooks/stop-guard.sh) | -| 7 | **Generator-evaluator split** | 双审查:Codex(主)+ Claude(次)在每一轮审查循环中并行分派 | [`rules/codex-invocation.md`](rules/codex-invocation.md) + [`rules/auto-loop.md`](rules/auto-loop.md) (Dual Review Mode) | +| 7 | **Generator-evaluator split** | Codex 审查 Claude 写的东西,自行研究 repo——绝不喂结论让它确认 | [`rules/codex-invocation.md`](rules/codex-invocation.md) + [`rules/auto-loop.md`](rules/auto-loop.md) (Review Dispatch) | | 8 | **Incremental progress tracking** | `iteration_history.current_round` + `max_rounds` + 收敛停滞侦测 | [`rules/auto-loop.md`](rules/auto-loop.md) (exit conditions + strategic reset) | | 9 | **Human-in-the-loop safety gates** | 针对破坏性操作使用 `/dev/tty` 确认 + `AskUserQuestion` | [`scripts/pre-push-gate.sh`](scripts/pre-push-gate.sh) + [`skills/push-ci/SKILL.md`](skills/push-ci/SKILL.md) | | 10 | **Self-improvement loop** | 纠正 → 记录 lesson → 累计 3 次以上后晋升为 rule | [`rules/self-improvement.md`](rules/self-improvement.md) | @@ -40,7 +40,7 @@ sd0x-dev-flow 是一个 reference implementation。下表的每一行都把一 | 没有防护时 | 有 sd0x-dev-flow | |---|---| | Context 过长时 AI 跳过审查 | **Hook 强制**:stop-guard 阻止未完成的审查 | -| 单一审查者遗漏问题 | **双审查分派**:Codex + 次要审查者并行 | +| 自我审查等于盖橡皮图章 | **独立 reviewer**:Codex 自行研究 repo,需要深度时再 opt-in `--dual` | | 「已修复」却没有重新验证 | **Auto-loop**:修复 → 重新审查 → 通过 → 继续 | | 审查状态在 compact 后丢失 | **状态追踪**:SessionStart hook 重新注入 | @@ -55,7 +55,7 @@ sd0x-dev-flow 是一个 reference implementation。下表的每一行都把一 /project-setup ``` -一个命令自动检测框架、包管理器、数据库、入口文件和脚本命令。安装部分 rules 和 hooks;完整插件包含 14 条 rules + 9 个 hooks。 +一个命令自动检测框架、包管理器、数据库、入口文件和脚本命令。安装部分 rules 和 hooks;完整插件包含 14 条 rules + 8 个 hooks。 使用 `--lite` 仅配置 CLAUDE.md(跳过 rules/hooks)。 @@ -73,34 +73,27 @@ flowchart LR S -.- S1["/smart-commit
/push-ci
/create-pr
/pr-review"] ``` -**Auto-Loop 引擎**自动执行质量关卡——代码编辑后,review 命令会分派**双 Reviewer 并行审查**(Codex MCP + 次要 reviewer 同步进行)。Findings 会去重、severity 正规化,并汇整为单一 gate。在 strict 模式下,Hooks 强制 fail-closed 语义:汇整 gate 未完成时,stop-guard 会阻止停止。详见 [docs/hooks.md](docs/hooks.md)。 +**Auto-Loop 引擎**自动执行质量关卡——代码编辑后,review 命令会在同一条回复内分派 **Codex**。什么算 blocking 由 tier 决定(`fast` P0 · `standard` P0/P1 · `thorough` P0/P1/P2);低于该门槛的 findings 只记录下来,loop 继续往前,不再多开一轮。在 strict 模式下,Hooks 强制 fail-closed 语义:gate 未完成时,stop-guard 会阻止停止。第二位 reviewer 走 `/codex-review-branch --dual`,默认不启用。详见 [docs/hooks.md](docs/hooks.md)。
-详细:双 Reviewer 时序图 +详细:Review Loop 时序图 ```mermaid sequenceDiagram participant D as Developer participant C as Claude participant X as Codex MCP - participant T as Secondary Reviewer participant H as Hooks D->>C: Edit code H->>H: Track file change - C->>H: emit-review-gate PENDING - par Dual Review - C->>X: Codex review (sandbox) - and - C->>T: Task(code-reviewer) - end - X-->>C: Findings (primary) - T-->>C: Findings (secondary) - C->>C: Aggregate + dedup + gate - C->>H: emit-review-gate READY/BLOCKED + C->>X: Codex review (sandbox, researches repo itself) + X-->>C: Findings + gate sentinel + H->>H: Parse sentinel into code_review.passed + C->>C: Gate on the tier's blocking severity - alt Issues found - C->>C: Fix all issues + alt Blocking findings + C->>C: Fix them (sub-threshold: log and move on) C->>X: --continue threadId X-->>C: Re-verify end @@ -113,16 +106,19 @@ sequenceDiagram
-## 功能亮点:双 Reviewer 架构 +## 功能亮点:分档 Review + +默认只有一位 reviewer——Codex。**tier** 决定一项改动要多严格,以及一个 finding 要多严重才会重开 loop: -v2.0 并行分派两个独立 reviewer — 默认双 reviewer 并行审查,支持降级 fallback 模式: +| Tier | 适用 | Blocking | 轮次上限 | +|------|------|----------|----------| +| `fast` | 文档、配置、低风险小改 | P0 | 3 | +| `standard` **(默认)** | 一般功能与 bug fix | P0、P1 | 5 | +| `thorough` | 安全性、数据完整性、release、public API | P0、P1、P2 | 10 | -| Reviewer | 角色 | 降级策略 | -|----------|------|----------| -| Codex MCP | 主要(sandbox,完整 diff) | 不可用时退回单 reviewer 模式 | -| 次要(pr-review-toolkit) | 置信度评分制审查 | strict-reviewer → 单 reviewer 模式 | +**80 分就是及格。** 低于该 tier blocking 门槛的 findings 会被记录(`[NIT_DEFERRED]`,带 TTL 持久化,下次 session 不会重复被提),loop 直接进 `/precommit`——不多一次修正、不多一轮 review。这些项目会在 `/codex-review-branch` 做深度审查时被捡回来。 -Findings 会**严重度正规化**(P0-Nit)、**去重**(file + issue key,±5 行容差),并**标记来源**(`codex` | `toolkit` | `both`)。 +第二位 reviewer 走 `/codex-review-branch --dual`,**不加标志就不启用**——它让每轮的 token 与时间成本翻倍,值得花在 release 或安全审查上,不值得花在日常修正。启用 `--dual` 时,findings 会做严重度正规化、去重(file + issue key,±5 行容差)与来源标记。 Gate:`✅ Ready` 或 `⛔ Blocked` — strict 模式下,未完成 gate = blocked。 @@ -131,7 +127,7 @@ Gate:`✅ Ready` 或 `⛔ Blocked` — strict 模式下,未完成 gate = blo | 能力 | sd0x-dev-flow | gstack | 通用 prompts | |---|---|---|---| | 强制审查关卡 | Hook + 行为层 | 仅建议 | 无 | -| 双审查者 | Codex + 次要(并行) | 单一 /review | 无 | +| 独立 reviewer | Codex 自行研究;`--dual` opt-in | 单一 /review | 无 | | 自动修复循环 | 修复 → 重新审查 → 通过 | 手动 | 无 | | 多 Agent 研究 | /deep-research(3 agents) | 无 | 无 | | 对抗式验证 | 纳什均衡辩论 | 无 | 无 | @@ -167,7 +163,7 @@ npx skills add sd0xdev/sd0x-dev-flow | `/codex-setup init` | Codex CLI | AGENTS.md kernel + git hooks | -**环境要求**:Claude Code 2.1+ | [Codex MCP](https://github.com/openai/codex)(选用 — `/codex-*` skills 需要;未安装时退回单 reviewer 模式) +**环境要求**:Claude Code 2.1+ | [Codex MCP](https://github.com/openai/codex)(安装 plugin 可不装,但 `/codex-*` review gate 必须有——Codex 本身就是那位唯一的 reviewer,未安装时 review 会直接输出 `⛔ Blocked` + `⚠️ Need Human`,没有可降级的对象) ## 工作流路径 @@ -249,9 +245,9 @@ flowchart TD |------|------|------| | Skills | 96 public (96 bundled) | `/project-setup`, `/codex-review-fast`, `/verify`, `/smart-commit`, `/deep-research` | | 代理 | 15 | strict-reviewer, verify-app, coverage-analyst, architecture-designer | -| 钩子 | 9 | pre-edit-guard, auto-format, review state tracking, stop guard, namespace hint, post-compact-auto-loop, post-skill-auto-loop, user-prompt-review-guard, session-init | +| 钩子 | 8 | pre-edit-guard, auto-format, review state tracking, stop guard, post-compact-auto-loop, post-skill-auto-loop, user-prompt-review-guard, session-init | | 规则 | 14 | auto-loop, auto-loop-project, codex-invocation, security, testing, git-workflow, self-improvement, context-management | -| 脚本 | 13 | precommit runner, verify runner, dep audit, namespace hint, skill runner, commit-msg guard, pre-push gate, utils (shared lib), emit-review-gate, build-codex-artifacts, resolve-feature (CLI + shell), feature-resolver, readme-catalog | +| 脚本 | 17 | precommit runner, verify runner, dep audit, namespace hint, skill runner, commit-msg guard, pre-push gate, emit-review-gate, emit-plan-gate, build-codex-artifacts, resolve-feature (CLI + shell), classify-docs, detect-scope, migration-audit, security-redact, readme-catalog | ### 极小的 Context 占用 @@ -419,7 +415,7 @@ Skills 按需加载。闲置 Skill 不占用任何 Token。 ## 规则与钩子 -14 条规则(常驻加载的规范)+ 9 个钩子(自动化防护栏)。 +14 条规则(常驻加载的规范)+ 8 个钩子(自动化防护栏)。 > **定制化**:编辑 `auto-loop-project.md` 可覆写项目的 auto-loop 行为。插件更新不会冲突 — 详见 [Rule Override Pattern](docs/features/rule-override-pattern/2-tech-spec.md)。 diff --git a/README.zh-TW.md b/README.zh-TW.md index 4276017..74e444b 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -6,7 +6,7 @@ > 給 Claude Code 的 harness 層。 -**AI 跳不過的品質關卡。** [Claude Code](https://claude.com/claude-code) 的 AI Agent Harness Engineering reference implementation — hook 強制雙 review、能在 context compaction 後續存的 state-machine gates,以及在關鍵處 fail-closed 的安全防線。 +**AI 跳不過的品質關卡。** [Claude Code](https://claude.com/claude-code) 的 AI Agent Harness Engineering reference implementation — hook 強制的 review gate、能在 context compaction 後續存的 state-machine gates,以及在關鍵處 fail-closed 的安全防線。 96 bundled · 96 public skills · 15 agents — 僅佔 Claude context window 的 ~4% @@ -28,7 +28,7 @@ sd0x-dev-flow 是一個 reference implementation。下表每一列都將一個 | 4 | **Lifecycle interceptors** | 5 種 hook 事件分派到 8 支腳本:PreToolUse / PostToolUse / Stop / SessionStart / UserPromptSubmit | [`hooks/`](hooks/)(8 支腳本)+ [`.claude/settings.json`](.claude/settings.json) | | 5 | **Capability-based tool gating** | Skill frontmatter 的 `allowed-tools` — 例如 `/ask` 不具備 Edit/Write | 95 個公開 skill 中有 86 個宣告 `allowed-tools` | | 6 | **Defense-in-depth safety** | 5 層防線:pre-edit-guard → commit-msg-guard → pre-push-gate → stop-guard → sidecar fail-closed marker | [`scripts/pre-push-gate.sh`](scripts/pre-push-gate.sh) + [`scripts/commit-msg-guard.sh`](scripts/commit-msg-guard.sh) + [`hooks/stop-guard.sh`](hooks/stop-guard.sh) | -| 7 | **Generator-evaluator split** | 雙 review:每個 review 循環都以並行方式分派 Codex(主要)+ Claude(次要) | [`rules/codex-invocation.md`](rules/codex-invocation.md) + [`rules/auto-loop.md`](rules/auto-loop.md)(Dual Review Mode) | +| 7 | **Generator-evaluator split** | Codex 審查 Claude 寫的東西,自行研究 repo——絕不餵結論要它確認 | [`rules/codex-invocation.md`](rules/codex-invocation.md) + [`rules/auto-loop.md`](rules/auto-loop.md)(Review Dispatch) | | 8 | **Incremental progress tracking** | `iteration_history.current_round` + `max_rounds` + 收斂平台期偵測 | [`rules/auto-loop.md`](rules/auto-loop.md)(exit conditions 與 strategic reset) | | 9 | **Human-in-the-loop safety gates** | 對破壞性操作使用 `/dev/tty` 確認 + `AskUserQuestion` | [`scripts/pre-push-gate.sh`](scripts/pre-push-gate.sh) + [`skills/push-ci/SKILL.md`](skills/push-ci/SKILL.md) | | 10 | **Self-improvement loop** | 被糾正 → 記錄 lesson → 重複 3 次以上後提升為 rule | [`rules/self-improvement.md`](rules/self-improvement.md) | @@ -40,7 +40,7 @@ sd0x-dev-flow 是一個 reference implementation。下表每一列都將一個 | 沒有護欄時 | 有 sd0x-dev-flow | |---|---| | Context 過長時 AI 跳過 review | **Hook 強制**:stop-guard 阻止未完成的 review | -| 單一 reviewer 遺漏問題 | **雙 Reviewer 分派**:Codex + 次要 reviewer 並行 | +| 自我審查等於蓋橡皮圖章 | **獨立 reviewer**:Codex 自行研究 repo,需要深度時再 opt-in `--dual` | | 「已修正」卻沒有重新驗證 | **Auto-loop**:修正 → 重新 review → 通過 → 繼續 | | Review 狀態在 compact 後遺失 | **狀態追蹤**:SessionStart hook 重新注入 | @@ -55,7 +55,7 @@ sd0x-dev-flow 是一個 reference implementation。下表每一列都將一個 /project-setup ``` -一個指令自動偵測 framework、package manager、資料庫、entry point 和 script 指令。安裝部分 rules 與 hooks;完整 plugin 包含 14 條 rules + 9 個 hooks。 +一個指令自動偵測 framework、package manager、資料庫、entry point 和 script 指令。安裝部分 rules 與 hooks;完整 plugin 包含 14 條 rules + 8 個 hooks。 使用 `--lite` 僅設定 CLAUDE.md(跳過 rules/hooks)。 @@ -73,34 +73,27 @@ flowchart LR S -.- S1["/smart-commit
/push-ci
/create-pr
/pr-review"] ``` -**Auto-loop 引擎**自動執行品質關卡——程式碼編輯後,review 指令會分派**雙 Reviewer 並行審查**(Codex MCP + 次要 reviewer 同步進行)。Findings 會去重、severity 正規化,並彙整為單一 gate。在 strict 模式下,Hooks 強制 fail-closed 語意:彙整 gate 未完成時,stop-guard 會阻止停止。詳見 [docs/hooks.md](docs/hooks.md)。 +**Auto-loop 引擎**自動執行品質關卡——程式碼編輯後,review 指令會在同一則回覆內分派 **Codex**。什麼算 blocking 由 tier 決定(`fast` P0 · `standard` P0/P1 · `thorough` P0/P1/P2);低於該門檻的 findings 只記錄下來,loop 繼續往前,不再多開一輪。在 strict 模式下,Hooks 強制 fail-closed 語意:gate 未完成時,stop-guard 會阻止停止。第二位 reviewer 走 `/codex-review-branch --dual`,預設不啟用。詳見 [docs/hooks.md](docs/hooks.md)。
-詳細:雙 Reviewer 時序圖 +詳細:Review Loop 時序圖 ```mermaid sequenceDiagram participant D as Developer participant C as Claude participant X as Codex MCP - participant T as Secondary Reviewer participant H as Hooks D->>C: Edit code H->>H: Track file change - C->>H: emit-review-gate PENDING - par Dual Review - C->>X: Codex review (sandbox) - and - C->>T: Task(code-reviewer) - end - X-->>C: Findings (primary) - T-->>C: Findings (secondary) - C->>C: Aggregate + dedup + gate - C->>H: emit-review-gate READY/BLOCKED + C->>X: Codex review (sandbox, researches repo itself) + X-->>C: Findings + gate sentinel + H->>H: Parse sentinel into code_review.passed + C->>C: Gate on the tier's blocking severity - alt Issues found - C->>C: Fix all issues + alt Blocking findings + C->>C: Fix them (sub-threshold: log and move on) C->>X: --continue threadId X-->>C: Re-verify end @@ -113,16 +106,19 @@ sequenceDiagram
-## 功能亮點:雙 Reviewer 架構 +## 功能亮點:分檔 Review + +預設只有一位 reviewer——Codex。**tier** 決定一項改動要多嚴格,以及一個 finding 要多嚴重才會重開 loop: -v2.0 預設平行分派兩個獨立 reviewer,支援降級 fallback 模式: +| Tier | 適用 | Blocking | 輪次上限 | +|------|------|----------|----------| +| `fast` | 文件、設定、低風險小改 | P0 | 3 | +| `standard` **(預設)** | 一般功能與 bug fix | P0、P1 | 5 | +| `thorough` | 安全性、資料完整性、release、public API | P0、P1、P2 | 10 | -| Reviewer | 角色 | 降級策略 | -|----------|------|----------| -| Codex MCP | 主要(sandbox,完整 diff) | 不可用時退回單 reviewer 模式 | -| 次要(pr-review-toolkit) | 信心分數制審查 | strict-reviewer → 單 reviewer 模式 | +**80 分就是及格。** 低於該 tier blocking 門檻的 findings 會被記錄(`[NIT_DEFERRED]`,帶 TTL 持久化,下次 session 不會重複被提),loop 直接進 `/precommit`——不多一次修正、不多一輪 review。這些項目會在 `/codex-review-branch` 做深度審查時被撿回來。 -Findings 會**嚴重度正規化**(P0-Nit)、**去重**(file + issue key,±5 行容差),並**標記來源**(`codex` | `toolkit` | `both`)。 +第二位 reviewer 走 `/codex-review-branch --dual`,**不加旗標就不啟用**——它讓每輪的 token 與時間成本翻倍,值得花在 release 或安全審查上,不值得花在日常修正。啟用 `--dual` 時,findings 會做嚴重度正規化、去重(file + issue key,±5 行容差)與來源標記。 Gate:`✅ Ready` 或 `⛔ Blocked` — strict 模式下,未完成 gate = blocked。 @@ -131,7 +127,7 @@ Gate:`✅ Ready` 或 `⛔ Blocked` — strict 模式下,未完成 gate = blo | 能力 | sd0x-dev-flow | gstack | 通用 prompts | |---|---|---|---| | 強制 review 關卡 | Hook + 行為層 | 僅建議 | 無 | -| 雙 Reviewer | Codex + 次要(並行) | 單一 /review | 無 | +| 獨立 reviewer | Codex 自行研究;`--dual` opt-in | 單一 /review | 無 | | 自動修正迴圈 | 修正 → 重新 review → 通過 | 手動 | 無 | | 多 Agent 研究 | /deep-research(3 agents) | 無 | 無 | | 對抗式驗證 | Nash 均衡辯論 | 無 | 無 | @@ -167,7 +163,7 @@ npx skills add sd0xdev/sd0x-dev-flow | `/codex-setup init` | Codex CLI | AGENTS.md kernel + git hooks | -**需求**:Claude Code 2.1+ | [Codex MCP](https://github.com/openai/codex)(選用 — `/codex-*` skill 需要;未安裝時退回單 reviewer 模式) +**需求**:Claude Code 2.1+ | [Codex MCP](https://github.com/openai/codex)(安裝 plugin 可不裝,但 `/codex-*` review gate 必須有——Codex 本身就是那位唯一的 reviewer,未安裝時 review 會直接輸出 `⛔ Blocked` + `⚠️ Need Human`,沒有可降級的對象) ## Workflow Tracks @@ -249,9 +245,9 @@ flowchart TD |------|------|------| | Skills | 96 public (96 bundled) | `/project-setup`, `/codex-review-fast`, `/verify`, `/smart-commit`, `/deep-research` | | Agents | 15 | strict-reviewer, verify-app, coverage-analyst, architecture-designer | -| Hooks | 9 | pre-edit-guard, auto-format, review state tracking, stop guard, namespace hint, post-compact-auto-loop, post-skill-auto-loop, user-prompt-review-guard, session-init | +| Hooks | 8 | pre-edit-guard, auto-format, review state tracking, stop guard, post-compact-auto-loop, post-skill-auto-loop, user-prompt-review-guard, session-init | | Rules | 14 | auto-loop, auto-loop-project, codex-invocation, security, testing, git-workflow, self-improvement, context-management | -| Scripts | 13 | precommit runner, verify runner, dep audit, namespace hint, skill runner, commit-msg guard, pre-push gate, utils (shared lib), emit-review-gate, build-codex-artifacts, resolve-feature (CLI + shell), feature-resolver, readme-catalog | +| Scripts | 17 | precommit runner, verify runner, dep audit, namespace hint, skill runner, commit-msg guard, pre-push gate, emit-review-gate, emit-plan-gate, build-codex-artifacts, resolve-feature (CLI + shell), classify-docs, detect-scope, migration-audit, security-redact, readme-catalog | ### 極小的 Context 佔用 @@ -419,7 +415,7 @@ Skills 按需載入。閒置 Skill 不佔用任何 Token。 ## Rules & Hooks -14 條 rules(常駐載入的慣例)+ 9 個 hooks(自動化護欄)。 +14 條 rules(常駐載入的慣例)+ 8 個 hooks(自動化護欄)。 > **客製化**:編輯 `auto-loop-project.md` 可覆寫專案的 auto-loop 行為。Plugin 更新不會衝突 — 詳見 [Rule Override Pattern](docs/features/rule-override-pattern/2-tech-spec.md)。 diff --git a/docs/features/auto-loop-enforcement/2-tech-spec.md b/docs/features/auto-loop-enforcement/2-tech-spec.md index 9234fa1..8b25d99 100644 --- a/docs/features/auto-loop-enforcement/2-tech-spec.md +++ b/docs/features/auto-loop-enforcement/2-tech-spec.md @@ -24,7 +24,7 @@ | `hooks/post-tool-review-state.sh` | State file update(code_review, doc_review, precommit) | 供 SessionStart compact hook 讀取 | | `hooks/hooks.json` | Hook event 註冊 | 新增 SessionStart compact entry | | `hooks/post-edit-format.sh:203-225` | Edit 後設定 has_code/doc_change + invalidate state | 供 SessionStart compact hook 判斷 pending | -| `rules/auto-loop.md:5-14` | Prohibited behaviors 列表 | Re-injection 內容來源 | +| `rules/auto-loop.md` § The Four Anchors | 四條不可協商的 anchor(2026-07-26 前名為 Prohibited Behaviors) | Re-injection 內容來源 | | `commands/install-hooks.md` | Hook 安裝指令 | 更新以包含 SessionStart compact | ### Key Insight diff --git a/docs/features/auto-loop-evolution/2-tech-spec.md b/docs/features/auto-loop-evolution/2-tech-spec.md index dc6187d..3b2731d 100644 --- a/docs/features/auto-loop-evolution/2-tech-spec.md +++ b/docs/features/auto-loop-evolution/2-tech-spec.md @@ -112,7 +112,7 @@ flowchart TD **Migration**: Hook 讀取 `schema_version`;缺失時視為 v1(向後相容)。新欄位全部 optional,舊 hook 忽略。 -**Rule alignment**: `max_rounds` 已在 `rules/auto-loop.md:86` 定義(預設 10),state file 追蹤 `current_round`。本 spec 擴展既有邏輯加入 convergence detection(fingerprint overlap),不修改 max_rounds 數值。 +**Rule alignment**: `max_rounds` 由 `rules/auto-loop.md` § Tiers 決定(`fast` 3 / `standard` 5 / `thorough` 10,未設定即 `standard`;`auto-loop-project.md` 的 `## Max Rounds` 可覆寫),state file 追蹤 `current_round`。本節原文寫「預設 10」,那是 2026-07-26 導入 tier 之前的單一預設值。本 spec 擴展既有邏輯加入 convergence detection(fingerprint overlap),不修改 max_rounds 數值。 #### Nit History File (`.claude_nit_history.json`) diff --git a/docs/features/auto-loop-evolution/4-implementation.md b/docs/features/auto-loop-evolution/4-implementation.md new file mode 100644 index 0000000..42c2d84 --- /dev/null +++ b/docs/features/auto-loop-evolution/4-implementation.md @@ -0,0 +1,253 @@ +# Auto-Loop Hook Internals — Implementation Notes + +Forensic record of *why* the auto-loop hooks have the shape they do. Every paragraph here was paid for by a real defect. + +**This document is deliberately not loaded into session context.** It moved out of `rules/auto-loop.md`, which is imported via `@` on every session: 71% of that rule was material like this — lock protocols, symlink archaeology, byte-offset pairing — that a model cannot act on but must nonetheless carry. The rule keeps the behavioural contract; this file keeps the reasoning. + +Read this when **changing** a hook, **debugging** a gate that did not fire, or **reviewing** a change to the state machine. Do not read it to decide what to do next in a loop — `rules/auto-loop.md` is authoritative for that. + +| Related | Path | +|---------|------| +| Behavioural contract | [`rules/auto-loop.md`](../../../rules/auto-loop.md) | +| Feature tech spec | [`2-tech-spec.md`](./2-tech-spec.md) | +| Writing hooks | `hooks/post-tool-review-state.sh`, `hooks/post-edit-format.sh`, `hooks/post-compact-auto-loop.sh`, `hooks/session-init.sh` | +| Reading hook | `hooks/stop-guard.sh` | + +--- + +## 1. State file + +**File**: `.claude_review_state.json` (locally ignored) + +```json +{ + "session_id": "abc123", + "updated_at": "2026-01-26T10:00:00Z", + "has_code_change": true, + "has_doc_change": false, + "code_review": { "executed": true, "passed": true, "last_run": "2026-01-26T10:00:00Z" }, + "doc_review": { "executed": false, "passed": false }, + "precommit": { "executed": true, "passed": true, "last_run": "2026-01-26T10:01:00Z" } +} +``` + +That is an **abridged illustration**, not the schema. The creation shape is the heredoc in `init_state_file()` (`post-tool-review-state.sh`), which also carries `review_mode`, `aggregate_gate`, `plan_review`, `iteration_history`, and `schema_version`. Further keys appear at runtime as their owners write them — `review_phase` (gate transitions), `changed_files_since_review` (written by `post-edit-format.sh`, cleared by `_reset_changed_files()`), `session_commit_scope` (`session-init.sh`). Each hook writes only the subtree it owns. + +All **four** writing hooks coordinate on the shared `${STATE_FILE}.lockdir`. `session-init.sh` was the exception until 2026-07-26: it rewrote the *whole* file on a session change without taking the lock, so a verdict committed between its `jq` read and its `mv` was silently clobbered by the reset. On contention it now skips the reset rather than writing unlocked; the retained state is safe because stale gate values only matter once something is edited, and the edit hook invalidates them first. + +### 1.1 State-lock ownership + +Acquisition writes a per-process `owner` token; release removes the directory only when that token still matches. Two consequences the old flag-based release did not have: a process whose lock was taken over no longer deletes its successor's lock on the way out, and each committing `mv` re-checks ownership so a displaced writer degrades through the fail-closed handler (a marker, or a logged skip) instead of committing over a live transaction. + +Stale recovery takes the lock by **renaming it aside** to a process-unique tombstone rather than `rm -rf` + `mkdir`: the delete-then-create pair let two contenders that both judged the lock stale each enter the critical section. A takeover between the ownership re-check and the `mv` remains — the same check-then-act residual the lock itself has. Narrowed, not closed. + +--- + +## 2. Round counter lifecycle + +`current_round` counts rounds **within one convergence loop**, not edits. + +| Event | `current_round` | `total_rounds_session` | +|-------|-----------------|------------------------| +| **Code**-review iteration (`_update_iteration()`), passing or not | +1 (best-effort) | +1 (same) | +| Doc review (`/codex-review-doc`), passing or not | unchanged | unchanged | +| Code edit (`post-edit-format.sh`) | unchanged | unchanged | +| **`precommit`** passes, counter valid and below the CLAMPED cap | reset to 0 | unchanged | +| Same, but counter invalid or at the clamped cap | **not reset** | unchanged | +| New session (`session-init.sh`) | reset to 0 | preserved | + +### 2.1 The `+1` is best-effort + +`_update_iteration()` degrades to a no-op (returns 0, never aborts the hook) when the state file is absent, when `_lock` times out against a concurrent writer, when `mktemp` fails, when the `jq` rewrite fails, or when the final rename fails — each path logs to stderr and leaves the counter where it was. The absent-state and rename paths were silent until 2026-07-25; the rename one additionally reported *success* under `HOOK_DEBUG=1`, which is why a hard cap that never fired was undiagnosable from the logs. + +A skipped increment always *undercounts*, so the effect is a hard cap reached later than the nominal `max_rounds`, never earlier. The same applies to any verdict dropped upstream (unanchored command, MCP output with no Merge Gate): no verdict recorded → no `_update_iteration()` call → no round. **Treat `current_round` as a lower bound on rounds actually run.** + +### 2.2 Why a doc gate does not reset it + +`_update_iteration()` is called from the code-review branches only (Bash and MCP), on **both** the pass and fail paths — a `⛔ Blocked` round costs budget exactly like a `✅ Ready` one. The doc-review branches record their verdict without touching `iteration_history`, so a doc-only loop has no round budget and never reaches the hard cap; it converges on `✅ Mergeable` or not at all. + +A passing `doc_review` does **not** reset `current_round`, even though it is a terminal gate. `current_round` is a **code** counter, so letting a doc gate zero it is a cross-plane refund. It was one until 2026-07-25, and the consequence was concrete: with `code_review.passed = false` at round 9 of 10, one passing `/codex-review-doc` rewound the round to 0 and emptied `findings_by_round`, so repeated doc passes held an unconverged code loop permanently below the cap — the only convergence exit that is actually enforced. A doc-only session is unaffected either way: it never increments the counter, so there was never anything for its reset to do. + +### 2.3 The reset is gate-conditioned and validity-conditioned + +Both conditions live in `update_state()`'s jq filter. + +The passing gate must be `precommit` specifically (`$passed == true and $key == "precommit"`) — no other terminal gate qualifies. + +The counter must also be one stop-guard would itself accept as unspent: the writer **mirrors the reader's `ITER_PARSED` validation** — object-typed `iteration_history`, a `number` that is integral and in range, and `current_round` below the cap **after the cap is clamped to the producer contract 3..50**. + +The mirror is load-bearing because the writer runs first. A guard that merely checked `type == "number"` against the raw `max_rounds` reset `{current_round: 51, max_rounds: 100000}` to a clean `0`, while the reader clamps that cap to 50 and reads the same state as an exhausted budget — the writer was refunding, from under the reader, the only convergence exit actually enforced. + +One asymmetry inside that mirror is easy to get backwards, and was: + +| | Raw literal must be digits-only? | Why | +|---|---|---| +| `current_round` | **Yes** | jq preserves each number's literal form (`1e2` stays `1E+2`), and it reaches the reader's final bash regex verbatim | +| `max_rounds` | **No** | The reader clamps it first; only the clamp's *output* is interpolated | + +Canonicality must therefore be tested on the raw `current_round` and on the **clamped** cap. Testing the raw cap reopened the same class of divergence pointing the other way: `{current_round: 4, max_rounds: 1e2}` reads to stop-guard as a valid, unspent `4 50`, while the writer refused to reset on `1E+2` — so the loop walked to the cap and latched on `⚠️ Need Human` with no visible cause. Both filters are pinned to the same answers under real `jq` by `test/hooks/jq-filter-fidelity.test.js`. + +### 2.4 Latching + +Once the counter reaches the clamped cap the reset stops applying even when precommit passes: the loop has already been escalated to a human, and letting one passing precommit refund the whole budget would hand the same unbounded loop back to the model. + +"Latched" means the **reset no longer fires**, not that the number is clamped. Nothing pins it to `max_rounds`, and under the default `warn` mode the counter keeps incrementing past the cap on every further code-review round. **A value above `max_rounds` is normal and is not evidence of corruption.** Clearing a latched counter is a human action — start a new session, or edit `iteration_history.current_round` deliberately. + +A code edit is a step *inside* the loop (review → fix → re-review), so it must not refund the round budget — resetting there made the hard cap unreachable, since auto-loop always edits between reviews. `findings_by_round` follows the same lifecycle and is capped at the 50 most recent entries. + +--- + +## 3. Fail-closed sidecar + +**Files**: `${STATE_FILE}.blocked` — one reason per line, treated as a **set**, not a scalar — **plus** per-event emergency markers written as **sibling files** named `${STATE_FILE}.blocked.event.`, one reason per file. The two are read as a single union; every reader consults both. + +When a hook cannot durably record a state transition (lock lost, `mktemp` failed, `jq` failed, state file uncreatable), it records a marker here instead of failing silently. + +### 3.1 Why there are two files, not one + +The shared `.blocked` file is rewritten or removed **wholesale** by a clearer holding the lock. That is only sound if every writer is serialized too, and the setter's last-resort path deliberately was not: on lock timeout it appended anyway, because dropping a marker is worse than duplicating one. A whole-file rewrite therefore raced an unserialized append, and no amount of re-reading closes that — the clearer's final snapshot is a subprocess, so an append landing between that read returning and the `rm`/`mv` is invisible to it and is then erased. Three rounds narrowed the window without removing it, which is what check-then-act always does. + +The last-resort path no longer touches the shared file. It creates its own file under a name no other writer will choose, so creation and retirement act on **disjoint names** and cannot destroy one another. + +| | Effect | +|---|---| +| Shared file | Now has **no** unserialized writers, which is what makes the clearers' snapshot comparison sufficient rather than merely narrow | +| Spin budget | Setters are back to the default 20 spins (~2 s). They briefly used 70 to out-wait `session-init.sh`'s `timeout 5` tree scan; that only bought anything while a timeout meant a lose-able append. It also **cost** something: setters call the sidecar lock *inside* the state lock, so a transaction with four failing writes waited 4 × 7 s — measured at 29.95 s against `LOCK_TTL=30`, running itself to its own takeover threshold. `test/hooks/post-edit-format.test.js` derives that bound from the three constants rather than restating a number | + +### 3.2 A timeout was not the only way to become unserialized + +A setter that *acquired* the lock and was then **displaced** — `_sidecar_lock` reclaims on age alone, and setters run inside the state lock, whose TTL is the same 30 s, so a slow transaction can drift past its own sidecar lock's expiry — appended anyway, because nothing between `mkdir` and `>>` re-read the owner token. + +One live displaced holder falsifies "no unserialized writers" just as completely as the timeout path did, and it is the harder one to see, because the process genuinely did hold the lock at the moment it decided to write. `_set_own_sidecar_locked` now re-checks ownership immediately before its first mutating statement and returns a distinct `rc=3`, which the caller diverts to a per-event marker with its own diagnostic. The clearers already carried the mirror-image check; the setter is what was missing. This narrows the window to two adjacent statements rather than closing it. + +### 3.3 Sibling files, not a marker directory + +The per-event plane is `${STATE_FILE}.blocked.event.` alongside the state file, and that is a **security boundary** rather than a layout preference. + +An earlier `.blocked.d/` directory made the orphan clear a `rm -f "$dir"/…`, which resolves **through** a symlink at `$dir` and unlinks the *target's* files — so a symlink committed at `.blocked.d` turned the clear into "delete every regular file in an arbitrary directory". Git stores symlinks, and `.claude_review_state.json.*` is gitignored, so cloning the repo was enough to arm it. `rm -f` on a symlink **file** unlinks the link and never its target, so the same accident against a sibling name destroys nothing. An `lstat` guard on the directory would not have been equivalent: that is check-then-act, and the name can be swapped between the check and the `rm`. + +### 3.4 Per-event marker lifetime + +Per-event markers are retired **only** by `session-init.sh`'s orphan clear, and only the specific filenames that clear enumerated — one created afterwards has a name the retiring loop never saw. Its precondition (a new session over a tree with no dirty reviewable file) is the one under which every marker is an orphan by definition. + +For the lock-timeout case they are rare, so holding one until the next clean session over-blocks briefly and in the safe direction. That "rare" does **not** transfer to the other creation path. A lock timeout is a transient race that resolves itself; the write-failure divert fires on a **persistent local condition** — a directory or an unwritable file sitting at the shared `.blocked` name — which recurs on every call for as long as it lasts. Two consequences: + +- Each failing call adds another undeduplicated marker file rather than re-raising one. +- A per-event marker is invisible to the ordinary retirement paths, so the session stays in strict mode even after the verdict the marker stood in for lands successfully: `_clear_own_sidecar` operates on the shared file alone, so a successful write cannot retire its private twin. + +Both are fail-closed, deliberately — the divert exists precisely because the ordinary bookkeeping could not be trusted to run. The escape is a human one (fix the path, then start a session over a clean tree), not something the next passing gate will clear. + +### 3.5 How stop-guard reads it + +Stop-guard **invalidates the affected gate in every case**. Whether that invalidation also *blocks* depends on which markers are present: + +| Sidecar contents | Gate | Mode | +|------------------|------|------| +| Only transient markers | Invalidated | Unchanged — `warn` still warns and exits 0 | +| Any non-transient marker, or a **mix** | Invalidated | Escalated to `strict`, overriding the configured `GUARD_MODE` | +| Present but **empty or unreadable** | Invalidated | Escalated to `strict` — a file that exists but yields no lines is itself evidence of a failed write | + +**Transient allowlist**: `edit_lock_contention:code`, `edit_lock_contention:doc`, `lock_failure`. A sidecar containing *only* these does not force strict mode. Any other marker, or any mix, does. Empty or unreadable escalates (`_SIDECAR_LINES_SEEN == 0` → not-all-transient). + +"Forces strict blocking regardless of `GUARD_MODE`" was once written here as an unconditional claim, and it is wrong in the first row: the transient allowlist exists precisely so a lock contention that resolves itself does not override a user's `warn` preference. + +### 3.6 Markers are keyed by plane + +The two writing hooks invalidate different gates. An unkeyed marker let a *doc* edit retire evidence of a lost *code* verdict — fail-OPEN, since the previous round's `✅` survived. Losing a `PASS` is safe (the gate stays unsatisfied and is re-requested); losing a `⛔` is not. + +| Plane | Markers | Cleared by | +|-------|---------|-----------| +| Edit — code | `edit_lock_contention:code`, `state_init_failed:code`, `state_write_failed:code` | A committed **code**-edit transaction (`post-edit-format.sh`), or `session-init.sh`'s clean-tree check | +| Edit — doc | `edit_lock_contention:doc`, `state_init_failed:doc`, `state_write_failed:doc` | A committed **doc**-edit transaction, or the same session check | +| Verdict | `verdict_write_failed:code_review` / `:doc_review` / `:precommit` | The next successful write of **that same gate**; also by the edit transaction that invalidates that gate (code branch → `code_review` + `precommit`; doc branch → `doc_review`) | +| Aggregate gate | `lock_failure`, `aggregate_write_failed` | A **committed** aggregate transition (`update_aggregate_gate`), **or** a committed edit transaction on either plane — both branches reset `aggregate_gate` to `executed=false`/`gate=null`, the same fail-closed value the lost transition would have left. A single-*review* write does **not** qualify: it never touches `aggregate_gate`, so erasing the marker there would drop a lost dual-gate transition | + +The "Cleared by" column describes markers in the **shared** file. A per-event marker carries the same reason string but the lifetime in §3.4. + +### 3.7 The sidecar lock + +Every sidecar mutation in all three writing hooks reaches for a dedicated `mkdir`-based lock (`SIDECAR_LOCKDIR`): **the same protocol in each copy**, same directory name and TTL, because a lock only some writers take excludes nothing. The two halves then diverge deliberately, and the difference is the fail-closed direction of each: + +| Half | Lock unavailable | Why | +|------|------------------|-----| +| **Set** (`_set_own_sidecar`) | Writes a **per-event marker** instead and logs it | Declining would DROP the marker, and a marker exists only because a blocking verdict was already lost. A private filename makes the fallback race-free: no clearer can retire a name it never enumerated | +| **Clear** (`_clear_own_sidecar`) | Declines — the set is retained | A clear removes evidence. Doing that unserialized can erase a line another writer added between the read and the rewrite | + +Ownership-aware clears commit via `mktemp` + `mv`; a clear that cannot stage its rewrite **retains the full set** rather than dropping it. + +The per-event marker is not only the lock-unavailable fallback. A setter that *holds* the lock and still cannot write the shared file **diverts to the same private marker, for every failure** — not just the symlink refusal. The two are reported separately because they mean different things (a symlink must not be written *through*; an ordinary failure was attempted and failed), but they no longer differ in whether the evidence survives. + +Restricting the divert to the symlink case read as "an ordinary write failure means nothing can be written at that path", and that inference does not hold: the shared sidecar has one fixed name, so a **directory** sitting on it fails the append with `EISDIR` while `_sidecar_emergency_mark` — which needs neither `mktemp` nor a lock, only a sibling filename — would have succeeded right beside it. Under the old rule the marker was dropped there, and the aggregate caller then read the empty sidecar plane as *total persistence loss* and escalated a recoverable condition to a blocking `exit 2`. That escalation is also what makes the `exit 2` branch's own reasoning sound: it infers "no on-disk channel remains" from the emergency marker having failed, which is only true if the emergency marker is always **attempted**. + +It is deliberately **not** the same protocol as the state lock: + +| | State lock (`${STATE_FILE}.lockdir`) | Sidecar lock (`${STATE_FILE}.blocked.lockdir`) | +|---|---|---| +| Staleness | TTL **or** dead owner (`pid` file + `kill -0`) | TTL only — no `pid` file, so a writer killed mid-section wedges every sidecar mutation for the full 30 s rather than being reclaimed by the next contender | +| Retry budget | `REVIEW_STATE_LOCK_TIMEOUT` seconds (env-overridable; the hook suites set `0`) | Fixed 20 spins (~2 s) — the env override has no effect on this path | + +### 3.8 The one full-file delete + +`session-init.sh` removes the sidecar when a new session finds the working tree free of dirty reviewable files, at which point every marker — whatever plane wrote it — is by definition an orphan, because no dirty file remains for any of them to stand in for. + +Its `git status -uall` scan runs **inside** the lock, not before it; scanning first left a seconds-wide window in which a concurrent `verdict_write_failed:*` append was unlinked, retiring evidence of a lost blocking verdict whose gate still read `passed=true`. Lock unavailable → the sidecar is retained. + +--- + +## 4. Sentinel parsing + +### 4.1 Who reads what + +| Role | Owner | What it does | +|------|-------|--------------| +| Producer | Review skills | Emit the sentinel in their output | +| State writer | `post-tool-review-state.sh` (PostToolUse) | Parses tool output and records the verdict into the state file — where a sentinel becomes durable | +| Primary enforcer | `stop-guard.sh` (Stop) | Reads the **state file**; it does not re-parse reviewer output when state exists | +| Fallback parser | `stop-guard.sh`, transcript mode | Only when there is no readable state file: scans `tail -500` of the JSONL transcript and **pairs** each verdict with the invocation it is credited to | + +### 4.2 Transcript verdict/invocation pairing + +The fallback's two scans used to be position-blind — "does a verdict appear anywhere" and "does the command appear anywhere", never related in time. A transcript reading `/precommit-fast` → `## Overall: ✅ PASS` → `/precommit` therefore satisfied the gate for the *newer* invocation using the *older* run's verdict, and the `PRECOMMIT_REQUIRE_FULL` branch — which reads the variant off that same trailing invocation — then declared the full gate met. Two checks, both satisfied, zero evidence. The code and doc planes had the identical shape. + +A verdict now counts only when it appears **after** the last matching invocation; unpaired reads as absent, i.e. `(invoked, no verdict)`. + +The comparison is by **byte offset**, not line number: the transcript is JSONL, so one line packs a whole message and a report of the previous gate routinely shares a line with the announcement of the next command — line granularity therefore had to accept equality, and accepting equality is exactly what let the older run's verdict through. Both scans run on the **plan-sentinel-stripped** stream, because stripping deletes bytes and measuring one side on the raw transcript would read the two off different rulers, letting upstream plan-review output push a genuine verdict behind its own invocation. + +**Known over-block (deliberate)**: the command detectors match prose, so `/precommit` written in a summary *after* a passing run re-opens the gate — the same weakness the `(invoked, no verdict)` branch already had for mentions *before* a verdict, and fail-closed in the same direction. The state-file branch pairs by construction and is unaffected. + +### 4.3 Precommit anchoring — state writer + +In `post-tool-review-state.sh` the precommit sentinel is the WHOLE line `## Overall: ✅ PASS` — matched at column 0, last `## Overall:` line wins, and **nothing may follow it** on that line. A prefix match would let the `## Overall: ✅ PASS / ❌ FAIL / ⚠️ NO CHECKS RUN` template line in `skills/precommit/SKILL.md` bank a pass; taking the first match instead of the last would let a `## Overall: ✅ PASS` inside the runner's embedded test tail mask a real final `❌ FAIL`. + +**On the phrase `✅ All Pass`.** It is behaviour-layer prose for "every gate passed" — *not* the precommit sentinel, and no hook treats it as a verdict. Be precise about the scope of that claim, because one grep does still contain it: `_skill_output_has_verdict()` lists it among the markers that distinguish a real review verdict from a Skill *launch acknowledgement*. That is a presence test — "is this output a verdict at all", deciding whether to parse further — not a classifier, and it decides nothing about pass or fail. The classification below it drops the phrase outright. + +An earlier version of that sentence claimed no hook scan matched it *at all*, which was flatly false; and before that it was untrue in the way that actually mattered: `stop-guard.sh`'s two **coarse, plane-agnostic** transcript scans (`REVIEW_PASSED` and `LAST_REVIEW`) both listed it among their passing patterns. In `LAST_REVIEW` that mattered in the wrong direction — `tail -1` takes the last matching line, so a message ending in the phrase out-ranked an earlier `⛔ Blocked` and cleared the coarse `BLOCKED_REASON`. The additive per-plane scans that run afterwards still caught that case, so it was not a live bypass; it was removed because a phrase the model emits freely in prose should not be able to out-rank a real verdict. + +### 4.4 Precommit anchoring — transcript fallback + +`stop-guard.sh`'s fallback cannot reuse that parser. It reads `tail -500` of a **JSONL** transcript, where a genuine sentinel sits inside a JSON string on a line beginning with `{`, so literal column-0 anchoring (`^`) would match nothing in production while still passing plain-text fixtures. + +It instead demands the sentinel both **start** and **end** a line, in whichever of the two encodings applies: + +| Side | Accepted | +|------|----------| +| Leading | `^`, a literal `\n` escape, or the opening `"` of the JSON string | +| Trailing | end-of-line, a literal `\n` escape, or `"` | + +**Both groups are load-bearing.** With only the trailing one, a narration *ending* in the sentinel (`…I'll report ## Overall: ✅ PASS`) still matched, and since it carries no FAIL marker `tail -1` let it override an earlier real `⛔ FAIL`. With both, prose mentions fail on the leading side (they are preceded by a space or a backtick), so ``I'll print `## Overall: ✅ PASS` when green`` is rejected. + +Command detection in the same mode uses a "not a command-name character" boundary for the same reason: `"/precommit"` and `/precommit` are the shapes a real transcript actually contains. + +### 4.5 Plan namespace isolation + +Plan sentinels live in the `plan_review.*` state subtree and never touch `code_review` / `doc_review` / `aggregate_gate`. Plan-review output must never contain bare `✅ Ready` / `✅ Mergeable` / `## Gate:` / bare `⛔ Blocked`. Stop-guard treats a pending plan review as **warn-only** (never blocks). Gate transitions flow through `scripts/emit-plan-gate.sh`; see `skills/plan-review/SKILL.md`. + +--- + +## 5. Convergence rows 3–4 are a V2 target + +`_update_iteration()` stores per-round counts and timestamps but no `fingerprints` array, so plateau detection (overlap ≥ 50% across 3+ rounds) is **not computable today**. + +Until hook-side fingerprint storage lands, the round cap is the only convergence exit the hook observes at all — and even that one only *blocks* in `strict`/dual mode; under the default `warn` it prints to stderr and allows the stop. That is precisely why its counter must never be silently rewound (§2): **in warn mode the behaviour layer is the enforcement.** Consistent with [plan-review-loop tech spec](../plan-review-loop/2-tech-spec.md) OQ-9. + +Row 2 of the decision table carries a related trap. `_update_iteration()` derives `total` by counting `- [P0]`/`#### P0`-style lines, so an output in any other shape (reviewer error, truncated response, a format change) yields a perfectly ordinary `0`. There is no `parse_ok` field to tell the two apart — which is why a zero must be corroborated by a passing gate verdict before it is read as convergence. diff --git a/docs/features/auto-loop-evolution/requests/2026-03-25-think-harder-near-cap-r10.md b/docs/features/auto-loop-evolution/requests/2026-03-25-think-harder-near-cap-r10.md index f543c64..9d9586a 100644 --- a/docs/features/auto-loop-evolution/requests/2026-03-25-think-harder-near-cap-r10.md +++ b/docs/features/auto-loop-evolution/requests/2026-03-25-think-harder-near-cap-r10.md @@ -13,6 +13,8 @@ autoresearch 在偵測到 >5 次連續失敗時觸發策略重置(re-read file **關鍵發現**(Codex debate R2): `current_round` 在 code edit 時會 reset(`post-edit-format.sh` code-edit iteration reset block),所以 near-cap 邏輯如果基於 `current_round` 將很少觸發。需要新的 state-file-lifetime counter `total_rounds_session`。 +> **⚠️ 已被後續變更取代(2026-07-25)**:上述「code edit 會 reset `current_round`」**已不再成立**。該 reset 正是讓 row-1 hard cap 永遠碰不到的原因(auto-loop 每輪 review 之間必然有 edit),因此已移除——code edit 現在**不會**動 `current_round`。目前的生命週期**一律以 [`docs/features/auto-loop-evolution/4-implementation.md`](../4-implementation.md) §2 Round counter lifecycle 為準**(2026-07-26 起該表從 `rules/auto-loop.md` 移出,規則本體只保留一句「`current_round` 是實際輪數的下界」)——本文不再複述歸零條件(先前那句已經過時兩處:`doc_review` 同為終端 gate 卻被刻意排除在歸零之外,且上界是 clamp 過的 cap 而非原始 `max_rounds`,複述本身就是過時來源)。本節保留原文以記錄當時的推導脈絡,**不代表現行行為**;`total_rounds_session` 這個結論本身仍然成立(它另有理由:永不歸零,反映累計投入)。 + ## Requirements - State file 新增 `iteration_history.total_rounds_session`(state-file lifetime,不因 edit 或 session change reset) diff --git a/docs/features/create-pr-ai-sanitization/2-tech-spec.md b/docs/features/create-pr-ai-sanitization/2-tech-spec.md index b550d1e..0d13d88 100644 --- a/docs/features/create-pr-ai-sanitization/2-tech-spec.md +++ b/docs/features/create-pr-ai-sanitization/2-tech-spec.md @@ -13,20 +13,20 @@ | Module | 可復用部分 | | ------ | ---------- | -| `scripts/commit-msg-guard.sh:19-23` | Canonical forbidden patterns(3 組 POSIX ERE) | -| `skills/smart-commit/SKILL.md:290-305` | AI trailer sanitization flow、`--ai-co-author` narrow whitelist | +| `scripts/commit-msg-guard.sh` | Canonical forbidden patterns(3 組 ERE + `\b` 字界) | +| `skills/smart-commit/SKILL.md`(AI Attribution Sanitization 節) | AI trailer sanitization flow、`--ai-co-author` narrow whitelist | | `skills/smart-commit/references/execute-mode.md` | `validate_msg()` 實作、post-commit detection | | `skills/create-pr/SKILL.md` | 現有 PR 建立/更新 workflow(Step 1-7) | ### Canonical Forbidden Patterns -來源:`scripts/commit-msg-guard.sh:19-23`(POSIX ERE, case-insensitive) +來源:`scripts/commit-msg-guard.sh`(ERE, case-insensitive — 僅 `AI` 加 `\b` 字界,避免在 `-i` 下誤中 "maintainer"、"domain" 等一般字詞;`GPT`/`OpenAI` 刻意不加字界,以匹配 `ChatGPT`/`GPT-4`) | Pattern Category | Regex | |-----------------|-------| | Co-Authored-By AI | `Co-Authored-By:.*(Claude\|Anthropic\|GPT\|OpenAI\|Copilot\|noreply@anthropic)` | -| Generated-by tag | `Generated (by\|with).*(Claude\|Claude Code\|AI\|GPT\|Copilot)` | -| Emoji robot tag | `🤖.*(Claude\|AI\|GPT)` | +| Generated-by tag | `Generated (by\|with).*(Claude\|\bAI\b\|GPT\|OpenAI\|Copilot)` | +| Emoji robot tag | `🤖.*(Claude\|\bAI\b\|GPT\|OpenAI)` | ### Files Requiring Changes @@ -94,7 +94,7 @@ sequenceDiagram # 1. Fetch actual published content gh pr view --json title,body --template '{{.title}}{{"\n"}}{{.body}}' -# 2. Scan for forbidden patterns (same 3 POSIX ERE from commit-msg-guard.sh) +# 2. Scan for forbidden patterns (same 3 ERE + \b patterns from commit-msg-guard.sh) # 3. If leak detected — auto-remediate (single attempt): # Title (safe escaping via printf): @@ -151,7 +151,7 @@ gh pr view --json title,body --template '{{.title}}{{"\n"}}{{.body}}' | Dependency | Status | |-----------|--------| -| `scripts/commit-msg-guard.sh` 的 3 組 POSIX ERE pattern | 已存在,穩定 | +| `scripts/commit-msg-guard.sh` 的 3 組 ERE + `\b` pattern | 已存在,穩定 | | `gh` CLI 安裝 | `/create-pr` 已假設存在 | | SKILL.md 現有 Step 1-7 workflow | 穩定,僅插入新步驟 | diff --git a/docs/features/feature-completeness/1-requirements.md b/docs/features/feature-completeness/1-requirements.md index 4ee1db3..a73024f 100644 --- a/docs/features/feature-completeness/1-requirements.md +++ b/docs/features/feature-completeness/1-requirements.md @@ -110,7 +110,7 @@ Priority: Must / Should / Could / Won't (MoSCoW) | NFR-1 | Correctness | Source-of-truth 一致性 — 所有 FR/AC 提取自 lifecycle docs,不從 code 反推;若 docs 缺漏需明確標示 `[NO_SPEC]` | Spec 提取 unit test:對 fixture docs 100% 命中 FR-N / AC-N 標記 | | NFR-2 | Performance | **SLO scope:baseline 5 維(FR-3 baseline)執行時適用**;若啟用 opt-in flag(`--include-security` / `--include-runtime` / `--include-risk`),SLO 額外加總對應 sub-skill 的 p50(如 `/codex-security` 自身 p50),以各 sub-skill 自身文件記錄的 p50 總和作為該次執行的參考上限;`--challenge` 亦視為 opt-in,SLO 同理加計。按 feature size 分級;**feature size = FR 數量 + 被計入的 request doc 數量**(v1 baseline 採 "open requests" 範圍,操作定義:request doc 的 `## Status` 值 ∉ `{Completed, Done, Superseded, Archived}`;無 `## Status` 區塊者視為 open;§9 Q7 僅定義 post-v1 override flag 語意,不影響 v1 baseline):**Small**(size ≤ 10)p50/p95 `--quick` < 10s/20s、`--standard` < 90s/180s、`--deep` < 5min/8min;**Medium**(10 < size ≤ 30)`--quick` < 30s/60s、`--standard` < 3min/5min、`--deep` < 10min/15min;**Large**(size > 30)`--quick` < 60s/120s、`--standard` < 6min/9min、`--deep` 不設硬上限(標示 slow 並提供 progress)。**拆分原因**:`/test-health --full` 自身文件記錄 2-5min(`skills/test-health/SKILL.md`),orchestration 累積成本需據 size 估算 | 每個 bucket 採樣 5 次取 p50 與 p95 | | NFR-3 | Maintainability | SKILL.md ≤ 200 行(thin entry 模式),dimension 定義 / orchestration map / output template 置於 `references/` | 行數驗證 + reference 拆分檢查 | -| NFR-4 | Consistency | Gate sentinel 為 **behavior-layer-only**(參 `@rules/auto-loop.md` Standard Gate Sentinels 表中 `⚠️ Need Human` 先例):**輸出 header 固定採 `## Completeness Verdict:`,禁止 `## Gate:`**(`hooks/stop-guard.sh:219-220` 以通配 prefix 解析,任何 `## Gate: ✅/⛔` 行都會被匹配);v1 不擴充 hook 契約 | grep 驗證:(a) 輸出**零次** `## Gate:` 字樣、(b) 未複用 `✅ Ready` / `✅ Mergeable` / `✅ All Pass`、(c) 自 sentinel 不在 `stop-guard.sh` parse list | +| NFR-4 | Consistency | Gate sentinel 為 **behavior-layer-only**(參 `@rules/auto-loop.md` Gate Sentinels 表中 `⚠️ Need Human` 先例):**輸出 header 固定採 `## Completeness Verdict:`,禁止 `## Gate:`**(`hooks/stop-guard.sh:219-220` 以通配 prefix 解析,任何 `## Gate: ✅/⛔` 行都會被匹配);v1 不擴充 hook 契約 | grep 驗證:(a) 輸出**零次** `## Gate:` 字樣、(b) 未複用 `✅ Ready` / `✅ Mergeable` / `✅ All Pass`、(c) 自 sentinel 不在 `stop-guard.sh` parse list | | NFR-5 | Reliability | 任一 sub-skill 失敗時 graceful degradation — 該維度標記 `[UNVERIFIED]` 並繼續,不整體失敗 | 注入 sub-skill failure 的 integration test | | NFR-6 | Security | 所有 sub-skill 呼叫遵循各自的 allowed-tools 邊界;本 skill 不額外擴權;READ-ONLY 強制 — **allowed-tools 清單不得含 Write / Edit / `Bash(git add:*)` / `Bash(git commit:*)` / `Bash(git push:*)`**;只 invoke mutation-free skill(FR-4 / FR-8) | allowed-tools 清單審核;無 git add/commit/push 命令;sub-skill mutability 白名單驗證 | | NFR-7 | Usability | 討論模式(`--discuss`)互動回合數預設上限 `max_rounds=3`,可由 `--max-rounds N`(1 ≤ N ≤ 10)覆蓋;達上限時輸出最終 verdict + 未釐清項清單 | 互動上限 unit test(default=3;override 邊界驗證 1 / 10 / 超界值) | diff --git a/docs/features/harness-engineering-adoption/2-tech-spec.md b/docs/features/harness-engineering-adoption/2-tech-spec.md index 7898ba9..9c992df 100644 --- a/docs/features/harness-engineering-adoption/2-tech-spec.md +++ b/docs/features/harness-engineering-adoption/2-tech-spec.md @@ -170,7 +170,7 @@ END { print p0+0, p1+0, p2+0, nit+0 } | 問題 | 位置 | Fix | |---|---|---| -| 每次 edit 同步呼叫 `npx prettier` cold-start 200-500ms | `npx prettier` 實際執行點為 `hooks/post-edit-format.sh` 單一 `npx prettier` 呼叫行(實作 PR 以 `grep -n 'npx prettier' hooks/post-edit-format.sh` 取得最新行號) | (a) TMPDIR 快取 prettier 檢測結果 TTL 1h:`$TMPDIR/.claude_prettier_$(pwd_hash)`;(b) prettier 以 `&` 背景執行(state write 不依賴它) | +| 每次 edit 同步做 prettier 偵測(config 探測 + binary 解析)並同步執行 prettier;npx cold-start 已於先前變更移除(`post-edit-format.sh` 現要求已安裝 binary:`node_modules/.bin/prettier` 或 PATH,config-only repo 不再 fallback 到 `npx prettier`) | `hooks/post-edit-format.sh` 的 prettier 偵測/執行區塊(`prettier_bin` 解析 + 呼叫;以 `grep -n 'prettier_bin' hooks/post-edit-format.sh` 定位) | (a) TMPDIR 快取 prettier 偵測結果 TTL 1h:`$TMPDIR/.claude_prettier_$(pwd_hash)`;(b) prettier 以 `&` 背景執行(state write 不依賴它) | **Target**: 平均 hook 執行時間 <50ms(預留 50% margin 到 100ms 官方目標)。 diff --git a/docs/features/harness-engineering-rebrand/1-requirements.md b/docs/features/harness-engineering-rebrand/1-requirements.md index 901e87c..a20cc1a 100644 --- a/docs/features/harness-engineering-rebrand/1-requirements.md +++ b/docs/features/harness-engineering-rebrand/1-requirements.md @@ -6,13 +6,32 @@ > > **⚠️ Supersession note** (2026-04-12): The following counts in this document were reconciled during implementation and should be read against the authoritative values in the request ticket + shipped README: > -> | Location | Original text | Actual value shipped | +> | Location | Original text | Value shipped **at 2026-04-12** | > |----------|--------------|---------------------| > | §4.2 FR-S1 "target: all surfaces say 92 skills" | 92 (filesystem count) | **90** (catalog-public count from `docs/skill-catalog.yml`; 2 local-only skills `readme-i18n-sync` + `update-readme` are gitignored) | > | §8.1.3 unified JSON description sample | "92 skills" | **90 skills** (byte-exact in `.claude-plugin/plugin.json`, `package.json`, `.claude-plugin/marketplace.json`) | > | §5 Pattern Map row 5 "83 of 92 skills with allowed-tools" | 83 of 92 (filesystem) | **81 of 90** (public, after subtracting 2 local-only skills which both declare `allowed-tools`) | > -> The reconciliation rationale: `scripts/generate-readme-catalog.js` reads public skill count from catalog, not filesystem, so the marketing surfaces must use catalog count (90) to avoid drift with auto-generated README blocks. +> The reconciliation rationale: `scripts/generate-readme-catalog.js` reads public skill count from catalog, not filesystem, so the marketing surfaces must use catalog count to avoid drift with auto-generated README blocks. +> +> **📌 Every number in this document is a dated snapshot, not a live value.** The catalog grows; these figures were correct on 2026-04-12 and are preserved as the historical record of that decision. Do not "correct" them in place — that would falsify what was reconciled at the time. Re-derive the current values instead: +> +> ```bash +> node -e "const fs=require('fs'); +> const b=fs.readFileSync('docs/skill-catalog.yml','utf8').split(/^skills:/m)[1]; +> const c=[...b.matchAll(/^\s+-\s+command:\s*\/(\S+)/gm)].map(m=>m[1]); +> const d=fs.readdirSync('skills').filter(x=>fs.existsSync('skills/'+x+'/SKILL.md')); +> const at=n=>/^allowed-tools:/m.test(fs.readFileSync('skills/'+n+'/SKILL.md','utf8')); +> console.log('catalog(public):',c.length,'| filesystem:',d.length); +> console.log('allowed-tools:',c.filter(at).length,'of',c.length,'(public) ·',d.filter(at).length,'of',d.length,'(filesystem)');" +> ``` +> +> | Snapshot | Catalog (public) | Filesystem | `allowed-tools` (public) | `allowed-tools` (filesystem) | +> |----------|-----------------|-----------|--------------------------|------------------------------| +> | 2026-04-12 (rebrand) | 90 | 92 | 81 of 90 | 83 of 92 | +> | 2026-07-25 (measured) | 98 | 100 | 89 of 98 | 91 of 100 | +> +> The invariant that survives both snapshots: filesystem = catalog + 2 (the gitignored local-only pair), and exactly **9** skills inherit default permissions rather than declaring `allowed-tools`. ## 1. Problem Statement @@ -43,9 +62,9 @@ Update the brand layer of `sd0x-dev-flow` to position it as a reference implemen | C1 | **`plugin.json` `name` field MUST NOT change** | Stable identifier for install/upgrade; derived paths in scripts hardcode the literal | `.claude-plugin/plugin.json:2` | | C2 | **GitHub repository slug MUST NOT change** | Marketplace redirect behavior is unverified; breaking it requires manual migration for every existing user | `.claude-plugin/marketplace.json:9` (`"repo": "sd0xdev/sd0x-dev-flow"`) | | C3 | **`package.json` `name` field MUST NOT change** | npm identity; sync-locked with plugin.json | `package.json:2` | -| C4 | **File names `README.md`, `CLAUDE.md`, `.claude/sd0x-dev-flow-lessons.md` MUST NOT change** | Claude Code discovery + derived state paths | Convention + `rules/self-improvement.md:67` (Two-Tier Model table canonical path) | +| C4 | **File names `README.md`, `CLAUDE.md`, `.claude/sd0x-dev-flow-lessons.md` MUST NOT change** | Claude Code discovery + derived state paths | Convention + `rules/self-improvement.md` § Lesson format (canonical log path) | | C5 | **`~/.config/sd0x-dev-flow/` git-profile registry path MUST NOT change** | User-specific config directory; renaming orphans git-profile configurations | `skills/git-profile/scripts/git-profile.sh:18,33` | -| C6 | **Hook regex patterns matching `sd0x-dev-flow:` prefix MUST NOT change** | Namespace-qualified command references | `hooks/stop-guard.sh:214-216`, `hooks/post-tool-review-state.sh:609-625` | +| C6 | **Hook regex patterns matching `sd0x-dev-flow:` prefix MUST NOT change** | Namespace-qualified command references | `hooks/stop-guard.sh` — the `HAS_CODEX_REVIEW` / `HAS_PRECOMMIT` / `HAS_REVIEW_DOC` transcript-fallback greps; `hooks/post-tool-review-state.sh` — the `_code_review_matched` / `_doc_review_matched` / `_precommit_matched` command greps and `_precommit_mode_of()`. Cited by symbol, not line range: these files shift on every hardening pass | ### 2.2 Soft Constraints @@ -152,13 +171,13 @@ Concise traceability from stakeholder → use case → primary requirement: | # | Harness sub-problem (source) | sd0x-dev-flow implementation | Code evidence | |---|------------------------------|------------------------------|---------------| | 1 | Tool loop control (Anthropic) | `/codex-review-fast` → `/precommit` auto-loop with sentinel-driven transitions | `rules/auto-loop.md` + `hooks/post-tool-review-state.sh` | -| 2 | Sentinel-driven state machine (emerging pattern) | `✅ Ready` / `⛔ Blocked` / `✅ All Pass` gate markers parsed into durable state | `scripts/emit-review-gate.sh` (producer) + `hooks/post-tool-review-state.sh` (consumer/parser) | -| 3 | Context recovery across compaction (Anthropic) | `[AUTO_LOOP_RESUME]` stdout injection after SessionStart(compact) | `hooks/post-compact-auto-loop.sh:133-144` | +| 2 | Sentinel-driven state machine (emerging pattern) | Two distinct planes. **Reviewer markers** — `✅ Ready` / `⛔ Blocked` / `✅ Mergeable` / `## Overall: ✅ PASS` — are emitted by the review skills and parsed into durable state. **Aggregate machine gates** — `REVIEW_GATE=PENDING\|READY\|BLOCKED` — are emitted by `emit-review-gate.sh`, which produces no emoji markers at all. (`✅ All Pass` is behaviour-layer prose; no hook reads it.) | Reviewer markers: review skills (producer) + `hooks/post-tool-review-state.sh` (parser). Aggregate gate: `scripts/emit-review-gate.sh` (producer) + the same hook's `update_aggregate_gate` (parser) | +| 3 | Context recovery across compaction (Anthropic) | `[AUTO_LOOP_RESUME]` stdout injection after SessionStart(compact) | `hooks/post-compact-auto-loop.sh` — the `[AUTO_LOOP_RESUME]` heredoc | | 4 | Lifecycle interceptors (Claude Agent SDK) | 5 hook event types dispatched to 8 scripts: PreToolUse / PostToolUse / Stop / SessionStart / UserPromptSubmit | `hooks/*.sh` (8 scripts) + `.claude/settings.json` | -| 5 | Capability-based tool gating (arXiv 2603.05344) | Skill frontmatter `allowed-tools` — e.g., `/ask` has no Edit/Write | 81 of 90 public skills use `allowed-tools` frontmatter (9 skills inherit default permissions; see header supersession note for 83/92 filesystem-count reconciliation) | -| 6 | Defense-in-depth safety (arXiv 2603.05344) | 5 layers: pre-edit-guard → commit-msg-guard → pre-push-gate → stop-guard → sidecar fail-closed marker | `scripts/pre-push-gate.sh` + `scripts/commit-msg-guard.sh` + `hooks/stop-guard.sh:114-149` | -| 7 | Generator-evaluator split (Anthropic) | Dual review: Codex (primary) + Claude (secondary) dispatched in parallel on every review cycle | `rules/codex-invocation.md` + `rules/auto-loop.md:34-42` (Dual Review Mode) | -| 8 | Incremental progress tracking (Anthropic) | `iteration_history.current_round` + `max_rounds` guard + convergence plateau detection (3+ rounds with ≥50% fingerprint overlap) | `rules/auto-loop.md:86-97` (exit conditions + strategic reset) | +| 5 | Least-privilege **permission posture** (arXiv 2603.05344 discusses capability gating) | Skill frontmatter `allowed-tools` declares a **pre-approval list** — e.g., `/ask` declares no Edit/Write, so those tools are never silently pre-authorised. It is _not_ a hard deny boundary: omitting a tool means it is not pre-approved, not that it is unavailable through the normal permission flow. The enforced boundaries are the hooks (`pre-edit-guard`, `stop-guard`) and `sandbox: 'read-only'` on Codex calls | 81 of 90 public skills declared `allowed-tools` **as of 2026-04-12** (9 inherit default permissions). The 9-skill inheritance gap is the durable figure; the totals move with the catalog — 89 of 98 when re-measured 2026-07-25. See the header snapshot table and the re-derivation command. | +| 6 | Defense-in-depth safety (arXiv 2603.05344) | 5 layers: pre-edit-guard → commit-msg-guard → pre-push-gate → stop-guard → sidecar fail-closed marker | `scripts/pre-push-gate.sh` + `scripts/commit-msg-guard.sh` + `hooks/stop-guard.sh` (the jq-unavailable fail-closed branch and the `.blocked` sidecar classification block) | +| 7 | Generator-evaluator split (Anthropic) | The evaluator is a separate model that researches the repo itself — Codex, dispatched on every review cycle. Since 2026-07-26 it is the *only* reviewer by default; a parallel secondary is opt-in via `--dual` on the branch and plan variants | `rules/codex-invocation.md` + `rules/auto-loop.md` § Review Dispatch | +| 8 | Incremental progress tracking (Anthropic) | `iteration_history.current_round` + `max_rounds` hard cap + `total_rounds_session` strategic reset. The cap is **hook-detected**, and blocks only in `strict` or dual-review mode — under the default `warn` mode `stop-guard.sh` prints to stderr and exits 0, leaving enforcement to the behaviour layer. Fingerprint-overlap plateau detection (3+ rounds ≥50% overlap) is a **designed V2 target, not shipped** — `_update_iteration()` stores per-round counts, not fingerprints, so the cap is the only convergence exit the hook observes at all | `rules/auto-loop.md` § Exit Conditions + [`4-implementation.md`](../auto-loop-evolution/4-implementation.md) §2 (round counter lifecycle) | | 9 | Human-in-the-loop safety gates (Martin Fowler) | `/dev/tty` confirmation in `pre-push-gate.sh` + `AskUserQuestion` for destructive ops | `scripts/pre-push-gate.sh` (`/dev/tty` read) + `skills/push-ci/SKILL.md` (AskUserQuestion flow) | | 10 | Self-improvement loop (novel) | Correction → record lesson → promote to rule (3+ recurrences) | `rules/self-improvement.md` + `.claude/sd0x-dev-flow-lessons.md` | diff --git a/docs/features/necessity-audit/1-requirements.md b/docs/features/necessity-audit/1-requirements.md index b4122ac..cb29f8a 100644 --- a/docs/features/necessity-audit/1-requirements.md +++ b/docs/features/necessity-audit/1-requirements.md @@ -25,7 +25,7 @@ | 對 lifecycle spec 文件(`1-requirements.md` / `2-tech-spec.md` / `3-architecture.md`)執行必要性審核,標記 Keep / Review / Cut 三類元素 | 取代 `/codex-review-spec`(FP 推理挑戰 — 問「推理對嗎」;本 skill 問「需要嗎」) | | 透過 `Skill("codex-brainstorm", ...)` 做雙邊 Nash-equilibrium 辯論(Claude 辯護現狀 vs Codex 質疑必要性),輸出 debate threadId + 引用 rounds | 取代 `/review-spec`(完整性 / 可行性 / 風險)或 `/feature-completeness`(跨維度完成度) | | 採 **spec 為主、code 為 evidence** 模式:Codex 可 grep 實際程式碼驗證「這個抽象有幾個 consumer」「這個 flag 有幾處使用」等必要性證據(Q2:C) | 審核 code 本身是否過度抽象(由 `/simplify` / `/refactor` 負責) | -| 沿用 doc review sentinel(`✅ Mergeable` / `⛔ Needs revision`)整合 auto-loop,**不引入新 sentinel** | 自動執行刪減(審核只標示與建議,使用者決定;升為 `/refactor` / `/simplify` 的輸入) | +| 以**專屬** behaviour-layer sentinel(`✅ Audit Clear` / `⛔ Audit Revise`)整合 auto-loop(修訂自「沿用 doc review sentinel、不引入新 sentinel」,理由見 FR-7) | 自動執行刪減(審核只標示與建議,使用者決定;升為 `/refactor` / `/simplify` 的輸入) | | 遵循 `@rules/codex-invocation.md`:不餵養結論、不餵養全文、Codex 獨立研究 | **預設**不對 `0-feasibility-study.md` 執行必要性審核(feasibility 階段本身即必要性辯論,重複);例外見 FR-1 `--include-feasibility` override | | 與 `/codex-review-spec` 可鏈式使用(先問「推理對嗎」→ 再問「需要嗎」)—兩者共存互補 | 跨 feature 的 portfolio 級必要性判斷(v1 限單 feature,單文件) | @@ -39,7 +39,7 @@ | Downstream 實作者(Developer) | Dependent | 避免接手過度複雜 spec 導致不必要實作成本 | | Codex MCP | Dependent | 承擔獨立必要性挑戰者角色;須遵循 `@rules/codex-invocation.md` | | `/codex-brainstorm`(Phase 3 辯論執行者) | Dependent | 本 skill 強制經由此 skill 發起辯論(對齊 `/best-practices` 的 Non-Negotiable Rule #2);不得裸呼叫 `mcp__codex__codex` | -| Auto-loop / Stop Hook | Operator | 辨識本 skill 的 `✅ Mergeable` / `⛔ Needs revision` sentinel(沿用 doc review 契約,相容 `hooks/stop-guard.sh:218-220`) | +| Auto-loop / Stop Hook | Operator | 辨識本 skill 的 `✅ Audit Clear` / `⛔ Audit Revise` sentinel。這是**行為層**契約:`stop-guard.sh` 不解析它們,且刻意讓它們落在 doc-review 兩組 pattern 之外(含粗略的 `⛔.*(Block\|Needs revision\|Must fix)` recency 掃描),見 FR-7 | | 既有審核 skills(`/codex-review-spec` / `/review-spec` / `/codex-review-doc` / `/feature-completeness` / `/best-practices`) | Peer | 本 skill 為縱向必要性軸;需在 `When NOT to Use` 清楚劃界 | | `/simplify` / `/refactor` | Downstream | 本 skill 輸出的 Cut/Review 清單可作為修剪任務的輸入來源 | @@ -64,13 +64,13 @@ | FR-4 | Codex 辯論 prompt **禁止餵養** Claude Phase A 分類結論;僅提供 (a) 目標文件路徑、(b) 必要性挑戰維度清單、(c) 允許 grep / cat 範圍 | Must | 遵循 `@rules/codex-invocation.md`;對抗 Claude 自我共謀 | | FR-5 | **Code-as-evidence 模式(條件性)**:**Greenfield 判定規則(單一 operational 來源)** — 以 `git grep -l -E "" -- . ':(exclude)docs/**' ':(exclude)**/*.md' 2>/dev/null` 為主策略;若 pathspec magic 不可用(舊 Git)則 fallback `git grep -l -E "" -- . \| grep -vE '^docs/\|\.md$'`;兩者皆無輸出即視為 greenfield。有實作情境下,Codex 於 debate 中執行`grep -r` / `cat` 驗證具體必要性證據(例:抽象層 consumer 數、flag 使用處、擴展點 consumer),evidence 以 `file:line` 引用。**Greenfield 降級**:evidence 改以 `doc:section` 形式引用 spec 本身的自相矛盾 / 跨 spec 衝突 / 假設未支撐段落,並於該條目標記 `[REASONING-ONLY]` | Must | Q2:C 決議;讓辯論基於事實而非修辭;Greenfield 判定採單一規則(與 Signal-5 / Assumption 同步),避免實作者歧義 | | FR-6 | 輸出結構:(1) 6 維度 rating table;(2) 三層分類清單(Keep / Review / Cut)每項附辯論結論引用;(3) Codex debate `threadId`(非空);(4) Debate Conclusion(引用具體 Phase 3 rounds,不得留空或 placeholder);(5) Suggested next commands(如 `/simplify` / `/refactor` / 手動修訂)。Evidence 欄位格式:`file:line`(code 可用時)或 `doc:section [REASONING-ONLY]`(greenfield 降級,見 FR-5) | Must | 對齊 `/best-practices` Non-Negotiable Rules #3-#4;讓下游 skill 可消費;與 FR-5 降級路徑一致 | -| FR-7 | Gate sentinel 採 doc review 標準:`✅ Mergeable`(無 Cut 項或所有 Cut 項已獲明確使用者認可) / `⛔ Needs revision`(有未認可 Cut 項) | Must | 沿用 `hooks/stop-guard.sh:218-220` 解析;不引入新 sentinel;與 `@rules/auto-loop.md` 既有契約相容 | +| FR-7 | **Gate sentinel 採本 skill 專屬命名空間(修訂自「沿用 doc review 標準」)**:`✅ Audit Clear`(無 Cut 項或所有 Cut 項已獲明確使用者認可) / `⛔ Audit Revise`(有未認可 Cut 項) | Must | 修正自原 FR-7:原文以「沿用 `hooks/stop-guard.sh:218-220` 解析、不引入新 sentinel」為理由,但該理由不成立——依修訂後的 FR-10,本 skill 的報告永遠不是 reviewer tool output,兩條 state 寫入路徑(Bash 端比對 command、MCP 端比對 prompt+output)都收不到它,所以「沿用」換不到任何 state。換到的是**碰撞**:`stop-guard.sh` 的 transcript fallback(無 state file 時的降級路徑)是位置無關的 grep,本 skill 報告提供 verdict,而 `/codex-review-doc` 這個 token 就出現在自己的 SKILL.md 路由表、`references/review-loop.md` 與 `preflight.js` advisory 中,兩半湊齊即可讓一次必要性稽核冒充 doc review 通過 Stop gate(已對真實 hook 實測重現:exit 0 `All steps completed`)。改採專屬 sentinel 即 `✅ Plan Ready` / `⛔ Plan Blocked` 對 plan plane 用的同一手法。用 `Revise` 而非 `Needs revision` 是刻意的:後者會被粗略 recency 掃描的 `.*` 命中,讓每次 blocking 稽核誤傷無關的 doc gate。詳見 2-tech-spec.md §3.9b 與 `test/skills/necessity-audit/stop-guard-isolation.test.js` | | FR-8 | 支援 `--continue ` 透過 `mcp__codex__codex-reply` 做 loop 審核(注意:此為 Phase C Verdict 層的直接 Codex 呼叫,**非** Phase B 辯論;Phase B 的 loop 沿用 `/codex-brainstorm` 自身機制) | Must | 匹配 codex skills 的 review loop 模式;讓修正後驗證不需重新載入整個文件 context;清楚劃分兩種 Codex 呼叫時機 | | FR-9 | `When NOT to Use` 清楚劃界:必須明列與 `/codex-review-spec`(FP 推理,規劃中)、`/review-spec`(完整性)、`/feature-completeness`(橫向完整性,規劃中)、`/best-practices`(產業標準)、`/simplify` / `/refactor`(code 層修剪)的職責差異;需包含 **Reasoning-vs-Necessity 2×2 決策矩陣**(見下方) | Must | 防止誤用;降低生態重疊爭議;2×2 矩陣於 §11 呈現 | -| FR-10 | 整合 auto-loop:**輸出方式**採 MCP tool 契約格式 — 當本 skill 透過 `mcp__codex__codex` / `mcp__codex__codex-reply`(TOOL_NAME 匹配 hook 路由條件,見 `hooks/post-tool-review-state.sh:639`)執行時,回應必須以 `## Document Review` header 開頭、結尾為 `✅ Mergeable` 或 `⛔ Needs revision` sentinel,該 hook 即設定 `doc_review.passed`(不依賴命令名稱)。另行為層 fallback:skill 於最終訊息尾部再次輸出裸 sentinel,供 `hooks/stop-guard.sh` 解析 — 該 hook 主路徑為 state file read,transcript parsing 為 fallback;本段特別描述 transcript fallback 行為,不代表 stop-guard 無 state read 主路徑。**不修改 hook 程式碼、不新增 state schema** | Must | 對操作必要性為 Must — 修正自原 FR-10 對 `doc_review.passed` 錯誤假設(該欄位由 MCP TOOL_NAME + output pattern 觸發,非命令名稱);TOOL_NAME 前提明確避免實作者誤以為任何 final output 都觸發 state update | -| FR-11 | 提供 `--depth brief\|normal\|deep` 旗標控制審核深度。**Depth 控制「維度覆蓋範圍 + equilibrium 嚴格度」,不控制 rounds**(下游 `/codex-brainstorm` 未暴露 `min_rounds` / `max_rounds` 輸入契約,其內部 min ~3 / max 5;spec 改為對齊可觀察行為):`brief` = 維度 1-3(Necessity / Abstraction / Extensibility)、任意終止可接受;`normal` = 全 6 維、任意終止可接受(預設);`deep` = 全 6 維 + **要求 Nash equilibrium**(若 `/codex-brainstorm` 以 convergence 或 max-rounds 終止,則觸發 `⚠️ Need Human` narrative + `⛔ Needs revision` gate)。實際 rounds 數僅作為 audit trail,不參與 gate 決策 | Could | 對齊 `/codex-brainstorm` 實際可觀察行為,避免宣告不可實施的 round 下限(見 2-tech-spec.md §3.5 Phase C) | +| FR-10 | 整合 auto-loop:**採顯式 review handoff,不自行寫 gate state**。本 skill 的報告由 `report.js` 在本地組裝、以模型自身訊息輸出——它**不是** Codex MCP 的 tool output,因此 `hooks/post-tool-review-state.sh` 的 MCP 路由收不到它,`doc_review.passed` 不會、也不應該被本 skill 觸發(見 2-tech-spec.md §3.3.3 / §3.9b)。稽核完成後由使用者或 auto-loop 明確執行 `/codex-review-doc` 走既有 doc gate。本 skill 仍在最終訊息尾部輸出裸 sentinel(`✅ Audit Clear` / `⛔ Audit Revise`,見修訂後的 FR-7)作為**行為層**訊號供人與模型判讀,但不宣稱任何 hook 會據此寫入 state。**不修改 hook 程式碼、不新增 state schema** | Must | 修正自原 FR-10:原文要求「MCP output 契約格式 → hook 設定 `doc_review.passed`」,但實作路徑下報告從不成為 MCP tool output,該 Must 驗收訊號永遠無法達成。與其為了滿足需求而讓一個本地組裝的報告偽裝成 reviewer 輸出(那正是 provenance 守衛要擋的東西),不如把契約改成它實際成立的樣子:稽核產出建議,doc gate 由真正的 doc review 負責 | +| FR-11 | 提供 `--depth brief\|normal\|deep` 旗標控制審核深度。**Depth 控制「維度覆蓋範圍 + equilibrium 嚴格度」,不控制 rounds**(下游 `/codex-brainstorm` 未暴露 `min_rounds` / `max_rounds` 輸入契約,其內部 min ~3 / max 5;spec 改為對齊可觀察行為):`brief` = 維度 1-3(Necessity / Abstraction / Extensibility)、任意終止可接受;`normal` = 全 6 維、任意終止可接受(預設);`deep` = 全 6 維 + **要求 Nash equilibrium**(若 `/codex-brainstorm` 以 convergence 或 max-rounds 終止,則觸發 `⚠️ Need Human` narrative + `⛔ Audit Revise` gate)。實際 rounds 數僅作為 audit trail,不參與 gate 決策 | Could | 對齊 `/codex-brainstorm` 實際可觀察行為,避免宣告不可實施的 round 下限(見 2-tech-spec.md §3.5 Phase C) | | FR-12 | 提供 `--output json` 旗標輸出結構化 JSON,便於下游 skill 消費 Cut/Review 清單 | Could | 與 `/simplify` / `/refactor` pipeline 整合的強化點;v1 可先以 Markdown 為主 | -| FR-13 | **預檢策略(修訂:移除 nested `Skill()` 呼叫以避免 state 副作用)**:(a) 行數檢查 `wc -l ` ≥ 50,否則 hard block;(b) 讀取 `.claude_review_state.json` `doc_review.passed`;若 `true` 且 `last_run` 晚於 target file 最近 commit 時間,silent 通過;否則輸出 **非阻斷** advisory: `ℹ️ No recent /codex-review-doc pass detected in this session. Recommend running /codex-review-doc first.`(**不因此 block,因 state 未做 target-binding,advisory 為 session-level 而非 file-bound**);(c) `--skip-preflight` flag 抑制 advisory,並在輸出頂端標示 `[PREFLIGHT SKIPPED]`;(d) 若工作區對 target 有未提交變更(`git status --porcelain -- ` 非空),輸出警告 `⚠️ Dirty working tree on target; necessity audit reflects uncommitted state`(獨立於 `--skip-preflight`,始終顯示) | Should | Nested `Skill("codex-review-doc")` 會觸發 `post-tool-review-state.sh` 對 `doc_review.passed` 的寫入,而 `check_passed()` 不認 `✅ Mergeable`(只認 `## Gate: ✅` / `✅ All Pass`),造成 inner 寫入可能誤判;改為 non-blocking advisory 消除副作用;target-binding 缺失靠 advisory 語氣與 dirty-tree warning 補償 | +| FR-13 | **預檢策略(修訂:移除 nested `Skill()` 呼叫以避免 state 副作用)**:(a) 行數檢查 `wc -l ` ≥ 50,否則 hard block;(b) 讀取 `.claude_review_state.json` `doc_review.passed`;若 `true` 且 `last_run` 晚於 target file 最近 commit 時間,silent 通過;否則輸出 **非阻斷** advisory: `ℹ️ No recent /codex-review-doc pass detected in this session. Recommend running /codex-review-doc first.`(**不因此 block,因 state 未做 target-binding,advisory 為 session-level 而非 file-bound**);(c) `--skip-preflight` flag 抑制 advisory,並在輸出頂端標示 `[PREFLIGHT SKIPPED]`;(d) 若工作區對 target 有未提交變更(`git status --porcelain -- ` 非空),輸出警告 `⚠️ Dirty working tree on target; necessity audit reflects uncommitted state`(獨立於 `--skip-preflight`,始終顯示) | Should | Nested `Skill("codex-review-doc")` 會觸發 `post-tool-review-state.sh` 對 `doc_review.passed` 的寫入,讓一次預檢在外層 skill 的 state 上留下副作用;改為 non-blocking advisory 消除之。(原文的附帶理由「`check_passed()` 不認 `✅ Mergeable`,只認 `## Gate: ✅` / `✅ All Pass`」自 2026-07-25 起不再成立:`check_passed()` 已改為僅認 review-plane sentinel — `✅ Ready` / `✅ Mergeable` / `## Gate: ✅` — 並移除 `✅ All Pass`,後者依 `@rules/auto-loop.md` 本就是行為層散文。副作用本身仍是改用 advisory 的理由。)target-binding 缺失靠 advisory 語氣與 dirty-tree warning 補償 | Priority: Must / Should / Could / Won't (MoSCoW) @@ -82,12 +82,12 @@ Priority: Must / Should / Could / Won't (MoSCoW) | NFR-2 | Correctness | Phase B 辯論必須經由 `Skill("codex-brainstorm", ...)`;禁止裸呼叫 `mcp__codex__codex` 做辯論 | SKILL.md grep 驗證 + Non-Negotiable Rule 文件化(對齊 `/best-practices` Rule #2) | | NFR-3 | Security | **僅本 skill 直接發起的 Codex 呼叫**(如 FR-8 `--continue` loop review)採 `sandbox: 'read-only'` + `approval-policy: 'never'`;Phase B 透過 `/codex-brainstorm` 發起的 Codex 呼叫沿用下游 skill 既有設定(見 FR-3 依賴契約繼承條款),本 skill 不 override | Prompt template grep;清楚區分本 skill 直接呼叫 vs 下游 skill 繼承呼叫 | | NFR-4 | Security | 目標 path 必須通過 **兩層驗證**:(a) **Resolver 層** — 沿用 `scripts/lib/feature-resolver.js:9` 之 slug regex `/^[a-z0-9][a-z0-9._-]*$/i`;(b) **Post-resolve Containment 層**(本 skill 新增)— 對 resolved path 執行 `realpath` 後驗證仍位於 `git rev-parse --show-toplevel` 之下,拒絕 `..` 穿越、絕對路徑逃逸、repo 外 symlink。**不假設** shared resolver 已做 containment — 實測 `feature-resolver.js:17` 無 `realpath/lstat` 檢查 | 路徑驗證 unit test 涵蓋:(1) 合法 slug、(2) `..` 穿越、(3) 絕對路徑、(4) repo 外 symlink 連結、(5) repo 內但目錄外 symlink | -| NFR-5 | Consistency | Gate sentinel 使用 doc review 標準(`✅ Mergeable` / `⛔ Needs revision`),**不引入新 sentinel** | 輸出 grep 驗證;與 `skills/doc-review/references/review-loop-doc.md` 一致 | +| NFR-5 | Consistency | Gate sentinel 使用本 skill 專屬命名空間(`✅ Audit Clear` / `⛔ Audit Revise`),**且輸出與所有隨稽核載入 transcript 的 skill 檔案中皆不得出現 doc-review sentinel**(修訂自「使用 doc review 標準、不引入新 sentinel」,理由見 FR-7) | `stop-guard-isolation.test.js`:(a) 對 5 個 audit surface 逐行掃描無 doc-review sentinel;(b) 對真實 `stop-guard.sh` 端到端驗證通過稽核不滿足 doc gate、blocking 稽核不撤銷既有 doc pass;(c) 反向 control | | NFR-6 | Consistency | 報告 schema 包含 `threadId` 與 `Debate Conclusion` 兩欄,缺一視為 Report rejected(對齊 `/best-practices` Non-Negotiable Rules #3-#4) | Report validator unit test | | NFR-7 | Maintainability | SKILL.md 採 thin entry 模式;完整 prompt、6 維度定義、debate guide、output template 置於 `references/`(對齊 `/codex-review-doc` / `/doc-review` / `/best-practices` 結構) | SKILL.md ≤ 200 行(對齊 `/best-practices` SKILL.md 195 行 peer 基準);references 分離 | | NFR-8 | Performance | 預設 `--depth normal` 對單一 spec 的耗時,相較於 `/codex-review-doc` 同文件 p50 耗時不超過 **2.0 倍**(含 Codex debate overhead)。**Baseline 環境固定**:(a) 同一 MCP session(warm cache);(b) 同一 target file revision(不跨 commit 比較);(c) 量測前排除首次冷啟動樣本;(d) 採樣方法:連續 5 次執行取 p50,1.5×IQR outlier 排除 | 可量測;baseline 環境完整;outlier 處理明確 | | NFR-9 | Reliability | Codex MCP 不可用時,skill 明確失敗並提示 fallback(如先執行 `/review-spec` 取得基礎審核) | 錯誤處理 unit test | -| NFR-10 | Reliability | Phase B 辯論若 Codex 未實質參與,skill 必須於輸出中標記 `⚠️ Need Human` narrative 並將 gate sentinel 設為 `⛔ Needs revision`(**narrative 與 sentinel 分離**:sentinel 永遠是 `✅ Mergeable` 或 `⛔ Needs revision` 以相容 `hooks/post-tool-review-state.sh:641` 之檢測格式;`⚠️ Need Human` 出現在 sentinel 前的敘述段落)。**Deterministic 觸發條件**(任一 fail 則 Need Human narrative + ⛔):(i) 辯論 rounds ≥ 2(最低有意義的辯論;`/codex-brainstorm` 預設 min ~3,< 2 視為異常);(ii) Codex 輸出含至少 1 筆 evidence citation(`file:line` 或 `doc:section`);(iii) 辯論內容含 ≥1 筆明確立場(Challenge / Defend / Accept / Reject / Concede);(iv) threadId 非空;(v) 對 `--depth deep`,`equilibrium_reached === true`(convergence / max-rounds 視為未達)。滿足所有 check 但 findings 為空屬**合法 pass**(`✅ Mergeable`) | 測試:(1) Codex 空回應、(2) rounds < 2、(3) 無 evidence、(4) 純立場重複、(5) 合法 empty-findings pass、(6) deep depth 無 equilibrium | +| NFR-10 | Reliability | Phase B 辯論若 Codex 未實質參與,skill 必須於輸出中標記 `⚠️ Need Human` narrative 並將 gate sentinel 設為 `⛔ Audit Revise`(**narrative 與 sentinel 分離**:sentinel 永遠是 `✅ Audit Clear` 或 `⛔ Audit Revise` 此為 **behavior-layer** 詞彙選擇,非 hook 相容性需求:state 寫入是 provenance-bound 的,這兩個 sentinel 不會由本 skill 觸發 hook 記錄,且自 FR-7 改用專屬命名空間後亦不再與 transcript fallback 碰撞(見 2-tech-spec.md §3.9b);`⚠️ Need Human` 出現在 sentinel 前的敘述段落)。**Deterministic 觸發條件**(任一 fail 則 Need Human narrative + ⛔):(i) 辯論 rounds ≥ 2(最低有意義的辯論;`/codex-brainstorm` 預設 min ~3,< 2 視為異常);(ii) Codex 輸出含至少 1 筆 evidence citation(`file:line` 或 `doc:section`);(iii) 辯論內容含 ≥1 筆明確立場(Challenge / Defend / Accept / Reject / Concede);(iv) threadId 非空;(v) 對 `--depth deep`,`equilibrium_reached === true`(convergence / max-rounds 視為未達);(vi) Debate Conclusion 明確引用具體 round(`R` 或 `round N`)——即 SKILL.md Rule #4,防止結論留白或 placeholder。滿足所有 check 但 findings 為空屬**合法 pass**(`✅ Audit Clear`) | 測試:(1) Codex 空回應、(2) rounds < 2、(3) 無 evidence、(4) 純立場重複、(5) 合法 empty-findings pass、(6) deep depth 無 equilibrium、(7) 結論未引用 round | | NFR-11 | Security | 輸出報告之 evidence citations(`file:line` / `doc:section`)、debate 內容不得包含 secrets / tokens / passwords / 完整地址;沿用 `@rules/security.md` 與 `@rules/logging.md` 遮罩規範;sensitive pattern 偵測於 SKILL.md 輸出前過濾層實作 | 輸出 grep 測試:對包含 mock secrets 的測試 spec 執行後,驗證輸出不含原始 secret;對齊 `docs/features/codex-review-spec/1-requirements.md` NFR-9 redaction 先例 | ## 7. Constraints & Assumptions @@ -111,11 +111,12 @@ Priority: Must / Should / Could / Won't (MoSCoW) - **Signal-3 (FR-3, NFR-2)**: Skill 執行過程中可觀察到 `Skill("codex-brainstorm", ...)` 被呼叫(非裸 `mcp__codex__codex`);辯論輸出含非空 threadId - **Signal-4 (FR-4, NFR-1)**: Codex prompt inspection 顯示不含 Phase A 分類結論;Codex 於辯論中主動執行 grep / cat - **Signal-5 (FR-5, FR-6)**: **條件性** — greenfield 判定沿用 FR-5 定義的單一 operational 規則(primary:`git grep -l -E "" -- . ':(exclude)docs/**' ':(exclude)**/*.md'`;fallback:`git grep -l -E "" -- . \| grep -vE '^docs/\|\.md$'`;feature-key 由 `scripts/lib/feature-resolver.js` 取得)。有輸出視為「有實作」,至少一個 Cut 項引用 `file:line`;無輸出視為 greenfield,至少一個 Cut 項引用 `doc:section` 並標記 `[REASONING-ONLY]` -- **Signal-6 (FR-7, FR-10, NFR-5)**: (a) Skill MCP 輸出以 `## Document Review` header 開頭、以 `✅ Mergeable` 或 `⛔ Needs revision` sentinel 結尾;(b) `hooks/post-tool-review-state.sh` 之 MCP output routing 解析後,`.claude_review_state.json` 之 `doc_review.passed` 欄位正確設定(前提:TOOL_NAME 匹配 `mcp__codex__codex` 系列);(c) `hooks/stop-guard.sh` 於 transcript parsing 路徑(非 state read)識別尾部裸 sentinel 作為 fallback gate 決策依據 +- **Signal-6 (FR-7, FR-10, NFR-5)**: (a) 報告尾部輸出裸 sentinel(`✅ Audit Clear` 或 `⛔ Audit Revise`),且輸出與所有隨稽核載入的 skill 檔案皆不含 doc-review sentinel;(b) 執行本 skill **不會**改動 `.claude_review_state.json` 的 `doc_review` 子樹——以 before/after 比對驗證,這是負向斷言:本地組裝的報告不得偽裝成 reviewer 輸出而取得 gate;(c) 稽核後執行 `/codex-review-doc`,由該 skill 的真實 MCP 輸出走既有路由設定 `doc_review.passed` - **Signal-7 (FR-8)**: `--continue ` 能接續同一 Codex session 執行 Phase C 的 verdict 層續審,驗證修正是否移除過度設計(Phase B 辯論續審由 `/codex-brainstorm` 自身 loop 機制處理) -- **Signal-8 (NFR-10)**: NFR-10 五條件任一 fail 時於 sentinel 前輸出 `⚠️ Need Human` narrative **並**設 gate 為 `⛔ Needs revision`;合法 empty-findings pass(Codex 明確表示無過度設計且五條件均滿足)不誤觸發,輸出 `✅ Mergeable` +- **Signal-8 (NFR-10)**: NFR-10 **六**條件任一 fail 時於 sentinel 前輸出 `⚠️ Need Human` narrative **並**設 gate 為 `⛔ Audit Revise`;合法 empty-findings pass(Codex 明確表示無過度設計且六條件均滿足)不誤觸發,輸出 `✅ Audit Clear`。條件數以 `consolidate.js` 實作為準(`rounds_ok` / `has_evidence_citation` / `has_explicit_stance` / `has_threadId` / `equilibrium_required_met` / `conclusion_references_rounds`)——需求原文寫「五條件」而實作與 tech spec 皆為六,差異即為 `conclusion_references_rounds` - **Signal-9 (NFR-8)**: 連續 5 次採樣(同 session、同 revision、排除冷啟動、1.5×IQR outlier 排除)`/necessity-audit` p50 ≤ `/codex-review-doc` p50 的 2.0 倍 - **Signal-10 (NFR-7)**: SKILL.md 行數 ≤ 200(peer 基準對齊 `/best-practices` SKILL.md);`references/` 目錄包含 prompt template / debate guide / 6-dimension definition / output template 四個分離檔案 + - ⚠️ **目前未達標(2026-07-25 量測:224 行)**。references 分離的部分已達成(7 個檔案);超出的是移除 `Bash(rm:*)` 後新增的 scratch-dir cleanup 契約說明,必須內嵌在 `cleanup.js` 呼叫前。判定與處置見 tech-spec §3.10「NFR-7 deviation」。此訊號維持 ≤ 200 不調整——要嘛裁到達標,要嘛明確修訂 NFR-7,不得默默放寬。 - **Signal-11 (NFR-11)**: 輸入包含 mock secret pattern 的測試 spec,輸出報告不含原始 secret 字串(通過 grep 驗證) - **Signal-12 (NFR-4)**: 路徑驗證 unit test 通過 5 個案例(合法 slug / `..` 穿越 / 絕對路徑 / repo 外 symlink / repo 內目錄外 symlink) @@ -141,7 +142,7 @@ Priority: Must / Should / Could / Won't (MoSCoW) ## 10. References -- Request ticket directory: [requests/](./requests/) — per-task tickets created via `/create-request` +- Request ticket directory: `requests/` — not yet created; per-task tickets go there once the first is filed via `/create-request`. Deliberately not a link while the target does not exist. - Tech Spec: [2-tech-spec.md](./2-tech-spec.md) - Ecosystem peers: - `docs/features/codex-review-spec/1-requirements.md` — **planned** (not yet implemented) FP 推理審核(縱向 · 推理對嗎)— 非本 skill(本 skill 問必要性) diff --git a/docs/features/necessity-audit/2-tech-spec.md b/docs/features/necessity-audit/2-tech-spec.md index 6cd685b..d5c4d0d 100644 --- a/docs/features/necessity-audit/2-tech-spec.md +++ b/docs/features/necessity-audit/2-tech-spec.md @@ -2,7 +2,7 @@ > **Doc class**: Lifecycle — Phase 2 tech spec (per `@rules/docs-numbering.md`). > **Created**: 2026-04-20 -> **Updated**: 2026-04-20 +> **Updated**: 2026-07-25 > **Requirements**: [1-requirements.md](./1-requirements.md) > **Skill name**: `necessity-audit` @@ -13,25 +13,28 @@ - **Scope (in)**: `1-/2-/3-/4-*.md` spec 審核;Codex debate via `/codex-brainstorm`;code 作為 evidence(條件性)。 - **Scope (out)**: 自動刪減(只建議);code 層抽象審核(交給 `/simplify`);跨 feature portfolio;預設排除 `0-feasibility-study.md`(`--include-feasibility` override)。 -## Implementation Status (2026-04-20) +## Implementation Status (2026-07-25) | Phase / Artifact | Status | |-----------------|--------| | SKILL.md (thin entry) | ✅ Implemented (`skills/necessity-audit/SKILL.md`) | -| `references/*.md` (6 reference files) | ✅ Implemented | +| `references/*.md` (7 reference files) | ✅ Implemented | | `preflight.js` (Phase 0) | ✅ Implemented | | `debate-topic.js` (Phase B build/parse) | ✅ Implemented | | `consolidate.js` (Phase C) | ✅ Implemented | | `report.js` (Markdown/JSON assembler) | ✅ Implemented | | `redact.js` (NFR-11 pre-emit filter) | ✅ Implemented | -| `elements.js` (Phase A extraction helper) | 🚧 Planned (currently Phase A runs inline in SKILL.md / LLM) | -| `classify.js` (Phase A rubric scoring) | 🚧 Planned | +| `cleanup.js` (scratch-dir removal, capability-token guarded) | ✅ Implemented (`scripts/skills/necessity-audit/cleanup.js`) — replaces the removed bare `Bash(rm:*)`; `--claim` mints a 24-byte token into a marker file, `--dir … --token …` removes only on constant-time token match | +| `elements.js` (Phase A extraction helper) | ⚠️ **Implemented but NOT wired** (`scripts/skills/necessity-audit/elements.js`) — exists and is unit/integration tested, but the live SKILL.md workflow runs Phase A inline in the LLM and never invokes it | +| `classify.js` (Phase A rubric scoring) | ⚠️ **Implemented but NOT wired** — same status as `elements.js` | | Unit tests (preflight / consolidate / debate-topic / redact / report) | ✅ Implemented | -| `elements.test.js` / `classify.test.js` | 🚧 Planned (extract with Phase B modules) | -| `integration.test.js` + fixture `test/fixtures/necessity-audit/` | 🚧 Planned | -| Performance timing evidence (NFR-8) | 🚧 Planned (deferred to T17) | +| `elements.test.js` / `classify.test.js` | ✅ Implemented | +| `cleanup.test.js` (token mint / match / mismatch / containment refusal) | ✅ Implemented | +| `integration.test.js` + fixture `test/fixtures/necessity-audit/` | ✅ Implemented (white-box, in-process; drives `extractElements → classifyAll → parseDebateResponse → consolidate → buildMarkdown`) | +| `skill-contract.test.js` (SKILL.md scratch-dir protocol) | ✅ Implemented | +| Performance timing evidence (NFR-8) | 🚧 Planned — **no work item yet**. It was listed as "deferred to T17", but T17 is the dependency-injected MCP/Skill boundary integration test and contains no performance work; a task must be added before this can be tracked as scheduled | -Unchecked wording like "implemented", "verified", "emitted" in later sections refers to the target state after the Planned items land. Cross-reference this table when auditing doc-code consistency. +**"Implemented" vs "wired"** — the distinction matters when auditing this feature. `elements.js` / `classify.js` are production-quality, exercised by CI, and are the executable reference for the Phase A rubric; they are simply not on the live path, because SKILL.md asks the model to classify inline. Treat them as a white-box specification of Phase A that CI keeps honest, not as dead code and not as shipped behaviour. Wording like "implemented", "verified", "emitted" in later sections refers to the target state after the remaining Planned items land. Cross-reference this table when auditing doc-code consistency. ## 2. Existing Code Analysis @@ -43,8 +46,8 @@ Unchecked wording like "implemented", "verified", "emitted" in later sections re | `skills/best-practices/references/output-templates.md` | Phase 4 schema(Debate threadId / Conclusion 欄位規則)| 報告 schema 參考 | | `skills/doc-review/references/codex-prompt-doc.md` | Codex doc prompt 模板(`${FILE_PATH}` 變數、獨立研究區塊)| Phase C verdict `--continue` 直呼時的 prompt 起點 | | `skills/doc-review/references/review-loop-doc.md` | `mcp__codex__codex-reply` 契約 | FR-8 實作範本 | -| `hooks/post-tool-review-state.sh` | MCP output 檢測 `## Document Review` + sentinel → 寫 `doc_review.passed` | FR-10 輸出格式貼合條件 | -| `hooks/stop-guard.sh` | state 主路徑 + transcript fallback 雙層 gate 檢測 | Sentinel 雙重相容 | +| `hooks/post-tool-review-state.sh` | MCP output 檢測 `## Document Review` + sentinel → 寫 `doc_review.passed`。**本 skill 不走這條**:報告由 `report.js` 在本地組裝、以模型自身訊息輸出,不是 Codex MCP 的 tool output(§3.9b) | 讀懂 sentinel 格式的來源,非本 skill 的寫入路徑 | +| `hooks/stop-guard.sh` | state 主路徑 + transcript fallback 雙層 gate 檢測 | 兩層都不會被本 skill 的 sentinel 滿足;state 路徑靠 provenance,transcript fallback 靠 sentinel 命名空間隔離(§3.9b) | | `skills/codex-review-doc/SKILL.md` | Doc review thin-entry 入口 | FR-13 optional advisory source(**state-read only**,不 nested-invoke) | | `scripts/security-redact.js` | 既有 executable redaction utility | NFR-11 `redact.js` 重用 high-confidence patterns | @@ -52,7 +55,7 @@ Unchecked wording like "implemented", "verified", "emitted" in later sections re ``` skills/necessity-audit/ -├── SKILL.md ✅ Thin entry, ≤200 lines +├── SKILL.md ✅ Thin entry (224 lines — over the 200 target, see NFR-7 deviation below) └── references/ ├── phase-a-classify.md ✅ Claude 分類 prompt ├── phase-b-debate-topic.md ✅ codex-brainstorm topic builder @@ -64,8 +67,8 @@ skills/necessity-audit/ scripts/skills/necessity-audit/ # Executable orchestration layer (testable) ├── preflight.js ✅ Phase 0: path validation + realpath containment + doc-kind + line count + dirty-tree + greenfield detection -├── elements.js 🚧 Phase A helper: extract FR/NFR/Component/Abstraction/Config from spec AST -├── classify.js 🚧 Phase A scoring: dim rubric → Keep/Review/Cut (depth-filtered) +├── elements.js ⚠️ Phase A helper: extract FR/NFR/Component/Abstraction/Config from spec AST (exists + tested; not invoked by SKILL.md) +├── classify.js ⚠️ Phase A scoring: dim rubric → Keep/Review/Cut (depth-filtered) (exists + tested; not invoked by SKILL.md) ├── debate-topic.js ✅ Phase B: build debate topic (depth-filtered dimension list) ├── consolidate.js ✅ Phase C: merge Claude+Codex, apply overrides, deterministic checks, gate selection ├── redact.js ✅ NFR-11 redaction filter (reuses scripts/security-redact.js + adds audit-specific patterns) @@ -77,20 +80,21 @@ test/skills/necessity-audit/ # node:test unit + integration ├── consolidate.test.js ✅ ├── redact.test.js ✅ ├── report.test.js ✅ -├── elements.test.js 🚧 -├── classify.test.js 🚧 -└── integration.test.js 🚧 Phase A→B→C with mocked Codex boundary - -test/fixtures/necessity-audit/ 🚧 (canonical fixture path; directory + content added in T18) -├── sample-over-designed-spec.md 🚧 hand-crafted spec with known Cut items -└── mock-debate-response.txt 🚧 Codex-style response with per-element verdicts +├── elements.test.js ✅ +├── classify.test.js ✅ +├── skill-contract.test.js ✅ SKILL.md scratch-dir protocol (prose contract, no other coverage) +└── integration.test.js ✅ Phase A→B→C in-process against a STATIC fixture (no boundary is mocked — see §7 note) + +test/fixtures/necessity-audit/ ✅ +├── sample-over-designed-spec.md ✅ hand-crafted spec with known Cut items +└── mock-debate-response.txt ✅ Codex-style response with per-element verdicts ``` **Rationale for executable module split** (resolves Codex review P0 #7-#8): - SKILL.md instructions are not deterministically testable — they describe Claude behavior - `scripts/skills/necessity-audit/*.js` holds all verifiable logic (path validation, gate selection, redaction, depth mapping) - SKILL.md delegates via `Bash` (`node scripts/skills/necessity-audit/.js `) following `scripts/lib/feature-resolver.js` + `scripts/security-redact.js` precedent -- Tests run via `node --test test/skills/necessity-audit/**/*.test.js`, mocking the MCP boundary via injected function (same as existing patterns) +- Tests run via `node --test test/skills/necessity-audit/**/*.test.js`. **No MCP boundary is mocked**: the suite calls the deterministic functions directly and feeds `parseDebateResponse` a checked-in Codex-style transcript (`test/fixtures/necessity-audit/mock-debate-response.txt`), so nothing is injected and no boundary is crossed. The `mcpInvoker` / `skillInvoker` injection this line used to describe is design intent from Open Q8, not shipped — see the §7 Integration table for which rows depend on it ## 3. Technical Solution @@ -120,26 +124,33 @@ flowchart TD C --> C1[Cross-reference Phase A vs Codex verdicts] C1 --> C2[NFR-10 deterministic checks + deep-equilibrium check] C2 --> C3[Apply user --override flags] - C3 --> C4[Gate selection: always ✅ Mergeable OR ⛔ Needs revision] + C3 --> C4[Gate selection: always ✅ Audit Clear OR ⛔ Audit Revise] C4 --> R[Output report via report.js] R --> R2[redact.js pre-emit filter] - R2 --> R1["## Document Review header + sentinel tail"] - R1 --> H[hooks detect → state update → auto-loop] + R2 --> R1["## Necessity Audit header + sentinel tail"] + R1 --> H["auto-loop (behaviour layer)
NO hook state write — §3.9b"] + H -.durable verdict wanted.-> HD["explicit handoff:
/codex-review-doc on the edited spec"] - C2 -.deterministic fail.-> NH["Emit ⚠️ Need Human NARRATIVE before sentinel; sentinel = ⛔ Needs revision"] + C2 -.deterministic fail.-> NH["Emit ⚠️ Need Human NARRATIVE before sentinel; sentinel = ⛔ Audit Revise"] NH --> R ``` ### 3.2 Module Layout & SKILL.md Contract -**SKILL.md responsibilities** (normative, thin entry ≤200 lines): -- Frontmatter: `name`, `description`, `allowed-tools: Read, Grep, Glob, Bash(git:*), Bash(node:*), Bash(wc:*), mcp__codex__codex, mcp__codex__codex-reply` (no `Skill` — built-in; `Bash(node:*)` required for §3.10 scripts bridge) +**SKILL.md responsibilities** (normative, thin entry — 200-line target, **currently 224**; see the NFR-7 deviation note in §3.10): +- Frontmatter: `name`, `description`, `allowed-tools: Read, Grep, Glob, Write, Bash(node:*), Bash(mktemp:*), mcp__codex__codex-reply, Skill`(`Bash(rm:*)` 已移除——刪除改走 `cleanup.js`,不再有裸 `rm`) + - `Skill` **is** declared: Non-Negotiable Rule 2 requires Phase B to run the debate through `/codex-brainstorm` via the Skill tool; a raw `mcp__codex__codex` debate is an invalid audit. (An earlier draft of this spec said "no `Skill` — built-in"; the implemented frontmatter is normative.) + - `Write` is used for the **scratch files the model itself produces** — `phase-a.json` (Phase A classification) and `debate.txt` (raw Codex response). The report files (`report.json` / `report.md` / `report.final.md`) are written by the Node modules, not by the `Write` tool. + - `Bash(mktemp:*)` creates the scratch dir; removal goes through `cleanup.js` under `Bash(node:*)`, which is also the §3.10 scripts bridge. + - **Removed for least privilege** (2026-07-25): `Bash(git:*)`, `Bash(wc:*)`, `mcp__codex__codex`. The dirty-tree warning and every other git read happen **inside** `preflight.js` via `execFileSync('git', …)`, which needs only `Bash(node:*)`; the line-count precheck is `fs.readFileSync` in the same module, not `wc`. Raw `mcp__codex__codex` is no longer **pre-approved** — Non-Negotiable Rule 2 requires the Phase B debate to go through `Skill("codex-brainstorm")`, and a raw MCP debate is defined as an invalid audit. `allowed-tools` is a pre-approval list, not a deny list (`skills/orchestrate/SKILL.md`), so removal narrows the declared surface rather than making the call impossible; the prohibition is normative and the frontmatter is what stops it being authorized by default. + - `mcp__codex__codex-reply` remains: it is the Phase C `--continue` verdict loop (FR-8). + - **Scratch-dir hazard**: do not write `TMPDIR=$(mktemp -d)` and dereference `$TMPDIR` later. Each Bash invocation is a fresh shell, and `TMPDIR` is ambient on macOS — the variable would resolve to the shared per-user temp root, and a closing `rm -rf` would target it. SKILL.md substitutes a literal path (``) and delegates the delete to `scripts/skills/necessity-audit/cleanup.js`, which **executes** the conditions rather than stating them: absolute path, `tmp.*` leaf, direct child of the temp root, not a symlink, and carrying the `--claim` marker written at creation. The marker is the part shape checks cannot supply — every concurrent process's scratch dir has the same shape. Pinned by `test/skills/necessity-audit/cleanup.test.js` + `skill-contract.test.js`. - Non-Negotiable Rules table(對齊 `/best-practices` 結構) - Trigger / When NOT to Use / Commands - Workflow 概覽(mermaid 或 phase table) - Args table + `--depth` / `--continue` / `--skip-preflight` / `--include-feasibility` / `--override` / `--output` -- Output format contract(`## Document Review` header + sentinel) +- Output format contract(`## Necessity Audit` header + sentinel) - References 目錄索引 - Verification checklist @@ -151,7 +162,7 @@ flowchart TD | 2 | Phase B **必須**經 `Skill("codex-brainstorm", ...)`;裸 `mcp__codex__codex` 用於辯論視為 invalid | Audit invalid | | 3 | Phase C 報告**必須**包含非空 `debate.threadId` | Report rejected | | 4 | Phase C 報告**必須**包含引用具體 rounds 的 `Debate Conclusion`(不得空值 / placeholder)| Report rejected | -| 5 | 輸出必須以 `## Document Review` 開頭、以 `✅ Mergeable` 或 `⛔ Needs revision` 結尾(FR-10)| Auto-loop 無法識別 | +| 5 | 輸出必須以 `## Necessity Audit` 開頭、以 `✅ Audit Clear` 或 `⛔ Audit Revise` 結尾(FR-10)| Auto-loop 無法識別 | ### 3.3 Data Model @@ -226,7 +237,7 @@ interface AuditReport { }; under_covered_dimensions: Dimension[]; // active dims not mentioned in debate.conclusion or rounds_text narrative: string[]; // advisory lines (e.g., "⚠️ Need Human: ...", "ℹ️ N elements kept via --override") - gate: '✅ Mergeable' | '⛔ Needs revision'; // sentinel ONLY; Need Human is narrative above, not gate itself + gate: '✅ Audit Clear' | '⛔ Audit Revise'; // sentinel ONLY; Need Human is narrative above, not gate itself suggested_next: string[]; // e.g., ["/simplify skills/foo/", "Manual revision of FR-5"] } ``` @@ -234,7 +245,7 @@ interface AuditReport { #### 3.3.3 State File Interaction - **Read**: `.claude_review_state.json` 的 `doc_review.passed`(FR-13 pre-flight optional advisory,非強制) -- **Write**: None directly — hooks handle `doc_review` state when our MCP output is parsed +- **Write**: None — 且**沒有任何 hook 會代寫**。本 skill 的報告不經過 MCP tool output,兩條 doc-review 路由都收不到(§3.9b)。此處先前寫「hooks handle `doc_review` state when our MCP output is parsed」,與 §3.9b 直接矛盾,已更正 ### 3.4 Public Interface (CLI Contract) @@ -243,7 +254,7 @@ interface AuditReport { | `` | Yes | — | Target spec path (absolute or repo-relative) | | `--depth brief\|normal\|deep` | No | `normal` | FR-11 depth mapping | | `--continue ` | No | — | Resume Phase C verdict loop via `mcp__codex__codex-reply` | -| `--skip-preflight` | No | `false` | Skip FR-13 inline `/codex-review-doc` check | +| `--skip-preflight` | No | `false` | Suppress the FR-13 **state-read** doc-review advisory (`.claude_review_state.json`) and emit a `[PREFLIGHT SKIPPED]` banner. There is no inline `/codex-review-doc` invocation to skip — that nesting was removed (see FR-13 pivot rationale). Does **not** suppress the dirty-tree warning, which still runs. | | `--include-feasibility` | No | `false` | Allow `0-feasibility-study.md` as target (FR-1 override) | | `--override :` | No (repeatable) | — | Mark Cut element as Kept with justification (Q8 resolved) | | `--output markdown\|json` | No | `markdown` | FR-12 output format | @@ -256,7 +267,7 @@ interface AuditReport { |-------|---------------------------------------------------------|-------------------------| | `brief` | Dims 1-3 only (Necessity Now / Abstraction Justification / Extensibility Speculation) | Any termination accepted (Nash / convergence / max-rounds) | | `normal` | All 6 dimensions | Any termination accepted (default) | -| `deep` | All 6 dimensions | **Nash equilibrium required** — if debate ends via convergence or max-rounds, emit `⚠️ Need Human` narrative and `⛔ Needs revision` gate | +| `deep` | All 6 dimensions | **Nash equilibrium required** — if debate ends via convergence or max-rounds, emit `⚠️ Need Human` narrative and `⛔ Audit Revise` gate | Rounds observed are reported in `AuditReport.debate.rounds` for audit trail but not used as gate input. @@ -302,7 +313,7 @@ Rounds observed are reported in `AuditReport.debate.rounds` for audit trail but 4. **Line-count check** (FR-13): `wc -l ` ≥ 50 else hard block with message 5. **Doc-review advisory** (FR-13, revised — **no nested Skill invocation**): - > **Pivot rationale**: Nested `Skill("codex-review-doc", path)` would trigger `post-tool-review-state.sh` state writes during an outer audit run, and `check_passed()` does not recognize `✅ Mergeable` (only `## Gate: ✅` / `✅ All Pass`). Nesting creates state-write races. Instead: **session-level advisory only** (cannot confirm target-binding because state schema lacks `last_file`). + > **Pivot rationale**: Nested `Skill("codex-review-doc", path)` would trigger `post-tool-review-state.sh` state writes during an outer audit run, creating state-write races. Instead: **session-level advisory only** (cannot confirm target-binding because state schema lacks `last_file`). The original text carried a second reason — that `check_passed()` "does not recognize `✅ Mergeable` (only `## Gate: ✅` / `✅ All Pass`)" — which stopped being true on 2026-07-25, when `check_passed()` was narrowed to the review-plane sentinels (`✅ Ready` / `✅ Mergeable` / `## Gate: ✅`) and `✅ All Pass` was dropped as behaviour-layer prose. The state-write race is the reason that survives. - Read `.claude_review_state.json` `doc_review` field (best-effort; non-existent file is OK) - If `doc_review.passed === true` AND `doc_review.last_run` is after target file's `git log -1 --format=%ct` → **silent continue** (session-level hint that **a** recent doc review passed; **not** a guarantee it was this file) @@ -404,7 +415,7 @@ Capture from Skill response: `threadId`, `rounds`, `equilibrium_reached`, debate - If Codex and Claude agree → `final = both` - If disagree → `final = stricter` (Keep→Review→Cut is strict direction); record both rationales 2. **Apply `--override`**: for each overridden element, set `final = Keep`, record `user_override` -3. **NFR-10 deterministic checks — 6 conditions** (all must pass for debate validity; any failure → emit `⚠️ Need Human` narrative + `⛔ Needs revision` gate): +3. **NFR-10 deterministic checks — 6 conditions** (all must pass for debate validity; any failure → emit `⚠️ Need Human` narrative + `⛔ Audit Revise` gate): ```javascript rounds_ok = debate.rounds ≥ 2 // minimum for meaningful debate (3 is codex-brainstorm min; <2 indicates error) @@ -415,19 +426,19 @@ Capture from Skill response: `threadId`, `rounds`, `equilibrium_reached`, debate conclusion_references_rounds = /\b(?:round\s+\d+|R\d+)\b/i.test(debate.conclusion) // SKILL.md Rule #4 enforcement ``` - Empty findings with **all 6 checks** pass = legitimate `✅ Mergeable`. Note: `equilibrium_required_met` is trivially true for `brief` / `normal` (no equilibrium required); only `deep` depth forces it false when debate ended via convergence or max-rounds. `conclusion_references_rounds` enforces SKILL.md Rule #4 (Debate Conclusion must reference specific rounds, not blank/placeholder). + Empty findings with **all 6 checks** pass = legitimate `✅ Audit Clear`. Note: `equilibrium_required_met` is trivially true for `brief` / `normal` (no equilibrium required); only `deep` depth forces it false when debate ended via convergence or max-rounds. `conclusion_references_rounds` enforces SKILL.md Rule #4 (Debate Conclusion must reference specific rounds, not blank/placeholder). 4. **Active-dimension coverage check** (single-debate mitigation; **separate from NFR-10 deterministic block** — failure is advisory, not gate-fatal): for each active dimension, verify ≥1 challenge exchange mentions it (regex on `debate.conclusion + debate.rounds_text`). Under-covered dims populate `AuditReport.under_covered_dimensions[]` and emit `⚠️ Need Human` narrative line; gate is still decided by Cut-count rule below (coverage issues do NOT force `⛔`). -5. **Gate selection** (sentinel always ✅ or ⛔ for hook compatibility per `hooks/post-tool-review-state.sh:641`; `⚠️ Need Human` is narrative only): +5. **Gate selection** (sentinel always ✅ or ⛔ so the **behaviour-layer** auto-loop has a determinate verdict — **not** because a hook will record it. Neither doc-review route in `hooks/post-tool-review-state.sh` fires for this skill, see §3.9b; the sentinel vocabulary is borrowed for readability and auto-loop consistency only. `⚠️ Need Human` is narrative only): | Condition | Narrative line | Final sentinel | |-----------|----------------|---------------| - | Any of 6 deterministic checks fails (includes `equilibrium_required_met` for deep, `conclusion_references_rounds` always) | `⚠️ Need Human: ` | `⛔ Needs revision` | - | Cut count == 0 AND no dimension under-covered AND checks pass | — | `✅ Mergeable` | - | All Cut elements have `user_override` AND checks pass | `ℹ️ N elements kept via --override with rationale` | `✅ Mergeable` | - | ≥1 Cut without override | `⛔ N elements flagged for removal` | `⛔ Needs revision` | - | Dim under-covered but Cut count == 0 | `⚠️ Need Human: dim X under-covered in debate` | `✅ Mergeable` (advisory) | + | Any of 6 deterministic checks fails (includes `equilibrium_required_met` for deep, `conclusion_references_rounds` always) | `⚠️ Need Human: ` | `⛔ Audit Revise` | + | Cut count == 0 AND no dimension under-covered AND checks pass | — | `✅ Audit Clear` | + | All Cut elements have `user_override` AND checks pass | `ℹ️ N elements kept via --override with rationale` | `✅ Audit Clear` | + | ≥1 Cut without override | `⛔ N elements flagged for removal` | `⛔ Audit Revise` | + | Dim under-covered but Cut count == 0 | `⚠️ Need Human: dim X under-covered in debate` | `✅ Audit Clear` (advisory) | - **Rationale**: `stop-guard.sh` state-read path + transcript-parsing fallback both expect `✅ Mergeable` / `⛔ Needs revision`. A final gate of `⚠️ Need Human` would leave auto-loop indeterminate. Therefore `⚠️ Need Human` is always a **narrative annotation** above the sentinel, never the sentinel itself. + **Rationale**: a final gate of `⚠️ Need Human` would leave the **behaviour-layer** auto-loop indeterminate, so `⚠️ Need Human` is always a **narrative annotation** above the sentinel, never the sentinel itself. This is *not* a hook-compatibility requirement: neither `stop-guard.sh` path is reached by this skill's output (§3.9b). The vocabulary is deliberately the skill's own (`✅ Audit Clear` / `⛔ Audit Revise`) rather than doc review's — see §3.9b for the collision that sharing it caused in the transcript fallback. 6. **Dimension aggregate severity**: `High` if ≥2 Cut in dim; `Med` if 1 Cut or ≥2 Review; `Low` if ≥1 Review; `Clean` otherwise; `Skipped` if inactive per depth ### 3.6 Prompt Templates (references/ contents) @@ -476,7 +487,7 @@ References file `references/redaction-rules.md` documents the pattern list for r ### 3.7 Output Report Format (Markdown) ```markdown -## Document Review +## Necessity Audit **Target**: `docs/features//2-tech-spec.md` **Feature**: `` (greenfield: false) @@ -513,7 +524,7 @@ References file `references/redaction-rules.md` documents the pattern list for r - **Rounds**: 3 · **Equilibrium**: ✅ - **Conclusion**: Round 2 established that FR-12's configurability has no named consumer; round 3 confirmed after Claude's defensive argument failed. See threadId for full trace. -### Deterministic Checks (NFR-10) +### Deterministic Checks (NFR-10 — all **six**) | Check | Result | |-------|--------| @@ -522,6 +533,7 @@ References file `references/redaction-rules.md` documents the pattern list for r | Codex evidence citations ≥ 1 | ✅ | | Codex stance keywords present | ✅ | | threadId non-empty | ✅ | +| conclusion references specific rounds (SKILL.md Rule #4) | ✅ | ### Suggested Next @@ -530,7 +542,7 @@ References file `references/redaction-rules.md` documents the pattern list for r ### Gate -⛔ Needs revision +⛔ Audit Revise ``` ### 3.8 Integration Points @@ -542,8 +554,26 @@ References file `references/redaction-rules.md` documents the pattern list for r | `mcp__codex__codex-reply` for Phase C loop | Direct MCP call with `--continue `; `sandbox: 'read-only'`, `approval-policy: 'never'` (per NFR-3) | Invalid threadId → user-visible error, no state write | | `scripts/lib/feature-resolver.js` | `require()` from `scripts/skills/necessity-audit/preflight.js`; input MUST be repo-relative (see §3.5 Phase 0.2) | Unresolved key → `⚠️ Need Human` narrative | | `scripts/security-redact.js` | `require()` from `scripts/skills/necessity-audit/redact.js`; reuse high-confidence patterns | Non-fatal: medium-confidence pattern mask; high-confidence AbortError throws | -| `hooks/post-tool-review-state.sh` | Emit `## Document Review` + `✅`/`⛔` sentinel → hook parses MCP output (line 641) | Malformed output → hook no-op (safe) | -| `hooks/stop-guard.sh` | State-read primary path; transcript sentinel as fallback safety-net | Two-layer defense consistent with existing doc review pattern | +| `hooks/post-tool-review-state.sh` | **No automatic integration.** See §3.9b — the report is behaviour-layer only | n/a — nothing is written to `doc_review` | +| `hooks/stop-guard.sh` | Neither path is satisfied by this skill's sentinel — see §3.9b, including the transcript-fallback analysis | n/a — the doc gate still asks for `/codex-review-doc` | + +### 3.9b Hook Integration: none (behaviour-layer only) + +The audit report is **not** parsed into `.claude_review_state.json` by any hook, and this spec must not be read as claiming otherwise. Both doc-review routes in `hooks/post-tool-review-state.sh` are provenance-bound and neither can see this report: + +| Route | Fires when | Why `/necessity-audit` misses it | +|-------|-----------|----------------------------------| +| Bash / Skill route | The invoked command matches `^/?(sd0x-dev-flow:)?(codex-review-doc\|review-spec)` | `/necessity-audit` is not in that alternation | +| MCP route | `TOOL_NAME` is `mcp__codex__codex` / `mcp__codex__codex-reply` **and** the tool output carries `## Document Review` + a sentinel | The outer report is assembled locally by `report.js` and emitted as the model's own message — it is never a Codex MCP tool output. The *inner* debate does go through MCP, but it carries no `## Document Review` header | + +Consequences, stated plainly: + +- The `## Necessity Audit` header + `✅ Audit Clear` / `⛔ Audit Revise` sentinel in the audit report are **behaviour-layer** markers. They drive Claude's auto-loop, not `doc_review.passed`. +- An `✅ Audit Clear` from this skill does **not** satisfy the Stop-hook doc gate. If a durable verdict is wanted, hand off explicitly: act on the audit findings, then run `/codex-review-doc` on the edited spec. +- **Including stop-guard's transcript fallback** — but only since 2026-07-26, and not for the reason this section used to give. The claim here was that the fallback "first requires the doc-review COMMAND to appear (`/codex-review-doc` or `/review-spec`), and `/necessity-audit` matches neither". That defence never held: the fallback greps the whole conversation, and the token `/codex-review-doc` is present in this skill's own SKILL.md routing table, in `references/review-loop.md`, and in `preflight.js`'s advisory. All of them reach the transcript **before** the report, so the verdict/invocation pairing added at the same time does not separate them either. Measured against the real hook with the then-current sources: a doc edit + this skill's SKILL.md + a report ending `✅ Mergeable` returned `{"ok":true,"reason":"All steps completed"}` at exit 0, with no doc review having run. +- **The fix was to stop sharing the vocabulary**, the same namespacing `✅ Plan Ready` / `⛔ Plan Blocked` already applies to the plan plane. Sharing bought nothing — no hook records this skill's verdict either way (rows above) — while costing a collision on the one path that greps rather than routes. `Revise` rather than `Needs revision` is deliberate: stop-guard's coarse recency scan is `⛔.*(Block|Needs revision|Must fix)`, whose `.*` would still have matched a near-miss like `⛔ Audit Needs revision` and turned every blocking audit into a spurious "Review not passed" on an unrelated doc gate. Both directions are pinned end-to-end against the real hook in `test/skills/necessity-audit/stop-guard-isolation.test.js`, together with the invariant that no audit surface reaching the transcript may contain a doc-review sentinel at all. + +**Why not simply add a `necessity-audit` route to the hook?** The existing alternation is a provenance control: a route exists so that an *independent reviewer* verdict becomes durable. `/necessity-audit` assembles its own sentinel from its own consolidation logic, so routing it would let a skill bank a doc-review pass on its own say-so. That is the trust boundary the alternation defends, and widening it is out of scope for this feature. ### 3.9a FR-9 `When NOT to Use` Matrix (SKILL.md embedded content) @@ -582,68 +612,94 @@ Trace row added to Appendix A for FR-9 compliance verification. ### 3.10 SKILL.md ↔ scripts/ Bridge SKILL.md delegates deterministic logic to `scripts/skills/necessity-audit/` modules via `Bash`. This pattern: -- Keeps SKILL.md thin (≤200 lines; NFR-7) +- Keeps SKILL.md thin (NFR-7; 200-line target) - Makes all gate/classification/redaction logic unit-testable - Matches existing precedent (`scripts/lib/feature-resolver.js`, `scripts/security-redact.js`) -**Invocation flow** (SKILL.md pseudocode; all intermediate artifacts pass via **temp files** under `${TMPDIR}/necessity-audit-/` to avoid shell-escape issues with multi-line content): +> **NFR-7 deviation (2026-07-25)**: SKILL.md is **224 lines**, 24 over the target. The target was set as a peer baseline against `/best-practices` (195 lines, still accurate), but it is not an ecosystem invariant — `plan-review` is 233 and `codex-code-review` is 290. The overage is not prompt bulk: all seven prompt/rubric/template assets are already in `references/`. It is the scratch-directory cleanup contract added when bare `Bash(rm:*)` was removed — the token-substitution warning has to sit inline, immediately before the `cleanup.js` invocation the model is about to run, because moving it to `references/` would put it where the model is not reading at the moment it acts. **Status: accepted deviation, not a resolved AC.** Signal-10 in the requirements measures ≤ 200 and currently fails; either trim to the target or amend NFR-7 deliberately — do not silently widen the number. + +**Invocation flow** (SKILL.md pseudocode; all intermediate artifacts pass via **temp files** inside a single `mktemp -d` workspace, to avoid shell-escape issues with multi-line content): ``` -SETUP: Create temp workspace: - Bash: mktemp -d → TMPDIR +SETUP: Create temp workspace and CLAIM it: + Bash: mktemp -d + → read the printed absolute path and substitute it as below. + Do NOT assign it to a shell variable: the next Bash call is a different shell, and the + name TMPDIR is already set in the ambient environment on macOS. + Bash: node scripts/skills/necessity-audit/cleanup.js --claim "" + → prints `token=<48 hex chars>`. Substitute it as in the CLEANUP step. + The claim is what makes the later delete safe: every concurrent process's scratch dir has + the same SHAPE (absolute, `tmp.*`, direct child of the temp root, not a symlink), so shape + checks alone cannot tell this directory from someone else's. The token can. 1. PREFLIGHT: - Bash: node scripts/skills/necessity-audit/preflight.js --path --depth [--skip-preflight] [--include-feasibility] --output $TMPDIR/preflight.json + Bash: node scripts/skills/necessity-audit/preflight.js --path --depth [--skip-preflight] [--include-feasibility] --output /preflight.json Exit: 0 = OK, non-zero = hard block (line count, invalid path, invalid doc kind, feature unresolved) Output JSON: { absPath, relPath, featureKey, docKind, greenfield, activeDimensions, skipPreflight, banners[], warnings[] } - Claude reads $TMPDIR/preflight.json; aborts if exit != 0. + Claude reads /preflight.json; aborts if exit != 0. 2. PHASE A (LLM, no subprocess): Claude reads using Read tool. Claude fills ${TARGET_PATH}, ${DOC_KIND}, ${ACTIVE_DIMENSIONS}, ${GREENFIELD} into references/phase-a-classify.md template. - Claude classifies elements and writes result as JSON to $TMPDIR/phase-a.json (via Write tool). + Claude classifies elements and writes result as JSON to /phase-a.json (via Write tool). Schema: { elements: ClassifiedElement[] } — only `claude.*` fields populated. 3. BUILD DEBATE TOPIC: - Bash: node scripts/skills/necessity-audit/debate-topic.js build --preflight $TMPDIR/preflight.json --output $TMPDIR/topic.txt - Topic string written to $TMPDIR/topic.txt (no stdin/stdout string passing). + Bash: node scripts/skills/necessity-audit/debate-topic.js build --preflight /preflight.json --output /topic.txt + Topic string written to /topic.txt (no stdin/stdout string passing). 4. INVOKE DEBATE: - Claude reads $TMPDIR/topic.txt. + Claude reads /topic.txt. Claude routes by mode: - **Initial run** (no `--continue`): `Skill("codex-brainstorm", )` — drives Phase B debate - **Loop run** (`--continue `): `mcp__codex__codex-reply` with the cached thread id — drives Phase C verdict recheck only (see `references/review-loop.md`) - Claude writes the raw debate response to $TMPDIR/debate.txt (via Write tool). + Claude writes the raw debate response to /debate.txt (via Write tool). 5. PARSE DEBATE: - Bash: node scripts/skills/necessity-audit/debate-topic.js parse --input $TMPDIR/debate.txt --output $TMPDIR/debate.json + Bash: node scripts/skills/necessity-audit/debate-topic.js parse --input /debate.txt --output /debate.json Output JSON: { threadId, rounds, equilibriumReached, conclusion, evidenceCitations[], roundsText } 6. CONSOLIDATE: - Bash: node scripts/skills/necessity-audit/consolidate.js --phase-a $TMPDIR/phase-a.json --debate $TMPDIR/debate.json --preflight $TMPDIR/preflight.json --overrides ":[;...]" --depth --output $TMPDIR/report.json + Bash: node scripts/skills/necessity-audit/consolidate.js --phase-a /phase-a.json --debate /debate.json --preflight /preflight.json --overrides ":[;...]" --depth --output /report.json Output JSON: full AuditReport schema (§3.3.2). 7. ASSEMBLE REPORT: - Bash: node scripts/skills/necessity-audit/report.js --input $TMPDIR/report.json --format markdown --output $TMPDIR/report.md + Bash: node scripts/skills/necessity-audit/report.js --input /report.json --format markdown --output /report.md Markdown assembled per references/output-template.md. 8. REDACT: - Bash: node scripts/skills/necessity-audit/redact.js --input $TMPDIR/report.md --output $TMPDIR/report.final.md + Bash: node scripts/skills/necessity-audit/redact.js --input /report.md --output /report.final.md Pre-emit secret/PII filter. 9. EMIT: - Claude reads $TMPDIR/report.final.md and emits its contents as final user-visible message. - Final message contains `## Document Review` header + ✅/⛔ sentinel for hook parsing. - -CLEANUP: Claude deletes $TMPDIR via Bash: rm -rf $TMPDIR. + Claude reads /report.final.md and emits its contents as final user-visible message. + Final message contains `## Necessity Audit` header + ✅/⛔ sentinel. These are BEHAVIOUR-LAYER + markers only — no hook parses them, and they are namespaced away from doc review's so the + transcript fallback cannot credit them either (see §3.9b). + +CLEANUP: Claude removes the scratch dir through the guard (never a bare rm): + Bash: node scripts/skills/necessity-audit/cleanup.js --dir "" --token "" + `--token` is REQUIRED — the CLI rejects a `--dir`-only call. The pseudocode previously omitted + both the claim and the token, so following it literally produced a non-zero exit and left the + scratch dir (and its report, which may quote spec content) behind on every run. ``` +> **Scratch-dir contract**: `` is a placeholder for the literal absolute path printed +> by a single `mktemp -d`, substituted into each command. It is NOT a shell variable — shell state +> does not survive between Bash tool invocations, and the name `TMPDIR` in particular is ambient on +> macOS. Every use is double-quoted. The directory is **claimed** immediately after creation and the +> delete refuses any unclaimed directory, so a substitution naming a *different* valid-looking +> scratch dir is rejected instead of honoured. See `test/skills/necessity-audit/cleanup.test.js` +> and `test/skills/necessity-audit/skill-contract.test.js`. + **CLI contract summary**: | Module | Inputs | Outputs | Exit codes | |--------|--------|---------|-----------| | preflight.js | `--path`, `--depth`, `--skip-preflight?`, `--include-feasibility?`, `--output ` | JSON written to `--output` path | 0 = OK, 1 = invalid path, 2 = short file, 3 = invalid doc kind, 4 = feature unresolved | | debate-topic.js `build` | `--preflight `, `--output ` | Topic string written to `--output` | 0/1 | +| cleanup.js `--claim` | `--claim ` — the path is the value FOLLOWING `--claim`, not a separate `--dir` flag; `--claim --dir ` makes the parser take the literal string `--dir` as the path and refuse it as non-absolute | `token=<48 hex>` on stdout; marker written inside the dir | 0 = claimed, 1 = refused | +| cleanup.js (delete) | `--dir `, `--token <48 hex>` (**required**) | — | 0 = removed, 1 = refused (shape / marker / token / identity) | | debate-topic.js `parse` | `--input `, `--output ` | Parsed JSON written to `--output` | 0/1 | | consolidate.js | `--phase-a `, `--debate `, `--preflight `, `--overrides ?`, `--depth `, `--output ` | Full AuditReport JSON written to `--output` | 0/1 | | report.js | `--input `, `--format markdown\|json`, `--output ` | Formatted report written to `--output` | 0/1 | @@ -655,12 +711,12 @@ CLEANUP: Claude deletes $TMPDIR via Bash: rm -rf $TMPDIR. | Risk | Severity | Mitigation | |------|----------|-----------| -| Nested `Skill()` invocation may conflict with outer auto-loop state | Med | Pre-flight only reads sentinel; does not trigger our own state writes (hooks handle both) | +| Nested `Skill()` invocation may conflict with outer auto-loop state | Med | Pre-flight only **reads** state; this skill triggers **no** state write, and no hook writes one on its behalf (§3.9b). A durable verdict requires an explicit `/codex-review-doc` handoff | | Codex debate drifts from necessity to reasoning (overlap with `/codex-review-spec`) | Med | Topic template explicitly says "necessity only; do not challenge reasoning"; dimension rubric enforces scope | | 6 dimensions produce N^6 element combos → report bloat | Low | Element-level classification with `primary_dimension` only (not cross-product) | | Greenfield detection false positive (feature key matches unrelated code) | Med | `git grep` restricted to non-docs/non-md; fallback manual via `--skip-preflight` awareness; document in release notes | | User abuses `--override` to bypass all Cut items | Low | Override rationale is logged in report; downstream reviewer sees pattern | -| `/codex-brainstorm` max rounds = 5 (not controllable); `--depth deep` requires Nash equilibrium | Med | Per-FR-11 revision: if debate ends via convergence or max-rounds instead of equilibrium, Phase C emits `⚠️ Need Human` narrative + `⛔ Needs revision`; user can retry or lower depth | +| `/codex-brainstorm` max rounds = 5 (not controllable); `--depth deep` requires Nash equilibrium | Med | Per-FR-11 revision: if debate ends via convergence or max-rounds instead of equilibrium, Phase C emits `⚠️ Need Human` narrative + `⛔ Audit Revise`; user can retry or lower depth | | Codex MCP unavailable (offline) | Med | NFR-9 hard fail with guidance; Claude-only fallback deferred (see Open Q3) | | Secret redaction regex miss (novel secret format) | Med | v1 best-effort; review quarterly; pair with `@rules/security.md` audit | @@ -702,7 +758,7 @@ Trackable tickets (each ≤1 day; one ticket = one `requests/YYYY-MM-DD-*.md`): | T14 | `--include-feasibility` flag + doc-kind gate override + `[OVERRIDE: feasibility included]` banner | T8 | XS | | T15 | `report.js` `--format json` serializer (FR-12 Could) | T6 | S | | T16 | Unit tests: path validation (5 cases), greenfield detection, depth→activeDimensions mapping, redaction filter, `--override` merging, gate selection (5 cases — matches §6 unit table), deterministic checks (all 6 conditions) | T6-T14 | L | -| T17 | Integration tests via **dependency-injected mock MCP/Skill boundary**: full Phase A→B→C mock, advisory state-read, `--continue` routing, hook state update via fixture subprocess | T16 | L | +| T17 | Integration tests via **dependency-injected mock MCP/Skill boundary**: full Phase A→B→C mock, advisory state-read, `--continue` routing, hook NON-integration via fixture subprocess (assert no `doc_review` write) | T16 | L | **未實作** — 現有 `integration.test.js` 是 white-box、直接以 fixture 餵 module exports,未注入或 mock `Skill()` / MCP invoker | | T18 | E2E test against `test/fixtures/necessity-audit/sample-over-designed-spec.md` (hand-crafted fixture with known Cut items) — **NOT** this skill's own tech-spec, to avoid meta-loop | T17 | M | | T19 | Register skill in `docs/skill-catalog.yml` + `/update-readme` + `/readme-i18n-sync` | T18 | S | | T20 | `CLAUDE.md` command reference row insertion | T19 | XS | @@ -723,37 +779,38 @@ Per `@rules/testing.md` three-layer pyramid. | preflight.js | Depth → activeDimensions | `brief→[1,2,3]`, `normal→[1..6]`, `deep→[1..6]` | | preflight.js | Doc-kind gate | `0-feasibility-study.md` rejected without flag; accepted with `--include-feasibility` | | preflight.js | State-read advisory | Present+fresh → silent; missing/stale → advisory line; `--skip-preflight` → `[PREFLIGHT SKIPPED]` banner | -| consolidate.js | Gate selection — 5 cases | No Cut → ✅ Mergeable; all Cut overridden → ✅ Mergeable + narrative; Cut without override → ⛔; deterministic fail → ⛔ + ⚠️ narrative; under-covered dim + no Cut → ✅ + ⚠️ narrative | -| consolidate.js | NFR-10 checks | rounds<2 / no evidence / no stance / empty threadId / deep-no-equilibrium — each triggers `⚠️ Need Human` narrative + `⛔` sentinel | +| consolidate.js | Gate selection — 5 cases | No Cut → ✅ Audit Clear; all Cut overridden → ✅ Audit Clear + narrative; Cut without override → ⛔; deterministic fail → ⛔ + ⚠️ narrative; under-covered dim + no Cut → ✅ + ⚠️ narrative | +| consolidate.js | NFR-10 checks | rounds<2 / no evidence / no stance / empty threadId / deep-no-equilibrium / conclusion-without-round-reference — each triggers `⚠️ Need Human` narrative + `⛔` sentinel (6 conditions, matching §3.5) | | consolidate.js | `--override` merging | Overridden elements get `user_override` field; counted in narrative | | consolidate.js | Under-covered dim | Active dim not mentioned in conclusion → enters `under_covered_dimensions[]`; warning emitted | | redact.js | Redaction filter | AWS key, token, PEM key, 0x-64-hex, email masked; high-confidence secret → exit 2 AbortError | -| report.js | Output MCP format | Response contains `## Document Review` header + one of `✅ Mergeable` / `⛔ Needs revision` at tail | +| report.js | Markdown report format | Report contains `## Necessity Audit` header + one of `✅ Audit Clear` / `⛔ Audit Revise` at tail, and NO doc-review sentinel anywhere (`stop-guard-isolation.test.js`) | | debate-topic.js | No-feed check | Output topic does NOT contain `claude.classification` / Phase A rationale fields | | debate-topic.js | Parse response | Valid response → full JSON; missing threadId → explicit null + error code | -### Integration (`test/skills/necessity-audit/integration.test.js`) — 🚧 Planned +### Integration (`test/skills/necessity-audit/integration.test.js`) — ✅ Implemented -> **Status**: Not yet implemented. File does not exist as of 2026-04-20 (see Implementation Status table at top). The matrix below is the **target design**, to be built in T17; current test coverage is unit-only. +> **Status**: Implemented as a **white-box, in-process** suite that drives `extractElements → classifyAll → parseDebateResponse → consolidate → buildMarkdown` against `test/fixtures/necessity-audit/`. The matrix below is the target design; rows describing an out-of-process run through the live SKILL.md workflow (`--continue` MCP routing, banner ordering as emitted by the skill) remain **design intent** rather than shipped assertions, because the live path runs Phase A inline in the LLM. See the Implementation Status table at top. -Boundary mocking: scripts modules receive `mcpInvoker` / `skillInvoker` function parameters; tests inject mocks (Open Q8 resolution). MCP boundaries stubbed via Node.js `t.mock` API. +Boundary mocking — **design intent, not shipped**: the Open Q8 resolution calls for scripts modules to receive `mcpInvoker` / `skillInvoker` function parameters with MCP boundaries stubbed via Node.js `t.mock`. The implemented suite does neither: it invokes the deterministic functions directly against a static fixture and never crosses an MCP or Skill boundary. Rows below that depend on that injection are **Planned / Not covered**, consistent with the white-box status note above. -| Test | Scope | -|------|-------| -| Full Phase A→B→C with injected mock `codex-brainstorm` invoker | Happy path; final report schema valid; sentinel = ✅ | -| State-read advisory — stale state | Warning line in output; skill continues (non-blocking) | -| `--skip-preflight` + dirty tree | Both banners emitted in order (`[PREFLIGHT SKIPPED]` + `⚠️ Dirty working tree`) | -| `--continue ` | Routes to `mcp__codex__codex-reply` (fresh `mcp__codex__codex` not called); threadId preserved in report | -| `--include-feasibility` on `0-feasibility-study.md` | Accepted; `[OVERRIDE: feasibility included]` banner present | -| Greenfield spec (fixture with no implementation) | All evidence tagged `[REASONING-ONLY]` | -| Codex returns empty findings + all 6 checks pass | Sentinel = `✅ Mergeable`; no `⚠️ Need Human` narrative (legit empty pass) | -| Codex returns rounds < 2 | `⚠️ Need Human` narrative + `⛔ Needs revision` sentinel | -| `--depth deep` with convergence-but-no-equilibrium | `⚠️ Need Human: deep depth requires Nash equilibrium` narrative + `⛔` sentinel | -| Hook state update fixture | Feed emit output to `post-tool-review-state.sh` fixture harness → verify `doc_review.passed` correctly toggled | +| Test | Scope | Status | +|------|-------|--------| +| Full Phase A→B→C with injected mock `codex-brainstorm` invoker | Happy path; final report schema valid; sentinel = ✅ | 🚧 Planned as written (no invoker is injected). The **data flow** it targets is covered white-box by `integration.test.js`; the injection is not | +| State-read advisory — stale state | Warning line in output; skill continues (non-blocking) | ✅ `preflight.test.js` | +| `--skip-preflight` + dirty tree | Both banners emitted in order (`[PREFLIGHT SKIPPED]` + `⚠️ Dirty working tree`) | ✅ `preflight.test.js` + `report.test.js` | +| `--continue ` | Routes to `mcp__codex__codex-reply` (fresh `mcp__codex__codex` not called); threadId preserved in report | 🚧 Planned — needs the invoker injection; nothing in the suite references `codex-reply` | +| `--include-feasibility` on `0-feasibility-study.md` | Accepted; `[OVERRIDE: feasibility included]` banner present | ✅ `report.test.js` | +| Greenfield spec (fixture with no implementation) | All evidence tagged `[REASONING-ONLY]` | ✅ `debate-topic.test.js` | +| Codex returns empty findings + all 6 checks pass | Sentinel = `✅ Audit Clear`; no `⚠️ Need Human` narrative (legit empty pass) | ✅ `consolidate.test.js` | +| Codex returns rounds < 2 | `⚠️ Need Human` narrative + `⛔ Audit Revise` sentinel | ✅ `consolidate.test.js` | +| `--depth deep` with convergence-but-no-equilibrium | `⚠️ Need Human: deep depth requires Nash equilibrium` narrative + `⛔` sentinel | ✅ `consolidate.test.js` | +| Hook NON-integration — `stop-guard.sh` | Compose the real report with the real hook: a passing audit must NOT satisfy the doc gate, a blocking one must NOT revoke a genuine doc pass, plus a reverse control (§3.9b) | ✅ `stop-guard-isolation.test.js` | +| Hook NON-integration — `post-tool-review-state.sh` | Feed emit output to a fixture harness → verify `doc_review.passed` is **NOT** written (§3.9b). The assertion is the absence of a state change; a test that expected a toggle would have pinned a behaviour the hook has never had | 🚧 Planned — the provenance argument in §3.9b is currently reasoning, not a test | ### E2E (`test/skills/necessity-audit/*.e2e.test.js`) — 🚧 Planned -> **Status**: Not yet implemented. Files + fixture will be added in T18 (see Implementation Status table). +> **Status**: Not yet implemented — no `*.e2e.test.js` exists as of 2026-07-25. The **fixture already landed** (`test/fixtures/necessity-audit/sample-over-designed-spec.md` + `mock-debate-response.txt`, currently consumed by the integration suite); only the E2E driver remains, in T18. See the Implementation Status table at top. | Test | Scope | |------|-------| @@ -815,7 +872,7 @@ Remaining for `/feature-dev`: | Phase C gate selection (sentinel always ✅/⛔, narrative ⚠️ Need Human) | FR-7, FR-10 | | Phase C user-override | Q8 resolution | | --continue via codex-reply | FR-8 | -| `--depth` rounds mapping | FR-11 | +| `--depth` → active-dimension + equilibrium-strictness mapping | FR-11 | | `--output json` | FR-12 | | Output header+sentinel via report.js + redact.js emit | FR-10, NFR-5 (sentinel consistency) | | Output redaction (redact.js executable) | NFR-11 | diff --git a/docs/features/plan-review-loop/0-feasibility-study.md b/docs/features/plan-review-loop/0-feasibility-study.md new file mode 100644 index 0000000..02fc046 --- /dev/null +++ b/docs/features/plan-review-loop/0-feasibility-study.md @@ -0,0 +1,234 @@ +# Feasibility Study: Plan-Review-Loop — Pre-ExitPlanMode Codex Review Gate + +> **Doc class**: Lifecycle — Phase 0 feasibility study (per [`rules/docs-numbering.md`](../../../rules/docs-numbering.md)). +> **Created**: 2026-05-15 +> **Canonical requirements**: [`./1-requirements.md`](./1-requirements.md) +> **Codex debate threadId**: `019e298f-3645-7801-b6ff-b60b8d1235e6` + +## 1. Requirement Decomposition + +Consumed from canonical [`1-requirements.md`](./1-requirements.md) §1 + §5; this study does **not** redecompose problem space. Three driver requirements anchor the feasibility evaluation: + +| Anchor | Source FR/NFR | Implication for feasibility | +|--------|---------------|------------------------------| +| Plan must reach user only after review converges | FR-4 | Triggering mechanism must guarantee ordering between review and plan delivery | +| Plan-review state must not pollute existing review state | FR-6 / NFR-7 | State carrier and sentinel naming are first-class design axes | +| Reviewer failure must degrade gracefully | NFR-3 | Architecture must support a deterministic fall-through path that still delivers plan | + +The 11 Open Questions in §9 of the requirements doc cluster around three strongly coupled architectural axes (A/B/C below); the remaining items (default mode, budget config, trail noise, dual-review trigger, `/codex-review-doc` boundary, plateau detection) are policy- or implementation-level and resolved derivatively. A 1:1 disposition mapping is given in §8. + +## 2. Constraint Inventory + +| Type | Constraint | Flexibility | Source | +|------|------------|-------------|--------| +| Hard | `ExitPlanMode` is harness-provided; not in plugin scope | None | `grep -rn ExitPlanMode` → only this feature's lifecycle docs | +| Hard | Existing `PreToolUse` matcher covers only `Edit\|Write` | Extensible, but harness behavior on `ExitPlanMode` matcher is unverified | [`hooks/hooks.json:32-41`](../../../hooks/hooks.json) | +| Hard | `rules/codex-invocation.md` mandates independent research / no feeding conclusions | None | [`rules/codex-invocation.md`](../../../rules/codex-invocation.md) | +| Assumption | Plan mode is read-only w.r.t. `Edit\|Write`; MCP and Skill calls are believed to remain available | Soft — needs smoke test before §7 recommendation is fully de-risked; tracked as OQ-Sx-2 in §8 | Inferred from harness UX; not yet confirmed by executable contract | +| Soft | `.claude_review_state.json` schema currently has `code_review`, `doc_review`, `precommit`, `aggregate_gate`, root `iteration_history` | Schema-extensible with migration | [`hooks/post-tool-review-state.sh:133`](../../../hooks/post-tool-review-state.sh) | +| Soft | MCP sentinel routing already discriminates via `## Document Review` header before `✅ Mergeable` to avoid sentinel collision | Pattern extensible | [`hooks/post-tool-review-state.sh:684`](../../../hooks/post-tool-review-state.sh) | +| Soft | `auto-loop-project.md` exposes only `Max Rounds`, `Git Memory`, `Think Harder` config points | New config point possible but non-standard | [`rules/auto-loop-project.md`](../../../rules/auto-loop-project.md) | + +## 3. Code Research + +Verified primitives that any implementation **must** reuse or coexist with: + +| Primitive | Reuse posture | Reference | +|-----------|---------------|-----------| +| `.claude_review_state.json` (lock-protected, schema-versioned, compact-resume aware) | Extend with namespaced field | [`hooks/post-tool-review-state.sh`](../../../hooks/post-tool-review-state.sh) | +| MCP sentinel routing pattern (`##
+ ✅/⛔ `) | Mirror for plan-specific header | [`hooks/post-tool-review-state.sh:684-690`](../../../hooks/post-tool-review-state.sh) | +| `emit-review-gate.sh` PENDING/READY/BLOCKED contract | Extend or mirror for plan tier | [`scripts/emit-review-gate.sh`](../../../scripts/emit-review-gate.sh) | +| `skills/doc-review/SKILL.md` Codex loop + `threadId` continuation | Adopt loop topology | [`skills/doc-review/SKILL.md:48`](../../../skills/doc-review/SKILL.md) | +| `skills/codex-brainstorm/SKILL.md` Nash equilibrium engine (independent research, attack/defense, termination) | Delegate for deep tier | [`skills/codex-brainstorm/SKILL.md:19`](../../../skills/codex-brainstorm/SKILL.md) | +| `rules/auto-loop.md` convergence decision table (max_rounds, plateau, strategic reset) | Reuse rule-level model; plateau requires fingerprint storage not yet implemented in hook | [`rules/auto-loop.md`](../../../rules/auto-loop.md) | +| `stop-guard.sh` aggregate-gate awareness | Extend to recognize plan gate | [`hooks/stop-guard.sh`](../../../hooks/stop-guard.sh) | + +**Confirmed negatives** (search returned nothing — clean namespace): + +- `grep -rn "plan_review\|REVIEW_PLAN_GATE\|Plan Ready"` in `hooks/` `scripts/` `skills/` → no hits +- `ExitPlanMode` not referenced anywhere outside this feature's own lifecycle docs (`0-feasibility-study.md` + `1-requirements.md`) + +## 4. Solution Exploration + +Three axes; each is a real fork in the architecture. Options derived from §9 of [`1-requirements.md`](./1-requirements.md). + +### Axis A — Triggering mechanism + +| Option | Description | Pros | Cons | +|--------|-------------|------|------| +| A1 | Skill-driven: `/plan-review` self-invoked by Claude in plan mode before `ExitPlanMode` | No `ExitPlanMode` PreToolUse dependency; full orchestration control; degrades cleanly when harness shifts | Trigger relies on rule + skill enforcement, not contract | +| A2 | PreToolUse hook intercepts `ExitPlanMode` | Hard gate at tool boundary | Harness PreToolUse semantics on `ExitPlanMode` unverified; hook cannot orchestrate model-mediated review; depends on harness retry behavior | +| A3 | Hybrid: A1 primary + hook tripwire as audit | Catches Claude self-invocation drift | Tripwire produces telemetry, not enforcement — see §5 debate | + +### Axis B — Review state carrier + +| Option | Description | Pros | Cons | +|--------|-------------|------|------| +| B1 | Extend `.claude_review_state.json` with `plan_review` field + own `iteration_history` sub-tree | Reuses the state-file integration surface (lock / migration / compact-resume); Stop Hook support is a v1 work item, not a free-lunch property | Schema migration cost (additive) | +| B2 | Separate `.claude_plan_state.json` file | Full isolation | Duplicates lock, migration, compact-resume, stop-guard, user-prompt reminder logic | +| B3 | Reuse `doc_review` field + sentinel discriminator | No schema change | Violates NFR-7 isolation; false-positive risk (prior `/codex-review-doc` can satisfy plan gate by accident) | + +### Axis C — Depth tier & relationship with `/codex-brainstorm` + +| Option | Description | Pros | Cons | +|--------|-------------|------|------| +| C1 | Inline 3-tier (quick / standard / deep); deep implements own adversarial debate | All logic in one skill | Duplicates `/codex-brainstorm`; prompt / termination drift | +| C2 | Quick / standard internal; deep delegates to `/codex-brainstorm` | Reuses Nash equilibrium engine; same precedent as `necessity-audit` deep tier | Composition complexity; tier-detection heuristic must be specified | +| C3 | Single fixed tier | Simplest | No escalation path; over-reviews simple plans, under-reviews complex ones | + +## 5. Codex Discussion Record (Nash Equilibrium) + +**threadId**: `019e298f-3645-7801-b6ff-b60b8d1235e6` + +### Round 0 — Independent positions + +| Side | Position | Core argument | +|------|----------|---------------| +| Claude | **A1 + B1 + C2** | PreToolUse on `ExitPlanMode` unverified; B3 collides with `doc_review`; `/codex-brainstorm` already ships Nash engine | +| Codex | **A3 + B1 + C2** | Skill primary, hook as tripwire for audit trail; B1/C2 identical to Claude | + +### Round 1 — Claude attacks A3 + +| # | Attack | Target | +|---|--------|--------| +| 1 | Tripwire produces telemetry only; FR-4 demands review-before-plan, telemetry-after-violation does not satisfy that | Hook value claim | +| 2 | Hook surface expansion (`Edit\|Write` → `Edit\|Write\|ExitPlanMode`) introduces self-suppression race between skill state-write and hook fire | Hook hygiene | +| 3 | "Claude forgot self-invoke" failure mode is symmetric: a Claude that forgot the skill also disregards a non-blocking warning sentinel | Tripwire effectiveness | + +### Codex response — concedes A1 for v1 + +Codex updated position: **A1 for v1**, with A3 promoted only if a future harness smoke test proves all five conditions: + +1. PreToolUse can match `ExitPlanMode` +2. Hook receives sufficient `tool_input` to identify outgoing plan +3. Non-zero exit reliably prevents plan delivery +4. Claude receives the denial in a form that triggers `/plan-review` + retry +5. State write ordering is deterministic before subsequent `ExitPlanMode` call + +Until any one is unprovable from inside the plugin, A3 ships as dead-or-noisy code. + +### Equilibrium + +**A1 + B1 + C2** stands. Neither side can attack further; remaining divergence is a v2 follow-up gated by an empirical harness probe, not a v1 design conflict. + +## 6. Quantitative Comparison + +Scoring against [`@rules/feasibility-study/references/analysis-phases.md`](../../../skills/feasibility-study/references/analysis-phases.md) dimensions (Green / Yellow / Red). + +### Axis A — Triggering mechanism + +| Dimension | A1 (skill) | A2 (hook) | A3 (hybrid) | +|-----------|------------|-----------|--------------| +| Technical feasibility | 🟡 reuses doc-review loop pattern, **but** plan-mode Skill/MCP availability is unverified (OQ-Sx-2 smoke test is a v1 hard precondition; failure → ⛔ architecture revisit) | 🔴 harness contract unverified | 🟡 inherits A2 unverifiability | +| Effort | 🟢 1-2 person-days | 🟡 3-5 person-days (matcher + block semantics) | 🔴 4-6 person-days (both paths + reconciliation) | +| Risk | 🟡 depends on Claude self-invocation + plan-mode Skill/MCP availability (unverified contract) | 🔴 silent failure if harness rejects | 🟡 partial if probe inconclusive | +| Extensibility | 🟢 trivial to upgrade to A3 later | 🟡 hook-only locks out skill-driven UX | 🟢 superset of A1 | +| Maintenance cost | 🟢 single source of orchestration | 🟡 dual paths to keep in sync | 🟡 dual paths | + +### Axis B — State carrier + +| Dimension | B1 (extend) | B2 (separate file) | B3 (reuse doc_review) | +|-----------|-------------|---------------------|------------------------| +| Technical feasibility | 🟢 schema migration pattern exists | 🟢 trivially possible | 🔴 violates FR-6 / NFR-7 | +| Effort | 🟢 0.5 person-day | 🟡 2 person-days (duplicate infra) | 🟢 0.5 person-day | +| Risk | 🟢 additive migration | 🟡 stop-guard / compact-resume must learn new file | 🔴 false passes from prior doc reviews | +| Extensibility | 🟢 same patterns as code/doc | 🟢 isolated | 🔴 collapses on first locale collision | +| Maintenance cost | 🟢 single state file | 🟡 two carriers | 🔴 ambiguous ownership | + +### Axis C — Depth tier + +| Dimension | C1 (inline) | C2 (delegate deep) | C3 (fixed) | +|-----------|-------------|---------------------|-------------| +| Technical feasibility | 🟡 reimplement Nash engine | 🟢 reuses `/codex-brainstorm` | 🟢 no tier logic | +| Effort | 🔴 5+ person-days | 🟢 1-2 person-days | 🟢 1 person-day | +| Risk | 🔴 prompt/termination drift vs brainstorm | 🟢 single source of truth | 🟡 deep plans under-reviewed | +| Extensibility | 🟡 own escalation path | 🟢 inherits brainstorm upgrades | 🔴 no upgrade path | +| Maintenance cost | 🔴 two adversarial engines | 🟢 shared engine | 🟢 minimal | + +## 7. Recommendation + +**Architecture for v1: A1 + B1 + C2**. + +### Rationale (one paragraph) + +FR-4 requires that the user does not see the plan before review converges. A1 is the **best-effort enforceable** option without depending on an unverified harness contract: Claude orchestrates the loop via a `/plan-review` skill before calling `ExitPlanMode`, with `auto-loop.md`-style rules + Stop Hook reinforcement raising self-invocation compliance toward — but not provably equal to — 100 %. **FR-4 honesty note**: under A1, FR-4 is satisfied at the *review-conclusion* layer (plan with unresolved P0/P1 is never delivered, because Claude only calls `ExitPlanMode` after `✅ Plan Ready`) but not at the *tool-boundary* layer (a Claude that forgets the skill could bypass review). The plugin's `auto-loop` + Stop Hook stack is the practical mitigation; the OQ-Sx-1 harness probe (deferred to v2) is the only way to upgrade to tool-boundary enforcement. Hard enforcement (A2 / A3) is therefore explicitly deferred to v2 pending that probe. Extending the existing review state file (B1) reuses lock, migration, and compact-resume infrastructure with a namespaced `plan_review.*` field that cannot collide with `code_review` / `doc_review` / `aggregate_gate`; `stop-guard.sh` does **not** recognize plan sentinels today and must be extended (tracked as a v1 work item, not a free-lunch property). Delegating the deep tier to `/codex-brainstorm` (C2) avoids duplicating an adversarial debate engine that the plugin already ships and trusts. + +### V1 hard preconditions + +Before any v1 implementation begins, the following must clear: + +| Precondition | Outcome required | Source | +|--------------|-------------------|--------| +| OQ-Sx-2 plan-mode MCP/Skill smoke test | Codex MCP + Skill invocations confirmed available inside plan mode | Required for A1 to be implementable at all | +| Stop Hook extension design | `stop-guard.sh` recognizes plan-review sentinels and aggregate state | Required for FR-6 / NFR-7 isolation guarantees | + +OQ-Sx-1 (PreToolUse on ExitPlanMode) remains v2-only; it is not a v1 precondition. + +### Backup option + +If the harness probe (see §8) shows PreToolUse can block `ExitPlanMode`, promote architecture to **A3 + B1 + C2** in v2. State carrier and tier choices are unchanged — only the trigger surface gains a hook tripwire. + +### Concrete v1 design contract + +| Element | Decision | +|---------|----------| +| Trigger | `/plan-review` skill, self-invoked by Claude in plan mode | +| State field | `.claude_review_state.json.plan_review` (namespaced, with own `iteration_history`) | +| Sentinels (plan-only namespace) | `## Plan Review`, `✅ Plan Ready`, `⛔ Plan Blocked`, `⚠️ Plan Needs Human`, `[PLAN_REVIEW_DEGRADED]` | +| Gate emission | New `scripts/emit-plan-gate.sh` (or extend `emit-review-gate.sh` with namespace flag) | +| Tier ladder | quick (1-pass) → standard (loop) → deep (`/codex-brainstorm` delegate) | +| Convergence | Reuse rule-level convergence decision table from `rules/auto-loop.md`, but plan-review owns its own `iteration_history` and does **not** consume code/doc `total_rounds_session` | +| Bypass | Skill flag `--skip-review` + user explicit "skip review" detection | +| Degrade | Reviewer unavailable → mark `plan_review.degraded=true`, emit `[PLAN_REVIEW_DEGRADED]`, proceed to ExitPlanMode | +| Forbidden | Plan-review must never emit bare `✅ Ready` / `✅ Mergeable` / `## Gate: ✅` (collides with code/doc/aggregate routing) | + +## 8. Remaining Open Questions — 1:1 Disposition + +All 11 Open Questions from [`1-requirements.md` §9](./1-requirements.md#9-open-questions) are addressed below. Disposition values: + +- **Resolved** — answered by §7 recommendation +- **Tech-spec** — must be resolved before implementation (gates v1) +- **V2** — explicitly deferred; not blocking v1 +- **Post-merge** — documentation / UX polish iterable after v1 ships + +| # | Requirements OQ (source) | Disposition | How addressed | +|---|--------------------------|-------------|---------------| +| OQ-1 | 觸發機制(skill / hook / hybrid) | **Resolved** | §7 → A1 (skill-driven); A3 v2-gated on OQ-Sx-1 | +| OQ-2 | Plan artifact 可見性 | **Resolved** | §7 → skill mediates plan text (Claude has plan in context); Codex prompt framing detail in OQ-Sx-5 | +| OQ-3 | Default 啟用 vs opt-in | **Tech-spec** | Default proposal: opt-in initially; promote to opt-out after 2-week pilot | +| OQ-4 | Review trail 訊噪比 (UC-3 vs NFR-4) | **Tech-spec** | Default proposal: summary block (rounds / findings count / modified-sections); `--verbose` = round-by-round | +| OQ-5 | Auto-loop 預算共享 / 隔離 | **Resolved** | §7 → independent `plan_review.iteration_history`; does not consume `total_rounds_session` | +| OQ-6 | State scope: per-session vs per-plan | **Tech-spec** | Default proposal: per-plan reset; `plan_review.history[]` keeps last-5 trails | +| OQ-7 | Dual-review trigger (FR-8 always vs deep tier) | **Tech-spec** | Default proposal: deep tier only (dual-review's marginal benefit is low on short plans); revisit if pilot shows false-negative rate elevated | +| OQ-8 | 與 `/codex-brainstorm` 的關係 / 深度光譜 | **Resolved** | §7 → C2 delegated; quick / standard inline, deep delegates | +| OQ-9 | Plateau / fingerprint 偵測可行性 | **V2** | Requires hook-level fingerprint storage (not yet implemented); v1 honors only `max_rounds` + plateau row 3 of rule-level decision table is unreachable until storage lands | +| OQ-10 | Plan-review 預算配置點 | **Tech-spec** | Default proposal: new `## Plan Review Max Rounds` block in `auto-loop-project.md`, default 5 | +| OQ-11 | 與 `/codex-review-doc` 的邊界 | **Resolved** | Boundary axiom: `/plan-review` reviews in-context plan drafts produced inside plan mode; `/codex-review-doc` reviews `.md` files on disk. Different trigger (pre-ExitPlanMode vs ad-hoc), different artifact (in-context text vs filesystem path), different repair model (Claude auto-revise vs human-revise). No reuse of `doc_review` state field | + +**Additional spike items (not in requirements §9, surfaced by feasibility):** + +| # | Question | Disposition | Notes | +|---|----------|-------------|-------| +| OQ-Sx-1 | Harness `PreToolUse` smoke test on `ExitPlanMode` | **V2** | Required to promote A1 → A3; spike ticket; not blocking v1 | +| OQ-Sx-2 | Plan-mode availability of MCP / Skill invocations (Codex MCP, `/plan-review`, `/codex-brainstorm`) | **Tech-spec — hard precondition** | If unavailable, A1 is not implementable; escalate to ⛔ architecture revisit | +| OQ-Sx-3 | `stop-guard.sh` extension to recognize plan-review sentinels and aggregate state | **Tech-spec — hard precondition** | Required for FR-6 / NFR-7 isolation guarantees | +| OQ-Sx-4 | Tier auto-detection heuristic | **Tech-spec** | Default proposal: `standard` always; explicit upgrade for deep | +| OQ-Sx-5 | Codex prompt framing for plan artifact handover | **Tech-spec** | Plan text passed as "candidate artifact to attack" per `rules/codex-invocation.md`; never "Claude's conclusion to confirm" | + +## 9. Verification + +- [x] 5-Why decomposition consumed from canonical `1-requirements.md` +- [x] Constraints inventoried with flexibility ratings +- [x] Existing code researched (grep / Read confirmed primitives + clean namespace) +- [x] Three solution options explored per axis with quantitative scoring +- [x] Codex discussion executed with independent research and adversarial rounds; equilibrium reached at R1 via convergence +- [x] Comparison table + recommendation + backup + open questions + +## 10. References + +- Canonical requirements: [`./1-requirements.md`](./1-requirements.md) +- Sibling lifecycle docs: [`docs/features/dual-reviewer/2-tech-spec.md`](../dual-reviewer/2-tech-spec.md), [`docs/features/codex-review-spec/1-requirements.md`](../codex-review-spec/1-requirements.md) +- Reused skills: [`skills/codex-brainstorm/SKILL.md`](../../../skills/codex-brainstorm/SKILL.md), [`skills/doc-review/SKILL.md`](../../../skills/doc-review/SKILL.md), [`skills/codex-code-review/SKILL.md`](../../../skills/codex-code-review/SKILL.md) +- Loop primitives: [`hooks/post-tool-review-state.sh`](../../../hooks/post-tool-review-state.sh), [`hooks/stop-guard.sh`](../../../hooks/stop-guard.sh), [`scripts/emit-review-gate.sh`](../../../scripts/emit-review-gate.sh) +- Rules: [`rules/auto-loop.md`](../../../rules/auto-loop.md), [`rules/codex-invocation.md`](../../../rules/codex-invocation.md), [`rules/auto-loop-project.md`](../../../rules/auto-loop-project.md), [`rules/docs-numbering.md`](../../../rules/docs-numbering.md) +- Codex debate threadId: `019e298f-3645-7801-b6ff-b60b8d1235e6` diff --git a/docs/features/plan-review-loop/1-requirements.md b/docs/features/plan-review-loop/1-requirements.md new file mode 100644 index 0000000..61fc617 --- /dev/null +++ b/docs/features/plan-review-loop/1-requirements.md @@ -0,0 +1,155 @@ +# Requirements: Plan-Review-Loop — Pre-ExitPlanMode Codex Review Gate + +> **Doc class**: Lifecycle — Phase 1 requirements (per `@rules/docs-numbering.md`). Feature-level problem-space analysis. **Not** a task tracking ticket; for per-task progress tracking see `requests/*.md` (created via `/create-request`). +> **Created**: 2026-05-14 +> **Updated**: 2026-05-14 +> **Tier**: standard + +## 1. Problem Statement + +Claude Code 的 plan mode 由 `ExitPlanMode` 工具終結:Claude 草擬完計畫後直接把 plan 文字交給使用者裁決。整個流程**只有一個視角**——草擬者就是定稿者,沒有獨立挑戰者;使用者既要當決策者又要當品管。當 plan 立基於錯誤假設、過度設計、或漏掉關鍵考量時,缺陷的偵測責任被推給使用者,造成「批准 → 開工 → 才發現方向錯」的反覆。 + +使用者希望在 ExitPlanMode 把 plan 還給使用者**之前**先讓 Codex(或任何具備獨立研究能力的 reviewer)對 plan 做對抗性審查,跑類似 `/codex-review-doc` 的多輪 review loop,討論完、收斂到 ✅ Plan Ready 後才把最終 plan(含精煉/修正後內容)呈現給使用者。本質是把「使用者人工 review plan」這道流程左移成「自動化 review + 收斂」,降低使用者的 cognitive load 並提升 plan 品質。 + +### 5-Why Trace + +1. **表層**:使用者要在 plan mode 流程中插入一個 Codex review loop,討論完才返回 plan +2. **Why**:目前 Claude → ExitPlanMode → User 是單一視角流;plan 任何缺陷只能靠使用者人工發現 +3. **Why**:缺乏獨立挑戰者,plan 的假設、推理、邊界、替代方案皆未經對抗檢驗 +4. **Why**:使用者批准 plan 後若才發現缺陷,已耗費實作成本,回頭成本高 +5. **根因**:plan 階段缺乏「fail-fast adversarial gate」——讓 plan 在進入「使用者裁決」與「實作」前先過獨立 FP 審查,把缺陷壓在最低成本的窗口 + +## 2. Goals / Non-Goals + +| Goals | Non-Goals | +|-------|-----------| +| 在使用者看到 plan 之前,plan 先經過獨立 reviewer 對抗性審查 | 修改 ExitPlanMode 工具本身(須在 harness 工具契約內運作) | +| 多輪 fix → re-review → 直到審查收斂才把 plan 呈現給使用者 | 取代使用者最終裁決——review 完仍由使用者批准 | +| 與既有 review 基礎建設(hooks/skills/rules)相容,不破壞或污染既有狀態 | 自動執行 plan(review 通過 ≠ 開工) | +| 與既有 code/doc/spec review 路徑保持 MECE,依各自職責處理對應審查對象 | review 非 plan-mode 場景的審查對象(lifecycle spec 等屬於既有/未來 spec-review 範疇) | +| 提供使用者逃生口(override / bypass / disable)避免 review 劫持控制權 | 強制所有 plan 都進 review(啟用光譜需可調:opt-in / opt-out / always)| +| 透明化 review 過程——使用者能知道 plan 是否被審、被挑戰了什麼 | 把 review 對話原文夾雜進 plan(須有訊噪比設計) | + +## 3. Stakeholders + +| Stakeholder | Role | Key Concern | +|-------------|------|-------------| +| 使用者(Plan Receiver) | User | 拿到品質更高、已過獨立挑戰的 plan,而不被 review 流程拖慢或淹沒 | +| Claude(Plan Author) | Operator | 在不破壞 ExitPlanMode 契約的前提下,能 orchestrate review loop | +| Codex MCP | Dependent | 收到符合 `@rules/codex-invocation.md` 的 prompt——獨立研究、不被餵養結論 | +| Secondary reviewer(Task agent) | Dependent | 對 plan 提供第二視角(可選 dual-review parity) | +| Auto-loop / Stop Hook | Operator | 須能辨識 plan-review gate sentinel,與 code/doc review 不衝突 | +| Plan-review state carrier(review 狀態的持久化載體)| Dependent | 新增的狀態紀錄不破壞既有 `code_review` / `doc_review` / `aggregate_gate`;具體 schema / 欄位名 / 載體(是否沿用 `.claude_review_state.json`)由 feasibility 決定 | +| 既有 review skills / features / infrastructure(`/codex-review-doc` 與 `/review-spec` 是現有 skill;`dual-reviewer` 與 `codex-review-spec` 目前僅有 feature docs,尚未 ship 為 skill) | Dependent | 不重複職責、不爭奪 sentinel/state namespace、Auto-loop 觸發條件不衝突 | +| Harness 開發者(Claude Code)| External Dependent | 提供 ExitPlanMode 工具契約、PreToolUse hook 能否攔截為未知,影響可行性 | + +## 4. Use Cases + +| # | Actor | Action | Expected Outcome | +|---|-------|--------|-----------------| +| UC-1 | Claude in plan mode | 準備 ExitPlanMode 前自動觸發 plan-review-loop | Codex 對 plan 跑 ≥1 輪 FP 審查;若有 P0/P1 finding,Claude 自動修 plan,再 review,直到 ✅ Plan Ready 才呼叫 ExitPlanMode | +| UC-2 | 使用者 | 想直接看 raw plan(跳過 review) | 透過 disable flag / 環境變數 / explicit 指示,可一次性 bypass 該輪 review loop | +| UC-3 | 使用者 | 想看 review 過程 | 取得 review trail 摘要:每輪 findings 數、最終解決狀態、被修改的 plan 段落 diff | +| UC-4 | Auto-loop | plan review 與既有 code/doc review 並存時 | 兩者 state field 隔離,stop-guard 不誤判;plan review 不消耗 code review max_rounds 預算 | +| UC-5 | Plan author (Claude) | review loop 達 max_rounds 仍未收斂 | 觸發 ⚠️ Need Human:把目前 plan + 殘餘 findings 列表交給使用者裁決,不強制進 ExitPlanMode | +| UC-6 | Codex MCP 不可用 | 連線/授權失敗 | Graceful degradation:跳過 review、附 warning 標籤 ExitPlanMode(plan 仍可送達使用者),不阻塞 plan mode | + +## 5. Functional Requirements + +> **Note**: FRs are stated as **observable capabilities / outcomes**, not implementation mechanisms. Specific mechanisms(hook attachment point、sentinel naming、state schema key、prompt 重用方式)皆推延至 `/feasibility-study` 評估,列入 §9 Open Questions。 + +| ID | Requirement | Priority | Rationale | +|----|-------------|----------|-----------| +| FR-1 | 系統必須能在 plan 被呈現給使用者之前介入並啟動 review;具體攔截點(harness-level hook、skill-level pre-step、或其他)由可行性研究決定 | Must | 無觸發點即無 review;機制屬於 solution-space | +| FR-2 | 系統必須將 plan 內容送獨立 reviewer(具備獨立研究能力,符合 `rules/codex-invocation.md`)做對抗性審查 | Must | 核心功能;reviewer 為什麼引擎與 prompt 設計屬於 solution-space | +| FR-3 | 系統必須支援多輪 fix → re-review → 收斂;有最大輪數上限(達上限後升 ⚠️ Need Human) | Must | 單輪不保證收斂;plateau / fingerprint 等進階偵測屬 Should,視可行性 | +| FR-4 | 系統必須在 review 收斂(無 P0/P1)後才把最終 plan 呈現給使用者。**「Blocked」** 在此 FR 指 reviewer 仍標示 P0/P1(review 結論為 `⛔`);reviewer 不可達 / 失效不視為 blocked,走 NFR-3 graceful degradation 例外路徑(plan 仍可送達,但 output 須附明確 degradation 標記) | Must | 「review 通過才回 plan」核心承諾;本款只規範 review 結論層級,不規範 reviewer infrastructure 可用性 | +| FR-5 | 系統必須提供使用者逃生口:明示要直接看 plan / disable review / dry-run 時,當輪立即跳出 loop 並呈現原 plan | Must | 控制權保留——review 不可劫持流程 | +| FR-6 | 系統必須確保 plan-review 狀態不污染既有 code/doc/aggregate review 狀態(任一邊狀態變化不互相覆蓋) | Must | research findings 指出 sentinel / state collision 為已知風險;隔離方式(獨立 sentinel namespace 或獨立 state field)屬 solution-space | +| FR-7 | 系統必須在跨 reply / 跨 round 場景保留 review 進度,使中斷後能恢復 | Must | 跨 reply 持久化需求;具體欄位/檔案/schema 為 solution-space | +| FR-8 | 系統應支援多 reviewer 並行視角(不僅單一 Codex),以降低單點與 false-negative | Should | 多視角顯著提升審查命中率;具體 reviewer 組合 / 並行模式參考既有 dual-reviewer 設計,但實現方式為 solution-space | +| FR-9 | 系統應在最終 plan output 附 review trail summary(至少輪數、findings 數、主要被修正項),讓使用者可審計 | Should | 透明度——避免「黑箱潤色」破壞信任 | +| FR-10 | Plan-review 的迭代 / 成本預算應與 code/doc review 預算邏輯隔離,避免互耗 | Should | UC-4 隔離需求;具體 max_rounds、token cap 透過配置或新配置點實現屬 solution-space | +| FR-11 | 系統可支援不同審查深度(淺/中/深)以匹配 plan 複雜度;深度模式可升級為 `/codex-brainstorm` 級對抗式辯論 | Could | 簡單 plan 不必跑完整辯論;`/codex-brainstorm` 提供成熟 pattern 可借鑑或鏈接 | +| FR-12 | 系統可依 plan domain(architecture / refactor / bug-fix...)路由到專長 reviewer | Could | 提升命中率,但會增加 prompt 維護負擔,需評估 ROI | +| FR-13 | 修改 ExitPlanMode 工具內部行為 | Won't | Harness 工具契約屬於 Claude Code 上游,本 plugin 只能在外圍 orchestrate | +| FR-14 | review 通過後自動執行 plan(auto-implement) | Won't | 使用者最終裁決權保留;review 通過 ≠ 開工 | +| FR-15 | 對 lifecycle spec(`1-requirements.md`、`2-tech-spec.md` 等)執行 review | Won't | 由既有 `/review-spec` 或未來 `/codex-review-spec` 負責;本範圍只覆蓋 plan-mode 輸出 | + +## 6. Non-Functional Requirements + +| ID | Category | Requirement | Metric | +|----|----------|-------------|--------| +| NFR-1 | Performance | review loop 應在合理輪數內收斂 | 典型 plan p50 ≤3 輪、p95 ≤5 輪收斂(具體 max_rounds 數值在 feasibility 階段決定) | +| NFR-2 | Cost | 系統須有 plan-review 預算上限機制,避免 plan-mode 變成 token sink | 達 max_rounds 或預算上限時不靜默通過,必須升 ⚠️ Need Human;預算配置點(複用既有 `auto-loop-project.md` 還是新增)由 feasibility 決定 | +| NFR-3 | Reliability | Reviewer 失效時須 graceful degradation,不阻塞 plan mode | Reviewer 不可達(連線錯誤、auth 失敗)情境下,plan 仍能在當輪送達使用者,且 plan output 含可被 grep 偵測的明確 degradation 標記(namespaced sentinel `[PLAN_REVIEW_DEGRADED]`,與既有 code/doc review routing 不衝突)| +| NFR-4 | Usability | 預設行為對使用者透明:是否進 review、進幾輪、最後改了什麼 | Plan output 含可辨識的 review summary 區塊(至少輪數、findings count、修正摘要 3 欄);verbose 模式可看 round-by-round | +| NFR-5 | Safety | 使用者可隨時 escape(明示「skip review」、reply override、其他逃生指令) | 從使用者發送 escape 訊號到下一個 plan output 之間最多經過 1 輪 review;不存在「卡死於 loop 無法跳出」的狀態 | +| NFR-6 | Maintainability | 與既有 review 基礎建設共用核心模式(loop convergence、sentinel emission、state persistence),盡量擴充而非複製 | 對既有介面的修改幅度最小化;可重用模式 vs 必要修改的取捨在 feasibility 階段量化 | +| NFR-7 | Boundary | plan-review state 與 code/doc/aggregate review state 不互相覆寫 | 自動化測試證明:plan-review 觸發後,既有 code_review / doc_review / aggregate_gate 欄位值不變;反之亦然 | +| NFR-8 | Security | review 過程不外流 secrets / tokens / API keys 到 reviewer 上下文 | plan 內容含 **regex-valid** dummy secret(須真正命中 [`scripts/security-redact.js`](../../../scripts/security-redact.js) 的高敏 pattern:`sk-` + ≥20 字元如 `sk-abcdefghijklmnopqrstUVWX`、`ghp_` + 恰 36 字元如 `ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`、PEM header)時,送 reviewer 前的 payload grep 結果不含任何高敏 pattern;遵循 [`rules/security.md`](../../../rules/security.md) 的「Logging private keys/passwords/tokens」禁令,redact 實作可重用 `security-redact.js` 或同等 primitive | + +## 7. Constraints & Assumptions + +| Type | Description | Source | +|------|-------------|--------| +| Constraint | ExitPlanMode 是 harness-provided tool,本 plugin 無法修改其行為 | 本 repo grep 找不到 ExitPlanMode 程式碼;屬 Claude Code harness 範疇 | +| Constraint | 本 repo `hooks/hooks.json:32-41` PreToolUse matcher 目前僅覆蓋 `Edit\|Write`;要攔截其他 tool 需擴充,且 harness 是否支援 PreToolUse 對 ExitPlanMode 為未知 | [`hooks/hooks.json:32-41`](../../../hooks/hooks.json) + harness 文件缺口 | +| Constraint | review 必須遵守 `rules/codex-invocation.md` 全文——獨立研究、不餵養結論、prompt 來自 reference template | [`rules/codex-invocation.md:1`](../../../rules/codex-invocation.md)(全文)| +| Constraint | sentinel / state field 不可污染既有 review state;既有可辨識 sentinel 與 state 欄位列於 `rules/auto-loop.md` Gate Sentinels 與 [`hooks/post-tool-review-state.sh`](../../../hooks/post-tool-review-state.sh) | [`rules/auto-loop.md`](../../../rules/auto-loop.md) Gate Sentinels 章節 | +| Constraint | Plan review 必須非 destructive:不刪除 plan 段落或改變語意,只能 surface findings 讓 Claude 重寫 | 衍生自 NFR-4 透明度 + UC-3 review trail | +| Assumption | Claude 在 plan mode 能取得自己即將傳給 ExitPlanMode 的 plan 文字 | 推論:Claude 是 plan author;plan 內容本來就在其 working context | +| Assumption | 獨立 reviewer(Codex MCP 為主要候選)在 plan mode 仍可被呼叫,不被 plan-mode 的 read-only constraint 排除 | 推論:Codex 屬 MCP 諮詢工具,plan-mode 限制針對 Edit/Write | +| Assumption | 使用者多數情境希望 plan 經過 review;少數情境(探索、demo、教學)需要 raw plan | 來自 user 原始陳述「希望...透過 codex review loop」——預設啟用為主要訴求 | +| Assumption | Plan review 平均 1-3 輪內收斂(plan 為高層敘述,issues 比 code review 少) | 經驗推論;確切數值待 feasibility / pilot 驗證 | +| Assumption | 使用者願意接受 plan 被 review 修改後再呈現,但要求透明度(FR-9) | 「討論完後才返回使用者 plan」隱含接受修改後版本 | + +## 8. Acceptance Signals + +Acceptance signals 列述使用者可觀察的外部行為,刻意避免綁定特定 state schema、sentinel 字串、或 hook 實作方式(那些屬 feasibility / design 階段決定)。 + +- **Signal 1 (FR-1, FR-4)**:在 plan mode 工作階段中,使用者實際看到的 plan output 之前,系統 log / output 中可觀察到 review 已執行過至少一次的證據(具體呈現方式由 feasibility 決定) +- **Signal 2 (FR-3, FR-4)**:給定一個含已知缺陷的 plan,系統觀察到 reviewer 標示 P0/P1,Claude 修改 plan 後再 review,直到 reviewer 不再標示 P0/P1,才把 plan 呈現給使用者;若 review 持續未收斂達上限,系統升 `⚠️ Need Human` 並把殘餘 findings 一併列出(不靜默通過) +- **Signal 3 (FR-5, NFR-5)**:使用者在 review loop 中明示「skip review」/「直接 show plan」/ 同義指令,在不超過 1 輪 review 後系統跳出 loop 並呈現 plan(review 已啟動者可顯示部分 trail,但不繼續迭代) +- **Signal 4 (FR-6, FR-7, NFR-7)**:對同一 session 並行觸發既有 code-review 與 plan-review 的測試情境下,兩者狀態互不覆蓋(自動化測試可驗證) +- **Signal 5 (FR-9, NFR-4)**:使用者最終看到的 plan output 包含可辨識的 review summary 區塊,至少含輪數、findings 數、被修正項摘要 3 欄 +- **Signal 6 (NFR-3)**:模擬 reviewer 不可用情境(offline / 401 / timeout),plan-review-loop 不卡死;plan 仍能在當輪交付使用者,且 output 含可被 grep 偵測的明確 degradation 標記 +- **Signal 7 (NFR-1, NFR-2)**:review 達 max_rounds 後系統明確輸出 `⚠️ Need Human` + 殘餘 findings 列表,並把目前狀態的 plan 交給使用者裁決 +- **Signal 8 (NFR-8)**:plan 內容含 **regex-valid** dummy secret(須真正命中 `security-redact.js` 高敏 pattern,如 `sk-abcdefghijklmnopqrstUVWX`、`ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`、PEM header;**不可**用 `sk-test-xxx`/`ghp_xxx` 這類長度不足、不會命中 regex 的假樣本,否則測試會假性通過而未實際驗證 redaction)時,送 reviewer 前的 payload grep 結果不含高敏 pattern;reviewer log(若可取)亦不含原始 secret + +## 9. Open Questions + +- [ ] **Solution concern — 觸發機制**:plan-review-loop 應該是 (a) skill-driven(Claude 在 plan mode 自覺呼叫 plan-review-loop skill)、(b) PreToolUse hook 攔截 ExitPlanMode、還是 (c) 兩者並存(hook 兜底 + skill 為主)?harness PreToolUse 對 ExitPlanMode 是否可用為未知 — 建議執行 `/feasibility-study` +- [ ] **Solution concern — Plan artifact 可見性**:Claude 在準備呼叫 ExitPlanMode 時,plan 內容存在哪?(in-context message draft?ExitPlanMode 的 `plan` 參數?)如果 plan 只在工具呼叫瞬間才具象化,review 須在 Claude 端 orchestrate 而非 hook 端攔截 — 建議 `/feasibility-study` +- [ ] **Solution concern — 預設啟用 vs opt-in**:plan-review-loop 預設 always-on、opt-out、還是 opt-in?影響 NFR-2 cost 與 UC-2 user override 設計 — 建議 stakeholder 討論 +- [ ] **Trade-off — review trail 訊噪比**:UC-3 要看 review 過程 vs NFR-4 透明度但不淹沒 plan,平衡點在哪?默認 summary、verbose 可選?需 UX 設計 +- [ ] **Boundary — auto-loop 預算共享**:plan_review 是否共用 `total_rounds_session` 還是獨立計?FR-10 傾向獨立,但若使用者一個 session 內進多次 plan mode,總成本上限如何控制? +- [ ] **State scope — per-session vs per-plan**:`plan_review` 欄位是覆蓋還是累積?每次新 plan mode 重置,還是保留歷史 plan 的 review trace? +- [ ] **Dual-review trigger**:FR-8 應在所有 tier 預設啟用 dual,還是只 deep tier?dual-review 對短 plan 文字的邊際效益可能較小 +- [ ] **與 `/codex-brainstorm` 的關係 / 深度光譜**:plan-review-loop 的 tier 譜系該如何切?quick = 單輪 Codex 文件審查、standard = 多輪 fix→re-review、deep = `/codex-brainstorm` 對抗式辯論?兩者皆有 Codex 多輪 loop,但 `/codex-brainstorm` 強調 Nash equilibrium 收斂條件、attack/defense 對稱、threadId 追溯 — plan-review 是否該完全內嵌或僅作為 escalation 路徑?需 `/feasibility-study` 評估深度與成本比 +- [ ] **Solution concern — Plateau / fingerprint 偵測可行性**:`rules/auto-loop.md` 定義 plateau detection(fingerprint overlap ≥50% 連 3 輪),但 `hooks/post-tool-review-state.sh` 目前只記錄 finding counts,未儲存 fingerprints。plan-review-loop 是否要在收斂偵測上同樣支援 plateau?若是,須先擴充 state schema — 屬 solution-space,建議列入 `/feasibility-study` +- [ ] **Solution concern — Plan-review 預算配置點**:NFR-2 要求 plan-review 須有預算上限,但目前 `rules/auto-loop-project.md` 僅支援 `Max Rounds`、`Git Memory`、`Think Harder` 三個配置點,無 token / cost cap 配置介面。是否新增獨立的 `Plan Review Budget` 配置欄位,還是重用 `Max Rounds` 但獨立 namespace?需 feasibility 評估 +- [ ] **與 `/codex-review-doc` 的邊界**:Goals 區塊主張「與既有 review 路徑保持 MECE」,但本 Open Question 列出的「是否為 `/codex-review-doc` 變體」尚未拍板——兩者皆審查文字內容,差別在於 review 對象(plan 文字 vs lifecycle .md 檔)、觸發時機(pre-ExitPlanMode vs ad-hoc)、修正模型(Claude 自動 revise vs 人工 revise)。需在 `/feasibility-study` 階段給出明確 boundary 公理 + +## 10. References + +- Related lifecycle docs (problem-space sibling features): + - [`docs/features/dual-reviewer/2-tech-spec.md`](../dual-reviewer/2-tech-spec.md) — 並行 Codex + Task 雙視角審查架構(feature spec,尚未 ship 為獨立 skill;可借鑑模式) + - [`docs/features/codex-review-spec/1-requirements.md`](../codex-review-spec/1-requirements.md) — Codex FP spec 審查(feature requirements,尚未 ship 為 skill;boundary 對照) +- Request-level implementation history: + - [`docs/features/review-state-tracking/requests/`](../review-state-tracking/requests/) — `.claude_review_state.json` schema 演進的 request 紀錄(非 lifecycle,僅供溯源) +- Related skills (currently shipped — reusable patterns): + - [`skills/codex-brainstorm/SKILL.md`](../../../skills/codex-brainstorm/SKILL.md) — 對抗式 Nash equilibrium 辯論(Phase 1-5 workflow、termination conditions、attack/defense templates);可作為 deep-tier 升級路徑或 prompt-pattern 來源 + - [`skills/codex-code-review/SKILL.md`](../../../skills/codex-code-review/SKILL.md) — review loop convergence + `--continue` threadId 延續 + - [`skills/doc-review/references/review-loop-doc.md`](../../../skills/doc-review/references/review-loop-doc.md) — 多輪文件審查 loop template + - [`skills/review-spec/SKILL.md`](../../../skills/review-spec/SKILL.md) — 現有 spec 審查 skill(FR-15 排除範圍的對應 owner) +- Hook / loop primitives: + - `hooks/post-tool-review-state.sh` — sentinel parser + iteration_history schema + - `hooks/stop-guard.sh` — gate enforcement + dual mode + - `scripts/emit-review-gate.sh` — gate emission contract +- Rules: + - [`rules/auto-loop.md`](../../../rules/auto-loop.md) — rule-level convergence model(decision table、max_rounds、plateau detection 與 strategic reset 的規格描述;plateau 之 fingerprint 偵測尚未在 hook 端實作,須 feasibility 評估補上) + - `rules/codex-invocation.md` — Codex prompt 規範 + - `rules/fix-all-issues.md` — analysis-only mode(plan-review 內在屬性) +- Research: + - Phase 2 Explore findings(2026-05-14): 確認 ExitPlanMode 不在本 repo(屬 harness)、`hooks/hooks.json` PreToolUse 僅 Edit\|Write、sentinel/state collision 風險、`auto-loop-project.md` 已支援 max_rounds 配置 diff --git a/docs/features/plan-review-loop/2-tech-spec.md b/docs/features/plan-review-loop/2-tech-spec.md new file mode 100644 index 0000000..9ef7c06 --- /dev/null +++ b/docs/features/plan-review-loop/2-tech-spec.md @@ -0,0 +1,492 @@ +# Technical Spec: Plan-Review-Loop — Pre-ExitPlanMode Codex Review Gate + +> **Doc class**: Lifecycle — Phase 2 technical spec (per [`rules/docs-numbering.md`](../../../rules/docs-numbering.md)). +> **Created**: 2026-05-18 +> **Canonical inputs**: [`./1-requirements.md`](./1-requirements.md) (Phase 1), [`./0-feasibility-study.md`](./0-feasibility-study.md) (Phase 0) +> **Architecture decision**: v1 = **A1 + B1 + C2** (feasibility Nash equilibrium, threadId `019e298f-3645-7801-b6ff-b60b8d1235e6`) + +## 1. Requirement Summary + +- **Problem**: Claude Code 的 plan mode 由 `ExitPlanMode` 終結,草擬者即定稿者,無獨立挑戰者;plan 缺陷的偵測責任全推給使用者,造成「批准 → 開工 → 才發現方向錯」的高成本反覆([`1-requirements.md` §1](./1-requirements.md))。 +- **Goals**: 在 plan 呈現給使用者**之前**插入一個 Codex 對抗式 review loop,多輪 fix → re-review 收斂後才呼叫 `ExitPlanMode`;與既有 code/doc/aggregate review 狀態與 sentinel **完全隔離**;reviewer 失效時 graceful degrade 不阻塞 plan mode;保留使用者逃生口。 +- **Scope**: + +| In Scope (v1) | Out of Scope | +|---------------|--------------| +| 新增 `/plan-review` skill(A1 skill-driven trigger) | 修改 `ExitPlanMode` 工具本身(harness 上游,FR-13 Won't) | +| `.claude_review_state.json` 加 namespaced `plan_review` 欄位(B1,schema v2→v3 additive migration) | PreToolUse hook 攔截 `ExitPlanMode`(A3,v2-gated on OQ-Sx-1 harness probe) | +| plan-only sentinel namespace + `scripts/emit-plan-gate.sh` | 對 lifecycle spec 執行 review(FR-15 → `/review-spec` owns) | +| 3-tier ladder:quick / standard / deep(deep 委派 `/codex-brainstorm`,C2) | review 通過後自動執行 plan(FR-14 Won't) | +| `stop-guard.sh` 擴充辨識 plan sentinel 並維持隔離(OQ-Sx-3 hard precondition) | plateau / fingerprint 偵測(OQ-9 → V2,需 hook 端 fingerprint 儲存) | +| Bypass(`--skip-review` + 使用者明示偵測)、graceful degradation、review trail summary | | + +## 2. Existing Code Analysis + +### 2.1 Related modules (verified) + +| Module | Role for plan-review | Reference | +|--------|---------------------|-----------| +| `hooks/post-tool-review-state.sh` | State carrier + MCP sentinel router;pre-v1 既有 state 為 `schema_version: 2`(as-built:`init_state_file()` 直接產出 v3 含 `plan_review`;既有 v2 檔由 migration 升級) | [`init_state_file()`](../../../hooks/post-tool-review-state.sh)(state init)、[MCP routing Priority 1.5 plan branch](../../../hooks/post-tool-review-state.sh)(`## Plan Review` discriminator;行號隨版本漂移,以符號名為準) | +| `hooks/stop-guard.sh` | Stop gate enforcement;`grep -E '✅ Mergeable\|✅ Ready'` 視為 REVIEW_PASSED | transcript-fallback 模式的 `REVIEW_PASSED` / `REVIEW_BLOCKED` / `LAST_REVIEW` 變數([`hooks/stop-guard.sh`](../../../hooks/stop-guard.sh);引用穩定符號名而非行號以免漂移) | +| `scripts/emit-review-gate.sh` | gate emission contract(`PENDING\|READY\|BLOCKED` → `REVIEW_GATE=$GATE`) | [`scripts/emit-review-gate.sh`](../../../scripts/emit-review-gate.sh) | +| `skills/doc-review/SKILL.md` | Codex loop topology:first `mcp__codex__codex`(存 threadId)→ `mcp__codex__codex-reply` loop;`sandbox: read-only`, `approval-policy: never` | [`skills/doc-review/SKILL.md:48-58`](../../../skills/doc-review/SKILL.md) | +| `skills/codex-brainstorm/SKILL.md` | Nash equilibrium engine(deep tier 委派目標) | [`skills/codex-brainstorm/SKILL.md`](../../../skills/codex-brainstorm/SKILL.md) | +| `rules/auto-loop.md` | Convergence decision table(max_rounds / plateau / strategic reset),rule-level model 可重用 | [`rules/auto-loop.md`](../../../rules/auto-loop.md) Exit Conditions | +| `hooks/hooks.json` | PreToolUse 僅 `Edit\|Write`;A1 **不需**改 hooks.json | [`hooks/hooks.json:32-41`](../../../hooks/hooks.json) | +| `scripts/security-redact.js` | secret redaction primitive(NFR-8 送 reviewer 前 sanitize) | [`scripts/security-redact.js`](../../../scripts/security-redact.js) | + +### 2.2 Reusable components + +| Component | Reuse posture | +|-----------|---------------| +| `.claude_review_state.json` lock / migration / compact-resume infra | **Extend**:加 `plan_review` 子樹,沿用既有 lock + atomic write | +| MCP sentinel routing `##
+ ✅/⛔ ` pattern | **Mirror**:新增 `## Plan Review` discriminator branch | +| doc-review `mcp__codex__codex` → `codex-reply` loop | **Adopt topology**:plan-review standard tier 同構 | +| `rules/auto-loop.md` convergence decision table | **Reuse rule-level model**:plan-review 擁有獨立 `iteration_history`,不消耗 code/doc `total_rounds_session` | +| `/codex-brainstorm` Nash engine | **Delegate**:deep tier 直接呼叫,不複製對抗引擎 | + +### 2.3 Files requiring changes + +| File | Change type | Detail | +|------|-------------|--------| +| `skills/plan-review/SKILL.md` | **New** | 核心 orchestration skill(A1) | +| `skills/plan-review/references/codex-prompt-plan.md` | **New** | Codex prompt template(OQ-Sx-5:plan 作為 "candidate artifact to attack") | +| `skills/plan-review/references/review-loop-plan.md` | **New** | re-review 續輪 template | +| `.claude/skills/plan-review/...`(經 `.claude/skills -> ../skills` dir symlink 自動可見)+ `docs/skill-catalog.yml` + 3 份 CLAUDE quick-ref row | **New/Modify** | 使用者可呼叫入口(v3 起無 `commands/` thin entry,skill 直接註冊) | +| `scripts/emit-plan-gate.sh` | **New** | plan gate emission contract | +| `hooks/post-tool-review-state.sh` | **Modify** | (a) `init_state_file()` 加 `plan_review`、`schema_version` 2→3;(b) schema migration 分支;(c) MCP routing 加 Priority 1.5 plan branch | +| `hooks/stop-guard.sh` | **Modify** | plan sentinel 隔離(`✅ Plan Ready` 不得滿足 code/doc gate;plan pending 獨立追蹤)— OQ-Sx-3 | +| `rules/auto-loop-project.md` | **Modify** | 新增 `## Plan Review Max Rounds` 配置區塊(OQ-10,default 5) | +| `rules/auto-loop.md` | **Modify** | Standard Gate Sentinels 表加 plan namespace 列 | +| `test/skills/plan-review.test.js` | **New** | skill 結構/契約測試(靜態斷言;行為驗證見 §6) | +| `test/scripts/emit-plan-gate.test.js` | **New** | gate emission 測試 | +| `test/hooks/post-tool-review-state.test.js` | **Modify** | 加 plan sentinel routing + schema migration fixtures | +| `test/hooks/stop-guard.test.js` | **Modify** | 加 plan isolation fixtures | + +## 3. Technical Solution + +### 3.1 Architecture Design + +```mermaid +sequenceDiagram + participant U as User + participant C as Claude (plan mode) + participant PR as /plan-review skill + participant RD as security-redact + participant CX as Codex MCP + participant SA as Secondary (Task agent) + participant BR as /codex-brainstorm + participant ST as .claude_review_state.json (plan_review.*) + participant EX as ExitPlanMode (harness) + + C->>C: 草擬 plan(in-context) + C->>PR: opt-in 啟用 → 呼叫 /plan-review(前置於 ExitPlanMode) + PR->>PR: Step1 tier 判定 (quick/standard/deep) + PR->>RD: Step2 scanHighConfidence(plan) + alt high-confidence secret 命中(fail-closed,終態) + RD-->>PR: {name,fingerprint} + PR->>ST: degraded=true; status_reason=secret-detected + PR->>C: [PLAN_REVIEW_DEGRADED](plan 不外送 reviewer) + C->>EX: ExitPlanMode(plan + degradation 標記) + EX->>U: 呈現 plan(未經 review,degradation 標記) + else 無 high → maskMediumConfidence(唯一進入 reviewer 的路徑) + RD-->>PR: masked plan + alt tier = quick + PR->>CX: 單輪 review(plan = candidate artifact) + else tier = standard + par dual dispatch + PR->>CX: Codex review loop(存 threadId) + and + PR->>SA: Secondary 視角(並行) + end + else tier = deep + PR->>BR: 委派 Nash equilibrium 辯論 + end + CX-->>PR: findings + ## Plan Review sentinel + PR->>ST: 寫 plan_review.iteration_history(獨立預算) + alt 有 P0/P1 (⛔ Plan Blocked) + PR->>C: surface findings(不改寫 plan) + C->>C: revise plan + C->>PR: re-review (codex-reply --continue) + Note over PR: loop 直到 ✅ Plan Ready 或 max_rounds + else 收斂 (✅ Plan Ready) + PR->>C: 附 review trail summary + C->>EX: 呼叫 ExitPlanMode(精煉後 plan) + EX->>U: 呈現最終 plan + else max_rounds 未收斂 + PR->>C: ⚠️ Plan Needs Human + 殘餘 findings + C->>U: 交付當前 plan + findings(不靜默通過) + else reviewer 不可達 + PR->>ST: plan_review.degraded=true; status_reason=reviewer-unavailable + PR->>C: [PLAN_REVIEW_DEGRADED] + C->>EX: ExitPlanMode(plan + degradation 標記) + end + end + Note over U,PR: 任一時點使用者明示「skip review」/ --skip-review → ≤1 輪內跳出 +``` + +### 3.2 Data Model + +`.claude_review_state.json` schema v2 → **v3**(additive)。新增 `plan_review` 頂層欄位,與 `code_review` / `doc_review` / `aggregate_gate` 同層但**互不覆寫**(NFR-7)。 + +> **以下 JSON 為部分節錄(partial excerpt),僅示 `plan_review` 新增子樹**。As-built:[`init_state_file()`](../../../hooks/post-tool-review-state.sh) 直接產出 **v3**(含 `plan_review`);**pre-v1 既有 state 檔為 v2**,由 migration 升級。v2/v3 頂層欄位(migration 必須完整保留**值**者):`session_id`、`updated_at`、`review_mode`、`has_code_change`、`has_doc_change`、`code_review`、`doc_review`、**`precommit`**、`aggregate_gate`、root `iteration_history`——不只 code/doc/aggregate/root iteration;`schema_version` 為唯一刻意改值的欄位(2→3,見 migration 一節)。 + +```jsonc +// partial excerpt — only the additive plan_review subtree shown +{ + "schema_version": 3, // bumped from 2 + // ...all existing v2 top-level fields preserved verbatim + // (session_id, updated_at, review_mode, has_code_change, + // has_doc_change, code_review, doc_review, precommit, + // aggregate_gate, iteration_history) ... + "plan_review": { + "executed": false, + "passed": false, + "degraded": false, + "skipped": false, + "status_reason": null, + "tier": null, + "last_run": "", + "iteration_history": { + "current_round": 0, + "max_rounds": 5, + "findings_by_round": [], + "total_rounds_session": 0 + }, + "history": [] + } +} +``` + +| Field | Semantics | OQ resolved | +|-------|-----------|-------------| +| `plan_review.iteration_history` | **獨立** loop 預算;不讀寫 root `iteration_history` / `total_rounds_session` | OQ-5 / FR-10 | +| `plan_review.iteration_history.max_rounds` | default `5`;可由 `auto-loop-project.md ## Plan Review Max Rounds` 覆寫 | OQ-10 / NFR-1 | +| `plan_review.history[]` | per-plan reset;保留最近 **5** 筆 trail(schema 見下),超出 FIFO 汰除 | OQ-6 | +| `plan_review.degraded` | reviewer 不可達 **或** 偵測到 high-confidence secret 時 `true`;output 伴隨 `[PLAN_REVIEW_DEGRADED]` | NFR-3 / NFR-8 | +| `plan_review.skipped` | 使用者明示 bypass 時 `true`(**與 `degraded` 區分**:使用者意圖 ≠ reliability 失效) | FR-5 / NFR-5 | +| `plan_review.status_reason` | `null \| "user-skip" \| "reviewer-unavailable" \| "secret-detected" \| "needs-human"`(最後者由 `update_plan_state()` 於 `NEEDS_HUMAN` 寫入) | NFR-3/5/8 | +| `plan_review.tier` | `"quick"\|"standard"\|"deep"` | OQ-Sx-4 | +| 無 `strategic_reset_fired` | plan-review v1 不啟用 strategic reset(plateau 屬 V2) | OQ-9 → V2 | + +`plan_review.history[]` 元素 schema(範例;v1 實作僅寫入以下 5 欄——`hooks/post-tool-review-state.sh update_plan_state()`): + +```json +{ "ts": "2026-05-18T10:00:00Z", "tier": "standard", "rounds": 3, + "findings_total": 5, "outcome": "ready" } +``` + +**Migration**(`post-tool-review-state.sh`,`init_state_file()` 之後新增分支;jq 以 `. +` 合併保留未知欄位): + +``` +讀 STATE_FILE.schema_version + == 3 → no-op + == 2 (或缺) → jq '. + {plan_review: $default} | .schema_version = 3'(atomic write,鎖保護) + 其他 → 保守不動,stderr 結構化告警 +``` + +Migration 為**純加法**:`. +` 合併確保**所有**既有頂層欄位(含 `session_id`/`updated_at`/`review_mode`/`has_*`/`precommit`)保留。**唯一刻意變更為 `schema_version` 2→3**;回歸測試斷言 migration 前後**除 `schema_version` 外**全部 v2 頂層欄位語意等價(`schema_version` 單獨斷言由 2 變 3)。 + +### 3.3 API Design + +#### T1 — Plan-only sentinel namespace(forbidden collision 表) + +| Sentinel | 意義 | Parsed by | +|----------|------|-----------| +| `## Plan Review` | section discriminator(**必出現**,路由前綴) | hook routing | +| `✅ Plan Ready` | 無 P0/P1,收斂 | hook + behavior | +| `⛔ Plan Blocked` | 有 P0/P1,續 loop | hook + behavior | +| `⚠️ Plan Needs Human` | max_rounds 未收斂 / hard precondition 未過 | behavior-only | +| `[PLAN_REVIEW_DEGRADED]` | reviewer 不可達 **或** high-confidence secret 偵測 → fail-closed,不送 reviewer(reason 區分) | hook + behavior | +| `[PLAN_REVIEW_SKIPPED]` | 使用者明示 bypass(≠ degrade;使用者意圖非 reliability 失效) | hook + behavior | + +> **Forbidden(硬約束)**:plan-review 路徑**永不**輸出裸 `✅ Ready` / `✅ Mergeable` / `## Gate: ✅` / 裸 `⛔ Block*`(會被 code/doc/aggregate routing 誤收)。Collision 分析(**已實測**:`printf '✅ Plan Ready' | grep -qE '✅ Ready'` → **SAFE**):`✅ Plan Ready` 不含子字串 `✅ Ready`(`✅` 與 `Ready` 間為 `" Plan "`),故 `✅` 方向安全。**真正風險在 `⛔` 方向**:`stop-guard.sh` 的 `REVIEW_BLOCKED` / `LAST_REVIEW` grep(`⛔.*Block`)會匹配 `⛔ Plan Blocked`——故 `## Plan Review` discriminator + routing 順序(T2)+ stop-guard 過濾(T4)三層防護缺一不可。 + +#### T2 — MCP sentinel routing 擴充(`post-tool-review-state.sh` MCP routing Priority 1.5 分支) + +於既有 doc(Priority 1) 與 code(Priority 2) 之間插入 **Priority 1.5 plan branch**: + +``` +if '## Document Review' && '✅ Mergeable' → doc_review pass # P1(不動) +elif '## Document Review' && '⛔ Needs revision' → doc_review fail # P1(不動) +elif '## Plan Review' && grep -F '[PLAN_REVIEW_DEGRADED]' → plan_review degraded # P1.5(token 先判) +elif '## Plan Review' && grep -F '[PLAN_REVIEW_SKIPPED]' → plan_review skipped # P1.5(token 先判) +elif '## Plan Review' && '⛔ Plan Blocked' → plan_review fail # P1.5(BLOCKED 先於 READY) +elif '## Plan Review' && '✅ Plan Ready' → plan_review pass # P1.5 +elif '✅ Ready' → code_review pass # P2(不動) +elif '⛔ Blocked' → code_review fail # P2(不動) +``` + +> **⚠️ Literal-match 硬約束**:`[PLAN_REVIEW_DEGRADED]` / `[PLAN_REVIEW_SKIPPED]` 含 `[` `]`,在 `grep -E`(ERE)中 `[...]` 是 **character class**(會匹配 `P/L/A/N/_/...` 任一字元),**絕不可**用 `grep -qE '[PLAN_REVIEW_DEGRADED]'`。**必須** `grep -qF '[PLAN_REVIEW_DEGRADED]'`(fixed string)或 escaped ERE `\[PLAN_REVIEW_DEGRADED\]`。Routing regression 必含:`## Plan Review` + `⚠️ Plan Needs Human`(無 degraded/skipped token)**不得**被標記為 degraded。 +> +> **分支優先序(as-built,fail-closed)**:machine token(DEGRADED/SKIPPED)**先於** verdict 文字——degraded/skipped 輸出若在 prose 中引述 verdict marker,不得丟失 flag/status_reason;再 `⛔ Plan Blocked` **先於** `✅ Plan Ready`——同時含兩個 verdict marker 的 ambiguous 輸出一律路由為 blocked。 + +**寫入路徑(as-built,history 單一擁有者)**:terminal `history[]` 由 emit-plan-gate Bash 路徑獨佔。MCP verdict 分支走 `_update_plan_iteration`(先記 round/finding counts,state file 缺失時 `init_state_file`)→ `update_plan_verdict(passed)`(僅 verdict,無 history append);MCP token 分支走 `update_plan_state(gate, "", "", "no-history")`。如此後續 `emit-plan-gate.sh` 的 history snapshot 取得 fresh counts 且不重複 append。MCP degraded 不帶 reason(恆 `reviewer-unavailable`):secret-detected 永不經 MCP 路由——skill 在外送 reviewer 前即 fail-closed,由 Bash 路徑記錄 reason。 + +#### T3 — `scripts/emit-plan-gate.sh` + **hook parse branch**(鏡射 `emit-review-gate.sh` 全鏈) + +`emit-review-gate.sh` 只是 emitter;**狀態真正被更新是因為 `post-tool-review-state.sh` 的 `emit-review-gate` parse 分支**(`Bash` PostToolUse)解析 `REVIEW_GATE=`。plan gate 必須補齊**兩端**: + +**(a) Emitter** `scripts/emit-plan-gate.sh`: + +``` +Usage: bash scripts/emit-plan-gate.sh PENDING [quick|standard|deep] # tier(僅 PENDING 接受) + bash scripts/emit-plan-gate.sh DEGRADED [reviewer-unavailable|secret-detected] # reason(僅 DEGRADED 接受) + bash scripts/emit-plan-gate.sh READY|BLOCKED|NEEDS_HUMAN|SKIPPED # 其餘 gate 拒絕額外參數 +→ echo "PLAN_REVIEW_GATE=$GATE" # namespace 前綴避免與 REVIEW_GATE 衝突 +→ 另輸出 PLAN_REVIEW_TIER= / PLAN_REVIEW_REASON=(有對應參數時) +非法值 / 空參數 / 非法 tier/reason → exit 1(set -euo pipefail) +``` + +**(b) Hook parse branch**(`post-tool-review-state.sh`,鏡射 `emit-review-gate` 分支,新增獨立分支): + +```bash +# === emit-plan-gate parse branch (as-built) === +if [[ "$TOOL_NAME" == "Bash" ]] && echo "$COMMAND" | grep -qF 'emit-plan-gate'; then + PLAN_GATE=$(echo "$TOOL_OUTPUT" | grep -oE '^PLAN_REVIEW_GATE=(PENDING|READY|BLOCKED|DEGRADED|NEEDS_HUMAN|SKIPPED)' | tail -1 | cut -d= -f2) || PLAN_GATE="" + # REASON 集合鏡射 emitter 實際輸出(僅 DEGRADED;SKIPPED 不發 REASON,user-skip 由 update_plan_state 內部硬編碼) + PLAN_REASON=$(... '^PLAN_REVIEW_REASON=(reviewer-unavailable|secret-detected)' ...) || PLAN_REASON="" + PLAN_TIER=$(... '^PLAN_REVIEW_TIER=(quick|standard|deep)' ...) || PLAN_TIER="" + update_plan_state "$PLAN_GATE" "$PLAN_REASON" "$PLAN_TIER" # 第 4 參數 history_mode 預設 append +fi +``` + +**全 6 值 → `update_plan_state()` 語意(無未定義值)**: + +| Gate | `executed` | `passed` | 其他 flag | stop-guard 視角 | +|------|-----------|----------|-----------|------------------| +| `PENDING` | `true` | `false` | reset per-plan cycle(round 0、findings 清空、degraded/skipped/status_reason 歸零;接受 tier) | loop 進行中:warn-only 提示,不阻塞 | +| `READY` | `true` | `true` | terminal → history append | 收斂;不滿足 code/doc gate(隔離) | +| `BLOCKED` | `true` | `false` | — | 續 loop;不阻塞 code/doc Stop(T4 過濾) | +| `DEGRADED` | `true` | `false` | `degraded=true`、`status_reason`(reason 參數,預設 reviewer-unavailable);terminal → history append | 非阻塞,warn-only | +| `SKIPPED` | `true` | `false` | `skipped=true`、`status_reason=user-skip`;terminal → history append | 非阻塞,warn-only | +| `NEEDS_HUMAN` | `true` | `false` | `status_reason=needs-human`(stop-guard 以此視為終態,不發 pending warn);terminal → history append | 終態 `⚠️ Plan Needs Human`;behavior-layer 升級,warn-only(非 hook hard-block) | + +> **as-built 補充**:terminal history(FIFO last-5,欄位 ts/tier/rounds/findings_total/outcome)僅由本 Bash 路徑 append(`history_mode=append` 預設);MCP 路由一律 no-history(見 T2)。Schema migration fail-closed:`schema_version` 非數字或 >3 → `_migrate_state_plan_review` 回傳 1,所有 plan writers(update_plan_state /_update_plan_iteration / update_plan_verdict)整段 skip 並 stderr 註記,state 完全不動。 +> +> hook 端測試**必須涵蓋全 6 值**(非僅 READY/BLOCKED/DEGRADED/SKIPPED)。 +> +> 測試必須涵蓋 **hook 端**(Bash 命令含 `emit-plan-gate` → `plan_review.*` 正確更新),不僅 script stdout。 + +#### T4 — `stop-guard.sh` 隔離擴充(OQ-Sx-3 hard precondition) + +| 問題 | 實際風險(已驗證) | 修正 | +|------|------|------| +| `REVIEW_PASSED` 的 `✅ Ready` 誤收 `✅ Plan Ready` | **低**:實測 `✅ Plan Ready` 不含 `✅ Ready`(SAFE) | 加防護但非主風險 | +| **`REVIEW_BLOCKED` + `LAST_REVIEW` 的 `⛔.*Block` 匹配 `⛔ Plan Blocked`** | **高(主風險)**:plan ⛔ 被誤判為 code/doc FAIL,阻塞無關 Stop | **as-built:`_strip_plan_sentinels()` substring strip**(sed 移除四個 plan sentinel token),套用於 `REVIEW_PASSED`/`REVIEW_BLOCKED`/`LAST_REVIEW` 三處掃描。**不採整行 `grep -v`**:transcript 為 JSONL(一行打包整則訊息),整行過濾會把同行的真 code/doc gate verdict 一併丟棄(false allow);substring strip 使 plan sentinel **既不滿足也不阻塞** code/doc gate,且保留同行其餘內容 | +| plan-review pending 未被 stop-guard 感知 | — | 讀 `plan_review.executed && !passed && !degraded && !skipped && status_reason != "needs-human"` → **warn-only** 提示「plan-review 進行中」,**不**併入 code/doc aggregate 決策(隔離)。`needs-human` 為終態(使用者仲裁中),排除於 pending 之外 | + +> stop-guard 對 plan-review 採 **warn-only**(不 strict-block):plan-review 是 analysis-only、skill-driven、ExitPlanMode 前置流程,非 precommit-style 強制 gate。 + +#### T5 — `/plan-review` skill 介面 + +| Arg | 行為 | +|-----|------| +| (無) | tier=standard(OQ-Sx-4 default);dual-dispatch(使用者決策:standard+deep 啟用 dual) | +| `--quick` | 單輪 Codex,無 loop,**不** dual | +| `--deep` | 委派 `/codex-brainstorm`(C2),dual(Nash engine 內含對抗雙視角) | +| `--skip-review` | 立即跳出,輸出 raw plan + `[PLAN_REVIEW_SKIPPED]`、`plan_review.skipped=true`、`status_reason=user-skip`(≤0 輪;**與 degrade 區分**) | +| `--verbose` | review trail round-by-round(預設僅 summary,OQ-4) | + +### 3.4 Core Logic + +#### 啟用光譜(OQ-3,使用者決策:opt-in → pilot → opt-out) + +| 階段 | 行為 | +|------|------| +| **v1(ship)** | **opt-in**:預設**不**啟用。使用者明示 `/plan-review`、或 `auto-loop-project.md` 設 `## Plan Review: enabled` 表達意圖。未 opt-in → plan mode 原行為(Claude → ExitPlanMode 直送) | +| **2 週 pilot 後** | 評估 false-negative / 使用者摩擦 → 升級 **opt-out**(預設開、`--skip-review` 可關)。升級為文件 + 預設值變更,無 schema 變動 | + +> **⚠️ v1 Acceptance Scope(A1 enforcement boundary)**:v1 的 review gate **僅在 `/plan-review` 實際被呼叫時生效**(review-gated only when invoked)。`## Plan Review: enabled` 在 v1 是 **advisory opt-in**——它表達「希望 Claude 在 ExitPlanMode 前 self-invoke `/plan-review`」的意圖,但 **v1 不提供「已啟用卻從未呼叫」的偵測或強制**。原因(已驗證):(a) stop-guard 的 plan-pending 檢查只在 `plan_review.executed` 已存在後才有意義,無法偵測「該執行卻從未執行」;(b) ExitPlanMode 為 harness 上游工具、v1 不攔截(A3 → v2,gated on OQ-Sx-1 harness probe),故已送入 ExitPlanMode 的 plan 無法事後 gate。因此 **v1 驗收明確界定為**:「`/plan-review` 被呼叫 → 跑收斂 loop → `✅ Plan Ready` 才呈現 plan」;**enabled-but-unexecuted 偵測 + pre-ExitPlanMode / tool-boundary 強制(FR-4 完整版)列為 v2**(見 R3、§7 OQ-Sx-1 / A3)。 + +#### Tier ladder + dual-review(OQ-7,使用者決策:standard + deep 啟用 dual) + +| Tier | Reviewer | Dual? | Loop | 委派 | +|------|----------|-------|------|------| +| quick | 單 Codex MCP | ❌ | 1-pass | — | +| **standard**(default) | Codex MCP + Secondary(Task agent,Explore) **並行** | ✅ | fix→re-review loop(`codex-reply --continue`) | — | +| deep | `/codex-brainstorm` | ✅(Nash 內含 attack/defense 雙視角) | brainstorm termination | `/codex-brainstorm` | + +> **決策來源(Decision Record DR-1)**:feasibility OQ-7 disposition 的 default proposal 為 **deep-only**;本 spec 採 **standard + deep** 係 **使用者於 `/tech-spec` 互動決策覆寫 feasibility default**(AskUserQuestion,2026-05-18)。此 spec 區塊**即為該決策的 durable record**(無獨立 request artifact;本 DR-1 註記讓未來審查者能區分「使用者決策」與「作者假設」)。Trade-off:標準 plan 命中率↑,但 standard tier 成本/延遲約翻倍(見 §4 R5)。**可逆性**:屬預設值層級、無 schema 變動;pilot 量測若不符 ROI,回退 feasibility deep-only default 僅需改本表預設,不影響已 ship 介面。 +> +> **⚠️ 已被後續變更取代(2026-07-26)**:standard tier 不再預設 dual。`auto-loop.md` § Review Dispatch 現在規定所有 review 預設單審(Codex),雙審只在明確傳入 `--dual` 時啟用——`/plan-review --dual` 即 plan 平面的對應開關。下方原文保留當時的推導脈絡,**不代表現行行為**。 +> +> ~~standard tier 的 dual 鏡射 `auto-loop.md` Dual Review Mode:Codex 為阻塞主審,Secondary 背景並行;late P0/P1 重開 loop。~~ 傳入 `--dual` 時,Secondary 仍用 Task agent(subagent_type: `Explore` 或 `strict-reviewer`),prompt 同樣遵守 `codex-invocation.md` 獨立研究原則。 + +#### Plan handover 與 Codex prompt framing(OQ-2 / OQ-Sx-5) + +- Claude 為 plan author,plan 文字本就在 working context(`1-requirements.md` Assumption)。 +- `/plan-review` 把 plan 文字作為 **"candidate artifact to attack"** 傳給 Codex,**絕不**寫成 "Claude 的結論,請確認"(`codex-invocation.md` Prohibited Pattern)。 +- **Secret redaction contract(NFR-8,依 [`scripts/security-redact.js`](../../../scripts/security-redact.js) **實測 API**)**。實測行為(勿信 source docstring,已驗證): + - `scanHighConfidence(text)` → 命中回傳 **`{name, fingerprint}`**、無命中回傳 **`null`**;**不 throw**(docstring 寫 throw 為誤導)。 + - `maskMediumConfidence(text)` → 僅遮罩 medium pattern,**不**遮罩 high-confidence(實測 `sk-…` 原樣穿透)。 + - `redact(text)` 預設 `abortOnHigh=true` → high 命中時 throw `AbortError`;`redact(text,{abortOnHigh:false})` 僅回傳遮罩字串、無 metadata、**無法分辨 high/medium**。 + - 送 reviewer 前的契約(採 truthy-return,主路徑,無例外控制流): + + ``` + const high = scanHighConfidence(planText); // {name,fingerprint} | null + if (high) { + // fail-closed:plan 不外送 reviewer + plan_review.degraded=true; status_reason=secret-detected; emit [PLAN_REVIEW_DEGRADED] + // plan 仍交付使用者(這是使用者自己的 plan),僅拒絕外送 reviewer + } else { + send(maskMediumConfidence(planText)) // medium → [REDACTED] 後才送 + } + ``` + + 等價替代:`try { send(redact(planText)) } catch (AbortError) { failClosed(...) }`(靠 `redact` 預設 throw)。 + - 高敏 pattern 範圍:PEM、`AKIA…`、`sk-[A-Za-z0-9_-]{20,}`、`ghp_[A-Za-z0-9]{36}`、`xox[aboprs]-…`、`AIza…`。 + - 反例(禁用):以 `redact(...,{abortOnHigh:false})` 回傳值判 high(high 已遮罩成 `[REDACTED]`、與 medium 不可區分)。 +- prompt template 強制含 §「You must independently research the project」+ 具體 git/grep 指令;plan 文字標註為待攻擊產物。 + +#### Convergence(reuse rule-level decision table) + +採 [`rules/auto-loop.md`](../../../rules/auto-loop.md) Exit Conditions 決策表,但作用於 `plan_review.iteration_history`: + +| # | Condition | Action | +|---|-----------|--------| +| 1 | `current_round >= max_rounds`(default 5) | `⚠️ Plan Needs Human` + 殘餘 findings(不靜默通過,Signal 7) | +| 2 | `findings_by_round[n].total == 0` | `✅ Plan Ready` → 附 trail summary → ExitPlanMode | +| 3 | plateau(fingerprint overlap ≥50% 連 3 輪) | **V1 不可達**(OQ-9 → V2:需 hook fingerprint 儲存);v1 僅靠 row 1 hard cap | +| 5 | `total < prev_total` | Continue loop | + +#### Bypass / Escape(FR-5 / NFR-5 / UC-2) + +``` +使用者明示「skip review」/「直接 show plan」/ --skip-review + → 偵測點:skill 入口 + 每輪 re-review 前 + → ≤1 輪 review 內跳出(NFR-5) + → 已啟動者輸出部分 trail + → plan_review.skipped=true; status_reason=user-skip; emit [PLAN_REVIEW_SKIPPED] + (使用者意圖 — 非 reliability 失效,與 degrade 分離) + → raw / 當前 plan → ExitPlanMode +``` + +#### Graceful degradation(NFR-3 / NFR-8 / Signal 6) + +兩種 degrade 來源,皆 `plan_review.degraded=true` + emit `[PLAN_REVIEW_DEGRADED]`,但 `status_reason` 區分: + +``` +(a) reviewer 不可達:Codex MCP 連線錯誤 / 401 / timeout + → 不 retry-storm(最多 1 retry) + → status_reason=reviewer-unavailable +(b) plan 含 high-confidence secret(redact contract fail-closed) + → 不外送 reviewer + → status_reason=secret-detected +共同:plan 當輪交付使用者 + output 含可 grep 的 [PLAN_REVIEW_DEGRADED] 標記;不阻塞 plan mode +``` + +## 4. Risks and Dependencies + +| # | Risk | Likelihood | Impact | Mitigation | +|---|------|-----------|--------|------------| +| R1 | **OQ-Sx-2**:plan mode 內 MCP / Skill 呼叫不可用 → A1 根本不可實作 | Medium | **Critical** | **Hard precondition**:實作前先跑 smoke test(見 §7);若不可用 → ⛔ 架構回頭(A1 不成立,需重評 A2/A3 或 harness 協作) | +| R2 | **OQ-Sx-3**:stop-guard 未隔離 → `⛔ Plan Blocked` 觸發 `⛔.*Block`(stop-guard `REVIEW_BLOCKED`/`LAST_REVIEW` grep)誤判 code/doc FAIL,阻塞無關 Stop(NFR-7 破功) | Medium | High | **Hard precondition**:T4 隔離擴充(as-built:`_strip_plan_sentinels()` **substring strip**——transcript 為 JSONL 一行打包整則訊息,整行 `grep -v` 會把同行真 code/doc verdict 一併丟棄造成 false allow)+ 回歸測試斷言互不覆寫 | +| R3 | A1 為 best-effort:即使使用者已 `## Plan Review: enabled`,Claude 忘記 self-invoke `/plan-review` → 繞過 review(**enabled-but-unexecuted gap**)。v1 無偵測路徑:stop-guard 的 plan-pending 檢查需 `plan_review.executed` 已存在才有意義、ExitPlanMode 不被攔截(FR-4 tool-boundary 層 v1 未保證) | Medium | Medium | **v1 明確界定為「review-gated only when /plan-review invoked」**(見 §3.4 v1 Acceptance Scope);`auto-loop` + Stop Hook 提示 raise compliance(advisory);enabled-but-unexecuted 偵測 + tool-boundary 強制留待 v2 OQ-Sx-1 harness probe(A3) | +| R4 | schema migration 破壞既有 state | Low | High | 純加法 jq 注入;atomic write + lock;回歸測試斷言除 `schema_version` 外語意等價 | +| R5 | dual-review 在 standard tier 使短 plan 成本/延遲翻倍 | Medium | Low | Secondary 背景並行不阻塞主 gate;pilot 量測;不符 ROI 可回退 deep-only(feasibility default) | +| R6 | Codex prompt 違反 `codex-invocation.md`(餵養結論) | Low | Medium | 強制使用 `references/codex-prompt-plan.md` template;plan 標註為待攻擊產物;review checklist 把關 | +| R7 | secret 洩漏到 reviewer context(NFR-8) | Low | High | redact contract(§3.4):medium→mask、high→fail-closed 不外送;測試用**regex-valid** dummy(`sk-` + ≥20 字元如 `sk-abcdefghijklmnopqrstUVWX`、`ghp_` + 恰 36 字元、PEM header),斷言 high-confidence 走 `[PLAN_REVIEW_DEGRADED]`、`status_reason=secret-detected` 且 payload 不外送 | +| R8 | plateau 偵測 v1 缺位 → 同類 finding 反覆但不升 Need Human | Low | Medium | v1 靠 max_rounds=5 hard cap 兜底;OQ-9 明列 V2;trail summary 讓使用者可觀測重複 | + +**Dependencies**: + +| Dependency | Type | Status | +|------------|------|--------| +| Codex MCP 在 plan mode 可呼叫 | External (harness) | **未驗證 — OQ-Sx-2 hard precondition** | +| Skill 在 plan mode 可呼叫 | External (harness) | **未驗證 — OQ-Sx-2 hard precondition** | +| `scripts/security-redact.js` 存在且涵蓋高敏 pattern | Internal | 需確認介面(W1 預檢) | +| `/codex-brainstorm` skill 穩定 | Internal | 已 ship(deep tier 委派) | + +## 5. Work Breakdown + +> **Gate**:W0 hard preconditions **必須全綠**才進 W1+。任一不過 → `⚠️ Plan Needs Human`,停工回報(不繞過)。 + +| ID | Task | Depends | Size | Test mapping | +|----|------|---------|------|--------------| +| **W0** | **Hard precondition spikes** | — | S | — | +| W0.1 | OQ-Sx-2 smoke test:plan mode 內呼叫 `mcp__codex__codex` + Skill 是否回傳;記錄結論 | — | S | spike report(非自動化測試) | +| W0.2 | OQ-Sx-3 stop-guard 隔離設計定稿(regex 過濾 + warn-only 策略確認) | — | S | 設計 note | +| **W1** | **State 基建** | W0 | M | | +| W1.1 | `post-tool-review-state.sh`:`init_state_file()` 加 `plan_review`、`schema_version` 2→3 | W0 | S | `test/hooks/post-tool-review-state.test.js`(新 fixtures) | +| W1.2 | schema v2→v3 additive migration 分支 + atomic/lock | W1.1 | M | 同上(migration:除 `schema_version` 外語意等價斷言) | +| W1.3 | MCP routing Priority 1.5 plan branch(`grep -F` literal for `[PLAN_REVIEW_*]`)+ `update_plan_state()` | W1.1 | M | 同上(plan routing + literal-match + collision 斷言) | +| **W2** | **Gate emission + 隔離** | W1 | M | | +| W2.1 | `scripts/emit-plan-gate.sh`(6 值含 SKIPPED + namespace 前綴)**+ `post-tool-review-state.sh` `emit-plan-gate` parse 分支**(鏡射 `emit-review-gate` parse 分支) | W1.3 | M | `test/scripts/emit-plan-gate.test.js`(emitter)+ `test/hooks/post-tool-review-state.test.js`(hook parse 分支) | +| W2.2 | `stop-guard.sh` 隔離擴充(T4) | W0.2,W1 | M | `test/hooks/stop-guard.test.js`(plan isolation fixtures) | +| **W3** | **`/plan-review` skill** | W1,W2 | L | | +| W3.1 | `skills/plan-review/SKILL.md`(tier ladder / loop / bypass / degrade) | W1,W2 | L | `test/skills/plan-review.test.js`(new) | +| W3.2 | `references/codex-prompt-plan.md` + `review-loop-plan.md`(OQ-Sx-5 framing) | W3.1 | M | skill test 引用斷言 | +| W3.3 | secret redaction 串接(NFR-8,呼叫 `security-redact.js`) | W3.1 | S | skill test(**as-built:靜態斷言** SKILL.md 含 redaction contract + fail-closed 流程;end-to-end dummy-payload 行為驗證留 pilot 手動) | +| W3.4 | standard tier dual-dispatch(Codex + Task secondary 並行) | W3.1 | M | skill test(**as-built:靜態斷言** dual-dispatch 段落存在;skill 為 model-driven markdown,行為驗證留 pilot 手動) | +| W3.5 | deep tier 委派 `/codex-brainstorm` | W3.1 | S | skill test(**as-built:靜態斷言** deep tier 委派段落存在;行為驗證留 pilot 手動) | +| **W4** | **Config + rules + 文件** | W3 | M | | +| W4.1 | `auto-loop-project.md` 加 `## Plan Review Max Rounds`(default 5)+ `## Plan Review: enabled` opt-in 開關 | W1 | S | Max Rounds:hook 解析測試(`_read_project_plan_max_rounds`,含 range/邊界);**`## Plan Review: enabled` 為 model-read advisory(v1 無 hook 解析,無自動化測試)** | +| W4.2 | `rules/auto-loop.md` Standard Gate Sentinels 加 plan namespace 列 | W3 | S | doc review | +| W4.3 | `/plan-review` skill 登錄(`.claude/skills -> ../skills` symlink 自動可見 + `docs/skill-catalog.yml`)+ `CLAUDE.md` Command Quick Reference 加列(v3 起無 thin command entry) | W3 | S | skills-schema 測試 + symlink parity 斷言 | +| W4.4 | request ticket(`/create-request`)追蹤 AC 進度 | W3 | S | — | + +預估:W0 ~1 day(gating);W1-W4 ~6-8 person-days(feasibility §6 A1+B1+C2 估值範圍內)。 + +## 6. Testing Strategy + +| Layer | Scope | Cases (key) | +|-------|-------|-------------| +| **Unit** | `emit-plan-gate.sh`(emitter) | 6 合法值(含 SKIPPED)→ 正確 `PLAN_REVIEW_GATE=`;非法值 exit 1;空參數 exit 1 | +| **Unit** | **emit-plan-gate hook parse 分支** | Bash 命令含 `emit-plan-gate` + stdout `PLAN_REVIEW_GATE=` **全 6 值**(`PENDING`/`READY`/`BLOCKED`/`DEGRADED`/`SKIPPED`/`NEEDS_HUMAN`)→ `update_plan_state()` 各依 §3.3 T3 語意表更新 `plan_review.*`(**hook 端**,非僅 script stdout) | +| **Unit** | schema migration | v2→v3 `. +` 合併注入 `plan_review`;v3 no-op;migration 後**全部 v2 頂層欄位**(`session_id`/`updated_at`/`review_mode`/`has_code_change`/`has_doc_change`/`code_review`/`doc_review`/**`precommit`**/`aggregate_gate`/root `iteration_history`)語意等價,**`schema_version` 單獨斷言 2→3**(NFR-7 Signal 4) | +| **Unit** | MCP routing | `## Plan Review`+`✅ Plan Ready` → pass;`+⛔ Plan Blocked` → fail;`+[PLAN_REVIEW_DEGRADED]`(**`grep -F` literal**)→ degraded;`+[PLAN_REVIEW_SKIPPED]` → skipped;**`## Plan Review`+`⚠️ Plan Needs Human`(無 token)不得標 degraded**(literal-match regression);實測 `printf '✅ Plan Ready' \| grep -qE '✅ Ready'` = SAFE 斷言;doc/code branch 不回歸 | +| **Unit** | stop-guard 隔離 | **主**:`⛔ Plan Blocked` 不觸發 `REVIEW_BLOCKED`(`⛔.*Block` regex)→ 不誤判 code/doc FAIL;`✅ Plan Ready` 不滿足 `REVIEW_PASSED`;plan pending → warn-only 不併入 aggregate | +| **Unit** | config 解析 | `## Plan Review Max Rounds` 覆寫 default 5(含 range 3-50 inclusive 邊界 + 超界 fallback + migration path 覆寫);缺區塊 → fallback 5;**`## Plan Review: enabled` 為 model-read advisory,v1 無 hook 解析 → 無自動化測試(doc review 覆蓋)** | +| **Integration**(v1 deferred → pilot 手動) | skill loop(mock Codex) | P0/P1 → revise → re-review → `✅ Plan Ready` 收斂;max_rounds=5 未收斂 → `⚠️ Plan Needs Human`+殘餘 findings(Signal 2/7)。**As-built:skill 為 model-driven markdown,無法以 node:test 驅動 loop;v1 以 `test/skills/plan-review.test.js` 靜態結構斷言 + pilot 手動驗證取代** | +| **Integration**(v1 部分自動化) | bypass vs degrade(分離斷言) | `--skip-review` ≤1 輪跳出 → `[PLAN_REVIEW_SKIPPED]`+`skipped=true`(Signal 3);mock reviewer offline/401/timeout → `[PLAN_REVIEW_DEGRADED]`+`status_reason=reviewer-unavailable` 不卡死(Signal 6);兩者 sentinel/flag 不混用。**As-built:sentinel/state 語意(含不混用)已由 emit-plan-gate + hook routing unit tests 覆蓋;end-to-end skill 行為留 pilot** | +| **Integration**(v1 deferred → pilot 手動) | secret redaction | medium dummy(`password=hunter2dummy`)→ `maskMediumConfidence` `[REDACTED]` 後照送;high dummy(**regex-valid**:`sk-` + ≥20 字元、`ghp_` + 恰 36 字元、PEM header)→ `scanHighConfidence` truthy → fail-closed:plan **不外送** reviewer + `[PLAN_REVIEW_DEGRADED]`、`status_reason=secret-detected`,payload grep 無高敏 pattern(Signal 8/NFR-8)。**As-built:redaction contract 由 skill 結構測試靜態斷言;`security-redact.js` 自身既有測試;end-to-end fail-closed 行為留 pilot** | +| **Integration**(v1 已於 unit 層覆蓋) | 並行隔離 | 同 session 觸發 code-review + plan-review → 兩者 state 互不覆寫(Signal 4/NFR-7)。**As-built:雙向隔離由 `post-tool-review-state.test.js` unit fixtures 自動化覆蓋** | + +Conventions 遵 [`rules/testing.md`](../../../rules/testing.md):AAA、`assert/strict`、≤7 assertions/case、realistic data。Evidence 對應(Acceptance Signals 定義於 [1-requirements.md §8](./1-requirements.md)):state / sentinel / gate / 隔離層 Signal 由 unit + 靜態結構自動化證據覆蓋(Evidence Model priority 1);上表標記「v1 deferred → pilot 手動」的 end-to-end skill 行為面向,v1 以結構斷言 + pilot 手動驗證為證據(priority 2/3,pilot 期間補齊)。 + +**Doc link-check**:本 lifecycle doc 位於 depth 3(`docs/features/plan-review-loop/`),跨 repo-root 引用用 `../../../`。CI doc-link 檢查涵蓋本檔;已 spot-check `../../../rules/auto-loop.md`、`../../../hooks/post-tool-review-state.sh`、`../dual-reviewer/2-tech-spec.md` 均可解析。 + +## 7. Open Questions + +### 7.1 Hard preconditions(gating W1+,必須先解) + +| OQ | Question | Resolution path | +|----|----------|-----------------| +| OQ-Sx-2 | plan mode 內 MCP / Skill 是否可呼叫? | **W0.1 smoke test**。不可用 → ⛔ A1 不成立,停工升 `⚠️ Plan Needs Human` | +| OQ-Sx-3 | `stop-guard.sh` 如何隔離 plan sentinel? | **W0.2 設計定稿** → T4 實作 + 回歸斷言 | + +### 7.2 已於本 spec 拍板(feasibility tech-spec disposition + 使用者決策) + +| OQ | 決策 | 來源 | +|----|------|------| +| OQ-3 啟用光譜 | opt-in v1 → 2 週 pilot → opt-out(= feasibility default,使用者確認) | **使用者決策**(`/tech-spec` AskUserQuestion,2026-05-18) | +| OQ-7 dual-review | standard + deep 啟用 dual;quick 單 Codex(**覆寫** feasibility deep-only default) | **使用者決策**(`/tech-spec` AskUserQuestion,2026-05-18) | +| OQ-4 trail 訊噪比 | 預設 summary(rounds/findings/modified-sections);`--verbose` round-by-round | feasibility default | +| OQ-6 state scope | per-plan reset;`history[]` 保留最近 5 筆 | feasibility default | +| OQ-10 預算配置點 | `auto-loop-project.md` 新增 `## Plan Review Max Rounds`,default 5 | feasibility default | +| OQ-Sx-4 tier 自動偵測 | default `standard`;`--quick`/`--deep` 顯式升降 | feasibility default | +| OQ-Sx-5 Codex framing | plan 作為 "candidate artifact to attack",遵 `codex-invocation.md` | feasibility default | + +### 7.3 Deferred to V2(不阻塞 v1) + +| OQ | Question | 條件 | +|----|----------|------| +| OQ-9 | plateau / fingerprint 偵測 | 需 hook 端 fingerprint 儲存;v1 僅 max_rounds hard cap | +| OQ-Sx-1 | PreToolUse 攔截 `ExitPlanMode`(A1→A3 升級) | harness probe;成立則 v2 升 A3+B1+C2(state/tier 不變,僅加 hook tripwire) | + +### 7.4 Pilot-revisit(v1 ship 後量測再定) + +| 項目 | 量測訊號 | +|------|---------| +| OQ-3 opt-in → opt-out 升級時機 | 2 週 false-negative rate + 使用者摩擦回饋 | +| OQ-7 standard dual ROI | dual vs single 在短 plan 的邊際命中率;不符 ROI → 回退 deep-only | + +## 8. References + +- Canonical: [`./1-requirements.md`](./1-requirements.md), [`./0-feasibility-study.md`](./0-feasibility-study.md) +- Sibling lifecycle: [`docs/features/dual-reviewer/2-tech-spec.md`](../dual-reviewer/2-tech-spec.md), [`docs/features/codex-review-spec/1-requirements.md`](../codex-review-spec/1-requirements.md) +- Reused skills: [`skills/codex-brainstorm/SKILL.md`](../../../skills/codex-brainstorm/SKILL.md), [`skills/doc-review/SKILL.md`](../../../skills/doc-review/SKILL.md), [`skills/codex-code-review/SKILL.md`](../../../skills/codex-code-review/SKILL.md) +- Loop primitives: [`hooks/post-tool-review-state.sh`](../../../hooks/post-tool-review-state.sh), [`hooks/stop-guard.sh`](../../../hooks/stop-guard.sh), [`scripts/emit-review-gate.sh`](../../../scripts/emit-review-gate.sh) +- Rules: [`rules/auto-loop.md`](../../../rules/auto-loop.md), [`rules/codex-invocation.md`](../../../rules/codex-invocation.md), [`rules/auto-loop-project.md`](../../../rules/auto-loop-project.md), [`rules/docs-numbering.md`](../../../rules/docs-numbering.md), [`rules/testing.md`](../../../rules/testing.md), [`rules/security.md`](../../../rules/security.md) +- Codex feasibility debate threadId: `019e298f-3645-7801-b6ff-b60b8d1235e6` diff --git a/docs/features/plan-review-loop/requests/2026-06-12-plan-review-v1.md b/docs/features/plan-review-loop/requests/2026-06-12-plan-review-v1.md new file mode 100644 index 0000000..ee57dd3 --- /dev/null +++ b/docs/features/plan-review-loop/requests/2026-06-12-plan-review-v1.md @@ -0,0 +1,73 @@ +# Plan Review Loop — V1 A1+B1+C2 全量實作 + +> **Doc class**: Request ticket (date-prefixed non-lifecycle — per `@rules/docs-numbering.md`). Per-task work breakdown unit for progress tracking. **Not** a feature-level requirements doc — for that see `../1-requirements.md`. +> **Created**: 2026-06-12 +> **Status**: Done +> **Priority**: P1 +> **Tech Spec**: [2-tech-spec.md](../2-tech-spec.md) +> **Requirements**: [1-requirements.md](../1-requirements.md) + +## Background + +實作 plan-review-loop v1(A1+B1+C2):skill 驅動的 `/plan-review` 對抗式計畫審查迴圈,在 plan mode 中於 `ExitPlanMode` 前讓 Codex 攻擊 plan 草稿並迭代收斂。State 以 namespaced `plan_review.*` subtree 存於 `.claude_review_state.json`(schema v2→v3 additive migration),與 code/doc/aggregate 平面完全隔離(NFR-7);deep tier 委派 `/codex-brainstorm`。工作分解依 tech-spec §5 W0–W4。 + +## Requirements + +- W1 state 基建 — `plan_review` 欄位 init + schema v2→v3 migration + `_read_project_plan_max_rounds`(`## Plan Review Max Rounds`,default 5) +- W2 gate 通道 — `scripts/emit-plan-gate.sh`(6 gates + tier/reason 參數)+ hook parse 分支 + MCP Priority 1.5 routing(`## Plan Review` discriminator)+ stop-guard plan sentinel 隔離(transcript grep filter + warn-only advisory) +- W3 skill — `skills/plan-review/SKILL.md` + `references/codex-prompt-plan.md`(candidate-artifact framing + 獨立研究指令)+ `references/review-loop-plan.md`(codex-reply VERIFY-not-CONFIRM) +- W4 設定與文件 — `rules/auto-loop.md` plan sentinel rows、`rules/auto-loop-project.md` 兩個 opt-in 區段、3 份 CLAUDE quick-ref row、`docs/skill-catalog.yml` 登錄 + +## Scope + +| Scope | Description | +|-------|-------------| +| In | `plan_review` state subtree(init/migration/update/iteration);`emit-plan-gate.sh` + hook parse;MCP plan routing;stop-guard 隔離(warn-only);`/plan-review` skill + 2 references;rules/CLAUDE/catalog 登錄;對應 unit tests | +| Out | Hook 強制 enabled-but-unexecuted 偵測(v2);plateau/fingerprint 收斂偵測(OQ-9, v2);ExitPlanMode hook 攔截(A2,已否決);aggregate_gate 整合 plan 平面 | + +## Related Files + +| File | Action | Description | +|------|--------|-------------| +| `scripts/emit-plan-gate.sh` | New | PLAN_REVIEW_GATE sentinel emitter(PENDING tier / DEGRADED reason 驗證) | +| `hooks/post-tool-review-state.sh` | Modify | plan_review init + v2→v3 migration + `update_plan_state` + `_update_plan_iteration` + emit-plan-gate parse + MCP Priority 1.5 routing | +| `hooks/stop-guard.sh` | Modify | transcript grep plan sentinel 過濾(×3 處)+ plan pending warn-only advisory | +| `skills/plan-review/SKILL.md` | New | Skill 主文件(tier ladder / redaction contract / convergence / degradation) | +| `skills/plan-review/references/codex-prompt-plan.md` | New | Codex 首輪 prompt 模板 | +| `skills/plan-review/references/review-loop-plan.md` | New | codex-reply 複審模板 | +| `.claude/skills/plan-review/**` | New | Install-visible path(經 `.claude/skills -> ../skills` symlink 可見,非獨立複本) | +| `rules/auto-loop.md` | Modify | Standard Gate Sentinels 表 + plan namespace isolation note | +| `rules/auto-loop-project.md` | Modify | `## Plan Review` + `## Plan Review Max Rounds` opt-in 區段 | +| `CLAUDE.template.md` / `CLAUDE.md` / `.claude/CLAUDE.md` | Modify | `/plan-review` quick-ref row | +| `docs/skill-catalog.yml` | Modify | `/plan-review` review category 登錄 | +| `test/scripts/emit-plan-gate.test.js` | New | 6 gates + tier/reason 驗證 + 拒絕非法參數 | +| `test/hooks/post-tool-review-state.test.js` | Modify | migration v2→v3 / update_plan_state 各 gate / plan iteration / MCP plan routing / 隔離雙向 | +| `test/hooks/stop-guard.test.js` | Modify | plan sentinel 不觸發 code 評估 + plan pending warn-only | +| `test/skills/plan-review.test.js` | New | SKILL.md 結構 + sentinel 約束 + references 存在 | + +## Acceptance Criteria + +- [x] `emit-plan-gate.sh` 對 6 個 gate 輸出 `PLAN_REVIEW_GATE=`;PENDING 接受 tier、DEGRADED 接受 reason、其餘拒絕額外參數;非法值 exit 1(FR-6/T3) +- [x] State schema v2→v3 additive migration:v2 檔案升級後保留既有 code/doc 欄位且新增完整 `plan_review` default subtree;v3 重跑 no-op;ver>3 / 非數字 warn skip(B1/T3) +- [x] `update_plan_state` 六種 gate 語意符合 T3(PENDING reset cycle、terminal gates 寫 history FIFO last-5、DEGRADED/SKIPPED 帶 status_reason),且不觸碰 `code_review`/`doc_review`/`aggregate_gate`/root `iteration_history`(NFR-7 方向一) +- [x] MCP Priority 1.5 routing:`## Plan Review` + `✅ Plan Ready`/`⛔ Plan Blocked`/`[PLAN_REVIEW_DEGRADED]`/`[PLAN_REVIEW_SKIPPED]` 正確更新 plan 平面;`⚠️ Plan Needs Human`(無 token)不誤觸任何分支;`✅ Plan Ready` 不誤觸 code review `✅ Ready` 分支(collision regression) +- [x] stop-guard:transcript 含 `⛔ Plan Blocked` 不產生 code/doc BLOCKED 誤判;plan pending(executed=true, passed/degraded/skipped 皆 false)僅 stderr warn、不加入 MISSING/blocking(NFR-7 方向二 + T4) +- [x] `## Plan Review Max Rounds` 設定可覆寫 plan max_rounds(default 5, range 3-50),且與 `## Max Rounds`(code/doc, default 10)互不干擾 +- [x] `/plan-review` skill 結構通過 skills-schema 驗證;references 存在;prompt 模板含獨立研究指令 + plan sentinel 約束 + 禁用 bare `✅ Ready`/`✅ Mergeable`/`## Gate:`(rules/codex-invocation.md 合規) +- [x] Pass /codex-review-fast + /precommit-fast + +## Progress + +| Phase | Status | Note | +|-------|--------|------| +| Analysis | Done | Tech spec §3–§6 定案;W0.1 spike(plan mode 工具可用性)via claude-code-guide agent 確認 A1 可行 | +| Development | Done | W1/W2/W3/W4 完成。Review 迭代追加:token-first + BLOCKED-first 路由、`update_plan_verdict`(MCP no-history,history 由 emit-plan-gate Bash 路徑獨佔)、`update_plan_state` history_mode 參數、migration fail-closed(非數字/>3 全 writer skip)、`status_reason=needs-human` 終態、`_update_plan_iteration` init-on-missing、stop-guard `_strip_plan_sentinels` substring strip(取代整行 grep -v) | +| Testing | Done | 4 個測試檔(emit-plan-gate 11、hook plan 區段、stop-guard plan T4/pending、skill 結構 17+);AC trace gap closure(migration subtree 形狀、max rounds 邊界 3/50、override 流經 migration path);stub/real divergence guard。全套 1495 pass / 0 fail | +| Acceptance | Done | Codex dual review ✅(多輪收斂)+ Secondary ×3 READY;/codex-test-review --ac-trace:✅ Adequate(gaps 清空);/precommit-fast ✅ All Pass ×2 | + +## References + +- Tech Spec: [../2-tech-spec.md](../2-tech-spec.md) §3.3 T1–T4, §5 W0–W4, §6 測試映射 +- Requirements: [../1-requirements.md](../1-requirements.md) FR-6/FR-9/FR-14, NFR-3/NFR-4/NFR-5/NFR-7/NFR-8 +- Feasibility: [../0-feasibility-study.md](../0-feasibility-study.md) A1+B1+C2 決策 +- Test evidence: [`test/scripts/emit-plan-gate.test.js`](../../../../test/scripts/emit-plan-gate.test.js)、[`test/hooks/post-tool-review-state.test.js`](../../../../test/hooks/post-tool-review-state.test.js)(plan 區段)、[`test/hooks/stop-guard.test.js`](../../../../test/hooks/stop-guard.test.js)(plan T4/pending)、[`test/skills/plan-review.test.js`](../../../../test/skills/plan-review.test.js) diff --git a/docs/features/precommit-tiering/2-tech-spec.md b/docs/features/precommit-tiering/2-tech-spec.md index 5877ea1..2636a08 100644 --- a/docs/features/precommit-tiering/2-tech-spec.md +++ b/docs/features/precommit-tiering/2-tech-spec.md @@ -1,10 +1,23 @@ # Precommit Test Tiering Technical Spec +> **本文件是 2026-03-06 原始提案 + 後續決策修訂的混合體。閱讀時請先分清兩者:** +> +> | 區段 | 狀態 | +> |------|------| +> | §1 Goals、§2.1 Test Distribution、§3.1 架構圖、§4 Risks | **2026-03-06 提案快照**(歷史)——其中 auto-loop 路由 `/precommit-fast` 的部分已被推翻 | +> | §2.2 Execution Paths、§2.4 Auto-Loop Routing、§3.2 Test Tier Hierarchy、§3.3 Runner Test Selection、§3.4 Fallback Behavior、§3.5 Package.json Scripts、§5 Work Items、§6 Testing Strategy、§7 Q4 | **現況**(as-built) | +> +> 上表原本把 Risks 標為 §6、把 preference chain 標為 §3.2,並漏掉 §3.3-3.5。實際上 Risks 是 **§4**,preference chain 在 **§3.3**(§3.2 是 Test Tier Hierarchy),而 §6 是 **Testing Strategy**——它收錄 `CLAUDE.md` 所連結的 as-built `precommit.mode` 語意邊界,把它歸進「提案快照」欄剛好顛倒。 +> +> **最新定論**:auto-loop 迭代路由是 **`/precommit`(full)**。`31510e6` 以 shift-left 為由反轉了 +> 原提案的 fast route。分層本身(`test:fast` / `test:ci`)仍然成立並已實作,被推翻的只有「auto-loop +> 走哪一個 variant」這一項。 + ## 1. Requirement Summary - **Problem**: Auto-loop 每次 fix iteration 都執行完整測試套件(369 tests, ~8 min),嚴重拖慢開發速度。Integration tests 佔 95% 時間但在迭代修復迴圈中提供的邊際價值低。 - **Goals**: - - 將 auto-loop 迭代路由從 `/precommit` 改為 `/precommit-fast`,使用 fast test tier + - ~~將 auto-loop 迭代路由從 `/precommit` 改為 `/precommit-fast`,使用 fast test tier~~ —— **已於 `31510e6` 反轉回 full**,理由見 §2.4;本行保留為提案原文 - 建立 `test:fast`(unit + schema)和 `test:ci`(全套)分層 - 確保 runner 和 command docs 同步更新,tiering 在兩條執行路徑都生效 - 通用專案(無 `test:fast` script)fast mode graceful degradation 到現有行為;full mode intentionally 改為偏好 `test` 以獲得更完整覆蓋 @@ -17,9 +30,13 @@ ## 2. Existing Code Analysis -### 2.1 Current Test Distribution +### 2.1 Test Distribution(2026-03-06 快照,已過時) + +下表是提案當時的分佈,保留以說明分層的動機(integration 佔絕大多數時間)。**檔案數與 glob 皆已不符現況**: +`test/commands/` 已隨 v3 的 commands/ 移除而不存在,`test:schema` 現在指向 `test/skills/`, +`test` / `test:ci` 也已改用 `find`(`/bin/sh` 不展開 `**`,`test/**/*.test.js` 會漏掉巢狀目錄)。 -| Script | Glob | Files | Approx Time | +| Script | Glob(2026-03-06) | Files | Approx Time | |--------|------|-------|-------------| | `test:unit` | `test/scripts/lib/*.test.js` | 1 | <1s | | `test:schema` | `test/commands/*.test.js` | 3 | <1s | @@ -27,6 +44,9 @@ | `test:hooks` | `test/hooks/*.test.js` | 4 | ~20s | | `test` (all) | `test/**/*.test.js` | ~21 | ~8 min | +現況(2026-07-26):全樹 119 個 test 檔——`test/skills/` 55、`test/scripts/` 38(另 `test/scripts/lib/` 11)、 +`test/hooks/` 15。以 `package.json` 為準,勿引用上表數字。 + ### 2.2 Execution Paths Precommit 有兩條執行路徑: @@ -37,34 +57,56 @@ Step 1: Glob check .claude/scripts/precommit-runner.js └─ Not found → Command markdown fallback ← 通用專案走這條 ``` -- **Runner** (`scripts/precommit-runner.js:180-190`): 硬編碼 `test:unit → test` fallback -- **Command docs** (`commands/precommit-fast.md:14-18`, `commands/precommit.md:20-26`): YAML intent 定義 preferred/alternatives +- **Runner** (`scripts/precommit-runner.js`, `main()` 內組 steps 時的 `testPreference` 常數): **已實作** by-mode preference chain(W2 完成)——`fast` = `test:fast → test:unit → test`,`full` = `test:ci → test → test:fast → test:unit`,與 §3 設計逐字相符。選中非 `test:unit` 時印出 `> test: using "