@mmerterden/multi-agent-pipeline 16.19.0 → 16.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +1 -1
- package/README.tr.md +1 -1
- package/docs/features.md +1 -0
- package/install/_codex-agents.mjs +2 -2
- package/install/_dev-only-files.mjs +1 -0
- package/install/templates/claude-hooks.json +12 -1
- package/package.json +4 -3
- package/pipeline/agents/android-architect.md +1 -0
- package/pipeline/agents/backend-architect.md +1 -0
- package/pipeline/agents/bulk-reader.md +57 -0
- package/pipeline/agents/code-reviewer.md +1 -0
- package/pipeline/agents/dev-critic.md +2 -1
- package/pipeline/agents/explorer.md +1 -0
- package/pipeline/agents/ios-architect.md +1 -0
- package/pipeline/agents/security-auditor.md +1 -0
- package/pipeline/agents/task-clarifier.md +1 -0
- package/pipeline/commands/multi-agent/manual-test/SKILL.md +5 -1
- package/pipeline/commands/multi-agent/resume/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/setup/SKILL.md +7 -5
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +22 -0
- package/pipeline/commands/sim-test.md +10 -5
- package/pipeline/multi-agent-refs/channels/pr.md +22 -4
- package/pipeline/multi-agent-refs/features/autopilot-circuit-breaker.md +15 -2
- package/pipeline/multi-agent-refs/features/review-delta.md +89 -0
- package/pipeline/multi-agent-refs/features/scope-check.md +41 -0
- package/pipeline/multi-agent-refs/features/verify-by-test.md +6 -5
- package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
- package/pipeline/multi-agent-refs/payload-contracts.md +1 -1
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +17 -2
- package/pipeline/multi-agent-refs/phases/phase-4-review.md +46 -8
- package/pipeline/multi-agent-refs/phases/phase-5-test.md +10 -0
- package/pipeline/multi-agent-refs/phases/phase-6-commit.md +2 -0
- package/pipeline/multi-agent-refs/phases/phase-7-report.md +4 -2
- package/pipeline/multi-agent-refs/picker-contract.md +1 -1
- package/pipeline/multi-agent-refs/rules.md +2 -2
- package/pipeline/schemas/agent-state.schema.json +129 -0
- package/pipeline/schemas/bulk-read-output.schema.json +52 -0
- package/pipeline/schemas/dev-critic-output.schema.json +5 -0
- package/pipeline/schemas/prefs.schema.json +121 -19
- package/pipeline/schemas/reviewer-output.schema.json +7 -2
- package/pipeline/schemas/scope-check.schema.json +55 -0
- package/pipeline/schemas/token-budget.json +3 -3
- package/pipeline/schemas/triage-output.schema.json +12 -2
- package/pipeline/scripts/README.md +3 -2
- package/pipeline/scripts/_fingerprint.mjs +173 -0
- package/pipeline/scripts/bulk-read.sh +277 -0
- package/pipeline/scripts/check-read-size.py +335 -0
- package/pipeline/scripts/check-read-size.sh +86 -0
- package/pipeline/scripts/evidence-gate.mjs +73 -5
- package/pipeline/scripts/finding-fingerprint.mjs +101 -0
- package/pipeline/scripts/pre-commit-check.sh +1 -0
- package/pipeline/scripts/review-delta.mjs +217 -0
- package/pipeline/scripts/run-metrics.mjs +20 -0
- package/pipeline/scripts/scope-check-gate.mjs +90 -0
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +11 -4
- package/pipeline/scripts/uninstall.mjs +1 -0
- package/pipeline/scripts/validate-reviewer.mjs +6 -0
- package/pipeline/scripts/validate-triage.mjs +20 -0
- package/pipeline/skills/shared/core/multi-agent-store-ready/SKILL.md +5 -0
- package/pipeline/skills/shared/external/backlog/SKILL.md +10 -6
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Feature: Cross-round review delta (Phase 4 Steps 2.1, 2.2, 3.8)
|
|
2
|
+
|
|
3
|
+
**Pattern**: reviewers re-read the whole diff every round with no memory of the round before, so they rediscover last round's findings in new words and nothing can tell "still broken" from "new". A finding therefore needs an identity that survives the fix: `finding-fingerprint.mjs` computes `F:xxxxxxxx` from the file and either the cited `ruleId` or the normalised issue text (lowercase, quotes stripped, paths reduced to basenames, digit runs collapsed). The line, the severity, the fix text and the reviewer take no part, because all of them change between rounds without the finding changing. `review-delta.mjs` then compares round N with round N-1 and reports `stillPresent`, `resolved`, `downgraded` (re-reported but no longer accepted by triage) and `new`, plus `recurrence`: how many consecutive rework cycles each survivor has lasted. That count is the autopilot circuit-breaker's trigger 2.
|
|
4
|
+
|
|
5
|
+
Gated by `prefs.global.autopilotCircuitBreaker` (`enabled` default true, `identicalFindingCycles` default 2). Computed by scripts, never by the model: an older triage JSON gets its fingerprints on the fly, and a reviewer that echoes one is respected but not relied on.
|
|
6
|
+
|
|
7
|
+
## Files per round
|
|
8
|
+
|
|
9
|
+
Phase 4 Step 3.2.1 writes `$WORKTREE/.pipeline/triage-round-<N>.json` (N = `state.reviewIterations | length`), validates it, annotates it and copies it to `$WORKTREE/triage-output.json`, the name Phase 7, `worktree-finalize.sh`, `render-work-summary.sh` and `diff-explain.mjs` read. A copy rather than a symlink because the salvage is `cp -R`, and `.pipeline/` is on the salvage list, so every round survives into `artifactsPath`. Step 3.7 rewrites the round file; the copy is repeated after it.
|
|
10
|
+
|
|
11
|
+
## Step 2.1 block: previous-round findings (iteration >= 2)
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
ITERATION=$(jq '.reviewIterations | length' "$STATE_FILE")
|
|
15
|
+
PREV_ROUND="$WORKTREE/.pipeline/triage-round-$((ITERATION-1)).json"
|
|
16
|
+
PREV_BLOCK=""
|
|
17
|
+
[ "$ITERATION" -ge 2 ] && [ -f "$PREV_ROUND" ] && PREV_BLOCK=$(node $HOME/.claude/scripts/finding-fingerprint.mjs annotate "$PREV_ROUND" 2>/dev/null \
|
|
18
|
+
| jq -r '[.accepted[] | select(.severity=="blocking" or .severity=="important")] | sort_by(.severity != "blocking") | .[:40][] | "- \(.fingerprint) [\(.severity)] \(.file): \(.issue)"')
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Rendered at the end of the shared prefix (Step 1.9), identical for every reviewer and for the triage call of that iteration:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
<previous-round-findings>
|
|
25
|
+
Each entry below was accepted last round and sent for rework.
|
|
26
|
+
- If the issue is still present, report it again with the SAME fingerprint value and the current line.
|
|
27
|
+
- If it is fixed, omit it. Omission is how you report resolution; never emit a "resolved" finding.
|
|
28
|
+
- Any finding not listed here is new: leave fingerprint unset.
|
|
29
|
+
{PREV_BLOCK}
|
|
30
|
+
</previous-round-findings>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The cap of 40 entries drops `important` before `blocking` so the prefix stays bounded. The block never enters the repo-stable prefix `prompt-assembly.md` describes: the diff it follows is already per-run. After each reviewer's validator gate, `finding-fingerprint.mjs annotate --in-place` fills in any fingerprint the reviewer left unset; `anonymize-findings.mjs` strips only identity keys, so the fingerprint reaches triage, and the triage prompt tells the model to preserve it verbatim.
|
|
34
|
+
|
|
35
|
+
## Step 2.2 block: scope self-check (every iteration)
|
|
36
|
+
|
|
37
|
+
Phase 3 Step 3.7 wrote `$WORKTREE/.pipeline/scope-check.json` (contract: `features/scope-check.md`). Render it so reviewers judge the diff against the dev's stated scope and do not re-propose what was rejected:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
SCOPE_JSON="$WORKTREE/.pipeline/scope-check.json"
|
|
41
|
+
SCOPE_GATE=$(git -C "$WORKTREE" diff --name-only "origin/$BASE_BRANCH"...HEAD \
|
|
42
|
+
| node $HOME/.claude/scripts/scope-check-gate.mjs --check "$SCOPE_JSON" --diff-files - --advisory 2>/dev/null)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
<scope-self-check>
|
|
47
|
+
Files and the reason the dev gave for touching each:
|
|
48
|
+
{jq -r '.files[] | "- \(.path): \(.reason)"' "$SCOPE_JSON"}
|
|
49
|
+
Files in the diff with no stated reason (flag as scope drift if the change is not obviously required):
|
|
50
|
+
{jq -r '.unjustified[]' <<< "$SCOPE_GATE"}
|
|
51
|
+
Deliberately not done (do not raise these as findings; they are known):
|
|
52
|
+
{jq -r '.notDone[] | "- \(.what) (\(.why))"' "$SCOPE_JSON"}
|
|
53
|
+
</scope-self-check>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
A missing record renders the block with `no scope-check.json written` and a `review.scope_check=missing` metric; the review proceeds, and the absence is itself information for the reviewer.
|
|
57
|
+
|
|
58
|
+
## Step 3.8: delta + trigger 2
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
TRIP=$(jq -r '.global.autopilotCircuitBreaker.identicalFindingCycles // 2' "$PREFS_FILE")
|
|
62
|
+
DELTA_JSON=$(node $HOME/.claude/scripts/review-delta.mjs --rounds-dir "$WORKTREE/.pipeline" --iteration "$ITERATION" --trip-cycles "$TRIP"); DELTA_RC=$?
|
|
63
|
+
jq -c --argjson d "$DELTA_JSON" --argjson i "$((ITERATION-1))" \
|
|
64
|
+
'{reviewIterations: (.reviewIterations | .[$i] += {delta: ($d + {computedAt: (now | todate)})})}' "$STATE_FILE" \
|
|
65
|
+
| node $HOME/.claude/scripts/write-state.mjs "$STATE_FILE"
|
|
66
|
+
$HOME/.claude/scripts/log-metric.sh "$TASK_ID" 4 review.delta iteration=$ITERATION \
|
|
67
|
+
new=$(jq '.counts.new // 0' <<< "$DELTA_JSON") still_present=$(jq '.counts.stillPresent // 0' <<< "$DELTA_JSON") \
|
|
68
|
+
resolved=$(jq '.counts.resolved // 0' <<< "$DELTA_JSON") plateau=$(jq '.plateau // false' <<< "$DELTA_JSON") tripped=$([ "$DELTA_RC" -eq 3 ] && echo true || echo false)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Exit | Meaning | Action |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| 0 | Progress, or nothing to compare (iteration 1, missing previous round) | Continue to Step 4. |
|
|
74
|
+
| 3 | A blocking/important finding survived `identicalFindingCycles` consecutive rework cycles | **Autopilot** with `enabled`: trip the breaker. `state.circuitBreaker = {tripped: true, trigger: 2, detail: "finding <fingerprint> (<file>: <issue>) survived <n> consecutive rework cycles", checkpoint: {phase: 4, step: "3.8", iteration: N}, trippedAt, counters: {identicalFindingCycles: <max recurrence>, reworkCycles: N-1}}`, then the halt-visibility protocol from `phases/operations.md` (`status=paused`, `haltReason="4:circuit-breaker:identical-finding"`, tracker meta, the `>&2 HALT` line, the usage report). **Interactive modes**: do not trip; show `delta.stillPresent` and ask (picker-contract) `Continue rework` / `Escalate to me` / `Accept as deferred`, the last moving those findings to `deferred[]` with reason `circuit-breaker: accepted by user after <n> cycles`. |
|
|
75
|
+
| 1 | Unreadable round file | Log `review.delta_skipped reason=invalid` and continue; the delta is advisory and never blocks on its own failure. |
|
|
76
|
+
|
|
77
|
+
`plateau` (the still-present set unchanged from the previous delta) is logged, not acted on: at the default threshold it coincides with trigger 2 and with the Phase 3 `retryCount` hard-kill. The rework-storm cap itself is trigger 3, recorded by the Phase 3 re-entry. The Phase 3 reflection prompt names each accepted finding by `fingerprint` and quotes `delta.stillPresent` first, marked `STILL PRESENT after round N-1's fix`.
|
|
78
|
+
|
|
79
|
+
## Why fingerprints are computed, not stored
|
|
80
|
+
|
|
81
|
+
Same argument `shortId()` in `_retrieval.mjs` makes for corpus rows: derived from the finding's own content, so existing triage files get ids without a migration and a re-annotated file keeps the ids it had. The known trade-off is over-merging: two findings in one file that differ only by a number share a fingerprint. The delta output prints `file: issue` beside every id and the trip needs two consecutive recurrences, so a merge is visible and cannot halt a run by itself. Under-merging (a reviewer that rewrites rather than echoes) only ever suppresses a trip; it never causes one.
|
|
82
|
+
|
|
83
|
+
## Telemetry
|
|
84
|
+
|
|
85
|
+
`review.delta` once per iteration >= 2: `iteration`, `new`, `still_present`, `resolved`, `plateau`, `tripped`. `run-metrics.mjs` reports `reviewDelta.stillPresentFinal`, `resolvedTotal` and `tripped`; Phase 7 renders the three counts per round.
|
|
86
|
+
|
|
87
|
+
## Reference
|
|
88
|
+
|
|
89
|
+
Scripts: `$HOME/.claude/scripts/_fingerprint.mjs`, `finding-fingerprint.mjs`, `review-delta.mjs`. Schemas: `reviewer-output` 1.2.0, `triage-output` 3.4.0, `dev-critic-output` (optional `fingerprint`), `agent-state` (`reviewIterations[].delta`, `circuitBreaker`). Breaker: `features/autopilot-circuit-breaker.md`. Smokes: `smoke-review-delta.sh`, `smoke-autopilot-circuit-breaker.sh`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Feature: Scope self-check (Phase 3 Step 3.7)
|
|
2
|
+
|
|
3
|
+
**Pattern**: Phase 4 reconstructs everything from the diff. The one thing it cannot reconstruct is why each file was touched and what was left out on purpose, so Dev states both before the handoff, and a deterministic gate checks the statement against the real diff. The record also carries the code-simplifier rationales (Step 3.6) that used to be discarded, and it feeds two later consumers: the `<scope-self-check>` block in the Phase 4 reviewer prefix and the PR body in Phase 6.
|
|
4
|
+
|
|
5
|
+
## The record
|
|
6
|
+
|
|
7
|
+
`$WORKTREE/.pipeline/scope-check.json`, schema `schemas/scope-check.schema.json`:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"version": "1.0.0",
|
|
12
|
+
"taskId": "<taskId>",
|
|
13
|
+
"files": [{ "path": "<repo-relative path>", "reason": "<why this task needs this exact file>" }],
|
|
14
|
+
"notDone": [{ "what": "<change considered and not made>", "why": "out-of-scope | follow-up | rejected-abstraction" }],
|
|
15
|
+
"simplifier": { "applied": 0, "skipped": 0, "rationales": ["<Step 3.6 rationale strings>"] }
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Rules:
|
|
20
|
+
|
|
21
|
+
- One entry per file in the diff, no entry for a file outside it. A reason names the task requirement, never "while I was here".
|
|
22
|
+
- `notDone` carries every hypothetical the dev chose not to defend against and every abstraction it considered and rejected, so review does not re-propose them.
|
|
23
|
+
- Component tasks (`taskType === "component"`) write the record too; the plugin skill's file table is the `files[]` source.
|
|
24
|
+
|
|
25
|
+
## The gate
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git -C "$WORKTREE" diff --name-only "origin/$BASE_BRANCH"...HEAD \
|
|
29
|
+
| node $HOME/.claude/scripts/scope-check-gate.mjs --check "$WORKTREE/.pipeline/scope-check.json" --diff-files -
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Output `{ok, justified, unjustified[], unlisted[], notDone}`. Exit 1 lists `unjustified[]` (in the diff, no reason or an empty one); `unlisted[]` (in the record, not in the diff) is reported and never fatal. Complete the record once and re-run. A second exit 1 does not block: log `dev.scope_check=incomplete unjustified=<n>` and hand off; Phase 4 receives the gate output inside `<scope-self-check>`, so reviewers see which files arrived without a stated reason. `--advisory` (used by Phase 4) always exits 0. Progress line: ` → checking scope self-check ({n} files, {m} not done)`.
|
|
33
|
+
|
|
34
|
+
## Consumers
|
|
35
|
+
|
|
36
|
+
- Phase 4 Step 2.2 renders file reasons, unjustified files and `notDone[]` into the shared reviewer prefix (`features/review-delta.md`).
|
|
37
|
+
- Phase 6 Step 3 builds the PR `## Changes` bullets from `files[].reason` and lists `notDone[]` under `## Related` as "Follow-ups not done in this PR", merged with the final triage `deferred[]` (`channels/pr.md`).
|
|
38
|
+
|
|
39
|
+
## Reference
|
|
40
|
+
|
|
41
|
+
Script: `$HOME/.claude/scripts/scope-check-gate.mjs`. Schema: `$HOME/.claude/schemas/scope-check.schema.json`. Smoke: `smoke-scope-check.sh`. Unit: `test/scope-check-gate.test.mjs`.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
**Gated by `prefs.global.verifyByTest.enabled`** (default: `false`). When enabled, after triage 3.6 and before Step 4, IF the validated triage output contains at least one `accepted` blocking finding:
|
|
6
6
|
|
|
7
7
|
1. Dispatch ONE verifier sub-agent for the iteration (model: `verifyByTest.model`, default `sonnet`) - never one dispatch per finding. Input: up to `verifyByTest.maxFindings` (default 3) accepted blocking findings, the diff hunks for their files, and the Phase 1 test conventions.
|
|
8
|
-
2. Per finding, the verifier writes ONE minimal repro test asserting the correct behavior the finding claims is broken, then runs ONLY that test via the Phase 3 single-test invocation (`xcodebuild test -only-testing:`, `pytest {file}::{name}`, `npm test -- --testPathPattern=`, `./gradlew test --tests`) under `acquire_build_lock`/`release_build_lock
|
|
8
|
+
2. Per finding, the verifier writes ONE minimal repro test asserting the correct behavior the finding claims is broken, then runs ONLY that test via the Phase 3 single-test invocation (`xcodebuild test -only-testing:`, `pytest {file}::{name}`, `npm test -- --testPathPattern=`, `./gradlew test --tests`) under `acquire_build_lock`/`release_build_lock`. One green run is not trusted: the same single-test invocation runs `verifyByTest.repeatCount` times (default 3; value 1 disables the repeat) as a shell loop, each run's log tee'd to `$WORKTREE/.pipeline/verify-<i>-<k>.test.log` for `k` in `1..repeatCount`. ALL runs must pass, and each log must clear `evidence-gate.mjs --claim test --status passed`, before the outcome counts as "passes". A first run that fails as predicted ends the loop early (`confirmed` needs no repeat).
|
|
9
9
|
3. Stamp each processed finding with a `verification` object (triage-output schema v3.2.0) and re-run `validate-triage.mjs` on the mutated triage file under the standard 3.2.1 gate protocol.
|
|
10
10
|
4. Findings beyond `maxFindings` keep their judgment-only verdict (log `verify_by_test=cap-exceeded`).
|
|
11
11
|
5. The whole step is bounded by `verifyByTest.stepTimeoutSec` (default 600); on breach or verifier crash, remaining findings keep judgment-only verdicts and the pipeline proceeds. Never blocks.
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
| Repro test outcome | `verification.result` | Action |
|
|
16
16
|
|---|---|---|
|
|
17
17
|
| Fails as the finding predicts | `confirmed` | Finding stays accepted blocking. Repro test KEPT in the worktree, recorded in `redTests[]`. |
|
|
18
|
-
| Passes (not reproducible) | `not-reproduced` |
|
|
18
|
+
| Passes (not reproducible) | `not-reproduced` | Only when ALL `verifyByTest.repeatCount` runs pass and every `verify-<i>-<k>.test.log` clears `evidence-gate.mjs --claim test --status passed` (exit 0 required on each). Finding moves `accepted[]` -> `deferred[]` with reason `verify-by-test: not reproduced - repro test <testRef> passed`; repro test file deleted. Evidence-gate failure on any run -> treat as `inconclusive`. |
|
|
19
|
+
| Passes on some runs, fails on others | `inconclusive` | Flake, not proof either way. Finding stays accepted blocking, `verification.note = "flaky: passed k/N"`, repro test file deleted, telemetry line gains `flaky=<count>`. |
|
|
19
20
|
| Compile error / timeout / not unit-testable | `inconclusive` | Finding stays accepted blocking (judgment stands). Partial test deleted, cause in `verification.note`. |
|
|
20
21
|
|
|
21
22
|
Downgrades go to `deferred`, never `rejected`: triage judged the issue real, and deferred items surface in the Phase 7 report for a human eye.
|
|
@@ -30,12 +31,12 @@ After Step 3.7, the only uncommitted verifier artifacts are the confirmed repro
|
|
|
30
31
|
|
|
31
32
|
## Telemetry
|
|
32
33
|
|
|
33
|
-
One `review.verify_by_test` metric per iteration: `attempted`, `confirmed`, `downgraded`, `inconclusive`, `duration_ms` (plus `tokens_in/out` when available), forwarded to the tracker like all Phase 4 metrics. Timeout emits `triage=verify-by-test-timeout`.
|
|
34
|
+
One `review.verify_by_test` metric per iteration: `attempted`, `confirmed`, `downgraded`, `inconclusive`, `flaky` (findings whose repeat runs disagreed), `duration_ms` (plus `tokens_in/out` when available), forwarded to the tracker like all Phase 4 metrics. Timeout emits `triage=verify-by-test-timeout`.
|
|
34
35
|
|
|
35
36
|
## Off by default reason
|
|
36
37
|
|
|
37
|
-
Adds one Sonnet call plus up to `maxFindings` single-test runs (and build-lock contention on Xcode projects) per review iteration that has accepted blockers. On clean runs it never fires, but on noisy-reviewer repos it can add minutes per iteration. Flip on for security-critical work, release branches, or repos where reviewer false-positive rate is high.
|
|
38
|
+
Adds one Sonnet call plus up to `maxFindings` single-test runs (and build-lock contention on Xcode projects) per review iteration that has accepted blockers. Each passing repro test is re-run `verifyByTest.repeatCount` times (default 3), so a downgrade costs up to `repeatCount` single-test runs rather than one. On clean runs it never fires, but on noisy-reviewer repos it can add minutes per iteration. Flip on for security-critical work, release branches, or repos where reviewer false-positive rate is high.
|
|
38
39
|
|
|
39
40
|
## Reference
|
|
40
41
|
|
|
41
|
-
Wiring: `$HOME/.claude/multi-agent-refs/phases/phase-4-review.md` Step 3.7. Schema: `$HOME/.claude/schemas/triage-output.schema.json` v3.2.0 (`$defs.verification`). Evidence gate: `$HOME/.claude/scripts/evidence-gate.mjs`. Prefs: `prefs.global.verifyByTest` in `$HOME/.claude/schemas/prefs.schema.json`.
|
|
42
|
+
Wiring: `$HOME/.claude/multi-agent-refs/phases/phase-4-review.md` Step 3.7. Schema: `$HOME/.claude/schemas/triage-output.schema.json` v3.2.0 (`$defs.verification`). Evidence gate: `$HOME/.claude/scripts/evidence-gate.mjs`. Prefs: `prefs.global.verifyByTest` (including `repeatCount`) in `$HOME/.claude/schemas/prefs.schema.json`.
|
|
@@ -49,7 +49,7 @@ This is why the removal is safe:
|
|
|
49
49
|
|
|
50
50
|
| Consumer | Reads | Without salvage |
|
|
51
51
|
|---|---|---|
|
|
52
|
-
| Phase 7 triage-memory ingest | `triage-output.json` | `[ -f ]`-guarded, so it degrades **silently**: the triage corpus and learnings ledger stop being fed and no error appears |
|
|
52
|
+
| Phase 7 triage-memory ingest | `triage-output.json` | `[ -f ]`-guarded, so it degrades **silently**: the triage corpus and learnings ledger stop being fed and no error appears. Since v16.20.0 Phase 4 writes `triage-output.json` itself (Step 3.2.1, the latest copy of `.pipeline/triage-round-<N>.json`), so the salvage is a second copy, not the only bridge |
|
|
53
53
|
| Phase 7 learnings-ledger distill | same file | same silent degradation |
|
|
54
54
|
| `render-work-summary.sh` | `agent-state.json`, `phase-tracker.json` (falls back to `logs/multi-agent/<task>/tracker-state.json`, which survives removal) | loses the salvaged copies but keeps the tracker via the logs fallback |
|
|
55
55
|
| `:resume` | `agent-state.json` | cannot continue a Phase 7 pause |
|
|
@@ -10,7 +10,7 @@ description: "Canonical required-reading list for outward-facing payloads (PR bo
|
|
|
10
10
|
|
|
11
11
|
| Read | Before | Governs |
|
|
12
12
|
|---|---|---|
|
|
13
|
-
| [`channels/pr.md`]($HOME/.claude/multi-agent-refs/channels/pr.md) | assembling the PR body | fixed section set (`summary` → `changes` → `architecture` cond. → `verification` → `dependencies` cond. → `related`), Markdown-only rule, reviewer-preserving Bitbucket PUT payload |
|
|
13
|
+
| [`channels/pr.md`]($HOME/.claude/multi-agent-refs/channels/pr.md) | assembling the PR body | fixed section set (`summary` → `changes` → `architecture` cond. → `verification` → `risk` cond. → `dependencies` cond. → `related`), Markdown-only rule, reviewer-preserving Bitbucket PUT payload |
|
|
14
14
|
| [`phases/phase-6-commit.md`]($HOME/.claude/multi-agent-refs/phases/phase-6-commit.md) | committing | commit convention, default-reviewer fetch, draft/ready prompt, push-must-succeed loop |
|
|
15
15
|
| [`rules.md`]($HOME/.claude/multi-agent-refs/rules.md) "External System Outputs" | any REST payload | real newlines, no HTML entities, no hand-rolled JSON, markup dialect per surface |
|
|
16
16
|
|
|
@@ -84,7 +84,8 @@ When re-entering from Phase 4:
|
|
|
84
84
|
3. `accepted.suggestion` items are applied opportunistically (no TDD loop required) unless the user asked for suggestions to be treated strictly.
|
|
85
85
|
4. `deferred` items are NOT actioned in this re-entry - they surface in Phase 7's "Follow-up items" section.
|
|
86
86
|
5. `rejected` items are never touched. Log their IDs + triage reasons for audit only.
|
|
87
|
-
6. After rework, increment `state.phases["3"].retryCount`; hard-kill at `retryCount === 3` and escalate to the user. Do not loop indefinitely.
|
|
87
|
+
6. After rework, increment `state.phases["3"].retryCount`; hard-kill at `retryCount === 3` and escalate to the user. Do not loop indefinitely. When `prefs.global.autopilotCircuitBreaker.enabled` is true (default), record the hard-kill before escalating as `state.circuitBreaker = {tripped: true, trigger: 3, detail: "rework cycles exhausted", checkpoint: {phase: 3, step: "re-entry", iteration: <n>}, trippedAt, counters: {reworkCycles: <n>}}`: the rework-storm trigger; `autopilotCircuitBreaker.maxReworkCycles` (default 3) is bounded above by this cap.
|
|
88
|
+
7. When `state.reviewIterations[-1].delta` exists, `stillPresent[]` findings come first in the task list, quoted `STILL PRESENT after round N-1's fix`; repeating the previous attempt is the loop the circuit-breaker stops.
|
|
88
89
|
|
|
89
90
|
If the latest iteration has `triage.approved === true` AND `accepted === []`, Phase 3 was entered by mistake - log the anomaly and return to Phase 5.
|
|
90
91
|
|
|
@@ -155,6 +156,7 @@ For each task (respecting dependency order):
|
|
|
155
156
|
**REFACTOR (if needed):**
|
|
156
157
|
- Only if duplication or naming is poor
|
|
157
158
|
- Re-run tests after refactor → still GREEN
|
|
159
|
+
- **Stability (required):** run every test added or changed in this diff `prefs.global.testStability.repeatCount` times (default 3; 1 disables) with the single-test invocation above. Disagreeing outcomes are not GREEN: log `test.flake_signal file=<f> passed=<k> of=<N>` and fix the test or the code first. A pass only on retry is a flake signal, not a pass. Same rule on the Phase 4 rework re-entry.
|
|
158
160
|
|
|
159
161
|
**Target resolution** (auto-detect once per project, cache in `agent-state.json`; ios resolves scheme + simulator, android resolves module + variant, backend/web need none):
|
|
160
162
|
```bash
|
|
@@ -243,7 +245,7 @@ After the build/test green step and BEFORE Phase 4 handoff, run one diff-shrink
|
|
|
243
245
|
- **Over-abstraction** - single-call-site protocols/wrappers/helpers from this diff
|
|
244
246
|
|
|
245
247
|
Progress line: ` → dispatching code-simplifier diff-shrink`
|
|
246
|
-
2. **Return contract** - a list of shrink edits `{ "file", "lines", "edit", "rationale", "risk": "safe|unsafe" }`; the subagent never writes files.
|
|
248
|
+
2. **Return contract** - a list of shrink edits `{ "file", "lines", "edit", "rationale", "risk": "safe|unsafe" }`; the subagent never writes files. Keep the `rationale` strings: Step 3.7 writes them into `scope-check.json`.
|
|
247
249
|
3. **Apply safe edits only** - skip `unsafe` (logged). Zero edits is normal - log and continue. Progress line: ` → applying shrink edits ({N} applied, {M} skipped)`
|
|
248
250
|
4. **Re-run build + tests** (same build-queue lock + evidence-gate rule as Step 4). Any breakage -> revert shrink edits wholesale and proceed pre-shrink; the simplifier must never cost a green state.
|
|
249
251
|
5. **Record tokens in the cost ledger** so Phase 7's Cost Breakdown captures the pass:
|
|
@@ -258,6 +260,19 @@ Scope guard: a single pass, never looped. Runs in a Short run too; component tas
|
|
|
258
260
|
|
|
259
261
|
---
|
|
260
262
|
|
|
263
|
+
#### Step 3.7 - Scope self-check (required handoff artifact)
|
|
264
|
+
|
|
265
|
+
Phase 4 cannot reconstruct why each file was touched or what was left out on purpose, so Dev states both before the handoff: write `$WORKTREE/.pipeline/scope-check.json` (`schemas/scope-check.schema.json`: one `{path, reason}` per file in the diff, `notDone[]` with `why` in `out-of-scope | follow-up | rejected-abstraction`, and the Step 3.6 `simplifier` rationales), then run the gate. **Record rules, consumers and the full contract: `$HOME/.claude/multi-agent-refs/features/scope-check.md`.**
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
git -C "$WORKTREE" diff --name-only "origin/$BASE_BRANCH"...HEAD \
|
|
269
|
+
| node $HOME/.claude/scripts/scope-check-gate.mjs --check "$WORKTREE/.pipeline/scope-check.json" --diff-files -
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Exit 1 lists `unjustified[]`: complete the record once and re-run. A second exit 1 does not block; log `dev.scope_check=incomplete unjustified=<n>` and hand off, and Phase 4 shows the gate output to reviewers inside `<scope-self-check>`. Progress line: ` → checking scope self-check ({n} files, {m} not done)`.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
261
276
|
#### Short pipeline (`state.onlyDevelop === true`)
|
|
262
277
|
|
|
263
278
|
Set by the Phase 0 Step 7.5 depth picker, or by autopilot never (autopilot always runs Full). When it is true, Phase 3 runs self-contained with **Opus** (not Sonnet). No Phase 2 plan exists - the agent creates its own scope.
|
|
@@ -152,6 +152,12 @@ echo "$RISK_FULL" | node $HOME/.claude/scripts/validate-diff-risk.mjs - >/dev/nu
|
|
|
152
152
|
RISK_JSON=$([ -n "$RISK_FULL" ] && jq -c '.files |= (sort_by(-.score) | .[:5])' <<< "$RISK_FULL" || echo "")
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
+
Persist the totals as `state.diffRisk` (Phase 6 `risk` section, Phase 7, `run-metrics.mjs` read them):
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
[ -n "$RISK_FULL" ] && jq -c '{diffRisk: (.totals + {signals: ([.files[].signals[]?.name] | unique)})}' <<< "$RISK_FULL" | node $HOME/.claude/scripts/write-state.mjs "$STATE_FILE"
|
|
159
|
+
```
|
|
160
|
+
|
|
155
161
|
**Signals & weights** (see `$HOME/.claude/schemas/diff-risk.schema.json`):
|
|
156
162
|
|
|
157
163
|
| Signal | Weight | Triggers when |
|
|
@@ -179,7 +185,9 @@ On success, emit a single summary metric:
|
|
|
179
185
|
$HOME/.claude/scripts/log-metric.sh "$TASK_ID" 4 review.diff_risk \
|
|
180
186
|
top_files=$(jq '.files | length' <<< "$RISK_JSON") \
|
|
181
187
|
max_score=$(jq '.totals.max_score' <<< "$RISK_JSON") \
|
|
182
|
-
loc_added=$(jq '.totals.loc_added' <<< "$RISK_JSON")
|
|
188
|
+
loc_added=$(jq '.totals.loc_added' <<< "$RISK_JSON") \
|
|
189
|
+
loc_removed=$(jq '.totals.loc_removed' <<< "$RISK_JSON") \
|
|
190
|
+
files=$(jq '.totals.files' <<< "$RISK_JSON")
|
|
183
191
|
```
|
|
184
192
|
|
|
185
193
|
**Opt-out**: `prefs.global.diffRiskAdvisory = false` skips this step entirely (no script invocation, no priority block injection). Default `true` because the cost is bounded and the signal-to-noise has been measured against the golden-task fixture set.
|
|
@@ -269,7 +277,7 @@ When `state.figmaAccess.tier === 3` (user-attached screenshot, no Code Connect s
|
|
|
269
277
|
|
|
270
278
|
Phase 4 sends the same diff to every reviewer and then to triage, so the diff is the dominant token cost. Two measures keep it bounded:
|
|
271
279
|
|
|
272
|
-
**Shared cache prefix.** Build the reviewer and triage prompts so the large invariant context - the full diff, the `${CRITERIA}` block from Step 1.78, the Phase 1 analysis summary, the Phase 2 plan - is a byte-identical leading block across all dispatches in this iteration. Only the per-reviewer focus + skill line varies, and it goes AFTER the shared block. `${CRITERIA}` goes in the prefix, identical for every reviewer: subsetting it per reviewer would invalidate the prefix for the whole panel and re-bill the largest block in the phase. Per-reviewer emphasis stays a one-line pointer in the suffix. When the host supports prompt caching, the 2nd/3rd reviewer and the triage call then read that prefix at the discounted cache-read rate instead of re-billing it as fresh input. Forward the host-reported cache-read count as `tokens_cached` per the Token telemetry contract so the saving lands in the cost ledger.
|
|
280
|
+
**Shared cache prefix.** Build the reviewer and triage prompts so the large invariant context - the full diff, the `${CRITERIA}` block from Step 1.78, the Phase 1 analysis summary, the Phase 2 plan - is a byte-identical leading block across all dispatches in this iteration. Only the per-reviewer focus + skill line varies, and it goes AFTER the shared block. `${CRITERIA}` goes in the prefix, identical for every reviewer: subsetting it per reviewer would invalidate the prefix for the whole panel and re-bill the largest block in the phase. Per-reviewer emphasis stays a one-line pointer in the suffix. When the host supports prompt caching, the 2nd/3rd reviewer and the triage call then read that prefix at the discounted cache-read rate instead of re-billing it as fresh input. Forward the host-reported cache-read count as `tokens_cached` per the Token telemetry contract so the saving lands in the cost ledger. The `<scope-self-check>` block and, from iteration 2, the `<previous-round-findings>` block (Step 2.1) close the shared block, after the plan and before the per-reviewer suffix.
|
|
273
281
|
|
|
274
282
|
**Single-repo diff cap.** If the diff exceeds the Phase 4 token allowance (`token-budget.json`), truncate the largest files and append a footer `[truncated - full diff in file://$WORKTREE/.review-diff.txt]`, writing the full diff to that path. Reviewers and triage receive the same capped view + the marker so they can flag "review the full diff manually." Log `review.diff_truncated bytes_dropped=<N>`. (Multi-repo already caps the combined diff at 80% of budget; this is the single-repo equivalent.)
|
|
275
283
|
|
|
@@ -328,7 +336,9 @@ Each reviewer inherits the `code-reviewer` agent's focus areas (Security, Archit
|
|
|
328
336
|
| Docker | `ai-backend-toolkit:docker-expert` | `ai-backend-toolkit:docker-expert` | `ai-backend-toolkit:ci-cd-pipelines` |
|
|
329
337
|
| Generic | `security-review` | `ai-backend-toolkit:clean-code` | `ai-backend-toolkit:clean-code` |
|
|
330
338
|
|
|
331
|
-
|
|
339
|
+
##### 2.1 Previous-round findings (iteration >= 2) and 2.2 scope self-check (every iteration)
|
|
340
|
+
|
|
341
|
+
A reviewer has no memory of the round before, so it rediscovers last round's findings in new words. From iteration 2, render the previous round's accepted blocking/important findings (`.pipeline/triage-round-$((ITERATION-1)).json`, max 40) into a `<previous-round-findings>` block at the end of the shared prefix: a still-present issue is reported with the SAME fingerprint and the current line, a fixed one is omitted, anything new leaves `fingerprint` unset. Every iteration also renders `.pipeline/scope-check.json` (Phase 3 Step 3.7) plus `scope-check-gate.mjs --advisory` output as `<scope-self-check>`: file reasons, unjustified files, and `notDone[]` (never re-raised as findings); a missing record logs `review.scope_check=missing`. Block text and recipes: `$HOME/.claude/multi-agent-refs/features/review-delta.md`.
|
|
332
342
|
|
|
333
343
|
#### Step 2.8 - Visual conformance gate (component / screen work only)
|
|
334
344
|
|
|
@@ -382,11 +392,14 @@ Step 2 produces N reviewer-output objects (one per dispatched reviewer), each co
|
|
|
382
392
|
REVIEWER_FILE="$WORKTREE/.pipeline/reviewer-$N.json"
|
|
383
393
|
printf '%s' "$REVIEWER_JSON" > "$REVIEWER_FILE"
|
|
384
394
|
node $HOME/.claude/scripts/validate-reviewer.mjs "$REVIEWER_FILE" \
|
|
385
|
-
--criteria "$WORKTREE/.pipeline/criteria-manifest.json"
|
|
395
|
+
--criteria "$WORKTREE/.pipeline/criteria-manifest.json" \
|
|
396
|
+
&& node $HOME/.claude/scripts/finding-fingerprint.mjs annotate --in-place "$REVIEWER_FILE"
|
|
386
397
|
```
|
|
387
398
|
|
|
388
399
|
Progress line: ` → checking validator validate-reviewer ({reviewer})`
|
|
389
400
|
|
|
401
|
+
`finding-fingerprint.mjs` stamps each finding with its cross-round id once the validator passes; an echoed one is kept, and anonymization leaves it intact.
|
|
402
|
+
|
|
390
403
|
Exit 0 = valid. Exit 2 = contradiction (approved=true with blocking findings) - flip `approved` to `false`, continue. With `--criteria`, exit 1 also covers the conformance checklist: a selected rule ID with no verdict, a verdict for an ID that was never selected, a `conformant` row with no file evidence, or a `violated` row with no matching finding. Those are the four ways a review can look complete without being complete, and the validator is what makes the checklist more than decoration - it is hand-written and does not apply `additionalProperties`, so an unchecked array would otherwise pass. Exit 1 = malformed; gate protocol (fails CLOSED, same handling as the evidence gate): emit the validator stderr + `errors[]` verbatim, attempt ONE self-correction rework (re-invoke that reviewer with the errors quoted, overwrite the file), re-run the validator. If it fails again -> HALT the phase (no merge, no triage). Recovery hint: `ERR: reviewer output failed validate-reviewer.mjs twice. Inspect $REVIEWER_FILE against $HOME/.claude/schemas/reviewer-output.schema.json, then resume with /multi-agent:resume #N.`
|
|
391
404
|
|
|
392
405
|
#### Step 2.5 - Disagreement-round loop (opt-in)
|
|
@@ -499,6 +512,7 @@ bash $HOME/.claude/scripts/log-metric.sh "$TASK_ID" 4 memory.hit rows=$CITED_COU
|
|
|
499
512
|
```
|
|
500
513
|
You are the Review Triage agent. Three reviewers (fewer on a single-scope run) returned findings on this diff.
|
|
501
514
|
Your job: separate signal from noise. Do NOT add new findings. Do NOT re-review code.
|
|
515
|
+
Preserve `fingerprint` verbatim on every finding you carry through; it is the finding's identity across rounds.
|
|
502
516
|
|
|
503
517
|
For each finding, decide:
|
|
504
518
|
- ACCEPTED: real issue, in scope, must be fixed now
|
|
@@ -529,14 +543,19 @@ Return ONLY valid JSON conforming to $HOME/.claude/schemas/triage-output.schema.
|
|
|
529
543
|
Run on the persisted file immediately after the triage agent returns, before acting on the verdict; the validator's exit code decides, not the LLM turn:
|
|
530
544
|
|
|
531
545
|
```bash
|
|
532
|
-
|
|
546
|
+
ITERATION=$(jq '.reviewIterations | length' "$STATE_FILE")
|
|
547
|
+
TRIAGE_FILE="$WORKTREE/.pipeline/triage-round-$ITERATION.json"
|
|
533
548
|
mkdir -p "$(dirname "$TRIAGE_FILE")"
|
|
534
549
|
printf '%s' "$TRIAGE_JSON" > "$TRIAGE_FILE"
|
|
535
|
-
node $HOME/.claude/scripts/validate-triage.mjs "$TRIAGE_FILE"
|
|
550
|
+
node $HOME/.claude/scripts/validate-triage.mjs "$TRIAGE_FILE" \
|
|
551
|
+
&& node $HOME/.claude/scripts/finding-fingerprint.mjs annotate --in-place "$TRIAGE_FILE" \
|
|
552
|
+
&& cp "$TRIAGE_FILE" "$WORKTREE/triage-output.json"
|
|
536
553
|
```
|
|
537
554
|
|
|
538
555
|
Progress line: ` → checking validator validate-triage`
|
|
539
556
|
|
|
557
|
+
One file per round; `triage-output.json` is the latest copy every downstream reader expects (Phase 7, finalize, work-summary, diff-explain). Step 3.7 rewrites `$TRIAGE_FILE`; repeat the `cp` after it.
|
|
558
|
+
|
|
540
559
|
| Exit | Meaning | Action |
|
|
541
560
|
| ----- | ---------------------------- | ------------------------------------------------- |
|
|
542
561
|
| **0** | Valid and clean | Act on triage output as-is |
|
|
@@ -607,7 +626,26 @@ After the triage verdict is computed, populate `triage.consensus`:
|
|
|
607
626
|
|
|
608
627
|
A triage verdict is judgment; a failing repro test is proof. Runs only when `prefs.global.verifyByTest.enabled` is `true` AND `accepted` contains a `blocking` finding; otherwise skip silently. **Full contract (verdict table, cleanup invariant, prompts): `$HOME/.claude/multi-agent-refs/features/verify-by-test.md` - read it before executing this step.**
|
|
609
628
|
|
|
610
|
-
Compressed flow: dispatch ONE verifier agent (model `verifyByTest.model`, default `sonnet`) for up to `maxFindings` (default 3) accepted blocking findings. Per finding it writes ONE minimal repro test and runs ONLY that test (Phase 3 single-test invocation, build lock, log tee'd to `$WORKTREE/.pipeline/verify-<i>.test.log`). Outcomes: test FAILS as predicted -> `confirmed`, finding stays blocking and the test is KEPT in `redTests[]` as the Phase 3 rework RED test; test PASSES -> `not-reproduced` ONLY if `evidence-gate.mjs --claim test --status passed` exits 0 on
|
|
629
|
+
Compressed flow: dispatch ONE verifier agent (model `verifyByTest.model`, default `sonnet`) for up to `maxFindings` (default 3) accepted blocking findings. Per finding it writes ONE minimal repro test and runs ONLY that test (Phase 3 single-test invocation, build lock, log tee'd to `$WORKTREE/.pipeline/verify-<i>.test.log`). Outcomes: test FAILS as predicted -> `confirmed`, finding stays blocking and the test is KEPT in `redTests[]` as the Phase 3 rework RED test; test PASSES on every one of `verifyByTest.repeatCount` runs (default 3) -> `not-reproduced` ONLY if `evidence-gate.mjs --claim test --status passed` exits 0 on each log; a run that disagrees with the others -> `inconclusive` with `flaky: passed k/N`, finding moves to `deferred[]`, test deleted; compile error / timeout / not unit-testable -> `inconclusive`, judgment verdict stands. Stamp findings with `verification` (schema v3.2.0), persist `state.reviewIterations[-1].verifyByTest = {attempted, confirmed, downgraded, inconclusive, redTests[]}`, recompute `approved`, re-run `validate-triage.mjs` under the 3.2.1 gate. Whole step bounded by `stepTimeoutSec` (default 600); on breach or crash remaining findings keep judgment verdicts - never blocks. Telemetry per 3.4: `review.verify_by_test attempted= confirmed= downgraded= inconclusive= duration_ms=`.
|
|
630
|
+
|
|
631
|
+
##### 3.8 Cross-round delta + circuit-breaker trigger 2 (iteration >= 2)
|
|
632
|
+
|
|
633
|
+
**Full contract (state merge, telemetry line, picker wording): `$HOME/.claude/multi-agent-refs/features/review-delta.md`.**
|
|
634
|
+
|
|
635
|
+
```bash
|
|
636
|
+
TRIP=$(jq -r '.global.autopilotCircuitBreaker.identicalFindingCycles // 2' "$PREFS_FILE")
|
|
637
|
+
DELTA_JSON=$(node $HOME/.claude/scripts/review-delta.mjs --rounds-dir "$WORKTREE/.pipeline" --iteration "$ITERATION" --trip-cycles "$TRIP"); DELTA_RC=$?
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
Merge the JSON into `state.reviewIterations[-1].delta` via `write-state.mjs`; emit `review.delta iteration= new= still_present= resolved= plateau= tripped=`. Progress line: ` → comparing round {N} vs {N-1} (new={n} still={n} resolved={n})`.
|
|
641
|
+
|
|
642
|
+
| Exit | Action |
|
|
643
|
+
|---|---|
|
|
644
|
+
| **0** | Continue to Step 4 (also iteration 1 and a missing previous round). |
|
|
645
|
+
| **3** | Autopilot with `prefs.global.autopilotCircuitBreaker.enabled` (default true): write `state.circuitBreaker = {tripped: true, trigger: 2, detail, checkpoint: {phase: 4, step: "3.8", iteration: N}, trippedAt, counters}`, then the `operations.md` halt protocol with `haltReason="4:circuit-breaker:identical-finding"`. Interactive: show `delta.stillPresent`, ask `Continue rework` / `Escalate to me` / `Accept as deferred`. |
|
|
646
|
+
| **1** | Log `review.delta_skipped reason=invalid`, continue; the delta never blocks on its own failure. |
|
|
647
|
+
|
|
648
|
+
`plateau` is logged, not acted on; trigger 3 (the rework cap) is recorded by the Phase 3 re-entry.
|
|
611
649
|
|
|
612
650
|
#### Step 4 - Consensus + Action (triage-driven)
|
|
613
651
|
|
|
@@ -615,7 +653,7 @@ If `triage.consensus.verdict` is `split` or `unverified`, surface `consensus.dis
|
|
|
615
653
|
|
|
616
654
|
Act **only on triage.accepted**:
|
|
617
655
|
|
|
618
|
-
- **accepted.blocking** → back to Phase 3 (max 3 iterations, with reflection prompt citing only accepted items). When Step 3.7 ran and `state.reviewIterations[-1].verifyByTest.redTests[]` is non-empty, the reflection prompt cites each red test: "a failing repro test already exists at <testRef>; make it green; do not delete or weaken it."
|
|
656
|
+
- **accepted.blocking** → back to Phase 3 (max 3 iterations, with reflection prompt citing only accepted items). The reflection prompt names findings by `fingerprint` and quotes `delta.stillPresent` first, marked `STILL PRESENT after round N-1's fix`. When Step 3.7 ran and `state.reviewIterations[-1].verifyByTest.redTests[]` is non-empty, the reflection prompt cites each red test: "a failing repro test already exists at <testRef>; make it green; do not delete or weaken it."
|
|
619
657
|
- **accepted.important** → fix and re-review
|
|
620
658
|
- **accepted.suggestion** → apply if reasonable
|
|
621
659
|
- **deferred** → append to Phase 7 report as "follow-up items" (do not block)
|
|
@@ -103,6 +103,16 @@ Tier 1 / Tier 2 records print `screenshotUrl` from the captured evidence (Tier 2
|
|
|
103
103
|
bash $HOME/.claude/scripts/phase-tracker.sh render
|
|
104
104
|
```
|
|
105
105
|
The waiting state persists in `tracker-state.json` across the handoff; `/multi-agent:resume-local` and `/multi-agent:manual-test` CONTINUE this state file and never re-init it (`$HOME/.claude/multi-agent-refs/tracker-contract.md` "Continuation runs").
|
|
106
|
+
|
|
107
|
+
**"ok" is a structured result, not a word.** Before "ok" is accepted, the run writes `$WORKTREE/.pipeline/manual-test.json`: one entry per acceptance criterion, the criteria taken from the analysis doc test plan (Section 15 / 20), the plan tasks, and the user's own words in the reply. Every criterion records what was seen; a criterion that was not tried says so with a reason.
|
|
108
|
+
```json
|
|
109
|
+
{"criteria":[{"spec":"<quote>","source":"analysis 15.2 | plan task 3 | user","observed":"<what was seen>","verdict":"pass|fail|not-tested","reason":"<required when not-tested>","screenshot":"<path or null>"}],"verdict":"passed|failed"}
|
|
110
|
+
```
|
|
111
|
+
Then gate it:
|
|
112
|
+
```bash
|
|
113
|
+
node $HOME/.claude/scripts/evidence-gate.mjs --claim manual --status passed --evidence "$WORKTREE/.pipeline/manual-test.json"
|
|
114
|
+
```
|
|
115
|
+
Exit 1 means the "ok" is not accepted: tell the user which criterion is missing evidence (a `fail` verdict, or `not-tested` without a reason) and wait for the next reply. Exit 0 marks Phase 5 completed with `Result "local test passed (user)"`. The "fix: ..." path below is unchanged.
|
|
106
116
|
6. If fix needed:
|
|
107
117
|
- Branch already has WIP commit (from step 2) - changes are safe
|
|
108
118
|
- **Heal stale admin state first** (same contract as Phase 0 - step 3's
|
|
@@ -136,6 +136,8 @@ Branch **deterministically**, no implicit fallback. Read `agent-state.json` and
|
|
|
136
136
|
|
|
137
137
|
Generate a structured PR description based on task type. The PR body targets **code reviewers** - it should be technical: what changed, why, architecture decisions, how to verify.
|
|
138
138
|
|
|
139
|
+
Two inputs are read from state and the worktree before writing, not recalled from the conversation: `$WORKTREE/.pipeline/scope-check.json` (Phase 3 Step 3.7) supplies the `## Changes` bullets from `files[].reason` and the "Follow-ups not done in this PR" list under `## Related` from `notDone[]`; `state.diffRisk.signals` (Phase 4 Step 1.75) decides whether the conditional `## Risk and Security` section is required. When a high-stakes signal is present and the section is missing, this step blocks until it is written; a placeholder answer ("TBD") counts as missing.
|
|
140
|
+
|
|
139
141
|
**required**: Run all generated text (PR body, commit message) through the `humanizer` skill before posting. This removes AI-generated patterns (inflated language, filler phrases, repetitive structure) and makes the output sound like a developer wrote it.
|
|
140
142
|
|
|
141
143
|
**IMPORTANT - Issue auto-close prevention**: Never use these keywords in PR title, body, or commit messages - they auto-close the issue on merge:
|
|
@@ -163,8 +163,10 @@ Skipped sections: when `planTodos.enabled` is false or no `plan.todos[]` was emi
|
|
|
163
163
|
|
|
164
164
|
## Review Iterations
|
|
165
165
|
|
|
166
|
-
| Iteration | Blocking | Important | Suggestion | Decision |
|
|
167
|
-
| --------- | -------- | --------- | ---------- | -------- |
|
|
166
|
+
| Iteration | Blocking | Important | Suggestion | Decision | Still present | Resolved | New |
|
|
167
|
+
| --------- | -------- | --------- | ---------- | -------- | ------------- | -------- | --- |
|
|
168
|
+
|
|
169
|
+
(the last three columns come from `reviewIterations[i].delta.counts` (Phase 4 Step 3.8) and read `-` on iteration 1 and on runs that predate the delta; a tripped circuit-breaker is one extra line under the table: `Circuit-breaker tripped: trigger {n}, {detail}`)
|
|
168
170
|
|
|
169
171
|
## Files Changed
|
|
170
172
|
|
|
@@ -81,6 +81,6 @@ In autopilot, `ask_choice` resolves to `default` (or the safe first option) with
|
|
|
81
81
|
|
|
82
82
|
## Deterministic gates note
|
|
83
83
|
|
|
84
|
-
Claude Code's `PreToolUse` exit-2 hooks are the HARD blocking gates.
|
|
84
|
+
Claude Code's `PreToolUse` exit-2 hooks are the HARD blocking gates. Three ship, none needing run-specific arguments so they are naturally hookable: (1) `pre-commit-check.sh` scans the staged diff on every `git commit` and blocks on a detected secret; (2) `agent-guard.sh` runs on `git commit` + `git push` and blocks AI/assistant attribution in a commit message and force-push to a protected branch (main/master/develop); (3) `check-read-size.sh` runs on `Read` and on the shell commands that read a file whole, and routes an oversized read to a cheap worker (`bulk-read.sh`) instead of the caller's own rung. The first two inspect what a run WRITES; the third inspects what it pays to READ, and it is inert until `bulkRead.mode` is set to `observe` or `enforce`, so merging the block changes nothing until the user opts in. Its `observe` mode blocks nothing and only logs, which is how the baseline is measured before anything is routed. All three are self-contained, fail-open on internal error, and never execute the inspected command. The recommended hook block ships at `install/templates/claude-hooks.json`; `multi-agent:setup` offers to merge it into `~/.claude/settings.json`. The other deterministic gates (evidence, consensus, intent, learnings) are invoked by the pipeline phases with per-run arguments (a build-log path, the triage JSON, the free-text input), so they are phase-enforced by contract, not OS-hookable.
|
|
85
85
|
|
|
86
86
|
Copilot CLI has no `PreToolUse` equivalent, so the secret scan there is workflow-enforced (run as a phase step, not OS-blocked) plus a CI smoke-gate step.
|
|
@@ -52,10 +52,10 @@ This is the single source of truth. When a contributor or model is unsure where
|
|
|
52
52
|
- **NEVER** commit without passing build (all gates in Phase 4 Step 1 must be green).
|
|
53
53
|
- **NEVER** commit without passing review (at least one AI reviewer must return `approved: true` with no blocking findings).
|
|
54
54
|
- **NEVER** skip tests. Every public method, every error path, every edge case.
|
|
55
|
-
- **NEVER** delete, rename, or weaken an existing test to get a green run. Existing tests are immutable during a task
|
|
55
|
+
- **NEVER** delete, rename, or weaken an existing test to get a green run. Existing tests are immutable during a task; one may change only when the task changes the spec it encodes, and the commit body names both. Deterministic backstop: the `test_lines_removed` diff-risk signal (Phase 4 Step 1.75) flags test files that shrink. A pass only on retry is a flake signal, not a pass: Phase 3 repeats new and changed tests (`testStability.repeatCount`, default 3) and logs `test.flake_signal` when runs disagree.
|
|
56
56
|
- **Follow existing code style and conventions.** Read neighbor files before writing new ones - match naming, structure, import order.
|
|
57
57
|
- **Use design tokens, no magic numbers.** `16` → `.Spacing.spacing16`. `#E31837` → `Color.Primary.primary`. `.font(.system(size: 14))` → `.typographyStyle(.body1)`.
|
|
58
|
-
- **Design system primitives before custom views.** Before writing a new
|
|
58
|
+
- **Design system primitives before custom views.** Before writing a new View / Configuration triplet in a feature module, grep the shared component library (e.g. `Common/UIComponents/`, `core-ui/`, `packages/ui/`) for a primitive that already solves it. Domain-level wrappers, custom modals, buttons or toasts are forbidden when the design system has an equivalent. If the primitive exists but lacks a modifier (placeholder, size, error binding), **add the modifier to the primitive** in its `+Modifiers` extension; never fork it into the consumer domain. The Figma `CodeConnectSnippet` is the authoritative pointer to which primitive to use.
|
|
59
59
|
|
|
60
60
|
## Swift-Specific Rules
|
|
61
61
|
|
|
@@ -650,6 +650,90 @@
|
|
|
650
650
|
"type": "string",
|
|
651
651
|
"enum": ["fix", "accept", "escalate"]
|
|
652
652
|
},
|
|
653
|
+
"delta": {
|
|
654
|
+
"type": "object",
|
|
655
|
+
"additionalProperties": true,
|
|
656
|
+
"description": "Written by Phase 4 Step 3.8 from review-delta.mjs: how this round's accepted findings relate to the previous round's rework mandate. Absent on iteration 1 and on runs from before the delta existed.",
|
|
657
|
+
"properties": {
|
|
658
|
+
"previousIteration": { "type": "integer", "minimum": 1 },
|
|
659
|
+
"new": {
|
|
660
|
+
"type": "array",
|
|
661
|
+
"items": {
|
|
662
|
+
"type": "object",
|
|
663
|
+
"additionalProperties": true,
|
|
664
|
+
"properties": {
|
|
665
|
+
"fingerprint": { "type": "string" },
|
|
666
|
+
"severity": { "type": ["string", "null"] },
|
|
667
|
+
"file": { "type": ["string", "null"] },
|
|
668
|
+
"issue": { "type": ["string", "null"] }
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
},
|
|
672
|
+
"stillPresent": {
|
|
673
|
+
"type": "array",
|
|
674
|
+
"items": {
|
|
675
|
+
"type": "object",
|
|
676
|
+
"additionalProperties": true,
|
|
677
|
+
"properties": {
|
|
678
|
+
"fingerprint": { "type": "string" },
|
|
679
|
+
"severity": { "type": ["string", "null"] },
|
|
680
|
+
"file": { "type": ["string", "null"] },
|
|
681
|
+
"issue": { "type": ["string", "null"] }
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
},
|
|
685
|
+
"resolved": {
|
|
686
|
+
"type": "array",
|
|
687
|
+
"items": {
|
|
688
|
+
"type": "object",
|
|
689
|
+
"additionalProperties": true,
|
|
690
|
+
"properties": {
|
|
691
|
+
"fingerprint": { "type": "string" },
|
|
692
|
+
"severity": { "type": ["string", "null"] },
|
|
693
|
+
"file": { "type": ["string", "null"] },
|
|
694
|
+
"issue": { "type": ["string", "null"] }
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
"downgraded": {
|
|
699
|
+
"type": "array",
|
|
700
|
+
"items": {
|
|
701
|
+
"type": "object",
|
|
702
|
+
"additionalProperties": true,
|
|
703
|
+
"properties": {
|
|
704
|
+
"fingerprint": { "type": "string" },
|
|
705
|
+
"severity": { "type": ["string", "null"] },
|
|
706
|
+
"file": { "type": ["string", "null"] },
|
|
707
|
+
"issue": { "type": ["string", "null"] }
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
"stillPresentBlocking": {
|
|
712
|
+
"type": "array",
|
|
713
|
+
"items": {
|
|
714
|
+
"type": "object",
|
|
715
|
+
"additionalProperties": true,
|
|
716
|
+
"properties": {
|
|
717
|
+
"fingerprint": { "type": "string" },
|
|
718
|
+
"severity": { "type": ["string", "null"] },
|
|
719
|
+
"file": { "type": ["string", "null"] },
|
|
720
|
+
"issue": { "type": ["string", "null"] }
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
"recurrence": {
|
|
725
|
+
"type": "object",
|
|
726
|
+
"additionalProperties": { "type": "integer", "minimum": 1 },
|
|
727
|
+
"description": "fingerprint -> consecutive rework cycles the finding has survived."
|
|
728
|
+
},
|
|
729
|
+
"plateau": {
|
|
730
|
+
"type": "boolean",
|
|
731
|
+
"description": "The still-present set is unchanged from the previous delta and non-empty."
|
|
732
|
+
},
|
|
733
|
+
"tripped": { "type": "boolean" },
|
|
734
|
+
"computedAt": { "type": "string", "format": "date-time" }
|
|
735
|
+
}
|
|
736
|
+
},
|
|
653
737
|
"reviewers": {
|
|
654
738
|
"type": "array",
|
|
655
739
|
"description": "One entry per reviewer dispatch that RETURNED. Typed because two consumers depend on the shape: anonymize-findings.mjs needs model+findings to build the label map, and run-metrics.mjs reports acceptedRatio per reviewer. Extra keys are allowed; nothing is required, so a run written before this shape existed still validates and surfaces as model \"unknown\" rather than failing.",
|
|
@@ -699,6 +783,51 @@
|
|
|
699
783
|
}
|
|
700
784
|
}
|
|
701
785
|
},
|
|
786
|
+
"circuitBreaker": {
|
|
787
|
+
"type": "object",
|
|
788
|
+
"additionalProperties": false,
|
|
789
|
+
"description": "Autopilot circuit-breaker record (refs/features/autopilot-circuit-breaker.md). Written only when a trigger trips: trigger 2 by Phase 4 Step 3.8 (a mandate finding survived identicalFindingCycles rework cycles), trigger 3 by the Phase 3 re-entry hard-kill. /multi-agent:resume clears tripped and keeps counters.",
|
|
790
|
+
"required": ["tripped"],
|
|
791
|
+
"properties": {
|
|
792
|
+
"tripped": { "type": "boolean" },
|
|
793
|
+
"trigger": { "type": ["integer", "null"], "minimum": 1, "maximum": 5 },
|
|
794
|
+
"detail": { "type": "string" },
|
|
795
|
+
"checkpoint": {
|
|
796
|
+
"type": "object",
|
|
797
|
+
"additionalProperties": false,
|
|
798
|
+
"properties": {
|
|
799
|
+
"phase": { "type": "integer", "minimum": 0, "maximum": 7 },
|
|
800
|
+
"step": { "type": "string" },
|
|
801
|
+
"iteration": { "type": "integer", "minimum": 1 }
|
|
802
|
+
}
|
|
803
|
+
},
|
|
804
|
+
"trippedAt": { "type": "string", "format": "date-time" },
|
|
805
|
+
"counters": {
|
|
806
|
+
"type": "object",
|
|
807
|
+
"additionalProperties": false,
|
|
808
|
+
"properties": {
|
|
809
|
+
"identicalFindingCycles": { "type": "integer", "minimum": 0 },
|
|
810
|
+
"reworkCycles": { "type": "integer", "minimum": 0 }
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
"diffRisk": {
|
|
816
|
+
"type": "object",
|
|
817
|
+
"additionalProperties": true,
|
|
818
|
+
"description": "Totals from diff-risk-score.mjs, persisted by Phase 4 Step 1.75 so Phase 6 (PR risk section), Phase 7 and run-metrics.mjs read the same numbers the review scope decision used.",
|
|
819
|
+
"properties": {
|
|
820
|
+
"files": { "type": "integer", "minimum": 0 },
|
|
821
|
+
"loc_added": { "type": "integer", "minimum": 0 },
|
|
822
|
+
"loc_removed": { "type": "integer", "minimum": 0 },
|
|
823
|
+
"max_score": { "type": "number" },
|
|
824
|
+
"signals": {
|
|
825
|
+
"type": "array",
|
|
826
|
+
"items": { "type": "string" },
|
|
827
|
+
"description": "Distinct signal names seen across files (security_path, migration, public_api, no_test_change, test_lines_removed, ...)."
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
},
|
|
702
831
|
"confluenceSpace": {
|
|
703
832
|
"type": ["string", "null"],
|
|
704
833
|
"description": "Cached Confluence space key for the project - avoids re-asking on every run."
|