@mmerterden/multi-agent-pipeline 20.23.1 → 20.24.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 +30 -1
- package/README.md +7 -6
- package/README.tr.md +6 -5
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +3 -3
- package/install/catalog-history.json +1 -1
- package/install/templates/claude-hooks.json +14 -1
- package/manifest.json +77 -56
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +11 -2
- package/pipeline/commands/multi-agent/postmortem/SKILL.md +78 -0
- package/pipeline/commands/multi-agent/setup/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/sync/SKILL.md +3 -3
- package/pipeline/lib/analysis-jira-write.sh +105 -22
- package/pipeline/lib/decisions.mjs +130 -0
- package/pipeline/lib/plan-todos.sh +156 -33
- package/pipeline/lib/task-graph.mjs +446 -0
- package/pipeline/lib/tdd-evidence.mjs +579 -0
- package/pipeline/multi-agent-refs/analysis-template-corporate.md +2 -0
- package/pipeline/multi-agent-refs/analysis-template.md +4 -2
- package/pipeline/multi-agent-refs/cross-cli-contract.md +4 -4
- package/pipeline/multi-agent-refs/features/analysis-jira.md +51 -3
- package/pipeline/multi-agent-refs/features/autopilot-decisions.md +34 -0
- package/pipeline/multi-agent-refs/features/model-fallback.md +4 -2
- package/pipeline/multi-agent-refs/features/mutation-probe.md +58 -0
- package/pipeline/multi-agent-refs/features/plan-checks.md +67 -0
- package/pipeline/multi-agent-refs/features/plan-todos.md +40 -1
- package/pipeline/multi-agent-refs/features/rework-escalation.md +49 -0
- package/pipeline/multi-agent-refs/features/scope-check.md +1 -0
- package/pipeline/multi-agent-refs/generate-issue.md +1 -1
- package/pipeline/multi-agent-refs/help/en.md +1 -0
- package/pipeline/multi-agent-refs/help/tr.md +1 -0
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +2 -3
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +5 -7
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +13 -11
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +3 -3
- package/pipeline/multi-agent-refs/picker-contract.md +3 -2
- package/pipeline/multi-agent-refs/response-shape.md +65 -0
- package/pipeline/rules/response-shape.md +18 -0
- package/pipeline/schemas/agent-state.schema.json +31 -1
- package/pipeline/schemas/plan-todos.schema.json +101 -0
- package/pipeline/schemas/planning-output.schema.json +46 -0
- package/pipeline/schemas/prefs.schema.json +117 -1
- package/pipeline/scripts/_jsonl.mjs +151 -0
- package/pipeline/scripts/_plan-overlap.mjs +231 -0
- package/pipeline/scripts/_redact.mjs +124 -10
- package/pipeline/scripts/analysis-story-body.mjs +26 -10
- package/pipeline/scripts/analysis-story-tree.mjs +210 -9
- package/pipeline/scripts/autopilot-publish.mjs +12 -1
- package/pipeline/scripts/cost-analyze.mjs +6 -24
- package/pipeline/scripts/decisions-log.mjs +118 -0
- package/pipeline/scripts/edit-syntax-gate.mjs +240 -0
- package/pipeline/scripts/edit-syntax-gate.sh +29 -0
- package/pipeline/scripts/find-polluter.mjs +281 -0
- package/pipeline/scripts/gate-ledger.mjs +46 -13
- package/pipeline/scripts/mutation-probe.mjs +484 -0
- package/pipeline/scripts/plan-complexity.mjs +259 -0
- package/pipeline/scripts/review-decision-gate.mjs +33 -1
- package/pipeline/scripts/rework-escalation.mjs +264 -0
- package/pipeline/scripts/test-strength.mjs +5 -3
- package/pipeline/scripts/transcript-index.mjs +950 -0
- package/pipeline/scripts/validate-planning.mjs +48 -6
- package/pipeline/skills/.skill-manifest.json +11 -7
- package/pipeline/skills/.skills-index.json +12 -1
- package/pipeline/skills/shared/README.md +4 -3
- package/pipeline/skills/shared/core/multi-agent-analysis-jira/SKILL.md +11 -2
- package/pipeline/skills/shared/core/multi-agent-postmortem/SKILL.md +66 -0
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +3 -3
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +15 -0
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +13 -0
- package/pipeline/skills/shared/external/debugging-strategies/SKILL.md +8 -0
- package/pipeline/skills/skills-index.md +2 -1
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
- [The story body is document text, escaped](#the-story-body-is-document-text-escaped)
|
|
11
11
|
- [An existing epic, linked the way the site links](#an-existing-epic-linked-the-way-the-site-links)
|
|
12
12
|
- [Channel clones are opt-in](#channel-clones-are-opt-in)
|
|
13
|
+
- [Story dependencies come from the document](#story-dependencies-come-from-the-document)
|
|
13
14
|
- [The document lists its tickets](#the-document-lists-its-tickets)
|
|
14
15
|
- [Every site-specific name is a VALUE, never a schema key](#every-site-specific-name-is-a-value-never-a-schema-key)
|
|
15
16
|
- [Auth](#auth)
|
|
@@ -122,6 +123,9 @@ Each part sits in a coloured wiki panel with its `h3.` heading first: the user
|
|
|
122
123
|
story in `{panel:borderColor=#d4d39e}`, the acceptance criteria in
|
|
123
124
|
`{panel:borderColor=#d4a19e}`, current vs target and links in
|
|
124
125
|
`{panel:borderColor=#9eb6d4}`. The `Sources:` line stays outside the panels.
|
|
126
|
+
`issueTree.storyPanels` replaces any of the three colours (`story`, `criteria`,
|
|
127
|
+
`other`), or turns the panels off with `false`; `/multi-agent:create-jira`
|
|
128
|
+
reads the same setting.
|
|
125
129
|
|
|
126
130
|
At most 8 criteria. What is missing stays missing and becomes a body warning
|
|
127
131
|
(no user story, fewer than 2 criteria, no negative criterion); nothing is filled
|
|
@@ -149,6 +153,46 @@ gets one clone per channel: its own label (the story's identity plus
|
|
|
149
153
|
the label search, and a link is checked before it is sent, so a re-run doubles
|
|
150
154
|
neither.
|
|
151
155
|
|
|
156
|
+
## Story dependencies come from the document
|
|
157
|
+
|
|
158
|
+
A requirement row may carry `Depends on: <ids>` (an extra trailing column, or
|
|
159
|
+
appended to any cell of that row): `| BR-pay-01 | ... | Depends on: BR-cart-01 |`.
|
|
160
|
+
The row's own id is the dependent, the ids after the clause its prerequisites.
|
|
161
|
+
The clause is machine syntax, so it stays English in a Turkish document, as
|
|
162
|
+
`THE SYSTEM SHALL` and Given/When/Then do. Only clauses inside a table row are
|
|
163
|
+
read, and a clause naming no requirement id is left as prose.
|
|
164
|
+
|
|
165
|
+
The planner reads the clauses first and removes them before it looks for ids,
|
|
166
|
+
because an id is defined by where it appears: a reference left in place would
|
|
167
|
+
define the id it points at, and a dependency on a requirement nobody wrote would
|
|
168
|
+
plan a story for it instead of being reported.
|
|
169
|
+
|
|
170
|
+
| Clause | Plan |
|
|
171
|
+
|---|---|
|
|
172
|
+
| both ids in one story | no edge |
|
|
173
|
+
| ids in two stories | the dependent story's `dependsOn` gets the prerequisite's label, and `dependencies.edges[]` names the pair of ids behind it |
|
|
174
|
+
| prerequisite id in no story | `dependencies.missing[]`, coverage `incomplete` |
|
|
175
|
+
| stories that wait for each other | `dependencies.cycles[]`, one per strongly connected component (from `lib/task-graph.mjs`); every edge inside one gets `onCycle: true`; coverage `incomplete` |
|
|
176
|
+
|
|
177
|
+
The preview prints each edge, its requirement pair and each cycle
|
|
178
|
+
prerequisite first, and the problems under the
|
|
179
|
+
coverage line, as `coverage.dependencyProblems`; an incomplete verdict they
|
|
180
|
+
cause carries them in `coverage.reason`. Its heading counts issues and links
|
|
181
|
+
apart (`N issue(s) and M link(s)`); `writeCount` is their sum, without the
|
|
182
|
+
edges on a cycle. A document with no clause plans
|
|
183
|
+
exactly as it did before: no `dependsOn`, no `dependencies`, and
|
|
184
|
+
`prefsUsed.dependencyLinkType` only when the site set it.
|
|
185
|
+
|
|
186
|
+
The writer links the stories after all of them have keys, one link per edge, of
|
|
187
|
+
`issueTree.dependencyLinkType` (default `Blocks`). The prerequisite is the
|
|
188
|
+
link's source - the POST's `inwardIssue` - so the site reads "prerequisite
|
|
189
|
+
blocks dependent" and lists the link on the prerequisite under `outwardIssue`.
|
|
190
|
+
That entry, same type and same direction, is what the writer checks for before
|
|
191
|
+
sending, so a re-run adds nothing; a link of another type between the same two
|
|
192
|
+
stories does not count. A written link is a `dependency` line in the ledger
|
|
193
|
+
(the dependent's label, the prerequisite's key). An edge with `onCycle` is not
|
|
194
|
+
linked, and the writer says so.
|
|
195
|
+
|
|
152
196
|
## The document lists its tickets
|
|
153
197
|
|
|
154
198
|
After the write, `analysis-tickets-writeback.mjs` puts a `## Tickets` table at
|
|
@@ -169,7 +213,11 @@ are values the site fills in:
|
|
|
169
213
|
channel, the value is the site's own name for it
|
|
170
214
|
- `subtaskRoles` ships **empty**. An empty list is an instruction to look at how
|
|
171
215
|
this board actually splits work; a ready-made list would be the guess most
|
|
172
|
-
worth avoiding
|
|
216
|
+
worth avoiding. An attended run asks once, offering the site's own sub-task
|
|
217
|
+
types (`analysis-jira-write.sh --subtask-types`), and saves the answer
|
|
218
|
+
(`analysis-story-tree.mjs --save-subtasks`): each chosen type becomes a role
|
|
219
|
+
mapped to itself, and "no sub-tasks" sets `subtaskRolesDeclined`
|
|
220
|
+
- `cloneLinkType` and `dependencyLinkType` are the site's own link type names
|
|
173
221
|
- `epicLinkFieldId`, when set, is the site's own field id; otherwise it is
|
|
174
222
|
discovered
|
|
175
223
|
- `subtaskIssueType: null` means discover it - `createmeta` returns whichever
|
|
@@ -178,8 +226,8 @@ are values the site fills in:
|
|
|
178
226
|
Center 9 answers, then the classic form). Same rule as
|
|
179
227
|
`features/jira-context.md`: the type travels as it comes from Jira and is
|
|
180
228
|
never a matching criterion in code
|
|
181
|
-
- a site with several sub-task types (
|
|
182
|
-
|
|
229
|
+
- a site with several sub-task types (research, build and QA sub-tasks)
|
|
230
|
+
names them per role: `subtaskIssueType` as a map from a `subtaskRoles`
|
|
183
231
|
entry to the type, `"*"` for any other role. Without one the first discovered
|
|
184
232
|
type is used and the run prints the types it found
|
|
185
233
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Feature: Decisions made on your behalf (autopilot)
|
|
2
|
+
|
|
3
|
+
**Pattern**: an attended run asks; an autopilot run answers its own questions. Every such answer is one entry in `state.decisions[]`, so whoever reads the PR or the Phase 5 report sees each place the run chose for them, why, and what it costs if the choice was wrong:
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{ "what": "Took the default base branch develop", "why": "no remembered branch inside the TTL",
|
|
7
|
+
"costIfWrong": "the PR targets the wrong base and is retargeted", "at": "<ISO-8601>", "phase": 0 }
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Written only while the quality gates are active (`lib/unattended.mjs` `gatesActive`: autopilot, or `MULTI_AGENT_UNATTENDED=1`). In an attended run the person answered, so nothing is recorded. Shape and rendering: `lib/decisions.mjs`; schema: `agent-state.schema.json` `decisions`.
|
|
11
|
+
|
|
12
|
+
## Writers
|
|
13
|
+
|
|
14
|
+
| Decision | Written by |
|
|
15
|
+
|---|---|
|
|
16
|
+
| Park (`awaiting-answer`, `verification-failed`) | `gate-ledger.mjs park`, in the same locked write as the park |
|
|
17
|
+
| Blocker lowered to important (waived finding) | `review-decision-gate.mjs`, one entry per downgrade |
|
|
18
|
+
| Rework dispatched one rung up | `rework-escalation.mjs --state` (`features/rework-escalation.md`) |
|
|
19
|
+
| Picker resolved without asking (`remembered` / `default` / `derived`) | the picker step, through `decisions-log.mjs add` (`picker-contract.md`) |
|
|
20
|
+
| Split / unverified consensus taken on the triage verdict | Phase 3 Step 4, through `decisions-log.mjs add` |
|
|
21
|
+
| Date-gate model fallback taken without the question | Phase 0 Step 0, through `decisions-log.mjs add` (`features/model-fallback.md`) |
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
node $HOME/.claude/scripts/decisions-log.mjs add --state "$STATE_FILE" --phase <0-5> \
|
|
25
|
+
--what "<what was chosen>" --why "<the rule or evidence that chose it>" \
|
|
26
|
+
--cost-if-wrong "<what a person has to undo if it was the wrong call>"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Exit 0 written or skipped (attended), 2 a missing field, 3 no state. A failed add never stops the run; log `decisions.add_failed`.
|
|
30
|
+
|
|
31
|
+
## Where it is shown
|
|
32
|
+
|
|
33
|
+
- **Phase 5 report**: `decisions-log.mjs render --state "$STATE_FILE"` inlined verbatim in `agent-log.md`. It prints its own `## Decisions made on your behalf` heading, and nothing at all for an empty list.
|
|
34
|
+
- **Autopilot PR body**: `autopilot-publish.mjs` appends the same section to the run's PR body before the outbound gate checks the title and body. The entries are passed through `lib/redact.mjs` when rendered, so a credential that reached a gate reason is replaced rather than published; the outbound gate still refuses a body that carries one anywhere else. A body that already holds the heading is not given a second one, and the section is trimmed to the 65536-character body limit with a pointer to the run report.
|
|
@@ -174,7 +174,8 @@ prints it once instead:
|
|
|
174
174
|
`WARN: premium tier plan window ended <date>; preferredModel personas will dispatch on <fallbackModel>. Set prefs.global.modelFallback.premiumTierUntil to null to keep the top tier on usage credits.`
|
|
175
175
|
Then export the override for every `preferredModel` persona this run. Interactive modes
|
|
176
176
|
may instead ask once (`Keep top tier (usage credits) / Fall back to <model>`);
|
|
177
|
-
autopilot never asks - it falls back and
|
|
177
|
+
autopilot never asks - it falls back, logs, and lists the fallback in `state.decisions[]`
|
|
178
|
+
(`features/autopilot-decisions.md`).
|
|
178
179
|
2. **Dispatch error (per dispatch).** On a model-unavailable / quota error from a
|
|
179
180
|
top-tier dispatch and `onDispatchError: true`: retry the same persona once with
|
|
180
181
|
`PHASE_MODEL_OVERRIDE=<fallbackModel>`. If that retry also fails with a dispatch
|
|
@@ -244,7 +245,8 @@ better internals.
|
|
|
244
245
|
## Non-goals
|
|
245
246
|
|
|
246
247
|
- No automatic re-upgrade mid-run (a run that fell back stays fallen back -
|
|
247
|
-
deterministic over clever).
|
|
248
|
+
deterministic over clever). Rework escalation (`features/rework-escalation.md`)
|
|
249
|
+
moves one rework dispatch up a rung and refuses to on a run that fell back.
|
|
248
250
|
- No edits to `pipeline/agents/*.md` at runtime; frontmatter is install-time
|
|
249
251
|
configuration only.
|
|
250
252
|
- Copilot CLI reviewer set is out of scope: Copilot CLI pins its own three
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Mutation probe (Phase 3 Step 1.763, opt-in)
|
|
2
|
+
|
|
3
|
+
A passing suite says the changed code runs, not that a test checks what it
|
|
4
|
+
decides. `mutation-probe.mjs` inverts a few changed lines one at a time and
|
|
5
|
+
reruns the tests; a mutant that still passes marks a line no test pins down.
|
|
6
|
+
|
|
7
|
+
Runs only when `prefs.global.mutationProbe.enabled` is true, and only when the
|
|
8
|
+
stack adapter names a test command.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
MP_FILE="$WORKTREE/.pipeline/mutation-probe-$ITERATION.json"
|
|
12
|
+
STACK=$(node $HOME/.claude/scripts/_stack-adapter.mjs resolve --dir "$WORKTREE" | cut -d' ' -f1)
|
|
13
|
+
TEST_CMD=$(node $HOME/.claude/scripts/_stack-adapter.mjs command --dir "$WORKTREE" --stack "$STACK" --kind test)
|
|
14
|
+
[ -n "$TEST_CMD" ] && node $HOME/.claude/scripts/mutation-probe.mjs --repo "$WORKTREE" \
|
|
15
|
+
--base "origin/$BASE_BRANCH" --head worktree --test-cmd "$TEST_CMD" --out "$MP_FILE"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`--max-mutants` and `--timeout-sec` default to `mutationProbe.maxMutants` (5)
|
|
19
|
+
and `mutationProbe.timeoutSec` (120 per test run). Each mutant costs one full
|
|
20
|
+
test run, so the bound is the cost bound.
|
|
21
|
+
|
|
22
|
+
## Mutations
|
|
23
|
+
|
|
24
|
+
Textual, single-site, and only on lines the diff added or changed (test files
|
|
25
|
+
excluded). The operator needs whitespace on both sides, so generics, arrows and
|
|
26
|
+
unary signs are left alone; comment lines and string contents are skipped.
|
|
27
|
+
|
|
28
|
+
| Kind | Change |
|
|
29
|
+
|---|---|
|
|
30
|
+
| comparison | `==` `!=` `===` `!==` `<` `<=` `>` `>=` flipped to the opposite |
|
|
31
|
+
| boolean | `true`/`false`, `True`/`False` negated |
|
|
32
|
+
| arithmetic | binary `+` and `-` swapped |
|
|
33
|
+
| return | `return <number>`: 0 becomes 1, anything else 0 |
|
|
34
|
+
|
|
35
|
+
Mutants are spread across lines: one per changed line in diff order before any
|
|
36
|
+
line gets a second.
|
|
37
|
+
|
|
38
|
+
## Isolation
|
|
39
|
+
|
|
40
|
+
`--head worktree` snapshots the uncommitted tree through a private index (the
|
|
41
|
+
caller's index, HEAD and refs are untouched), and every mutant is applied in a
|
|
42
|
+
detached worktree under the system temp directory, removed and pruned on exit,
|
|
43
|
+
SIGINT/SIGTERM included. The checkout is never written.
|
|
44
|
+
|
|
45
|
+
## Output and merge
|
|
46
|
+
|
|
47
|
+
| Exit | Meaning | `$MP_FILE` |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| 0 | every mutant killed or timed out | `findings: []` |
|
|
50
|
+
| 1 | at least one survivor | one finding per survivor |
|
|
51
|
+
| 4 | nothing judged: no mutable changed line, or the tests fail before mutation | `findings: []`, `reason` set |
|
|
52
|
+
| 2 | usage or setup error | not written; the step is skipped |
|
|
53
|
+
|
|
54
|
+
A finding is reviewer-shaped (`severity: important`, `file`, `line`, `issue`,
|
|
55
|
+
`fix`) plus `mutation`, `testCommand` and `tag: mutation_probe`. Step 3.0
|
|
56
|
+
appends `$MP_FILE` with the other deterministic findings; triage keeps a
|
|
57
|
+
survivor unless the line is genuinely untestable here (logging, a default no
|
|
58
|
+
caller observes) and says so.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Feature: Plan checks (Phase 1, after the plan is written)
|
|
2
|
+
|
|
3
|
+
Two deterministic checks run on `$WORKTREE/.pipeline/plan.json` before the approval gate: the overlap check inside `validate-planning.mjs`, and the complexity score from `plan-complexity.mjs`.
|
|
4
|
+
|
|
5
|
+
## Overlap check
|
|
6
|
+
|
|
7
|
+
**Pattern**: a plan contradicts itself at the seams between tasks, not inside one. Two tasks that both create the same file, a rename in one task while another still calls the old name, a consumer that can start before its producer: each task reads fine alone. The check is deterministic and runs inside `validate-planning.mjs`, so it fires on every plan with no extra step.
|
|
8
|
+
|
|
9
|
+
### What a task may state
|
|
10
|
+
|
|
11
|
+
Optional per-task lists in `planning-output.schema.json`, next to `files[]`:
|
|
12
|
+
|
|
13
|
+
| Field | Holds | Role it gives the task |
|
|
14
|
+
|-------|-------|------------------------|
|
|
15
|
+
| `creates` | files the task brings into existence | `creates` |
|
|
16
|
+
| `removes` | files or symbols deleted, or the old name of a rename (a file: a `/`, a known extension, a well-known name such as `Makefile`, or a dotfile; else a symbol, `UserService.fetch` included) | `removes` |
|
|
17
|
+
| `produces` | interfaces / symbols introduced or reshaped | `produces` |
|
|
18
|
+
| `consumes` | interfaces / symbols used unchanged | `consumes` |
|
|
19
|
+
|
|
20
|
+
A file listed only in `files[]` gives the role `edits`. Fill the lists when Step 8 decided the approach (New file / Modify / Refactor): a New-file task lists its file under `creates`, a rename lists the old name under `removes` and the new one under `produces`, a task that calls an interface another task adds lists it under `consumes`. A plan without them still gets the shared-file rows below. `plan-todos.sh set` carries `creates` onto each todo next to `files[]`, and the per-step drift check (`features/plan-todos.md`, Drift propagation) counts both as planned, along with the test file named after a planned file (`a.test.ts` for `a.ts`, `FooTests.swift` for `Foo.swift`).
|
|
21
|
+
|
|
22
|
+
### Output
|
|
23
|
+
|
|
24
|
+
`validate-planning.mjs` adds two fields to its result: `overlaps[]`, one anchored row per pair of tasks naming the same file or symbol, and `warnings[]`, one sentence per suspicious row.
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{ "a": "T1", "b": "T3", "kind": "file", "name": "Sources/FareService.swift",
|
|
28
|
+
"aRole": "removes", "bRole": "edits", "order": "unordered", "finding": "removes-used" }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`order` comes from `dependsOn`, transitively: `a-first`, `b-first` or `unordered`.
|
|
32
|
+
|
|
33
|
+
| Finding | When | Effect |
|
|
34
|
+
|---------|------|--------|
|
|
35
|
+
| `double-create` | both tasks create the same file (paths compared normalised, `./a` is `a`) | error, exit 1; unordered: make one depend on the other; ordered: drop it from one task's `creates` |
|
|
36
|
+
| `removes-used` | one task removes what the other edits, consumes, creates or produces | warning |
|
|
37
|
+
| `consumer-not-after-producer` | the user of a created file or produced symbol is not ordered after its producer | warning |
|
|
38
|
+
| `unordered-edit` | both tasks edit one file with no `dependsOn` between them | warning |
|
|
39
|
+
|
|
40
|
+
An error goes through the validator gate's single rework like any other. Warnings never change the exit code: render them in the Step 13 approval view under `⚠️ Plan overlaps` (autopilot: log `plan.overlaps={n} warnings={m}`), and fix the plan when the warning is real, usually by adding the missing `dependsOn` edge or merging the two tasks.
|
|
41
|
+
|
|
42
|
+
## Complexity score
|
|
43
|
+
|
|
44
|
+
**Pattern**: "is this task too big" answered from what the task touches, not from its title. The score is a sum of points over counted factors, so the same plan and graph always give the same score and each point traces to a number.
|
|
45
|
+
|
|
46
|
+
| Factor | Counted as | Points |
|
|
47
|
+
|--------|-----------|--------|
|
|
48
|
+
| `files` | files in `files[]` | 1 → 0, 2-3 → 1, 4-5 → 2, 6-8 → 3, 9+ → 4 |
|
|
49
|
+
| `fanIn` | non-test files depending on the task's declarations, one ring out in the code graph, the task's own files excluded | 0 → 0, 1-3 → 1, 4-10 → 2, 11+ → 3 |
|
|
50
|
+
| `modules` | modules the task's non-test files span | one point per module past the first, at most 2 |
|
|
51
|
+
| `crossModuleEdges` | fan-in files outside every module the task touches | 0 → 0, 1-3 → 1, 4+ → 2 |
|
|
52
|
+
| `testSurface` | test files listed plus test files depending on the task's files | 0-2 → 0, 3-5 → 1, 6+ → 2 |
|
|
53
|
+
|
|
54
|
+
`score = 1 + points`, capped at 10. A module is the SPM target (`Sources/<Target>/`, `Tests/<Target>Tests/`), else the directory above the first `src` / `test` / `tests` / `lib` segment (the Gradle module), else the first directory. Without a graph, `fanIn`, `crossModuleEdges` and `notIndexed` are `null` and score nothing, and the output says `graph: absent`. `notIndexed` counts listed files the graph does not know, usually the ones the task creates.
|
|
55
|
+
|
|
56
|
+
`recommendSplit` is `score > prefs.global.planComplexity.splitAbove` (default 7; `--split-above N` overrides it). Splitting rules, applied before Step 13:
|
|
57
|
+
|
|
58
|
+
- Split along the factor that scored: files by module first, then producer before consumer, tests with the code they test.
|
|
59
|
+
- Traceability: every requirement id on the original appears on at least one child, and no child carries an id the original did not.
|
|
60
|
+
- Ordering: a task that depended on the original depends on the child that completes the original's output; children chain through `dependsOn` where one consumes another.
|
|
61
|
+
- One round. Re-run the validator and the scorer; a task still above the line is shown at Step 13 with its factors, not split again.
|
|
62
|
+
|
|
63
|
+
Output: `{splitAbove, graph, split: [ids], tasks: [{id, score, recommendSplit, factors, points}]}`. `--write` stores `{score, recommendSplit, factors}` as each task's `complexity` in the plan file. Autopilot log: `plan.complexity split={ids} graph={used|absent}`.
|
|
64
|
+
|
|
65
|
+
## Reference
|
|
66
|
+
|
|
67
|
+
Overlap module: `$HOME/.claude/scripts/_plan-overlap.mjs` (`planOverlaps(tasks)`). Validator: `$HOME/.claude/scripts/validate-planning.mjs`. Unit: `test/plan-overlap.test.mjs`, fixtures under `test/fixtures/plan-overlap/`. Scorer: `$HOME/.claude/scripts/plan-complexity.mjs` (`planComplexity(tasks, {graph, splitAbove})`), reusing `graph-affected.mjs`'s reverse walk. Unit: `test/plan-complexity.test.mjs`, golden fixtures under `test/fixtures/plan-complexity/`.
|
|
@@ -7,9 +7,14 @@ while next=$(bash "$HOME/.claude/lib/plan-todos.sh" next "$TASK_ID"); [ -n "$nex
|
|
|
7
7
|
id=$(printf '%s' "$next" | cut -f1)
|
|
8
8
|
task=$(printf '%s' "$next" | cut -f2)
|
|
9
9
|
bash "$HOME/.claude/lib/plan-todos.sh" start "$TASK_ID" "$id"
|
|
10
|
-
# ...
|
|
10
|
+
# ... RED: write the failing test, run it, keep the JUnit XML / .xcresult / log ...
|
|
11
|
+
bash "$HOME/.claude/lib/plan-todos.sh" tdd-red "$TASK_ID" "$id" "$RED_RESULTS" --tests "$TEST_NAME"
|
|
12
|
+
# ... GREEN: implement, run the suite ...
|
|
13
|
+
bash "$HOME/.claude/lib/plan-todos.sh" tdd-green "$TASK_ID" "$id" "$GREEN_RESULTS"
|
|
11
14
|
if build_passed; then
|
|
12
15
|
bash "$HOME/.claude/lib/plan-todos.sh" complete "$TASK_ID" "$id" "${COMMIT_HASH} · tests:${TEST_COUNT}"
|
|
16
|
+
git -C "$WORKTREE" diff --name-only HEAD~1 HEAD \
|
|
17
|
+
| bash "$HOME/.claude/lib/plan-todos.sh" drift "$TASK_ID" "$id" --files - --not-done "$NOT_DONE"
|
|
13
18
|
else
|
|
14
19
|
bash "$HOME/.claude/lib/plan-todos.sh" fail "$TASK_ID" "$id" "build failed after 3 retries"
|
|
15
20
|
break
|
|
@@ -18,3 +23,37 @@ done
|
|
|
18
23
|
```
|
|
19
24
|
|
|
20
25
|
When disabled, the loop still walks `tasks[]` from `planning-output` - `plan-todos.sh` adds visibility (status, notes, deps-respecting `next`) but the underlying TDD contract is unchanged.
|
|
26
|
+
|
|
27
|
+
## Drift propagation
|
|
28
|
+
|
|
29
|
+
A step that ends off-plan invalidates the plan of every step built on it. `drift` takes the same two signals the scope self-check records for the whole run (`features/scope-check.md`), per step: files the step changed that its `files[]` does not list, and planned items it did not do (`--not-done N`, the entries that go to `scope-check.json` `notDone[]`). `--reason <text>` records drift the two counts do not show, such as an interface whose shape changed.
|
|
30
|
+
|
|
31
|
+
On drift the step gets `drift: {outside, notDone, at}` and every **pending** step that depends on it, directly or transitively, gets `needsReplan: {because, at, reason}`; steps that already started or finished keep their state, and steps on other branches of the graph are untouched. Output: `{drifted, outside, notDone, flagged}`.
|
|
32
|
+
|
|
33
|
+
A flagged step:
|
|
34
|
+
|
|
35
|
+
- is reported by `next` as waiting (`task-graph.mjs` `reason: "needs-replan"`), and named on stderr when nothing else is ready;
|
|
36
|
+
- is refused by `start` and `complete` (exit 2), which name the drifted step;
|
|
37
|
+
- starts again after `plan-todos.sh confirm-replan "$TASK_ID" <todo-id> [note]`, run once its plan has been re-read against what the drifted step actually did (interactive: ask; autopilot: confirm when the drifted files are not in the flagged step's `files[]` and none of its `files[]` were deleted, otherwise stop the loop and log `plan.replan_needed={ids}`).
|
|
38
|
+
|
|
39
|
+
A later drift replaces the flag and its confirmation. `files[]` and `creates[]` come from the planning-output task through `set`, and both count as planned; paths are normalised on both sides (`./a`, `a` and `a//b/..` compare equal). A changed test file is planned when its name covers the stem of a planned file: `a.test.ts`, `a.spec.ts`, `a_test.go`, `ATests.swift`, `ATest.kt` and `test_a.py` cover `a.ts` / `A.swift` / `a.py` in any directory. A step with neither list is judged on `--not-done` and `--reason` only.
|
|
40
|
+
|
|
41
|
+
## TDD evidence
|
|
42
|
+
|
|
43
|
+
`tdd-red` and `tdd-green` read the runner's own results (`lib/tdd-evidence.mjs`): JUnit XML file or directory (Gradle, Surefire, Jest/Vitest junit, `pytest --junitxml`), an `.xcresult` bundle via `xcrun xcresulttool`, that summary exported to JSON, or the xcodebuild / `swift test` log. They write `plan.todos[].tdd`.
|
|
44
|
+
|
|
45
|
+
| Call | Holds when | Refused (exit 2) when |
|
|
46
|
+
|------|-----------|------------------------|
|
|
47
|
+
| `tdd-red <task> <todo> <results> [--tests a,b]` | at least one test fails; with `--tests`, one of the failing tests is a named one | nothing failed, nothing ran, or every failure is in another test; under `enforce`, `--tests` on results that name no failing test (use JUnit XML); `warn` records that run with `red.testsUnverifiable` and a warning |
|
|
48
|
+
| `tdd-red ... --already-passing <reason>` | the run has no failure: the behaviour already exists; the run stands for RED and GREEN, the reason is kept | any test fails |
|
|
49
|
+
| `tdd-green <task> <todo> <results>` | RED is recorded (not needed under test policy `tests-after`), the run has no failure, and it is the RED suite: RED's named tests pass in it, or, without names on either side, it ran at least as many tests | a test still fails, nothing ran, no RED, a RED test is missing, fewer tests than RED |
|
|
50
|
+
|
|
51
|
+
Both refuse results written before the todo's `startedAt`.
|
|
52
|
+
|
|
53
|
+
`complete` then reads `prefs.global.tddEvidence.mode`:
|
|
54
|
+
|
|
55
|
+
- `off`: no check.
|
|
56
|
+
- `warn` (default): a todo without both runs completes with a warning and `tdd.outcome: "unverified"`; Phase 3 notes it.
|
|
57
|
+
- `enforce`: `complete` refuses (exit 2) and the todo stays open.
|
|
58
|
+
|
|
59
|
+
Never blocking, always recorded with `tdd.outcome: "exempt"` and a reason: a `docs` or `chore` todo (type carried from `planning-output`), a `component` task (the component skill short-circuits the TDD path), project test policy `none`, and `complete ... --no-tests "<reason>"` for a repo with no test target. The last one is the todo's own claim, so it is stored with `tdd.selfDeclared: true` and Phase 3 notes it next to the unverified todos.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Feature: Rework model escalation (Phase 2 re-entry, Phase 3 Step 4)
|
|
2
|
+
|
|
3
|
+
**Pattern**: a rework round that dispatches the same model with the same findings tends to produce the same fix. Two signals say the current rung is not enough: the round is the **last** one the rework cap allows, or `review-delta.mjs` reported findings **still present** after the previous round's fix. On either, the fresh dev agent for the next round runs one rung up the model-fallback ladder (`fable -> opus -> sonnet -> haiku`, `features/model-fallback.md`), and only one. The decision is a pure function in `scripts/rework-escalation.mjs`, never a model's judgement.
|
|
4
|
+
|
|
5
|
+
## Prefs
|
|
6
|
+
|
|
7
|
+
`prefs.global.reworkEscalation`:
|
|
8
|
+
|
|
9
|
+
| Field | Default | Meaning |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `reworkEscalation.enabled` | `true` | Master switch. `false` keeps every rework round on the rung the dev dispatch resolved to. |
|
|
12
|
+
| `reworkEscalation.maxTier` | `opus` | Highest rung an escalation may reach. `fable` additionally needs `modelFallback.fableEnabled: true`. |
|
|
13
|
+
|
|
14
|
+
On by default because it fires at most on the rounds where a failure costs the most (the last one, or one that repeats the previous attempt), moves one rung, and stops at `opus`. Every existing cost control outranks it.
|
|
15
|
+
|
|
16
|
+
## The call (before each rework dispatch)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
node $HOME/.claude/scripts/cost-budget-check.mjs --task-id "$TASK_ID" --prefs "$PREFS_FILE" >/dev/null; BUDGET_RC=$?
|
|
20
|
+
STILL=$(jq '.reviewIterations[-1].delta.counts.stillPresent // 0' "$STATE_FILE")
|
|
21
|
+
ESC=$(node $HOME/.claude/scripts/rework-escalation.mjs --iteration "$ITERATION" --max-iterations 3 \
|
|
22
|
+
--still-present "$STILL" --current-tier "$DEV_TIER" --budget-exit "$BUDGET_RC" \
|
|
23
|
+
--prefs "$PREFS_FILE" --state "$STATE_FILE" ${FELL_BACK:+--fallen-back})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`ITERATION` is the rework round about to run (1-based, `retryCount + 1`); `DEV_TIER` is the rung the Phase 2 dev dispatch resolves to without this override (persona and routing, `skills/shared/core/multi-agent/SKILL.md`), so escalations never compound; `FELL_BACK` is set when this run emitted a `model_fallback` metric. Pass `BUDGET_RC` on every call: without `--budget-exit` the budget counts as disabled only when `costBudget.enabled` is not `true`, otherwise the rung holds with `budget-unchecked`. Prefs come from `--prefs`, then `$MULTI_AGENT_PREFS`, then `~/.claude/multi-agent-preferences.json`. `--state` records the result as `reviewIterations[-1].reworkEscalation`, including a refusal and its reason, and in an autopilot run lists an escalation in `decisions[]` (`features/autopilot-decisions.md`). Exit 2 or 3: log `rework.escalation_skipped`, dispatch on `DEV_TIER`; it never blocks the rework.
|
|
27
|
+
|
|
28
|
+
| `reason` | Meaning |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `escalated` | Dispatch on `toTier` |
|
|
31
|
+
| `not-triggered` | Neither the last round nor a still-present finding |
|
|
32
|
+
| `disabled` / `past-cap` | Switch off, or the cap has already stopped Phase 2 |
|
|
33
|
+
| `run-fell-back` | The run degraded; it is never re-upgraded mid-run |
|
|
34
|
+
| `budget-warn` / `budget-exceeded` / `budget-unknown` | `cost-budget-check.mjs` exit 10 / 11 / other |
|
|
35
|
+
| `budget-unchecked` | No `--budget-exit` while `prefs.global.costBudget.enabled` is `true`: an unrun check is no evidence the budget has room |
|
|
36
|
+
| `unknown-tier` / `top-of-ladder` | Not an Anthropic rung, or nothing above it |
|
|
37
|
+
| `fable-off` / `at-ceiling` | The next rung is fable with the rung off, or above `maxTier` |
|
|
38
|
+
|
|
39
|
+
Telemetry: `log-metric.sh "$TASK_ID" 2 rework.escalation escalate= from= to= reason=`.
|
|
40
|
+
|
|
41
|
+
## The escalated dispatch
|
|
42
|
+
|
|
43
|
+
`escalate: true` -> `PHASE_MODEL_OVERRIDE=<toTier>` for that one dev dispatch (the override outranks routing, so the fable and ceiling checks above are the only ones that apply). The agent is **fresh**: no forked history, no conversation. It gets files:
|
|
44
|
+
|
|
45
|
+
- `$WORKTREE/.pipeline/triage-round-$ITERATION.json`: the persisted findings of the review that sent this round back (accepted, with fingerprints)
|
|
46
|
+
- `$WORKTREE/.pipeline/rework-attempt-$((ITERATION-1)).diff`: the prior attempt's change, written by the orchestrator before the dispatch as `git diff <round start>..HEAD` (round 0 starts at the run's base), so the escalated agent sees what was tried without the conversation that tried it
|
|
47
|
+
- `delta.stillPresent[]` quoted first, as Phase 2 re-entry item 7 already requires
|
|
48
|
+
|
|
49
|
+
An escalation is per dispatch: the next round asks again from `DEV_TIER`, so the rework never sits more than one rung above the dev dispatch.
|
|
@@ -33,6 +33,7 @@ Output `{ok, justified, unjustified[], unlisted[], notDone}`. Exit 1 lists `unju
|
|
|
33
33
|
|
|
34
34
|
## Consumers
|
|
35
35
|
|
|
36
|
+
- With plan todos on, Phase 2 applies the same two signals per step as it goes: `plan-todos.sh drift` compares each step's changed files with its planned `files[]` and counts its not-done items, and flags the steps built on a drifted one for re-plan (`features/plan-todos.md`).
|
|
36
37
|
- Phase 3 Step 2.2 renders file reasons, unjustified files and `notDone[]` into the shared reviewer prefix (`features/review-delta.md`).
|
|
37
38
|
- Phase 4 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
|
|
|
@@ -66,7 +66,7 @@ Each type has a standard section skeleton. `A` = always present. `C` = condition
|
|
|
66
66
|
| Acceptance Criteria | `{panel:borderColor=#d4a19e}` |
|
|
67
67
|
| Every other section | `{panel:borderColor=#9eb6d4}` |
|
|
68
68
|
|
|
69
|
-
Each panel closes with `{panel}` on its own line. Task and Bug keep plain `h3.` sections.
|
|
69
|
+
Each panel closes with `{panel}` on its own line. Task and Bug keep plain `h3.` sections. The colours are defaults: `prefs.global.issueTree.storyPanels` (`{story, criteria, other}`, `#rrggbb` each) replaces any of them, and `false` writes the Story with plain `h3.` sections. A value that is not a `#rrggbb` colour keeps the default. The `/multi-agent:analysis-jira` stories read the same setting.
|
|
70
70
|
|
|
71
71
|
Section headings render in `outputLanguage` (e.g. tr: "Detaylı Açıklama", "Kapsam", "Kabul Kriterleri", "Test Senaryoları", "Adımlar", "Beklenen Sonuç", "Gerçekleşen Sonuç", "Ortam", "Ekran Görüntüleri", "API Contract", "Design Reference", "Notlar").
|
|
72
72
|
|
|
@@ -103,6 +103,7 @@ Post-Hoc & Side-Channel:
|
|
|
103
103
|
/multi-agent:build-optimize iOS-only Xcode build perf wrapper → benchmark + analyze + recommend-first .build-benchmark/optimization-plan.md
|
|
104
104
|
/multi-agent:create-jira ["desc"] [figma-url] [swagger-url] Create a Jira Task/Bug/Story matching team conventions (type, sprint, preview + approval)
|
|
105
105
|
/multi-agent:diff-explain Map a Phase 3 triage finding back to specific diff lines
|
|
106
|
+
/multi-agent:postmortem [#id|file.jsonl] Post-mortem of a run from its transcripts: loops, stalls, root-cause hypotheses on file:line (read-only, redacted)
|
|
106
107
|
/multi-agent:estimate <doc|text> Effort range from similar closed work: P25-P75 per role, with the tickets behind it
|
|
107
108
|
/multi-agent:scenario-audit <csv> Test scenarios vs the code: PRESENT / PARTIAL / ABSENT / UNCLEAR, each PRESENT on a resolving file:line
|
|
108
109
|
/multi-agent:graph Build and query the repo code graph (symbols, imports, references), LLM-free
|
|
@@ -102,6 +102,7 @@ Post-Hoc & Side-Channel:
|
|
|
102
102
|
/multi-agent:build-optimize iOS-only Xcode build performance wrapper → benchmark + analiz + recommend-first .build-benchmark/optimization-plan.md
|
|
103
103
|
/multi-agent:create-jira ["açıklama"] [figma-url] [swagger-url] Takım standartlarına uygun Jira Task/Bug/Story oluştur (tip, sprint, önizleme + onay)
|
|
104
104
|
/multi-agent:diff-explain Phase 3 triage bulgusunu diff satırlarına eşle
|
|
105
|
+
/multi-agent:postmortem [#id|dosya.jsonl] Koşunun transkriptlerinden post-mortem: döngüler, takılmalar, dosya:satır dayanaklı kök-neden hipotezleri (salt-okunur, redakte)
|
|
105
106
|
/multi-agent:estimate <doc|metin> Benzer kapanmış işlerden efor aralığı: rol başına P25-P75, dayandığı ticket'larla
|
|
106
107
|
/multi-agent:scenario-audit <csv> Test senaryolarını koda karşı denetle: VAR / KISMEN / YOK / BELİRSİZ, her VAR çözülen bir dosya:satır ile
|
|
107
108
|
/multi-agent:graph Repo kod grafiğini kur ve sorgula (semboller, import'lar, referanslar), LLM'siz
|
|
@@ -402,6 +402,8 @@ Progress line: ` → checking validator validate-planning`
|
|
|
402
402
|
|
|
403
403
|
Non-zero exit fails CLOSED: emit the validator stderr + `errors[]` verbatim, attempt ONE self-correction rework (re-invoke the planner with the errors quoted, overwrite `$PLAN_FILE`), re-run the validator. If it fails again -> HALT the phase (never enter Phase 2 with an invalid plan) with recovery hint: `ERR: plan output failed validate-planning.mjs twice. Inspect $PLAN_FILE against $HOME/.claude/schemas/planning-output.schema.json, then resume with /multi-agent:resume #N.` Record `agent-state.phases["1"].validator` (`pass` | `pass-after-rework` | `halted`).
|
|
404
404
|
|
|
405
|
+
Then `plan-complexity.mjs "$PLAN_FILE" --graph "$GRAPH_PATH" --write` (`$GRAPH_PATH` as in `features/code-graph.md`; unset, the default graph path); split `split[]` tasks (`features/plan-checks.md`).
|
|
406
|
+
|
|
405
407
|
Log: "Phase 1: Plan - {N} tasks created, {M} with architecture review, validator:pass"
|
|
406
408
|
|
|
407
409
|
#### Step 10 - Put the plan on the widget (required)
|
|
@@ -423,9 +425,6 @@ and stored, only invisible.
|
|
|
423
425
|
printf '%s' "$PLAN_JSON" | bash "$HOME/.claude/lib/plan-todos.sh" set "$TASK_ID" -
|
|
424
426
|
```
|
|
425
427
|
|
|
426
|
-
`set` converts a planning-output document itself, so the `tasks[]`-to-`todos[]`
|
|
427
|
-
mapping has one definition, the tested one.
|
|
428
|
-
|
|
429
428
|
Phase 2 (Dev) then iterates with `plan-todos.sh next "$TASK_ID"` until empty, calling `start` before each step and `complete` (with notes) or `fail`/`skip` after. Phase 3 (Review) reads the Todo list to verify all `completed` items map to diff hunks. Phase 5 (Report) renders `list` into the agent-log + PR body.
|
|
430
429
|
|
|
431
430
|
**Why opt-in:** `planning-output.schema.json` already drives Phase 2 dependency order; `plan.todos[]` adds notes, durations and status transitions at the cost of a state write per step. Flip on for visibility into long features.
|
|
@@ -47,13 +47,13 @@ The analysis document is the SOLE design source in Phase 2. Variant choices, pad
|
|
|
47
47
|
|
|
48
48
|
<!-- progress-contract: applied -->
|
|
49
49
|
|
|
50
|
-
**Progress (per `$HOME/.claude/multi-agent-refs/progress-contract.md`):** every non-trivial step emits one `→ <verb> <object>` line at immediate flush. Canonical set for standard TDD: `→ reading <file>`, `→ writing test <name>`, `→ running xcodebuild RED`, `→ writing code <file>`, `→ running xcodebuild GREEN`, `→ committing WIP <summary>`. Component-task dispatch emits the line `→ dispatching create-component <componentName>`
|
|
50
|
+
**Progress (per `$HOME/.claude/multi-agent-refs/progress-contract.md`):** every non-trivial step emits one `→ <verb> <object>` line at immediate flush. Canonical set for standard TDD: `→ reading <file>`, `→ writing test <name>`, `→ running xcodebuild RED`, `→ writing code <file>`, `→ running xcodebuild GREEN`, `→ committing WIP <summary>`. Component-task dispatch emits the line `→ dispatching create-component <componentName>` before the Skill-tool call, then relays the plugin skill's progress lines verbatim under indent ` →`.
|
|
51
51
|
|
|
52
52
|
#### Input contract
|
|
53
53
|
|
|
54
54
|
Phase 2 consumes the Phase 1 output object conforming to `$HOME/.claude/schemas/planning-output.schema.json` - the task graph (`tasks[]` with `id`, `title`, `type`, `files`, and optional `dependsOn` / `acceptanceCriteria`) plus the architecture review notes. Tasks execute in dependency order; the schema's `dependsOn` field drives the ready-task picker.
|
|
55
55
|
|
|
56
|
-
**Plan Todo iteration (opt-in)**: gated by `prefs.global.planTodos.enabled` (default
|
|
56
|
+
**Plan Todo iteration (opt-in)**: gated by `prefs.global.planTodos.enabled` (default `false`). When enabled and Phase 1 Step 11 emitted `plan.todos[]`, iterate via `$HOME/.claude/lib/plan-todos.sh next/start/complete/fail/drift`; disabled, walk `tasks[]`, same TDD contract. Each todo records RED and GREEN runs (`tdd-red` / `tdd-green`); `complete` checks them per `prefs.global.tddEvidence.mode`. Loop + exemptions: `$HOME/.claude/multi-agent-refs/features/plan-todos.md`. A todo with `sourceTag: Reuse` binds the file analysis already found; `Modify` edits in place. Writing a new file over a `Reuse` step is a Locked 11 violation and Phase 3 flags it.
|
|
57
57
|
|
|
58
58
|
**Shadow-Git checkpoints (opt-in)**: gated by `prefs.global.shadowGit.enabled` (default: `false`). When enabled, the orchestrator snapshots the worktree via `$HOME/.claude/lib/shadow-git.sh` so sub-phase rollback is possible without touching the project's real `.git` history. Lifecycle: `shadow-git.sh init` (Phase 0 baseline), `shadow-git.sh snapshot` (per step after `plan-todos complete`), `shadow-git.sh restore <sha> --files` (rollback). Modes: `per-todo-step` (default) or `per-tool-call`. Full wiring + storage cap: `$HOME/.claude/multi-agent-refs/features/shadow-git.md`.
|
|
59
59
|
|
|
@@ -88,6 +88,7 @@ When re-entering from Phase 3:
|
|
|
88
88
|
5. `rejected` items are never touched. Log their IDs + triage reasons for audit only.
|
|
89
89
|
6. After rework, increment `state.phases["2"].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.
|
|
90
90
|
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.
|
|
91
|
+
8. Before each rework dispatch, `rework-escalation.mjs` picks the rung; findings and the prior report go as files (`features/rework-escalation.md`).
|
|
91
92
|
|
|
92
93
|
If the latest iteration has `triage.approved === true` AND `accepted === []`, Phase 2 was entered by mistake - log the anomaly and return to Phase 3.
|
|
93
94
|
|
|
@@ -103,7 +104,7 @@ $HOME/.claude/scripts/log-metric.sh "$TASK_ID" 2 rework.started \
|
|
|
103
104
|
For each task (respecting dependency order), on start and on end: `bash $HOME/.claude/scripts/phase-tracker.sh sub 2 <task_id> "<title>" in_progress|completed|failed`.
|
|
104
105
|
|
|
105
106
|
1. **Update task status: `in_progress` (required - do not skip or defer)**
|
|
106
|
-
- **Why
|
|
107
|
+
- **Why**: the board shows "Todo" until this runs, and status reports and swimlanes read it.
|
|
107
108
|
- **Where it applies**:
|
|
108
109
|
- Jira: transition issue to "In Progress" via REST API (`/rest/api/2/issue/{id}/transitions`)
|
|
109
110
|
- GitHub Projects V2: `updateProjectV2ItemFieldValue` on the Status field (singleSelectOptionId)
|
|
@@ -112,10 +113,7 @@ For each task (respecting dependency order), on start and on end: `bash $HOME/.c
|
|
|
112
113
|
- Re-read the Status field after mutation.
|
|
113
114
|
- If the value is NOT `In Progress` (Jira) / `In Develop` (Projects V2), re-fetch current option IDs and retry ONCE.
|
|
114
115
|
- If verify still fails after retry → log error, do NOT proceed to TDD. The mutation silently failed and continuing would leave the task mis-tracked.
|
|
115
|
-
2. **Multi-submodule awareness**:
|
|
116
|
-
- New types/tokens in the **common** submodule
|
|
117
|
-
- The component itself in the **uicomponents** submodule
|
|
118
|
-
- Work in BOTH submodules in the same task - this is normal and expected
|
|
116
|
+
2. **Multi-submodule awareness**: a task may span the submodules Phase 0 Step 7 detected (e.g. tokens in **common**, the component in **uicomponents**); that is expected.
|
|
119
117
|
3. **TDD cycle** (Launch Agent with `model: "sonnet"`) - gated by `state.testPolicy`: `tdd` = the loop below; `tests-after` = skip RED, author the same tests once green; `none` = author no tests (existing ones never weakened), report "tests not written - project policy" rather than a gap.
|
|
120
118
|
|
|
121
119
|
**RED - Write ONE failing test first:**
|
|
@@ -100,9 +100,9 @@ Persist the totals as `state.diffRisk` (Phase 4 `risk` section, Phase 5, `run-me
|
|
|
100
100
|
| `ui_critical` | 1.5 | path matches `*View.swift`, `*Screen.kt`, `*Configuration.swift`, etc. |
|
|
101
101
|
| `loc_changed` | 1.0 | base sensitivity to total `+/-` lines |
|
|
102
102
|
|
|
103
|
-
**Reviewer prompt injection**: when `$RISK_JSON` is non-empty, the orchestrator builds a `${PRIORITY_FILES}` block (numbered list of top-N files with their score + signals) and injects it once per reviewer.
|
|
103
|
+
**Reviewer prompt injection**: when `$RISK_JSON` is non-empty, the orchestrator builds a `${PRIORITY_FILES}` block (numbered list of top-N files with their score + signals) and injects it once per reviewer. Reviewers (`code-reviewer.md`) treat it as advisory and do not echo it back. Triage does not see the priority list.
|
|
104
104
|
|
|
105
|
-
**Gate behavior**:
|
|
105
|
+
**Gate behavior**: if risk scoring fails (git, parse, validator), continue with no priority hint: reviewers get the full diff in default order. Failures are logged:
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
108
|
[ -z "$RISK_JSON" ] && $HOME/.claude/scripts/log-metric.sh "$TASK_ID" 3 review.diff_risk_skipped reason=$REASON
|
|
@@ -119,11 +119,11 @@ $HOME/.claude/scripts/log-metric.sh "$TASK_ID" 3 review.diff_risk \
|
|
|
119
119
|
files=$(jq '.totals.files' <<< "$RISK_JSON")
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
**Opt-out**: `prefs.global.diffRiskAdvisory = false` skips this step
|
|
122
|
+
**Opt-out**: `prefs.global.diffRiskAdvisory = false` skips this step. Default `true`.
|
|
123
123
|
|
|
124
124
|
#### Step 1.76 - Test-integrity gate (produces BLOCKING findings)
|
|
125
125
|
|
|
126
|
-
Step 1.75's `test_lines_removed` hint is too weak for
|
|
126
|
+
Step 1.75's `test_lines_removed` hint is too weak for a suite made green by deleting tests, so this makes it blocking findings triage must adjudicate. Pure function of the full report. `--out` always writes the file, the empty result included.
|
|
127
127
|
|
|
128
128
|
```bash
|
|
129
129
|
TI_FILE="$WORKTREE/.pipeline/test-integrity.json"
|
|
@@ -136,6 +136,8 @@ TI_COUNT=$(jq -r '.count // 0' "$TI_FILE" 2>/dev/null || echo 0)
|
|
|
136
136
|
|
|
137
137
|
The gate never blocks the phase; it *emits* blocking findings (unreadable input: zero). Feed it the FULL report: `--top` hides a shrinking test file below the cut. **No opt-out**: a run that can switch off its own anti-reward-hacking control cannot be trusted to report a pass.
|
|
138
138
|
|
|
139
|
+
**TDD evidence**: `node $HOME/.claude/lib/tdd-evidence.mjs unverified --state "$STATE_FILE"` lists todos closed without RED/GREEN or by `--no-tests`, as reviewer notes, not findings.
|
|
140
|
+
|
|
139
141
|
#### Step 1.761 - Owned-path gate (BLOCKING findings)
|
|
140
142
|
|
|
141
143
|
Phase 2 Gate 0 again, same base:
|
|
@@ -146,7 +148,7 @@ node $HOME/.claude/scripts/owned-path-gate.mjs --repo "$WORKTREE" --base "origin
|
|
|
146
148
|
--state "$STATE_FILE" --out "$OP_FILE"
|
|
147
149
|
```
|
|
148
150
|
|
|
149
|
-
`--out` drops the old file; exit 2 writes none. A missing `$OP_FILE` is a gate error, never "no findings" (`review-decision-gate.mjs` exit 3 under the gates). Findings merge at Step 3.0. Contract: `features/repo-profile.md`. Step 1.762: `features/valven.md`.
|
|
151
|
+
`--out` drops the old file; exit 2 writes none. A missing `$OP_FILE` is a gate error, never "no findings" (`review-decision-gate.mjs` exit 3 under the gates). Findings merge at Step 3.0. Contract: `features/repo-profile.md`. Step 1.762: `features/valven.md`; opt-in 1.763: `features/mutation-probe.md`.
|
|
150
152
|
|
|
151
153
|
#### Step 1.77 - Reviewer scope (cost gate)
|
|
152
154
|
|
|
@@ -378,11 +380,11 @@ ANON=$(jq -n --argjson r "$REVIEWERS_JSON" --arg t "$TASK_ID" --argjson i "$ITER
|
|
|
378
380
|
|
|
379
381
|
`$REVIEWERS_JSON` is `state.reviewIterations[i].reviewers`. Findings come back with `foundBy: "Source A|B|C"` and every identity key removed. Persist the map to `state.reviewIterations[i].anonymizationMap` for Phase 5 per-reviewer telemetry, and **never put the map in a prompt**.
|
|
380
382
|
|
|
381
|
-
Then append the deterministic findings (Steps 1.76
|
|
383
|
+
Then append the deterministic findings (Steps 1.76-1.763, 2.7):
|
|
382
384
|
|
|
383
385
|
```bash
|
|
384
386
|
SA_FILE="$WORKTREE/.pipeline/security-audit-$ITERATION.json"
|
|
385
|
-
MERGED=$(printf '%s' "$ANON" | cat - "$TI_FILE" "$OP_FILE" "$VG_FILE" "$SA_FILE" 2>/dev/null \
|
|
387
|
+
MERGED=$(printf '%s' "$ANON" | cat - "$TI_FILE" "$OP_FILE" "$VG_FILE" "$MP_FILE" "$SA_FILE" 2>/dev/null \
|
|
386
388
|
| jq -s '.[0] + ([.[1:][] | .findings // []] | add // [])')
|
|
387
389
|
```
|
|
388
390
|
|
|
@@ -390,7 +392,7 @@ Deterministic findings keep their `tag` (`test_integrity`, `owned_path`, `valven
|
|
|
390
392
|
|
|
391
393
|
##### 3.1 Short-circuit: no findings
|
|
392
394
|
|
|
393
|
-
If **merged** findings `length === 0`, **skip triage**: write empty result `{"accepted": [], "deferred": [], "rejected": [], "approved": true}`, log, proceed to Phase 3.
|
|
395
|
+
If **merged** findings `length === 0`, **skip triage**: write empty result `{"accepted": [], "deferred": [], "rejected": [], "approved": true}`, log, proceed to Phase 3. This is the merged count from 3.0: zero reviewer findings plus any gate finding must NOT short-circuit.
|
|
394
396
|
|
|
395
397
|
**Autopilot** (or `MULTI_AGENT_UNATTENDED=1`): the triage output, this empty one too, goes through `verify-citations.mjs "$TRIAGE_FILE" --repo "$WORKTREE" --worktree --state "$STATE_FILE"` (every bucket, each `quote` against its line, pre-existing claims against the base commit): `$HOME/.claude/multi-agent-refs/features/unattended-gates.md` section 5. Then `review-decision-gate.mjs` (end of 3.7).
|
|
396
398
|
|
|
@@ -574,11 +576,11 @@ Merge the JSON into `state.reviewIterations[-1].delta` via `write-state.mjs`; em
|
|
|
574
576
|
|
|
575
577
|
#### Step 4 - Consensus + Action (triage-driven)
|
|
576
578
|
|
|
577
|
-
If `triage.consensus.verdict` is `split` or `unverified`, surface `consensus.disagreements[]` to the user before acting: interactive modes show the split and ask whether to treat the unverified agreement as a pass (picker-contract: Trust / Re-review / Treat-as-blocking); autopilot logs `review.consensus=<verdict
|
|
579
|
+
If `triage.consensus.verdict` is `split` or `unverified`, surface `consensus.disagreements[]` to the user before acting: interactive modes show the split and ask whether to treat the unverified agreement as a pass (picker-contract: Trust / Re-review / Treat-as-blocking); autopilot logs `review.consensus=<verdict>`, proceeds on the triage verdict, recorded per `features/autopilot-decisions.md`. Never silently average a split into a pass.
|
|
578
580
|
|
|
579
581
|
Act **only on triage.accepted**:
|
|
580
582
|
|
|
581
|
-
- **accepted.blocking** → back to Phase 2 (max 3 iterations,
|
|
583
|
+
- **accepted.blocking** → back to Phase 2 (max 3 iterations, rung per `features/rework-escalation.md`, reflection prompt cites 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."
|
|
582
584
|
- **accepted.important** → fix and re-review
|
|
583
585
|
- **accepted.suggestion** → apply if reasonable
|
|
584
586
|
- **deferred** → append to Phase 5 report as "follow-up items" (do not block)
|
|
@@ -595,7 +597,7 @@ node $HOME/.claude/scripts/learnings-ledger.mjs add --kind fact \
|
|
|
595
597
|
--scope "<file-or-area glob from the finding>" --task "$TASK_ID"
|
|
596
598
|
```
|
|
597
599
|
|
|
598
|
-
Statement
|
|
600
|
+
Statement: the root cause, not the symptom ("force-unwrapped optional in async callback path", not "fixed crash in LoginView"). Always pass `--diagnosis`, the causal why: a bare outcome does not prevent recurrence. Dedup built in (exit 2 = already stored). Lessons re-enter Phase 1 + triage via `learnings-ledger.mjs brief` (renders diagnosis as "(why: ...)").
|
|
599
601
|
|
|
600
602
|
Progress line: ` → writing lesson to learnings ledger ({N} entries)`
|
|
601
603
|
|
|
@@ -170,6 +170,8 @@ Skipped sections: when `planTodos.enabled` is false or no `plan.todos[]` was emi
|
|
|
170
170
|
|
|
171
171
|
(the last three columns come from `reviewIterations[i].delta.counts` (Phase 3 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}`)
|
|
172
172
|
|
|
173
|
+
(autopilot: `decisions-log.mjs render --state "$STATE_FILE"` output verbatim; own heading, nothing when empty)
|
|
174
|
+
|
|
173
175
|
## Files Changed
|
|
174
176
|
|
|
175
177
|
(git diff --stat output)
|
|
@@ -317,8 +319,6 @@ bash $HOME/.claude/scripts/memory-save.sh "$PROJECT_ROOT" --from-json memory-syn
|
|
|
317
319
|
- **user**: only if the user disclosed new context about their role, expertise, or team layout
|
|
318
320
|
- **Otherwise return `[]`** - blank is the correct default. Do not save derivable facts (code structure, git history, command outputs).
|
|
319
321
|
|
|
320
|
-
Zero new memories is normal and expected. A typical run produces one or none.
|
|
321
|
-
|
|
322
322
|
Tracker: `phase-tracker.sh sub 5 3 "Knowledge+Memory" completed`.
|
|
323
323
|
|
|
324
324
|
Log: "Phase 5: Knowledge captured ({N} entries to {files})"
|
|
@@ -335,7 +335,7 @@ If user does not respond at the channels multi-select menu within 30 minutes (wa
|
|
|
335
335
|
4. Session exits cleanly. State persisted: `state.currentPhase=7, state.waitingFor="user-channels-choice", state.channelsTimeout=true`.
|
|
336
336
|
5. Resume contract: `/multi-agent:resume <task-id>` re-opens the channels menu with the original state bundle (pipeline log, PR metadata, prefs pre-ticks).
|
|
337
337
|
|
|
338
|
-
Rationale:
|
|
338
|
+
Rationale: posting defaults after a timeout would publish text nobody approved.
|
|
339
339
|
|
|
340
340
|
---
|
|
341
341
|
|