@mmerterden/multi-agent-pipeline 14.0.0 → 14.1.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 +85 -0
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/log/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/resume/SKILL.md +1 -0
- package/pipeline/commands/multi-agent/status/SKILL.md +1 -1
- package/pipeline/commands/multi-agent/sync/SKILL.md +2 -2
- package/pipeline/multi-agent-refs/features/stack-skill-routing.md +72 -0
- package/pipeline/multi-agent-refs/features/worktree-finalize.md +66 -0
- package/pipeline/multi-agent-refs/phases/phase-3-dev.md +4 -4
- package/pipeline/multi-agent-refs/phases/phase-6-commit.md +17 -4
- package/pipeline/multi-agent-refs/phases/phase-7-report.md +4 -1
- package/pipeline/preferences-template.json +2 -1
- package/pipeline/schemas/agent-state.schema.json +13 -0
- package/pipeline/schemas/criteria-manifest.schema.json +223 -53
- package/pipeline/schemas/prefs.schema.json +5 -0
- package/pipeline/schemas/token-budget.json +2 -2
- package/pipeline/scripts/gc-worktrees.sh +3 -2
- package/pipeline/scripts/migrate-prefs.mjs +1 -0
- package/pipeline/scripts/render-work-summary.sh +51 -3
- package/pipeline/scripts/skill-conformance.mjs +10 -0
- package/pipeline/scripts/test-integrity-gate.mjs +10 -2
- package/pipeline/scripts/worktree-finalize.sh +299 -0
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +2 -2
|
@@ -16,13 +16,21 @@
|
|
|
16
16
|
"findings"
|
|
17
17
|
],
|
|
18
18
|
"properties": {
|
|
19
|
-
"schemaVersion": {
|
|
19
|
+
"schemaVersion": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"const": "1.0.0"
|
|
22
|
+
},
|
|
20
23
|
"generatedFor": {
|
|
21
24
|
"type": "object",
|
|
22
25
|
"additionalProperties": true,
|
|
23
26
|
"properties": {
|
|
24
|
-
"repoRoot": {
|
|
25
|
-
|
|
27
|
+
"repoRoot": {
|
|
28
|
+
"type": "string"
|
|
29
|
+
},
|
|
30
|
+
"changedFileCount": {
|
|
31
|
+
"type": "integer",
|
|
32
|
+
"minimum": 0
|
|
33
|
+
}
|
|
26
34
|
}
|
|
27
35
|
},
|
|
28
36
|
"ledger": {
|
|
@@ -41,7 +49,10 @@
|
|
|
41
49
|
"enum": ["absent", "empty", "declared"],
|
|
42
50
|
"description": "Three distinct states, deliberately not collapsed. absent = no telemetry.skillCalls[] key at all. empty = the key exists and is []. declared = at least one entry. Rendering absent as 'nothing was applied' would make the completeness claim unfalsifiable."
|
|
43
51
|
},
|
|
44
|
-
"entryCount": {
|
|
52
|
+
"entryCount": {
|
|
53
|
+
"type": "integer",
|
|
54
|
+
"minimum": 0
|
|
55
|
+
},
|
|
45
56
|
"entriesMissingTargetFiles": {
|
|
46
57
|
"type": "integer",
|
|
47
58
|
"minimum": 0,
|
|
@@ -49,16 +60,39 @@
|
|
|
49
60
|
},
|
|
50
61
|
"declaredButNotResolved": {
|
|
51
62
|
"type": "array",
|
|
52
|
-
"items": {
|
|
63
|
+
"items": {
|
|
64
|
+
"type": "string"
|
|
65
|
+
},
|
|
53
66
|
"description": "Skills Phase 3 claims it used that the resolver could not bind to any changed file. Flagged, not trusted."
|
|
54
67
|
},
|
|
55
|
-
"note": {
|
|
68
|
+
"note": {
|
|
69
|
+
"type": "string"
|
|
70
|
+
},
|
|
71
|
+
"routedByToolkit": {
|
|
72
|
+
"type": "array",
|
|
73
|
+
"description": "Self-report entries a stack toolkit's own index skill chose (Phase 3 step 9 stamps routedBy). Reported separately because 'the project's toolkit says this governs this task' is a stronger statement than 'the model opened it'. It carries no extra weight in the coverage maths - the deterministic resolver stays primary either way.",
|
|
74
|
+
"items": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"additionalProperties": false,
|
|
77
|
+
"properties": {
|
|
78
|
+
"skill": {
|
|
79
|
+
"type": "string"
|
|
80
|
+
},
|
|
81
|
+
"routedBy": {
|
|
82
|
+
"type": "string"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
56
87
|
}
|
|
57
88
|
},
|
|
58
89
|
"languages": {
|
|
59
90
|
"type": "object",
|
|
60
91
|
"description": "Language census of the diff by file extension, the deterministic substitute for Phase 1 detectedStack (which does not exist in the dev modes). Describes what the diff CONTAINS rather than what the repo is nominally built in.",
|
|
61
|
-
"additionalProperties": {
|
|
92
|
+
"additionalProperties": {
|
|
93
|
+
"type": "integer",
|
|
94
|
+
"minimum": 1
|
|
95
|
+
}
|
|
62
96
|
},
|
|
63
97
|
"registries": {
|
|
64
98
|
"type": "array",
|
|
@@ -67,27 +101,60 @@
|
|
|
67
101
|
"additionalProperties": true,
|
|
68
102
|
"required": ["name", "totalRules", "selectedRules", "droppedRules", "loadedAs"],
|
|
69
103
|
"properties": {
|
|
70
|
-
"name": {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
|
|
104
|
+
"name": {
|
|
105
|
+
"type": "string"
|
|
106
|
+
},
|
|
107
|
+
"rulesPath": {
|
|
108
|
+
"type": "string"
|
|
109
|
+
},
|
|
110
|
+
"version": {
|
|
111
|
+
"type": ["string", "null"]
|
|
112
|
+
},
|
|
113
|
+
"totalRules": {
|
|
114
|
+
"type": "integer",
|
|
115
|
+
"minimum": 0
|
|
116
|
+
},
|
|
117
|
+
"selectedRules": {
|
|
118
|
+
"type": "integer",
|
|
119
|
+
"minimum": 0
|
|
120
|
+
},
|
|
121
|
+
"droppedRules": {
|
|
122
|
+
"type": "integer",
|
|
123
|
+
"minimum": 0
|
|
124
|
+
},
|
|
76
125
|
"droppedReasons": {
|
|
77
126
|
"type": "object",
|
|
78
|
-
"additionalProperties": {
|
|
127
|
+
"additionalProperties": {
|
|
128
|
+
"type": "integer",
|
|
129
|
+
"minimum": 1
|
|
130
|
+
},
|
|
79
131
|
"description": "Reason to count. A dropped rule is reported, never silent: silently narrowing the rule set is indistinguishable from the code passing it."
|
|
80
132
|
},
|
|
81
133
|
"scopeDeclared": {
|
|
82
134
|
"type": "boolean",
|
|
83
135
|
"description": "False means the registry did not declare languages or paths. Such a registry loads as reference-only and contributes no rule IDs, because applying an undeclared rule set to an unknown language manufactures findings and buries the real ones."
|
|
84
136
|
},
|
|
85
|
-
"scopeLanguages": {
|
|
86
|
-
|
|
87
|
-
|
|
137
|
+
"scopeLanguages": {
|
|
138
|
+
"type": "array",
|
|
139
|
+
"items": {
|
|
140
|
+
"type": "string"
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"languagesMatched": {
|
|
144
|
+
"type": "array",
|
|
145
|
+
"items": {
|
|
146
|
+
"type": "string"
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
"inScopeFileCount": {
|
|
150
|
+
"type": "integer",
|
|
151
|
+
"minimum": 0
|
|
152
|
+
},
|
|
88
153
|
"notCovered": {
|
|
89
154
|
"type": "object",
|
|
90
|
-
"additionalProperties": {
|
|
155
|
+
"additionalProperties": {
|
|
156
|
+
"type": "string"
|
|
157
|
+
},
|
|
91
158
|
"description": "Languages the registry explicitly disclaims. Quoted into the coverage gap so the report names WHY nothing applied."
|
|
92
159
|
},
|
|
93
160
|
"loadedAs": {
|
|
@@ -95,21 +162,34 @@
|
|
|
95
162
|
"enum": ["rule-checklist", "reference-only", "not-applicable"],
|
|
96
163
|
"description": "rule-checklist = its rule IDs entered the denominator. reference-only = readable context, no IDs (undeclared scope). not-applicable = in scope by language but no changed file matched its paths."
|
|
97
164
|
},
|
|
98
|
-
"referenceOnlyReason": {
|
|
165
|
+
"referenceOnlyReason": {
|
|
166
|
+
"type": "string"
|
|
167
|
+
},
|
|
99
168
|
"toolchains": {
|
|
100
169
|
"type": "array",
|
|
101
170
|
"items": {
|
|
102
171
|
"type": "object",
|
|
103
172
|
"additionalProperties": false,
|
|
104
173
|
"properties": {
|
|
105
|
-
"tool": {
|
|
106
|
-
|
|
107
|
-
|
|
174
|
+
"tool": {
|
|
175
|
+
"type": "string"
|
|
176
|
+
},
|
|
177
|
+
"wired": {
|
|
178
|
+
"type": "boolean"
|
|
179
|
+
},
|
|
180
|
+
"configCandidates": {
|
|
181
|
+
"type": "array",
|
|
182
|
+
"items": {
|
|
183
|
+
"type": "string"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
108
186
|
}
|
|
109
187
|
},
|
|
110
188
|
"description": "Linters the registry delegates its mechanical rules to. An unwired toolchain is itself a finding: those rules are unverified, and a review reporting no violations for them is reporting that nothing was measured."
|
|
111
189
|
},
|
|
112
|
-
"exceptionMarker": {
|
|
190
|
+
"exceptionMarker": {
|
|
191
|
+
"type": ["string", "null"]
|
|
192
|
+
}
|
|
113
193
|
}
|
|
114
194
|
}
|
|
115
195
|
},
|
|
@@ -120,9 +200,15 @@
|
|
|
120
200
|
"type": "object",
|
|
121
201
|
"additionalProperties": false,
|
|
122
202
|
"properties": {
|
|
123
|
-
"name": {
|
|
124
|
-
|
|
125
|
-
|
|
203
|
+
"name": {
|
|
204
|
+
"type": "string"
|
|
205
|
+
},
|
|
206
|
+
"path": {
|
|
207
|
+
"type": "string"
|
|
208
|
+
},
|
|
209
|
+
"reason": {
|
|
210
|
+
"type": "string"
|
|
211
|
+
}
|
|
126
212
|
}
|
|
127
213
|
}
|
|
128
214
|
},
|
|
@@ -134,11 +220,28 @@
|
|
|
134
220
|
"additionalProperties": false,
|
|
135
221
|
"required": ["ruleId", "registry", "severity", "enforcement", "files"],
|
|
136
222
|
"properties": {
|
|
137
|
-
"ruleId": {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
"
|
|
223
|
+
"ruleId": {
|
|
224
|
+
"type": "string",
|
|
225
|
+
"minLength": 1
|
|
226
|
+
},
|
|
227
|
+
"registry": {
|
|
228
|
+
"type": "string"
|
|
229
|
+
},
|
|
230
|
+
"severity": {
|
|
231
|
+
"type": "string",
|
|
232
|
+
"enum": ["blocking", "important", "suggestion"]
|
|
233
|
+
},
|
|
234
|
+
"enforcement": {
|
|
235
|
+
"type": "string",
|
|
236
|
+
"enum": ["format", "lint", "scan", "judgement"]
|
|
237
|
+
},
|
|
238
|
+
"files": {
|
|
239
|
+
"type": "array",
|
|
240
|
+
"items": {
|
|
241
|
+
"type": "string"
|
|
242
|
+
},
|
|
243
|
+
"minItems": 1
|
|
244
|
+
},
|
|
142
245
|
"statusDeclared": {
|
|
143
246
|
"type": "boolean",
|
|
144
247
|
"description": "Whether the rule carried an explicit status. No shipped registry populates status today, so a missing one means active and is recorded rather than assumed."
|
|
@@ -146,7 +249,10 @@
|
|
|
146
249
|
}
|
|
147
250
|
}
|
|
148
251
|
},
|
|
149
|
-
"selectedRuleCount": {
|
|
252
|
+
"selectedRuleCount": {
|
|
253
|
+
"type": "integer",
|
|
254
|
+
"minimum": 0
|
|
255
|
+
},
|
|
150
256
|
"coverage": {
|
|
151
257
|
"type": "object",
|
|
152
258
|
"additionalProperties": false,
|
|
@@ -157,10 +263,24 @@
|
|
|
157
263
|
"declaredGaps"
|
|
158
264
|
],
|
|
159
265
|
"properties": {
|
|
160
|
-
"changedFiles": {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
266
|
+
"changedFiles": {
|
|
267
|
+
"type": "integer",
|
|
268
|
+
"minimum": 0
|
|
269
|
+
},
|
|
270
|
+
"filesWithRuleCriteria": {
|
|
271
|
+
"type": "integer",
|
|
272
|
+
"minimum": 0
|
|
273
|
+
},
|
|
274
|
+
"filesWithoutRuleCriteria": {
|
|
275
|
+
"type": "integer",
|
|
276
|
+
"minimum": 0
|
|
277
|
+
},
|
|
278
|
+
"uncoveredFiles": {
|
|
279
|
+
"type": "array",
|
|
280
|
+
"items": {
|
|
281
|
+
"type": "string"
|
|
282
|
+
}
|
|
283
|
+
},
|
|
164
284
|
"declaredGaps": {
|
|
165
285
|
"type": "array",
|
|
166
286
|
"description": "A language in the diff that no registry covers. Declared explicitly so 'no rule applied' is distinguishable from 'every rule passed'.",
|
|
@@ -169,11 +289,25 @@
|
|
|
169
289
|
"additionalProperties": false,
|
|
170
290
|
"required": ["language", "fileCount", "reason"],
|
|
171
291
|
"properties": {
|
|
172
|
-
"language": {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
"
|
|
176
|
-
|
|
292
|
+
"language": {
|
|
293
|
+
"type": "string"
|
|
294
|
+
},
|
|
295
|
+
"fileCount": {
|
|
296
|
+
"type": "integer",
|
|
297
|
+
"minimum": 1
|
|
298
|
+
},
|
|
299
|
+
"files": {
|
|
300
|
+
"type": "array",
|
|
301
|
+
"items": {
|
|
302
|
+
"type": "string"
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
"reason": {
|
|
306
|
+
"type": "string"
|
|
307
|
+
},
|
|
308
|
+
"consequence": {
|
|
309
|
+
"type": "string"
|
|
310
|
+
}
|
|
177
311
|
}
|
|
178
312
|
}
|
|
179
313
|
}
|
|
@@ -183,9 +317,22 @@
|
|
|
183
317
|
"type": "object",
|
|
184
318
|
"additionalProperties": false,
|
|
185
319
|
"properties": {
|
|
186
|
-
"guides": {
|
|
187
|
-
|
|
188
|
-
|
|
320
|
+
"guides": {
|
|
321
|
+
"type": "array",
|
|
322
|
+
"items": {
|
|
323
|
+
"type": "string"
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
"dropped": {
|
|
327
|
+
"type": "array",
|
|
328
|
+
"items": {
|
|
329
|
+
"type": "string"
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
"cap": {
|
|
333
|
+
"type": "integer",
|
|
334
|
+
"minimum": 1
|
|
335
|
+
}
|
|
189
336
|
},
|
|
190
337
|
"description": "In-repo convention guides on the changed files' directory chains. One criteria source inside this manifest, not a parallel list."
|
|
191
338
|
},
|
|
@@ -197,13 +344,32 @@
|
|
|
197
344
|
"additionalProperties": false,
|
|
198
345
|
"required": ["severity", "file", "line", "issue", "fix"],
|
|
199
346
|
"properties": {
|
|
200
|
-
"severity": {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
"
|
|
205
|
-
|
|
206
|
-
|
|
347
|
+
"severity": {
|
|
348
|
+
"type": "string",
|
|
349
|
+
"enum": ["blocking", "important", "suggestion"]
|
|
350
|
+
},
|
|
351
|
+
"file": {
|
|
352
|
+
"type": "string",
|
|
353
|
+
"minLength": 1
|
|
354
|
+
},
|
|
355
|
+
"line": {
|
|
356
|
+
"type": "integer",
|
|
357
|
+
"minimum": 0
|
|
358
|
+
},
|
|
359
|
+
"issue": {
|
|
360
|
+
"type": "string",
|
|
361
|
+
"minLength": 4
|
|
362
|
+
},
|
|
363
|
+
"fix": {
|
|
364
|
+
"type": "string",
|
|
365
|
+
"minLength": 4
|
|
366
|
+
},
|
|
367
|
+
"ruleId": {
|
|
368
|
+
"type": "string"
|
|
369
|
+
},
|
|
370
|
+
"criteriaSource": {
|
|
371
|
+
"type": "string"
|
|
372
|
+
}
|
|
207
373
|
}
|
|
208
374
|
}
|
|
209
375
|
},
|
|
@@ -215,8 +381,12 @@
|
|
|
215
381
|
"additionalProperties": false,
|
|
216
382
|
"required": ["path", "existed"],
|
|
217
383
|
"properties": {
|
|
218
|
-
"path": {
|
|
219
|
-
|
|
384
|
+
"path": {
|
|
385
|
+
"type": "string"
|
|
386
|
+
},
|
|
387
|
+
"existed": {
|
|
388
|
+
"type": "boolean"
|
|
389
|
+
}
|
|
220
390
|
}
|
|
221
391
|
}
|
|
222
392
|
},
|
|
@@ -487,6 +487,11 @@
|
|
|
487
487
|
"type": "boolean",
|
|
488
488
|
"default": true,
|
|
489
489
|
"description": "Phase 6 policy. When true, commit + push with rebase-retry up to pushRetryMax. When false, single push attempt, pause on fail."
|
|
490
|
+
},
|
|
491
|
+
"worktreeAutoRemoveOnPr": {
|
|
492
|
+
"type": "boolean",
|
|
493
|
+
"default": true,
|
|
494
|
+
"description": "v14.1.0+ - Phase 6 policy. When true, a task's worktree is removed once its PR is open: artefacts (agent-state, triage output, .pipeline/, build+test logs) are salvaged into the log dir first, then `git worktree remove` runs. The BRANCH is kept and is NOT checked out, so the user's own HEAD and uncommitted work are untouched. Default true because the complaint it answers is forgetting to clean up, and every destructive path is gated: a worktree with real uncommitted changes, an unpushed HEAD, --local mode, or a cwd inside the worktree all skip with a reason. Set false to keep worktrees until /multi-agent:kill or :garbage-collect."
|
|
490
495
|
}
|
|
491
496
|
}
|
|
492
497
|
},
|
|
@@ -36,6 +36,6 @@
|
|
|
36
36
|
"warn_tokens": 5600
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
|
-
"total_max_tokens":
|
|
40
|
-
"note": "Token estimate = ceil(chars / 4). Per-phase budget rule: warn = current+10% (rounded to nearest 50), max = current+25%. Gives ~6 edit cycles of headroom before warn trips - intentionally quiet under normal maintenance, loud when a phase grows unusually. Only the active phase is loaded (lazy). Recalibrated at v10.0.0 after the validator/consistency/simplifier/lesson gate contracts landed in phases 1-4. Recalibrated again at v10.9.0 after the verify-by-test (Phase 4 Step 3.7), update-check (Phase 0 Step 0.6), immutable-test (Phase 3 GREEN) and redTests re-entry contracts landed - Step 3.7 prose was compressed to a pointer into refs/features/verify-by-test.md before the recalibration. Total bumped 50000 -> 51000 at v12.5.0 after the worktree residue/traversal-prune contract (Phase 0 + Phase 5 heal) and the Reflexion causal-diagnosis contract (Phase 4 lesson memory) landed; the prose was compressed first (161 tokens reclaimed) and every per-phase max still passes - only the aggregate needed room. Recalibrated again at v13.6.0 after the install-relative path correction: an instruction that names `pipeline/scripts/x` resolves only from a repo checkout, and a run happens in the user's worktree, so 157 references across these docs moved to `$HOME/.claude/...` at +5 bytes each - 196 tokens of pure correctness cost. Same discipline as before: prose was compressed FIRST (149 tokens reclaimed, by pointing Phase 1's Figma tier table at the Phase 0 probe that already resolved it and Phase 4's Codex constraints at the always-loaded AGENTS.md block), and only then were the budgets moved. Five warn lines had been permanently amber, which makes the amber tier useless as a signal, so every warn was reset to the documented current+10% and the four maxes that the new warn would have collided with were reset to current+25%. Aggregate 51000 -> 51500. Total bumped 51500 -> 52200 at v14.0.0 after Phase 4 Review entered the four --dev mode phase sets and the criteria-resolution contract (Step 1.78) landed. Same discipline as every prior bump: prose was compressed FIRST, 820 tokens reclaimed, before the number moved. Two of those compressions are structural rather than cosmetic - the hardcoded SwiftUI interaction list in Step 1.5 and the SwiftUI convention paragraph in Step 2.8 were transcriptions of rules that now live in a scoped registry, so keeping them here would have re-created the drift this release exists to remove, and the third moved the Step 1.78 full contract into refs/features/skill-conformance.md leaving a pointer. What remains is contract text that cannot be inferred: the manifest's four consumer-visible parts, the conformance checklist the reviewers must return, and the fail-closed semantics. Every per-phase max still passes (phase-4 12405/14750); only the aggregate needed room."
|
|
39
|
+
"total_max_tokens": 52700,
|
|
40
|
+
"note": "Token estimate = ceil(chars / 4). Per-phase budget rule: warn = current+10% (rounded to nearest 50), max = current+25%. Gives ~6 edit cycles of headroom before warn trips - intentionally quiet under normal maintenance, loud when a phase grows unusually. Only the active phase is loaded (lazy). Recalibrated at v10.0.0 after the validator/consistency/simplifier/lesson gate contracts landed in phases 1-4. Recalibrated again at v10.9.0 after the verify-by-test (Phase 4 Step 3.7), update-check (Phase 0 Step 0.6), immutable-test (Phase 3 GREEN) and redTests re-entry contracts landed - Step 3.7 prose was compressed to a pointer into refs/features/verify-by-test.md before the recalibration. Total bumped 50000 -> 51000 at v12.5.0 after the worktree residue/traversal-prune contract (Phase 0 + Phase 5 heal) and the Reflexion causal-diagnosis contract (Phase 4 lesson memory) landed; the prose was compressed first (161 tokens reclaimed) and every per-phase max still passes - only the aggregate needed room. Recalibrated again at v13.6.0 after the install-relative path correction: an instruction that names `pipeline/scripts/x` resolves only from a repo checkout, and a run happens in the user's worktree, so 157 references across these docs moved to `$HOME/.claude/...` at +5 bytes each - 196 tokens of pure correctness cost. Same discipline as before: prose was compressed FIRST (149 tokens reclaimed, by pointing Phase 1's Figma tier table at the Phase 0 probe that already resolved it and Phase 4's Codex constraints at the always-loaded AGENTS.md block), and only then were the budgets moved. Five warn lines had been permanently amber, which makes the amber tier useless as a signal, so every warn was reset to the documented current+10% and the four maxes that the new warn would have collided with were reset to current+25%. Aggregate 51000 -> 51500. Total bumped 51500 -> 52200 at v14.0.0 after Phase 4 Review entered the four --dev mode phase sets and the criteria-resolution contract (Step 1.78) landed. Same discipline as every prior bump: prose was compressed FIRST, 820 tokens reclaimed, before the number moved. Two of those compressions are structural rather than cosmetic - the hardcoded SwiftUI interaction list in Step 1.5 and the SwiftUI convention paragraph in Step 2.8 were transcriptions of rules that now live in a scoped registry, so keeping them here would have re-created the drift this release exists to remove, and the third moved the Step 1.78 full contract into refs/features/skill-conformance.md leaving a pointer. What remains is contract text that cannot be inferred: the manifest's four consumer-visible parts, the conformance checklist the reviewers must return, and the fail-closed semantics. Every per-phase max still passes (phase-4 12405/14750); only the aggregate needed room. Total bumped 52200 -> 52700 at v14.1.0 after two more contracts landed: stack skill routing (Phase 3 pre-flight step 9) and worktree finalize (Phase 6 step 9). Compression came first, as always, and twice: 224 tokens out of Phase 3 by pointing its criteria-ledger and routing steps at their feature files instead of restating them, and 190 out of Phase 6 by moving the finalize contract into refs/features/worktree-finalize.md and leaving the invocation plus the exit-3 semantics. Both new contracts follow the pattern the earlier ones set: the phase doc carries the call and the decision, the feature file carries the reasoning, and the feature files are outside this budget because it loops only the eight phase-N-* keys. Every per-phase max still passes (phase-3 7677/8950, phase-6 5223/6150 and both under warn); only the aggregate needed room."
|
|
41
41
|
}
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
# 4. missing residue guard -> ensures `.worktrees/` is in
|
|
16
16
|
# .git/info/exclude so the gitlink class cannot re-occur.
|
|
17
17
|
#
|
|
18
|
-
# Registered, healthy worktrees are NEVER touched
|
|
19
|
-
#
|
|
18
|
+
# Registered, healthy worktrees are NEVER touched. Finishing a task removes its
|
|
19
|
+
# own worktree in Phase 6 (worktree-finalize, v14.1.0+); killing one is
|
|
20
|
+
# /multi-agent:kill. This sweep only reaps orphans neither of those left behind.
|
|
20
21
|
#
|
|
21
22
|
# SAFE BY DEFAULT: dry-run. Deletes nothing until you pass --yes.
|
|
22
23
|
#
|
|
@@ -74,6 +74,42 @@ if [ -n "$WORKTREE" ]; then
|
|
|
74
74
|
[ -f "$WORKTREE/phase-tracker.json" ] && TRACKER_FILE="$WORKTREE/phase-tracker.json"
|
|
75
75
|
fi
|
|
76
76
|
|
|
77
|
+
# Fall back to the salvaged artefacts when the worktree is gone. Phase 6 removes it
|
|
78
|
+
# once the PR is open (worktree-finalize), and this script used to resolve state
|
|
79
|
+
# ONLY from the worktree - so it exited 2 and the whole Work Summary silently
|
|
80
|
+
# disappeared from the PR body and the Jira comment. render-agent-log-cost.sh has
|
|
81
|
+
# had a log-dir fallback all along; this is the same list.
|
|
82
|
+
if [ -z "$STATE_FILE" ] || [ -z "$TRACKER_FILE" ]; then
|
|
83
|
+
task_bare="${TASK_ID##*-}"
|
|
84
|
+
for base in \
|
|
85
|
+
"$HOME/.claude/logs/multi-agent/$TASK_ID/artifacts" \
|
|
86
|
+
"$HOME/.claude/logs/multi-agent/$task_bare/artifacts" \
|
|
87
|
+
"$HOME"/.claude/logs/multi-agent/*/"$TASK_ID"/artifacts \
|
|
88
|
+
"$HOME"/.claude/logs/multi-agent/*/"$task_bare"/artifacts
|
|
89
|
+
do
|
|
90
|
+
[ -d "$base" ] || continue
|
|
91
|
+
[ -z "$STATE_FILE" ] && [ -f "$base/agent-state.json" ] && STATE_FILE="$base/agent-state.json"
|
|
92
|
+
[ -z "$TRACKER_FILE" ] && [ -f "$base/phase-tracker.json" ] && TRACKER_FILE="$base/phase-tracker.json"
|
|
93
|
+
done
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# The diffstat below runs in the worktree. For a finalized task the worktree is
|
|
97
|
+
# gone but the BRANCH is still local in the project root, so derive a repo to diff
|
|
98
|
+
# in from the salvaged state rather than dropping the Changed-files section.
|
|
99
|
+
if [ -z "$WORKTREE" ] && [ -n "$STATE_FILE" ]; then
|
|
100
|
+
DIFF_REPO="$(node -e '
|
|
101
|
+
const fs=require("fs");
|
|
102
|
+
try { const s=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));
|
|
103
|
+
process.stdout.write(s.projectRoot || "");
|
|
104
|
+
} catch { process.stdout.write(""); }
|
|
105
|
+
' "$STATE_FILE" 2>/dev/null || echo "")"
|
|
106
|
+
[ -n "$BRANCH" ] || BRANCH="$(node -e '
|
|
107
|
+
const fs=require("fs");
|
|
108
|
+
try { process.stdout.write(JSON.parse(fs.readFileSync(process.argv[1],"utf8")).branch || ""); }
|
|
109
|
+
catch { process.stdout.write(""); }
|
|
110
|
+
' "$STATE_FILE" 2>/dev/null || echo "")"
|
|
111
|
+
fi
|
|
112
|
+
|
|
77
113
|
if [ -z "$STATE_FILE" ] && [ -z "$TRACKER_FILE" ] && [ -z "$BRANCH" ]; then
|
|
78
114
|
# Not enough to render anything meaningful
|
|
79
115
|
exit 2
|
|
@@ -129,9 +165,21 @@ total_add=0; total_del=0
|
|
|
129
165
|
# Only the directory needs to exist here; `git rev-parse HEAD` below is the real
|
|
130
166
|
# repository check. Testing -d "$WORKTREE/.git" would be wrong regardless: in a
|
|
131
167
|
# linked worktree .git is a file, not a directory.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
168
|
+
# Prefer the worktree; fall back to the project root with the branch by name. A
|
|
169
|
+
# finalized task (Phase 6 removed the worktree after the PR) has no worktree but
|
|
170
|
+
# the branch is still local, so without this the Changed-files section vanished
|
|
171
|
+
# from the PR body and the Jira comment even though the data was right there.
|
|
172
|
+
DIFF_IN=""; DIFF_TIP=""
|
|
173
|
+
if [ -n "$WORKTREE" ] && [ -d "$WORKTREE" ] && git -C "$WORKTREE" rev-parse HEAD >/dev/null 2>&1; then
|
|
174
|
+
DIFF_IN="$WORKTREE"; DIFF_TIP="HEAD"
|
|
175
|
+
elif [ -n "${DIFF_REPO:-}" ] && [ -n "$BRANCH" ] \
|
|
176
|
+
&& git -C "$DIFF_REPO" rev-parse --verify --quiet "$BRANCH" >/dev/null 2>&1; then
|
|
177
|
+
DIFF_IN="$DIFF_REPO"; DIFF_TIP="$BRANCH"
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
if [ -n "$DIFF_IN" ]; then
|
|
181
|
+
if true; then
|
|
182
|
+
diffstat=$(git -C "$DIFF_IN" diff --numstat "$state_base"..."$DIFF_TIP" 2>/dev/null || true)
|
|
135
183
|
if [ -n "$diffstat" ]; then
|
|
136
184
|
files_count=$(echo "$diffstat" | grep -c . || true)
|
|
137
185
|
total_add=$(echo "$diffstat" | awk '{ if ($1 ~ /^[0-9]+$/) s+=$1 } END { print s+0 }')
|
|
@@ -874,6 +874,15 @@ const declaredNames = ledger.entries.map((e) => e?.skill).filter(Boolean);
|
|
|
874
874
|
const declaredNotResolved = [...new Set(declaredNames)].filter((n) => !resolvedNames.has(n));
|
|
875
875
|
const ledgerMissingTargets = ledger.entries.filter((e) => !Array.isArray(e?.targetFiles) || e.targetFiles.length === 0).length;
|
|
876
876
|
|
|
877
|
+
// Entries a stack toolkit's own index chose (Phase 3 step 9 stamps `routedBy`).
|
|
878
|
+
// Surfaced separately because "the project's toolkit says this skill governs this
|
|
879
|
+
// task" is a stronger statement than "the model happened to open it" - but it does
|
|
880
|
+
// NOT earn extra trust in the coverage maths: the resolver stays primary either
|
|
881
|
+
// way, so this is reported, not weighted.
|
|
882
|
+
const routedEntries = ledger.entries
|
|
883
|
+
.filter((e) => typeof e?.routedBy === "string" && e.routedBy.length > 0)
|
|
884
|
+
.map((e) => ({ skill: e.skill, routedBy: e.routedBy }));
|
|
885
|
+
|
|
877
886
|
// Two values, not three. An earlier version branched on
|
|
878
887
|
// `declaredNotResolved.length === 0` and returned "mixed" from BOTH arms, so the
|
|
879
888
|
// condition was dead and the flagged-vs-corroborated distinction it implied was
|
|
@@ -891,6 +900,7 @@ const manifest = {
|
|
|
891
900
|
entryCount: ledger.entries.length,
|
|
892
901
|
entriesMissingTargetFiles: ledgerMissingTargets,
|
|
893
902
|
declaredButNotResolved: declaredNotResolved,
|
|
903
|
+
routedByToolkit: routedEntries,
|
|
894
904
|
note: "The resolver below is primary. This block is a self-report from Phase 3 and is corroborating evidence only: an unrecorded consultation and no consultation are indistinguishable in state, so coverage is never computed from it.",
|
|
895
905
|
},
|
|
896
906
|
languages: Object.fromEntries(Object.entries(languages).map(([k, v]) => [k, v.length])),
|
|
@@ -61,14 +61,22 @@ function main() {
|
|
|
61
61
|
for (const f of files) {
|
|
62
62
|
const sig = (f.signals || []).find((s) => s.name === "test_lines_removed");
|
|
63
63
|
if (!sig) continue;
|
|
64
|
+
// `path`, not `file`: that is what diff-risk-score.mjs emits and what
|
|
65
|
+
// diff-risk.schema.json declares. Reading `f.file` made EVERY finding this
|
|
66
|
+
// gate produced carry `file: undefined` and say "Test file 'undefined'
|
|
67
|
+
// shrinks" - unattributable for the developer, and rejected by
|
|
68
|
+
// reviewer-output.schema.json, which requires `file` with minLength 1. This is
|
|
69
|
+
// the one gate with no opt-out, so a silently unusable finding is the worst
|
|
70
|
+
// possible failure here.
|
|
71
|
+
const path = f.path ?? f.file;
|
|
64
72
|
findings.push({
|
|
65
|
-
file:
|
|
73
|
+
file: path,
|
|
66
74
|
severity: "blocking",
|
|
67
75
|
tag: "test_integrity",
|
|
68
76
|
signal: "test_lines_removed",
|
|
69
77
|
linesRemovedNet: sig.value ?? null,
|
|
70
78
|
message:
|
|
71
|
-
`Test file '${
|
|
79
|
+
`Test file '${path}' shrinks (removed > added). The immutable-test rule ` +
|
|
72
80
|
`(refs/rules.md) forbids weakening tests to get a green run. Keep blocking ` +
|
|
73
81
|
`unless the task's spec changed AND the commit body names the changed test; ` +
|
|
74
82
|
`then triage may defer it.`,
|