amsd-pipeline-shared 0.5.10 → 0.5.11

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/README.md CHANGED
@@ -9,6 +9,8 @@ workflows (`amsd-pipeline-workflow`). A fix lands here, once, and both run the s
9
9
  |---|---|---|
10
10
  | The model-call gateway | `orchestrations/scripts/llm-handler.sh` and the 34 libraries it runs (`docs/gateway-files.txt`) | Every model call: retry up to `EPAM_CALL_MAX_ATTEMPTS`, the agent's ladder (next rung on a failure), the previous-refusal block, the KB episode for self-heal, cost and Langfuse |
11
11
  | The Writer's reviewer | `orchestrations/scripts/team-lead-review.sh` | A failed review call is no verdict — retried up its ladder, then *review incomplete*, never approved |
12
+ | The reviewer's instructions and model policy | `orchestrations/prompts/templates/{team-lead-review,blocker-discipline,review-shared,code-review-cycle}.json`, `config/reviewer-policy.json`, `lib/reviewer-model.js` | The reviewer rates the work 0 to 10 and approves at 8.5 or higher; an unmet criterion the implementer can meet is a blocker. The review runs on the model the policy names (`top-of-ladder` by default: the top of the active provider set's highest ladder), read by the bash reviewer and the Claude workflow alike. Evidence: run 11, real tokens — Sonnet rated a thin and a rich delivery 9 and 9, Opus 6 and 7.5 |
13
+ | The failure analysts' templates, the seam output contracts | `prompts/templates/{failure-analyst,agent-failure-analyst,gate-finding-analyst,lint-finding-analyst,story-recovery-analyst,post-failure-analyst}.json`, `config/seam-output-contracts.json` | Moved into the component byte-identical to the 2.0.80 release (the analysts' instructions are unchanged) |
12
14
  | The Spec runner | `orchestrations/scripts/spec-mode-runner.js` | A failed mint re-proposal keeps the accepted roles and says so |
13
15
  | The KB synthesiser | `orchestrations/scripts/lib/kb-synthesizer.js` | Runs on its own seam's model |
14
16
  | Agent-to-agent messages | `orchestrations/scripts/lib/agent-messages.js` | send / receive / list / route on the pipeline's own bus format (`logs/messages/`, `agent-messages.jsonl`); "router" goes to the stage's router |
@@ -92,3 +92,14 @@ a01137cb36b65a3b80a8327404f4ed7a scripts/lib/story-acs-block.sh
92
92
  043b6c85d1505ebc5da6c6b158000330 scripts/lib/plan-of-record.sh
93
93
  1cc1978d614fca875ad1117e9ac42f93 scripts/lib/render-engine-prompt.sh
94
94
  bad91b18bf546145a58423c770ba1a3a prompts/templates/criterion-origin.json
95
+ 8c3bd8f0b375b047b8f85909f43cf4f5 config/seam-output-contracts.json
96
+ a8c558184595777b8f2f7c62f313f622 prompts/templates/agent-failure-analyst.json
97
+ 583b47885d69f2c917db6514eef09d8e prompts/templates/code-review-cycle.json
98
+ d9cd85a83389a52a09477573a33fa314 prompts/templates/failure-analyst.json
99
+ 8142977265d30ecfae6128b09a3584c5 prompts/templates/gate-finding-analyst.json
100
+ 7e605cada6c96d1591811434251605d5 prompts/templates/lint-finding-analyst.json
101
+ 9ba265eb3db0d8329276856a3a006814 prompts/templates/post-failure-analyst.json
102
+ 089b704c8a96cacd0b929d8078432796 prompts/templates/review-shared.json
103
+ 7785cb19d58eb65321df380839accf85 prompts/templates/story-recovery-analyst.json
104
+ 71db8785df56a99c694b2447f45bd0a7 prompts/templates/team-lead-review.json
105
+ 271ec0ed7881aeb741758c3b79f3e77b prompts/templates/blocker-discipline.json
@@ -3,10 +3,22 @@ config/doctor-signatures.json
3
3
  config/model-pricing.json
4
4
  config/repo-artifacts.json
5
5
  config/resume-preserves.json
6
+ config/reviewer-policy.json
6
7
  config/run-modes.json
8
+ config/seam-output-contracts.json
7
9
  prompts/doctor-repair.md
10
+ prompts/templates/agent-failure-analyst.json
11
+ prompts/templates/blocker-discipline.json
12
+ prompts/templates/code-review-cycle.json
8
13
  prompts/templates/codeline-discovery.json
9
14
  prompts/templates/criterion-origin.json
15
+ prompts/templates/failure-analyst.json
16
+ prompts/templates/gate-finding-analyst.json
17
+ prompts/templates/lint-finding-analyst.json
18
+ prompts/templates/post-failure-analyst.json
19
+ prompts/templates/review-shared.json
20
+ prompts/templates/story-recovery-analyst.json
21
+ prompts/templates/team-lead-review.json
10
22
  scripts/agent-attempt-analyst.sh
11
23
  scripts/lib/agent-contract.js
12
24
  scripts/lib/agent-ladder.sh
@@ -88,6 +100,7 @@ scripts/lib/release-lines.sh
88
100
  scripts/lib/render-engine-prompt.sh
89
101
  scripts/lib/render-prompt-section.js
90
102
  scripts/lib/resume-semantics.sh
103
+ scripts/lib/reviewer-model.js
91
104
  scripts/lib/run-checkpoint.sh
92
105
  scripts/lib/run-modes.sh
93
106
  scripts/lib/seam-invocation.js
@@ -0,0 +1,5 @@
1
+ {
2
+ "$comment": "WHICH MODEL THE WRITER'S REVIEW RUNS ON — one declared policy, read by the bash reviewer (scripts/team-lead-review.sh reviewer_model_for) and by the Claude workflow's generator, through scripts/lib/reviewer-model.js, so the two pipelines can never differ. 'writer-rung': the review runs on the model that produced the work (the rule since 2026-08-22). 'top-of-ladder': the review runs on the top of the active provider set's highest ladder, derived from the set's own file (no model name is written here).",
3
+ "$why": "Run 11 (AMSD-2974, 2026-10-21): the same reviewer prompt on the Writer's rung model (Sonnet 5) rated a thin delivery and a richer delivery 9 and 9 and approved both; on Opus 5.5 it rated them 6 and 7.5 and sent both back (about $0.25 more per review). A reviewer that is no stronger than the work it judges cannot see what that rung gets wrong.",
4
+ "model": "top-of-ladder"
5
+ }
@@ -0,0 +1,353 @@
1
+ {
2
+ "$comment": "WHAT EACH SEAM'S OUTPUT IS HELD TO. Every declared seam appears here exactly once. A seam whose output nothing validates is a seam whose bad answer flows onward looking authoritative — the roster stage proved it on 2026-08-25, when a model narrated ('I need to create a valid JSON file. Let me fix the formatting:') instead of answering.",
3
+ "$why": "Declared rather than inferred in the test: matching a seam's `produces` string against a schema tag name is a guess — `roster-review` produces `roster-verdict` but its tag is ROSTER_REVIEW, so a name-based map called it unvalidated while it was validated. The link is a fact about the pipeline and belongs in config.",
4
+ "$kinds": {
5
+ "schema": "validated by lib/agent-output-schema.js validateTaggedOutput(tag, parsed)",
6
+ "verdict": "validated by lib/gate_verdict_schema.py validate(gate, text)",
7
+ "artefact": "writes a file; the caller parses and contract-checks it",
8
+ "none": "NOTHING validates this seam's output — a declared gap, see $gapRatchet",
9
+ "declared": "required keys declared here, taken from the shape the seam PROMPT states; validated by lib/agent-output-schema.js validateDeclaredOutput(seam, parsed)",
10
+ "per-story-map": "one object keyed by story id, each entry carrying the required keys declared here; the consumer applies an entry per story and skips one whose list keys are empty (lib/handlers/tc-apply-to-prd.py)"
11
+ },
12
+ "$gapRatchet": "The `none` list may only SHRINK. Adding a seam with no contract is adding a place a bad answer flows on unchecked.",
13
+ "seams": {
14
+ "estate-survey": {
15
+ "kind": "schema",
16
+ "tag": "ESTATE_SURVEY"
17
+ },
18
+ "guard-vocabulary": {
19
+ "kind": "schema",
20
+ "tag": "GUARD_VOCABULARY"
21
+ },
22
+ "role-assigner": {
23
+ "kind": "schema",
24
+ "tag": "ROLE_ASSIGNMENTS"
25
+ },
26
+ "spec-coordinator": {
27
+ "kind": "schema",
28
+ "tag": "SPEC_ASSIGNMENTS"
29
+ },
30
+ "spec-agent": {
31
+ "kind": "schema",
32
+ "tag": "SPEC_AGENT"
33
+ },
34
+ "ticket-links": {
35
+ "kind": "schema",
36
+ "tag": "TICKET_LINKS"
37
+ },
38
+ "agent-mint": {
39
+ "kind": "schema",
40
+ "tag": "PROJECT_AGENTS"
41
+ },
42
+ "prd-change-reviewer": {
43
+ "kind": "verdict"
44
+ },
45
+ "project-roster-review": {
46
+ "kind": "verdict",
47
+ "tag": "ROSTER_REVIEW",
48
+ "_why_tag": "Judged by validateTaggedOutput against TOOL_ROSTER_REVIEW; the verdict vocabulary is that tool's enum, and the rehearsal stand-in reads it from there."
49
+ },
50
+ "prompt-claim-adjudicator": {"kind": "declared", "tag": "ADJUDICATION", "requiredKeys": ["holds"], "knownKeys": ["holds", "evidence"], "_why": "lib/prompt-build-calls.js reads ONE <ADJUDICATION>{holds, evidence}</ADJUDICATION> block; an absent or unreadable ruling leaves the reviewer's strike standing (lib/prompt-review.js)."},
51
+ "prompt-review": {
52
+ "kind": "declared",
53
+ "tag": "PROMPT_REVIEW",
54
+ "requiredKeys": [
55
+ "falseClaims"
56
+ ],
57
+ "knownKeys": [
58
+ "falseClaims"
59
+ ],
60
+ "_why": "lib/prompt-review.js reads ONE <PROMPT_REVIEW>{falseClaims:[...]}</PROMPT_REVIEW> block out of the reply and installs the prompt UNREVIEWED when it is absent. Declared 'verdict' (bare gate JSON), every prompt of the £0 greenfield harness was installed unreviewed behind a stand-in that satisfied the wrong judge (run 19, 2026-09-14)."
61
+ },
62
+ "roster-review": {
63
+ "kind": "verdict",
64
+ "tag": "ROSTER_REVIEW",
65
+ "_why_tag": "Judged by validateTaggedOutput against TOOL_ROSTER_REVIEW; the verdict vocabulary is that tool's enum, and the rehearsal stand-in reads it from there."
66
+ },
67
+ "survey-review": {
68
+ "kind": "verdict"
69
+ },
70
+ "vc-coverage": {
71
+ "kind": "verdict"
72
+ },
73
+ "qa-gate:e2e": {
74
+ "kind": "verdict"
75
+ },
76
+ "qa-gate:fuzz-weaver": {
77
+ "kind": "verdict"
78
+ },
79
+ "qa-gate:mutant-hunter": {
80
+ "kind": "verdict"
81
+ },
82
+ "qa-gate:perf-sentinel": {
83
+ "kind": "verdict"
84
+ },
85
+ "qa-gate:review-ranger": {
86
+ "kind": "verdict"
87
+ },
88
+ "qa-gate:runtime-boundary": {
89
+ "kind": "verdict"
90
+ },
91
+ "qa-gate:sast": {
92
+ "kind": "verdict"
93
+ },
94
+ "qa-gate:spec-validator": {
95
+ "kind": "verdict"
96
+ },
97
+ "roster-specialiser": {
98
+ "kind": "declared",
99
+ "requiredKeys": [
100
+ "agents"
101
+ ],
102
+ "knownKeys": [
103
+ "agents"
104
+ ],
105
+ "_why": "The delta the engine composes the roster from (lib/project-roster.js composeFromDelta): {agents:{name:{persona,ancestor,kind?,seam?,rationale?}}}. Was 'artefact' while the agent wrote the file itself."
106
+ },
107
+ "codeline-bridge-agent": {
108
+ "kind": "artefact"
109
+ },
110
+ "prd-change-summarizer": {
111
+ "kind": "artefact"
112
+ },
113
+ "prompt-builder": {
114
+ "kind": "artefact"
115
+ },
116
+ "repro-test-writer": {
117
+ "kind": "artefact"
118
+ },
119
+ "story-writer": {
120
+ "kind": "artefact"
121
+ },
122
+ "topology-router": {
123
+ "kind": "artefact",
124
+ "checkedBy": "lib/llm-call.js callLlmJson — refuses a reply with no JSON"
125
+ },
126
+ "cpa-inference": {
127
+ "kind": "declared",
128
+ "requiredKeys": [
129
+ "confidence"
130
+ ],
131
+ "knownKeys": [
132
+ "confidence",
133
+ "complexityAdjustment",
134
+ "adjustedEstimate",
135
+ "riskFlags",
136
+ "missingKbCoverage",
137
+ "citedSources",
138
+ "reasoning"
139
+ ],
140
+ "exemplarFromPrompt": true,
141
+ "prefer": {
142
+ "confidence": 1
143
+ },
144
+ "_whyPrefer": "contextualize-stories.sh gates on .confidence (block below GATE_BLOCK); the prompt's exemplar states confidence 0.0, and a stand-in that copied it blocked every brownfield story at CPA (£0 brownfield harness runs 1–3, 2026-09-14). The rehearsal's estimate is fully confident — the join is what is rehearsed, not the estimate.",
145
+ "checkedBy": "contextualize-stories.sh reads .confidence, .complexityAdjustment, .adjustedEstimate.*, .riskFlags, .missingKbCoverage, .citedSources, .reasoning from the reply (an unparseable reply is replaced by a zero-confidence one); the shape is stated by cpa-system, the fragment cpa-inference embeds as __SYSTEM_PROMPT__."
146
+ },
147
+ "ac-classification": {
148
+ "kind": "declared",
149
+ "rejectsWith": {
150
+ "verdict": "insufficient"
151
+ },
152
+ "_whyRejectsWith": "ac-elaboration runs only on an insufficient verdict (lib/ac-gate.js, AC_GATE_AUTO_ELABORATE=1): the first classification of a rehearsal is insufficient on purpose so the elaboration seam executes; the retry meets the passing form the prompt offers first.",
153
+ "requiredKeys": [
154
+ "verdict",
155
+ "codeline"
156
+ ],
157
+ "_whyCodeline": "The prompt asks which codeline the story touches and the PRD synthesiser keys the story on the answer (id suffix, agentGroup, worktree shape); a verdict alone left the story with no codeline, on main, with nothing for the topology router to route (£0 brownfield harness run 18, 2026-09-14). The stand-in names a repository the run discovered.",
158
+ "knownKeys": [
159
+ "verdict",
160
+ "reason",
161
+ "codeline",
162
+ "gaps",
163
+ "enrichedAcs"
164
+ ]
165
+ },
166
+ "ac-elaboration": {
167
+ "kind": "declared",
168
+ "requiredKeys": [
169
+ "enrichedAcs"
170
+ ],
171
+ "knownKeys": [
172
+ "enrichedAcs"
173
+ ]
174
+ },
175
+ "agent-failure-analyst": {
176
+ "kind": "declared",
177
+ "_whyDiagnosisOnly": "Its consumer, agent-attempt-analyst.sh, records the diagnosis as a KB episode and reads nothing else — the target/patch branches belong to the impl analyst (claude.sh run_failure_analyst), whose contract asks for the prompt exemplar.",
178
+ "requiredKeys": [
179
+ "diagnosis"
180
+ ],
181
+ "shapes": {
182
+ "diagnosis": "string",
183
+ "reason": "string",
184
+ "target": "string"
185
+ },
186
+ "_whyShapes": "diagnosis is ONE diagnosis, a prose paragraph the KB records as the episode; the rehearsal stand-in read the trailing s as a list and recorded \"[]\" as the diagnosis of every healing event (2026-09-14). A shape is stated where the key's name misleads.",
187
+ "knownKeys": [
188
+ "diagnosis",
189
+ "target",
190
+ "ac_patches",
191
+ "tc_patches",
192
+ "skill_note",
193
+ "tool_spec",
194
+ "reason"
195
+ ]
196
+ },
197
+ "code-graph-detective": {
198
+ "kind": "declared",
199
+ "list": true,
200
+ "_whyList": "The detective answers with a JSON ARRAY of fix sites — parseDetectiveFindings (spec-mode-runner.js) matches `[ { … } ]` and reads nothing else; its prompts state the array. The keys below describe one element. A stand-in delivered as a bare object was 'NO parseable JSON' three attempts running (£0 brownfield harness run 2, 2026-09-14).",
201
+ "exemplarFromPrompt": true,
202
+ "shapes": {
203
+ "file": {
204
+ "fromCodeline": "source"
205
+ }
206
+ },
207
+ "_whyShapes": "A fix site is a file this codeline HAS: the rehearsal stand-in names the first source file of the story's codeline (by its ecosystem's own source/test convention), never an invented path — CPA blocks a defect whose fix site does not exist, by design (£0 brownfield harness run 1, 2026-09-14).",
208
+ "requiredKeys": [
209
+ "file"
210
+ ],
211
+ "knownKeys": [
212
+ "file",
213
+ "function",
214
+ "reason",
215
+ "brokenLine",
216
+ "fix",
217
+ "helper",
218
+ "changeRequired",
219
+ "requiredPackages",
220
+ "deliveryRole",
221
+ "runsIn"
222
+ ]
223
+ },
224
+ "code-review-cycle": {
225
+ "kind": "declared",
226
+ "requiredKeys": [
227
+ "verdict"
228
+ ],
229
+ "knownKeys": [
230
+ "verdict",
231
+ "issues",
232
+ "summary"
233
+ ]
234
+ },
235
+ "codeline-discovery": {
236
+ "kind": "declared",
237
+ "requiredKeys": [
238
+ "codelines"
239
+ ],
240
+ "knownKeys": [
241
+ "codelines",
242
+ "unsure"
243
+ ]
244
+ },
245
+ "impl-failure-analyst": {
246
+ "kind": "declared",
247
+ "exemplarFromPrompt": true,
248
+ "prefer": {
249
+ "target": "skill"
250
+ },
251
+ "overDeclaredLimit": {
252
+ "skill_note": "config/self-heal.json#skillNote.maxChars"
253
+ },
254
+ "_whyOverDeclaredLimit": "A note within the declared length and opening imperatively is accepted by the deterministic format check and never reaches the LLM reviewer; the rehearsal stand-in exceeds the limit the config declares — the exact shape the reviewer, its rejection and the summarizer rewrite exist for — so that loop executes at £0 (run 28, 2026-09-14).",
255
+ "_whyPrefer": "Of the targets the prompt offers, skill is the one whose change goes through the reviewer's summarize-and-resubmit loop (claude.sh run_change_with_reviewer_retry: skill_note, kb_entry, tool_creation); an ac/tc patch reverts on rejection and reaches no summarizer. The rehearsal stand-in takes it so prd-change-summarizer executes at £0.",
256
+ "_whyExemplar": "The rehearsal stand-in carries the whole answer the prompt states — target: prd with one ac_patch — so the change reviewer and, on a rejection, the summarizer execute at £0; with diagnosis alone neither ever ran (2026-09-14).",
257
+ "requiredKeys": [
258
+ "diagnosis"
259
+ ],
260
+ "shapes": {
261
+ "diagnosis": "string",
262
+ "reason": "string",
263
+ "target": "string",
264
+ "evidence": "string",
265
+ "expected_outcome": "string"
266
+ },
267
+ "_whyShapes": "diagnosis is ONE diagnosis, a prose paragraph the KB records as the episode; the rehearsal stand-in read the trailing s as a list and recorded \"[]\" as the diagnosis of every healing event (2026-09-14). A shape is stated where the key's name misleads.",
268
+ "knownKeys": [
269
+ "diagnosis",
270
+ "target",
271
+ "ac_patches",
272
+ "tc_patches",
273
+ "skill_note",
274
+ "tool_spec",
275
+ "reason",
276
+ "evidence",
277
+ "expected_outcome",
278
+ "escalation",
279
+ "provisioning"
280
+ ],
281
+ "_whyEvidence": "2026-09-20: evidence (what was read/run) and expected_outcome (what changes next attempt) let the ledger and the next analyst judge whether a prescription worked, instead of only whether the diagnosis text repeated.",
282
+ "_whyKnownKeys": "evidence, expected_outcome, escalation and provisioning are asked for by the failure-analyst prompt and read by failure-healing.sh (provisioning raises the next attempt's budgets; evidence/expected_outcome reach the healing ledger and the next analyst; escalation is filed by _apply_analyst_escalation). They were missing here while `shapes` already named two of them (found 2026-09-25)."
283
+ },
284
+ "phase-assessment": {
285
+ "kind": "declared",
286
+ "requiredKeys": [
287
+ "event"
288
+ ],
289
+ "knownKeys": [
290
+ "timestamp",
291
+ "phase_id",
292
+ "agent_role",
293
+ "event",
294
+ "skill",
295
+ "skill_category",
296
+ "context",
297
+ "added_by"
298
+ ]
299
+ },
300
+ "prd-model-coordinator": {
301
+ "kind": "none"
302
+ },
303
+ "tc-writer": {
304
+ "kind": "per-story-map",
305
+ "requiredKeys": [
306
+ "facts",
307
+ "sourceFiles",
308
+ "mockStrategy",
309
+ "bannedPatterns"
310
+ ],
311
+ "_why": "One object keyed by story id (lib/handlers/tc-apply-to-prd.py): a story whose entry lacks a non-empty facts list is skipped, so an answer of {} applies nothing and the gate reports every test story still missing criteria. Was 'none' — the rehearsal stand-in answered {} and the gate could never pass at £0 (2026-09-13)."
312
+ },
313
+ "team-lead-review": {
314
+ "kind": "declared",
315
+ "requiredKeys": [
316
+ "verdict"
317
+ ],
318
+ "knownKeys": [
319
+ "verdict",
320
+ "issues",
321
+ "summary",
322
+ "rating",
323
+ "unmet"
324
+ ]
325
+ },
326
+ "kb-synthesizer": {
327
+ "kind": "declared",
328
+ "requiredKeys": [
329
+ "enforcement",
330
+ "reason"
331
+ ],
332
+ "knownKeys": [
333
+ "enforcement",
334
+ "reason"
335
+ ],
336
+ "shapes": {
337
+ "enforcement": {
338
+ "schemaCommand": [
339
+ "python3",
340
+ "lib/kb_schema.py",
341
+ "json-schema",
342
+ "constraint"
343
+ ],
344
+ "at": "properties.enforcement"
345
+ },
346
+ "reason": "string"
347
+ },
348
+ "checkedBy": "lib/kb-synthesizer.js — binds {enforcement, reason} as EPAM_RESPONSE_SCHEMA (additionalProperties false) and validates the enforcement against lib/kb_schema.py; unparseable, declined and unmapped replies are quarantined, never stored",
349
+ "_why": "Was 'artefact' — a stand-in of {note} was rejected fifteen times by the runner's schema enforcement and the synthesizer recorded no_output (£0 greenfield harness run 29, 2026-09-14). The enforcement's shape is read from the same schema command the synthesizer itself uses; nothing here spells a kind."
350
+ }
351
+ },
352
+ "$declaredWhy": "A seam whose output nothing validates lets a bad answer flow on looking authoritative — the roster-specialiser answered with prose on a paid run and only its artefact check stopped that becoming the identity every downstream agent reads. requiredKeys is the key a CONSUMER cannot proceed without, taken from the shape the seam PROMPT already states, so the contract and the prompt cannot drift. knownKeys is the rest of that shape, reported but not enforced: requiring every optional field would reject valid answers, and the defect being caught is an answer that is not the artefact at all."
353
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "$why": [
3
+ "Moved out of agent-attempt-analyst.sh on 2026-08-16, found by the prompt-chain test: the",
4
+ "agent-failure-analyst seam had a ladder, an effort and tool grants but no prompt in the",
5
+ "template layer, so whatever instructed it was still embedded. That is the orphan the chain",
6
+ "is meant to make impossible to keep.",
7
+ "",
8
+ "The five class hints are prompt text, not configuration -- each is a sentence addressed to",
9
+ "the analyst -- so they live here as bodies rather than as a case statement in the script.",
10
+ "The engine chooses WHICH by failure class; it no longer holds the wording of any of them.",
11
+ "",
12
+ "The role instructions still come from the project's profiles.json when it has them: this",
13
+ "body is the fallback for a project whose roster carries no failure-analyst role.",
14
+ "",
15
+ "2026-10-06 (operator: \"once and for all\"): every caller knows WHY it refused an attempt and none of it reached this prompt — self-heal.js dropped the reason and the shell callers passed none. Live the same day, the prompt reviewer rejected a generated prompt as narrowed to one ticket and this analyst was told the agent had produced no parseable output. The reason is now its own section, and a content rejection has its own hint."
16
+ ],
17
+ "description": "Diagnoses why an agent attempt failed and prescribes the minimum corrective directive for its retry.",
18
+ "id": "agent-failure-analyst",
19
+ "seams": [
20
+ "agent-failure-analyst"
21
+ ],
22
+ "placeholders": [
23
+ "__CLASS_HINT__",
24
+ "__CONTEXT__",
25
+ "__FAILED_OUTPUT__",
26
+ "__FAILURE_CLASS__",
27
+ "__FAILURE_REASON__",
28
+ "__FIX_SITES__"
29
+ ],
30
+ "version": 3,
31
+ "bodies": {
32
+ "prompt": "An AI agent failed its task and must retry. Diagnose the SPECIFIC reason it failed from its output below, then prescribe a SHORT, CONCRETE corrective directive (1-3 sentences) to prepend to its next attempt so it SUCCEEDS this time. __CLASS_HINT__\n\nGround the directive in what the agent ACTUALLY did (below) and the REAL task — do not give generic advice. Output ONLY the corrective directive text, nothing else.\n\n=== FAILURE CLASS ===\n__FAILURE_CLASS__\n\n=== WHY THIS ATTEMPT WAS REFUSED ===\n__FAILURE_REASON__\n\n=== WHAT THE AGENT PRODUCED (its output/log) ===\n__FAILED_OUTPUT__\n\n=== THE TASK IT WAS GIVEN (ground truth) ===\n__CONTEXT__\n\n=== WHERE THE SPEC PASS'S DETECTIVE FOUND THE FIX (its reading of the code before the writer ran — evidence, not instruction: confirm it against the code; a site marked NO EDIT REQUIRED is part of the fix as it stands) ===\n__FIX_SITES__",
33
+ "hint_max_iterations": "The agent EXHAUSTED its iteration budget exploring and NEVER produced its output. The corrective directive must make it commit output EARLY — e.g. 'you have N calls; do the minimum lookup then WRITE your file/answer as your very next action; do NOT keep exploring.'",
34
+ "hint_no_file": "The agent finished WITHOUT writing the required file. The directive must make writing the file its FIRST action, at the exact required path.",
35
+ "hint_no_json": "The agent produced no parseable structured output (prose, or a tool-call wrapper). The directive must make it emit ONLY the required output inline, no tool calls, no prose.",
36
+ "hint_invalid_test": "The agent DID write the file, but it is not valid, runnable code — it failed to parse/compile, so the test never executed (the runner's exact error is in the output below). This is a SYNTAX/structure problem, not a logic problem: the directive must tell it to emit a complete, syntactically valid file — balanced braces/brackets, correct object-literal and array syntax, every import present — and to re-read its own output end-to-end before finishing. Do NOT tell it to change the assertion or the scenario; the previous attempt's intent was fine, only the code was malformed.",
37
+ "hint_default": "The agent failed to produce usable output.",
38
+ "hint_rejected": "The agent DID produce its answer, in the required shape. It was refused for its CONTENT, for the reason stated under WHY THIS ATTEMPT WAS REFUSED. Do not tell it to change its format: name what in its output that reason points at, and what it must write instead."
39
+ },
40
+ "mayBeEmpty": [],
41
+ "$mayBeEmpty": "__PROFILE__ reuses the failure-analyst role instructions, and the caller says so in its own words: 'Reuse the failure-analyst profile WHEN PRESENT'. It sets _profile=\"\" and fills it only if agents/profiles.json exists and holds that entry — so absent is a state the caller deliberately allows, while the render refused it. Declaring it here makes the template agree with the caller instead of contradicting it."
42
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "id": "blocker-discipline",
3
+ "layer": "project",
4
+ "version": 3,
5
+ "description": "What a reviewer may and may not raise as a BLOCKER. Bounds a rejection to the plan of record and to what the implementer can actually do.",
6
+ "$why": [
7
+ "A blocker the writer cannot satisfy is not a review finding, it is an unwinnable gate, and",
8
+ "the story cannot progress at any level of effort. Three appeared in one live review,",
9
+ "2026-08-12:",
10
+ "",
11
+ " 1. A demand to change a file the prescription never named, described as 'the prescribed",
12
+ " fix requires...'. The writer duly changed 402 lines in it.",
13
+ " 2. A demand for a dependency that DOES NOT EXIST, alongside the real one.",
14
+ " 3. A demand for tests the writer was explicitly FORBIDDEN to write (fixed separately, in",
15
+ " prompts/test-ownership.json).",
16
+ "",
17
+ "The writer looks like it is going rogue on every run. It is following orders from an",
18
+ "authority nobody reconciled against the plan.",
19
+ "",
20
+ "The escape hatch matters as much as the rule: a reviewer that genuinely believes the",
21
+ "prescription is WRONG must have somewhere to say so. Without one, 'the plan is incomplete'",
22
+ "has nowhere to go except into a blocker against the writer, who cannot change the plan.",
23
+ "",
24
+ "v3, 2026-10-21: the policy bounded blockers to the plan of record, so a verification criterion the diff left unmet was only ever an advisory note and no review sent it back (AMSD-2974, run 11: focus-pause and contrast gaps flagged 'minor', approved; the bash pipeline's richer delivery needed about 7 rounds). Item 5 and the rating: the reviewer judges, the loop follows its verdict; no code decides quality."
25
+ ],
26
+ "placeholders": [],
27
+ "bodies": {
28
+ "reviewer": "## What may be a BLOCKER\n\nA blocker REJECTS the implementer's work and forces another attempt. It must therefore be something that implementer can actually do. Before raising one, check all three:\n\n1. IS IT IN THE PLAN? The root-cause analysis above is the plan of record — the same one the implementer was given. A blocker must concern a file that plan names. If you believe work is needed somewhere the plan does not mention, that is a finding about the PLAN, not a failure by the implementer: raise it as an ADVISORY note beginning 'SPEC:' and explain what the plan appears to be missing. Do NOT describe it as 'prescribed' or 'required' — the implementer will follow you out of scope, and the change will be rejected later for exceeding the plan.\n\n2. DOES THE PLAN EXEMPT IT? A site marked NO EDIT REQUIRED is part of the fix and correctly left untouched. Never raise a finding of any severity because such a file is unchanged. The implementer was told not to change it.\n\n3. CAN IT BE SATISFIED? If you claim something is missing — a package, a file, an export, an API — CONFIRM IT with the tools you have before raising it. A blocker naming a dependency that does not exist, or an API the installed version does not provide, can never be resolved: the implementer will fail on it repeatedly and the story will exhaust its attempts on your mistake.\n\n4. IS IT INSIDE THE PLAN? A file the plan NAMES, and does not mark NO EDIT REQUIRED, was PRESCRIBED. Changing it is compliance, not over-engineering. Never raise a finding of any severity because a prescribed file was changed, and never assert a smaller set of files than the plan names — you were handed the plan; it is the authority on scope, not your own estimate of what a minimal fix would touch. If you believe the plan names more files than the work needs, that is a finding about the PLAN: raise it as an ADVISORY note beginning 'SPEC:'. Live failure, 2026-08-14: a reviewer rejected a change for \"modifying 6 files when the prescribed minimal fix requires only 2\" against a plan that named FIVE — every one of which the implementer changed, and no others. Obeying the plan was the thing being rejected, so no attempt could ever pass, and the story burned its whole ladder.\n\n5. IS A CRITERION UNMET THAT THE IMPLEMENTER CAN MEET? Judge the diff against every acceptance criterion and every verification criterion above, one by one. A criterion the diff leaves unmet, or only half-meets, is a BLOCKER when meeting it needs changes only in files the plan names or in this story's own test files: name the criterion and the change that would meet it. It is an ADVISORY 'SPEC:' note only when meeting it needs a file the plan does not name, or something this review cannot do (a manual walkthrough, an external tool). A behaviour the diff changes that no test in the diff would catch if the change were reverted is also a BLOCKER: the story's test files are in scope.\n\nRATE THE WORK. Give it a rating from 0 to 10 (10 = every criterion met, every changed behaviour tested, nothing broken, nothing left for the next person). Approve only when your rating is 8.5 or higher; below that, request changes and list what would lift it. Anchor the scale: 8.5 is work where every acceptance criterion the code can meet is met, every behaviour the diff changes is covered by a test that fails when the change is reverted, you found no regression, no hard-coded user-facing text where the codebase translates its text, and what remains are cosmetic notes. 6 is work that has the core change but leaves a criterion unmet or a changed behaviour untested. Judge honestly: an approval of work you rate below 8.5 is a wrong verdict, and so is a rejection of work you rate 8.5 or higher. Rate only what the diff and the codebase in front of you show; a criterion you cannot check from here is not a defect of the work.\n\nAnything failing these is ADVISORY. Advisory findings are still valuable — they make the change smaller, clearer or safer — but they do not reject the work.\n\nJudge the FIX ITSELF as a blocker freely: it does not address the prescribed root cause, it is materially larger than the PLAN OF RECORD — measured against the files and changes that plan names, never against a smaller fix you would have written instead, it removes a property the existing code guaranteed, it is unsafe. Those are yours to enforce."
29
+ },
30
+ "seams": [
31
+ "team-lead-review"
32
+ ]
33
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "$why": [
3
+ "Moved out of code-review-cycle.sh on 2026-08-15 (18 of 20).",
4
+ "",
5
+ "The reviewer PROFILE arrives as a value: it is the project-authority half of what this",
6
+ "agent is told, and it belongs to profiles.json rather than to this template.",
7
+ "2026-08-19: issues carried no evidence, and code-review-json.py accepted any object with a verdict. The reviewer twice asserted the Contentstack SDK would not recognise `management_token`; the installed index.d.ts declares exactly that and never mentions preview_token. An earlier draw of the same seam checked the types unprompted and got it right — correctness was luck. Unevidenced issues are now discarded, so the prompt has to ask for evidence."
8
+ ],
9
+ "body": "__REVIEW_PROFILE__\n\n---\nREVIEW TASK (Iteration __ITERATION__): Story __STORY_ID__ — __STORY_TITLE__\nAGENT: __STORY_AGENT__\n\nDESCRIPTION: __STORY_DESCRIPTION__\n\n__STORY_ACS__RELEVANT FILES: __STORY_FILES__\n\nGIT DIFF:\n```diff\n__STORY_DIFF__\n```\n__PRIOR_CONTEXT__\n__SCOPE_BLOCK__\n__VC_BLOCK__\nPROJECT ROOT: __PROJECT_ROOT__\n\nReview the implementation against every verification criterion above.\nCheck: TypeScript strict compliance, test coverage, error handling, security.\n\nRespond with ONLY a JSON object:\n{\"verdict\":\"approved\",\"issues\":[],\"summary\":\"...\"}\n OR\n{\"verdict\":\"changes_requested\",\"issues\":[{\"severity\":\"blocker|major|minor\",\"file\":\"...\",\"line\":0,\"description\":\"...\",\"evidence\":\"<path>:<line> you read, or the command you ran and its result\",\"suggestedFix\":\"...\"}],\"summary\":\"...\"}\n\nEvery issue MUST carry an `evidence` field: the exact thing you READ that establishes it — `<path>:<line>` you opened, or the command you ran and what it returned. Not a restatement of the claim, not a plan reference: what you looked at. An issue without evidence is DISCARDED before it reaches anyone, so an unsupported finding is worse than no finding — it costs you the issue. If a claim is about a library's API, the evidence is that library's installed types or source in this repository, and the installed contract beats the plan.\n",
10
+ "description": "Review an implementation and return a verdict with issues.",
11
+ "id": "code-review-cycle",
12
+ "placeholders": [
13
+ "__ITERATION__",
14
+ "__PRIOR_CONTEXT__",
15
+ "__PROJECT_ROOT__",
16
+ "__REVIEW_PROFILE__",
17
+ "__SCOPE_BLOCK__",
18
+ "__STORY_ACS__",
19
+ "__STORY_AGENT__",
20
+ "__STORY_DESCRIPTION__",
21
+ "__STORY_DIFF__",
22
+ "__STORY_FILES__",
23
+ "__STORY_ID__",
24
+ "__STORY_TITLE__",
25
+ "__VC_BLOCK__"
26
+ ],
27
+ "version": 2,
28
+ "seams": [
29
+ "code-review-cycle"
30
+ ],
31
+ "mayBeEmpty": [
32
+ "__PRIOR_CONTEXT__",
33
+ "__SCOPE_BLOCK__",
34
+ "__STORY_ACS__",
35
+ "__VC_BLOCK__"
36
+ ],
37
+ "$mayBeEmpty": "__PRIOR_CONTEXT__ carries the PREVIOUS iteration's findings, and iteration 1 has no previous iteration — code-review-cycle.sh sets it only `when iteration > 1`. Without this declaration the library refused every render on a first review, so the seam could not perform an initial review at all: it printed 'cannot render its prompt — refusing to run with no instructions' and the caller saw a review that never happened. Absent is a REAL state here, which is exactly what mayBeEmpty is for.",
38
+ "_storyAcs": "__STORY_ACS__ RESTORED 2026-09-11 as a BLOCK, mayBeEmpty: the caller supplies the heading AND the numbered criteria together, or nothing. ea920ee7 removed the placeholder because brownfield never has acceptance criteria (it judges on VCs) and an empty heading is worse than none — that ruling stands: on brownfield the prompt carries no heading. It threw away the greenfield case: a greenfield PRD is authored WITH acceptance criteria and they are in scope (operator, 2026-09-11), so this seam sees them there. Absent is a real state; present is the greenfield contract.",
39
+ "$whyMayBeEmpty": {
40
+ "__SCOPE_BLOCK__": "Empty when every file in the story's commit is one the story declares — the common case."
41
+ }
42
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "id": "failure-analyst",
3
+ "layer": "project",
4
+ "version": 5,
5
+ "description": "Diagnoses a verification failure and routes a correction. Emits a single JSON object.",
6
+ "extractedFrom": "orchestrations/scripts/claude.sh heredoc ANALYST_PROMPT_END (lines 7003-7047), verbatim",
7
+ "placeholders": [
8
+ "__ATTEMPT_CHANGES__",
9
+ "__DEPENDENCY_CONTRACTS__",
10
+ "__FIX_SITES__",
11
+ "__HEALING_HISTORY__",
12
+ "__MANIFEST_FILE__",
13
+ "__SHARED_CRITERIA__",
14
+ "__SKILL_ADDENDUM__",
15
+ "__SKILL_NOTE_MAX__",
16
+ "__STORY_ACS__",
17
+ "__STORY_ID__",
18
+ "__STORY_ROLE__",
19
+ "__VERIFICATION_FAILURE__",
20
+ "__WRITE_SCOPE__"
21
+ ],
22
+ "body": "STORY: __STORY_ID__\nAGENT ROLE: __STORY_ROLE__\n\n__STORY_ACS__AGENT SKILL ADDENDUM (instructions in the agent's system prompt):\n__SKILL_ADDENDUM__\n\nDEPENDENCY CONTRACTS (ground truth — auto-generated from actual source, not\nmodel-transcribed; trust this over any assumption you'd otherwise make about\nwhat a dependency exports, including its exact class/function names and\ncasing):\n__DEPENDENCY_CONTRACTS__\n\nWHERE THE SPEC PASS'S DETECTIVE FOUND THE FIX (its reading of the code before the writer ran — evidence, not instruction: confirm it against the code; a site marked NO EDIT REQUIRED is part of the fix as it stands):\n__FIX_SITES__\n\nWHAT THIS WRITER MAY TOUCH, AND WHO OWNS THE REST:\n__WRITE_SCOPE__\n\nCRITERIA OTHER STORIES HOLD ON THIS STORY'S FILES (the contract in force — a fix that\ncontradicts one of these is a spec conflict, not a code fix):\n__SHARED_CRITERIA__\n\nWHAT WAS ALREADY TRIED ON THIS STORY (this run's ledger):\n__HEALING_HISTORY__\n\nTEST FAILURE OUTPUT:\n__VERIFICATION_FAILURE__\n\nYou have read-only tools available (list/search/read files, run read-only\nshell commands). If the failure claims something about the filesystem or an\ninstalled package — \"not installed,\" \"missing,\" \"does not exist,\" a wrong\npath — VERIFY it with your tools before stating it as your diagnosis. Do not\nassume; a confident wrong diagnosis is worse than a slower correct one,\nbecause every retry after it repeats the same mistake unchanged. Your tool\nbudget is small — check the ONE fact your diagnosis depends on, not the\nwhole codebase.\n\nIf the attempt was STARVED rather than wrong — truncated at its output cap, or out of\niterations before it could finish — say so in \"provisioning\" and the next attempt runs with\nwhat you set there. Raise only what the evidence supports, and leave it out entirely when the\nattempt had room and simply got the work wrong: a bigger budget cannot fix a wrong approach.\n\nOutput ONLY a single JSON object. No markdown fences, no prose outside the JSON:\n{\"diagnosis\":\"<one sentence: what specifically went wrong in the code>\",\"target\":\"prd|tc|skill|kb|tool|escalate|spec|environment|none\",\"evidence\":\"<what you READ or RAN that establishes the diagnosis: <path>:<line>, or the command and its result>\",\"expected_outcome\":\"<what will be observably different on the next attempt if the prescription is followed, e.g. which test passes>\",\"escalation\":{\"targetFile\":\"<if target=escalate: the file in ANOTHER story's scope where the fix belongs>\",\"ownerStoryId\":\"<that story, from the scope list above>\",\"requiredFix\":\"<the narrow fix that story must make>\"},\"ac_patches\":[{\"index\":<0-based AC index>,\"new_text\":\"<exact replacement text for that AC>\"}],\"tc_patches\":[{\"index\":<0-based TC fact index>,\"new_text\":\"<exact replacement text for that TC fact>\"}],\"provisioning\":{\"maxOutputTokens\":<raise ONLY if the attempt was truncated at its cap; omit otherwise>,\"maxIterations\":<raise ONLY if it ran out of iterations before it could finish; omit otherwise>,\"why\":\"<one sentence naming the evidence that the attempt was starved rather than wrong>\"},\"skill_note\":\"<if target=skill or target=kb: concrete coding instruction>\",\"tool_spec\":{\"name\":\"<kebab-case tool name, e.g. add-dependency>\",\"purpose\":\"<one sentence: what repeated mechanical step this automates>\",\"recipe\":\"<the exact shell commands the tool script should run, using $1 $2 ... for its arguments>\"},\"reason\":\"<why this change prevents the same failure on retry>\"}\n\nDecision rules:\n- target=prd: the AC wording was ambiguous or contradictory, causing the agent to write wrong code. Fix the AC.\n- target=tc: the testCriteria facts are wrong or incomplete — the test agent followed them but they described incorrect behavior. Fix the TC facts.\n- target=skill: the agent used a bad coding pattern that should be injected into this retry's prompt only. This INCLUDES any diagnosis that names a specific, checkable fact about a type, property, field, or signature (e.g. \"missing required X field\", \"Config doesn't match Y's type\", \"wrong argument count for Z\") — that fact will not change no matter how many times or how strong a model you retry with, so it must be told, not re-guessed.\n- target=kb: the failure reveals a reusable coding rule that ALL future agents with this agent role should know — append to the role-specific KB.\n- target=tool: the failure is a repeated MECHANICAL step (not a knowledge gap) that a small shell script can perform reliably every time — e.g. \"add a dependency to this codeline's manifest (__MANIFEST_FILE__) and install it before importing it\". Only use target=tool when a KB rule alone has already failed to prevent the same class of failure, or the fix is a multi-step shell recipe error-prone to repeat by hand. Provide tool_spec.\n- target=escalate: the defect is in a file listed under OTHER stories' scope above. This writer cannot fix it and every retry that tells it to is wasted (six times in one run). Name the file, its owner and the narrow fix in `escalation`; the pipeline applies it in the owning story and gives this story a free retry. A file this story SHARES with other stories (it appears in this story's scope AND under another story's) is escalated the same way when the defect is in the part the other story owns: name that story in ownerStoryId. With no ownerStoryId, a shared file stays this story's fix.\n- target=spec: this story's criteria and another story's criteria (listed above) contradict each other on the same file — no code satisfies both. Say which criterion is wrong in tc_patches with its storyId (index into THAT story's facts); a reviewer judges the change. Never prescribe the writer to satisfy one and break the other.\n- target=environment: the failure is the environment's (a missing interpreter, a service that is down, a dependency the provision step did not install, a generation or build step the repository requires and this checkout never ran), not the writer's code. No stronger model fixes it, and the retry runs without spending a rung. When the repository itself says how to put it right (a script it declares, a README step, a generator, an install), give that repair as tool_spec: once a reviewer approves it, the engine runs it before every verification. Never prescribe the writer a workaround in correct code to avoid an environment fault.\n- target=none: reserved for a genuinely non-reproducible mistake — a stray typo, a one-off slip with no identifiable pattern — where your own diagnosis does not name a specific, checkable, nameable fact (a property, type, field, or signature) that a future attempt could look up and get right. If your diagnosis DOES name such a fact, that is deterministic, not transient, and target=none is the wrong answer even if you are uncertain what the exact correct value should be — use target=skill and describe the fact you found, even incompletely, rather than defaulting to a retry that cannot discover it on its own.\n- Only include ac_patches when target=prd, tc_patches when target=tc or target=spec (with storyId), escalation when target=escalate; use [] for other targets. Only include tool_spec when target=tool or target=environment; omit otherwise.\n- skill_note must be AT MOST __SKILL_NOTE_MAX__ characters. It is injected into a retry prompt and may persist into the agent's profile, so a note past that length is rejected and has to be rewritten before the retry can use it — write within it the first time.\n- skill_note must be a concrete \"do/don't\" instruction, written so it can be ACTED ON without further interpretation:\n * OPEN WITH AN IMPERATIVE VERB (Use, Always, Never, Prefer, Avoid). A note opening with a subordinate clause (\"When integrating X, ...\") is reformatted before storage, which costs a round trip and risks losing detail.\n * STATE THE RULE COMPLETELY. A note that stops mid-sentence is worse than no note, because the reader cannot tell it is incomplete and will act on the fragment.\n * CARRY ANY LITERAL VERBATIM AND UNABBREVIATED — a regex, a path, a command, a flag, a config value. Never elide one with \"...\" or \"e.g.\", and never shorten it to save space. THE LITERAL IS THE FIX; the prose around it is not actionable without it.\n * If the complete literal will not fit alongside the explanation, KEEP THE LITERAL AND SHORTEN THE EXPLANATION. Never the reverse.\n * Example: \"Never use backtick template literals in test files — use single-quoted strings only.\"\n- tool_spec.recipe must be idempotent shell — safe to run more than once (e.g. check before installing).\n- Keep diagnosis under 20 words, reason under 15 words.\n\n## What The Failed Attempt Actually Did\n\n__ATTEMPT_CHANGES__\n\nThis is a diffstat, not a judgement. It is the evidence you were previously asked to diagnose\nwithout: an attempt that changed nothing and an attempt that rewrote four files produce the same\nerror text, and they are not the same failure. If the summary says NO files were changed, the\nquestion is why the implementer produced nothing — not what is wrong with code that does not\nexist. You have tools: open the files named here before concluding anything about them.\n",
23
+ "$why": [
24
+ "__ATTEMPT_CHANGES__ added 2026-08-12: this analyst was asked why an implementation failed while being shown no implementation. It answered 'Target=none — Transient import slip', a fair reading of the text it had and useless as guidance, and its answer escalates the model — so a blind diagnosis spends the writer's ladder.",
25
+ "",
26
+ "2026-08-17: the worked EXAMPLE of a reliable instruction named package.json — so the model was shown one ecosystem as the shape of a good answer.",
27
+ "2026-08-18: this template specified the skill_note's shape in detail and never its LENGTH, so the analyst wrote 331 and 356 characters against a 200-character limit it was never told. The reviewer rejected on length and a summarizer rewrote — a model round trip per attempt, costing both lanes about three of twelve attempts. The number comes from config/self-heal.json, the same declaration the checker reads."
28
+ ],
29
+ "seams": [
30
+ "impl-failure-analyst"
31
+ ],
32
+ "mayBeEmpty": [
33
+ "__HEALING_HISTORY__",
34
+ "__SHARED_CRITERIA__",
35
+ "__SKILL_ADDENDUM__",
36
+ "__STORY_ACS__"
37
+ ],
38
+ "$mayBeEmptyWhy": "A role may carry no accumulated skill notes — skill_addendum defaults to \"\" and the lookup ends `// \"\"`. Undeclared, the analyst could not build its prompt, so the thing that diagnoses a failed gate died of the same fault as the gate (2026-08-28).",
39
+ "_storyAcs": "__STORY_ACS__ RESTORED 2026-09-11 as a BLOCK, mayBeEmpty: the caller supplies the heading AND the numbered criteria together, or nothing. ea920ee7 removed the placeholder because brownfield never has acceptance criteria (it judges on VCs) and an empty heading is worse than none — that ruling stands: on brownfield the prompt carries no heading. It threw away the greenfield case: a greenfield PRD is authored WITH acceptance criteria and they are in scope (operator, 2026-09-11), so this seam sees them there. Absent is a real state; present is the greenfield contract.",
40
+ "$whyMayBeEmpty": {
41
+ "__SHARED_CRITERIA__": "Empty when no other story holds criteria on this story's files.",
42
+ "__HEALING_HISTORY__": "Empty on the first diagnosis of a story."
43
+ }
44
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "$why": [
3
+ "Moved out of run-agent-orchestration.sh on 2026-08-16.",
4
+ "",
5
+ "Turns a QA gate finding into an actionable diagnosis. The role instructions arrive as a",
6
+ "value now; they used to be a command substitution piping profiles.json through python",
7
+ "inside the heredoc, which is prompt assembly performed by a shell pipeline -- unreviewable",
8
+ "as prose, and silently empty when the file or the key is missing.",
9
+ "",
10
+ "The finding carries `work`, the analyst's judgement of WHO can resolve it. Every finding used to go to the AC",
11
+ "remediator and back to the code writer — including a surviving mutation in correct code, which only a test can",
12
+ "kill; the code writer may not touch tests, committed nothing, and the phase looped. `tests` routes the finding to",
13
+ "the test-writer (brownfield, where it owns the tests)."
14
+ ],
15
+ "description": "Analyses one QA gate finding and reports what it means, quoting the log line that proves it.",
16
+ "id": "gate-finding-analyst",
17
+ "seams": [
18
+ "impl-failure-analyst"
19
+ ],
20
+ "placeholders": [
21
+ "__PROFILE__",
22
+ "__GATE_LABEL__",
23
+ "__GATE_LOG__",
24
+ "__PRD_FILE__",
25
+ "__PHASE_ID__"
26
+ ],
27
+ "version": 2,
28
+ "body": "__PROFILE__\n\nGate: __GATE_LABEL__\nGate log file: __GATE_LOG__\nPRD file: __PRD_FILE__\nCurrent phase: __PHASE_ID__\n\n## Who can resolve it\nAdd one more field to the JSON, `\"work\"`, with your judgement of what must change for this finding to be resolved:\n- `\"tests\"` — the product code already does what the finding requires, and only the tests are insufficient: they do not exercise, observe or distinguish the behaviour the finding names (for example a mutation that survives because no assertion can tell the mutant from the original, or a branch no test reaches).\n- `\"code\"` — the product code itself must change.\nDecide from the log and the code it names, not from the gate's name.\n\nRun your analysis now. Paste the verbatim log line proving the finding, then emit the JSON output."
29
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "$why": [
3
+ "Moved out of run-agent-orchestration.sh on 2026-08-16.",
4
+ "",
5
+ "Four of its inputs were command substitutions inside the heredoc: the lint log, the",
6
+ "writer-output manifest, the active PRD stories, and the role instructions. Each is an INPUT,",
7
+ "and each was being computed by a shell pipeline that fails to an empty string in silence.",
8
+ "",
9
+ "The lint log arrived `head -200`. That is a truncation of the evidence the analyst is asked",
10
+ "to attribute, and it cuts mid-file-list: the standing rule is drop whole entries or nothing,",
11
+ "and a lint log severed at 200 lines drops exactly the findings nobody looked at. The caller",
12
+ "passes the whole log.",
13
+ "",
14
+ "2026-08-17: named .spec.ts/.test.ts, so the reasoning about which files are declared held only for one ecosystem.",
15
+ "",
16
+ "2026-08-17: named .spec.ts/.test.ts, so the reasoning about which files a story declares held for one ecosystem only."
17
+ ],
18
+ "description": "Maps a lint or type-check failure to the story that owns the offending file.",
19
+ "id": "lint-finding-analyst",
20
+ "seams": [
21
+ "impl-failure-analyst"
22
+ ],
23
+ "placeholders": [
24
+ "__ACTIVE_STORIES__",
25
+ "__LINT_LOG__",
26
+ "__PHASE__",
27
+ "__PROFILE__",
28
+ "__TEST_FILE_CONVENTIONS__",
29
+ "__WRITER_OUTPUTS__"
30
+ ],
31
+ "version": 1,
32
+ "body": "You are the gate-finding-analyst. A lint gate (the project's type check + eslint) failed during the '__PHASE__' phase of an automated TypeScript project build.\n\n## Lint Gate Log\n__LINT_LOG__\n\n## Files THIS RUN produced or modified (the writer-output manifest)\n__WRITER_OUTPUTS__\n\nA finding in ANY file above was produced by this run and is in scope, even if\nthat file is not declared in a story's technicalNotes.files. Reproducing tests are written by a separate\nagent AFTER the story declares its files, so a test file (__TEST_FILE_CONVENTIONS__) will normally be\nabsent from the declared list while still being this run's work — live\n2026-07-26, a real finding in a newly-written test file was dropped for exactly\nthat reason. Attribute such a file to the story whose source file it tests.\n\n## PRD Stories (active)\n__ACTIVE_STORIES__\n\n## Agent Profile\n__PROFILE__\n\nIdentify: which story owns the file with the lint/type error? Output JSON only:\n{\"gate\":\"lint\",\"story_id\":\"<id>\",\"file\":\"<path>\",\"line\":<n>,\"rule\":\"<tsc-error-code or eslint-rule>\",\"message\":\"<description>\",\"suggested_fix\":\"<one-line fix>\"}"
33
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "$why": [
3
+ "Moved out of run-agent-orchestration.sh on 2026-08-16.",
4
+ "",
5
+ "Nothing to do with the skill assessment despite sharing its PROMPT_HEADER delimiter — a",
6
+ "reminder that a heredoc's name says nothing about what it carries.",
7
+ "",
8
+ "It caps the investigation at a tool budget and requires an answer BEFORE the budget is",
9
+ "spent: exploring past it without answering returns nothing at all, which is the failure",
10
+ "mode this wording exists to prevent.",
11
+ "",
12
+ "2026-08-17: this prompt named *.test.ts, *.spec.ts, typescript-engineer, test-engineer, package.json, tsconfig.json and vitest.config.ts. It decides how stories are SPLIT and which files a writer must never touch, so on any non-Node codeline it split on a convention that does not exist and protected files that are not there while leaving the real manifest unprotected. Injected from lib/handlers/stack-facts.js at render time."
13
+ ],
14
+ "description": "Bounds a post-failure investigation to a tool budget and requires an answer within it.",
15
+ "id": "post-failure-analyst",
16
+ "seams": [
17
+ "phase-assessment"
18
+ ],
19
+ "placeholders": [
20
+ "__IMPL_ROLE__",
21
+ "__PFA_FACTS__",
22
+ "__PFA_TOOL_BUDGET__",
23
+ "__PHASE_ID__",
24
+ "__PRD_REL__",
25
+ "__PROFILES_REL__",
26
+ "__PROTECTED_FILES__",
27
+ "__TEST_FILE_CONVENTIONS__",
28
+ "__TEST_ROLE__"
29
+ ],
30
+ "version": 1,
31
+ "body": "CONVERGE FAST — HARD LIMIT: __PFA_TOOL_BUDGET__ tool calls total. This is not a suggestion.\nBy your __PFA_TOOL_BUDGET__th tool call you MUST stop investigating and return your BEST current\nanswer. Exploring past the budget WITHOUT answering means you return nothing at all and every\nbit of your investigation is discarded — a partially-informed augmentation is worth far more\nthan none. If you are unsure, decide from what you have already seen and answer now.\n\nYou are the skill assessment agent running in PRE-PHASE mode. Your job is to deeply reason about what each assigned agent will need to succeed — not just check a list of requiredSkills, but actively anticipate pitfalls given the tech stack, file types, and implementation patterns the stories demand. You augment agent profiles with the specific knowledge needed to avoid failures before they happen.\n\n## PRD STRUCTURE (read this carefully before issuing any jq commands)\nThe PRD file uses a FLAT structure — not nested phases. Key paths:\n- Story list: .stories[]\n- Phase story order: .implementationOrder[\"__PHASE_ID__\"] — returns an array of story IDs\n- Story lookup: .stories[] | select(.id == \"<id>\")\n- Agent role field: .agentRole on each story object\n- Files field: .technicalNotes.files[] on each story object\n\nDO NOT use .phases[0] — that path does not exist in this PRD.\n\n__PFA_FACTS__\n\n## Task\n1. Run: jq -r '.implementationOrder[\"__PHASE_ID__\"][]' __PRD_REL__\n This gives you the ordered list of story IDs for this phase.\n\n2. For each story ID, run: jq -c --arg id \"<id>\" '.stories[] | select(.id == $id) | {id, agentRole, unitTests, technicalNotes}' __PRD_REL__\n\n3. ROLE ASSIGNMENT — For any story where agentRole is null or empty:\n a. Examine the story's technicalNotes.files list\n b. If ALL files in the list are test files (matching this codeline's test conventions — __TEST_FILE_CONVENTIONS__), assign agentRole \"__TEST_ROLE__\"\n c. If the story has unitTests:true AND its files include test files mixed with implementation files, this story MUST be split:\n - Implementation child: files that are NOT test files, agentRole \"__IMPL_ROLE__\"\n - Test child: only the test files, agentRole \"__TEST_ROLE__\"\n - Report the agentRole for each child in storyRoleAssignments\n d. Otherwise assign the most appropriate role from __PROFILES_REL__ based on the story's tech stack\n e. Report it in storyRoleAssignments. Do NOT edit __PRD_REL__ yourself.\n\n4. PROFILE CREATION — For any agentRole assigned in step 3 that does NOT exist as a key in __PROFILES_REL__:\n a. Read the project context from __PRD_REL__ (projectName, techStack, constraints)\n b. Read the story's technicalNotes to understand the testing conventions for this project\n c. Generate a new profile string for that role, derived from THIS project: its name, its test framework and version, its module system, its mocking convention, its forbidden packages, its test config path and include pattern — every one of these read from the project's own manifest and existing tests, never assumed. Include the instruction that a test-only role writes test files and never implementation files.\n d. Report it in newProfiles. Do NOT edit __PROFILES_REL__ yourself.\n\n5. PROACTIVE SKILL INFERENCE — For each story's agentRole, reason beyond the requiredSkills list. Read the story's full technicalNotes, acceptanceCriteria, and files. Then ask: given this tech stack and these implementation patterns, what are the specific pitfalls an agent is likely to walk into that are NOT already covered in the profile?\n\n Infer gaps by reasoning about the code the agent will write, not the labels in requiredSkills. The SHAPE of the reasoning is:\n \"<something concrete this story's code will do, read from THIS repository>\n → the specific mistake an agent tends to make there\n → the exact rule that prevents it\"\n\n EVERY rule you report must come from THIS project's code. Name a real file, a\n real dependency, a real convention you have actually seen in this repository.\n You are given no worked examples on purpose: any example would be from some\n other project, and reproducing it here would hand this project's agents\n another codebase's rules. If you cannot ground a rule in something you have\n read, do not report it — an empty profileAdditions is a correct answer.\n\n Be specific and actionable — state the exact rule, not a general category. Do NOT edit __PROFILES_REL__ yourself.\n\n6. EXPLICIT SKILL GAP FILL — After proactive inference, also do the traditional check:\n a. Compare each story's technicalNotes.requiredSkills against the agent's profile text\n b. For any skills explicitly listed but not covered in the profile, report them in profileAdditions\n\n## YOUR OUTPUT — A DECISION, NOT AN EDIT\n\nYou do NOT write any file. You have no write tool, and hand-rolling scripts to\nedit a 136,000-character JSON file is what made every previous attempt at this\ntask run out of iterations without ever finishing — one of them appended the same\nrule four times and then spent its remaining turns undoing that.\n\nReturn ONE JSON object and nothing else. The pipeline applies it deterministically:\n\n {\n \"storyRoleAssignments\": [{\"storyId\": \"<id>\", \"agentRole\": \"<role>\"}],\n \"profileAdditions\": [{\"role\": \"<role>\", \"rules\": [\"<exact rule>\", \"...\"]}],\n \"newProfiles\": [{\"role\": \"<role>\", \"profile\": \"<full profile text>\"}]\n }\n\nAll three keys are required; use an empty array when there is nothing for one.\nRules already present in a profile are ignored, so do not try to de-duplicate.\nA role that already has a profile is never replaced — use profileAdditions for it.\nA story that already has an agentRole is never reassigned.\n\nKnown skill categories: deployment_platform, language, framework, testing, database, infrastructure, api, cloud_service\n\nCRITICAL RULES:\n- Keep __PROFILES_REL__ valid JSON at all times. Only ADD to existing profile strings, never remove content.\n- A test-authoring profile (__TEST_ROLE__) must instruct the agent to ONLY write test files — never touch implementation files.\n- The same agentRole must NEVER appear on both an implementation story and its paired test story in the same phase.\n- Inferred skill additions must be specific and actionable (a concrete rule the agent can follow), not vague capability claims.\n- NEVER write example API keys, tokens, or secrets into any source file — not even as placeholders. If example values are needed in documentation, read the credential from the environment (whatever variable THIS project already uses for it) or write the literal string `YOUR_API_KEY_HERE`. Any string matching `/sk-[a-z]+-[a-zA-Z0-9]+/` or resembling a credential will trigger a SAST blocker.\n- NEVER modify this codeline's build or scaffold configuration (__PROTECTED_FILES__), or any other scaffold-phase infrastructure file. These are owned by the scaffold phase and are immutable to all subsequent phases. If a story appears to require changing these files, flag it as a blocker in skills-gap-report.jsonl instead.\n- NEVER rewrite the PRD file (__PRD_REL__) with a different story structure. You may only update agentRole fields and append to __PROFILES_REL__. Any other structural change to the PRD is forbidden.\n- NEVER modify .env, .env.*, *credentials*, or any file containing API keys or secrets. These files are immutable to all agents — modification would break the entire pipeline for all subsequent runs.",
32
+ "$whySeams": "It runs as seam phase-assessment (phase-assessment.sh renders it and invokes that seam; assessment_apply.py consumes the reply). It declared impl-failure-analyst, whose contract (diagnosis) it never answers (found 2026-09-25)."
33
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$why": [
3
+ "The diff reviewers' half of the detective's 'shared' finding (team-lead-review and",
4
+ "code-review-cycle, through lib/review-criteria.sh). Full consumer review 2026-10-02: neither",
5
+ "reviewer was shown it, so a required manifest bump read as a thin change and a saved local-path",
6
+ "redirection to a sibling's working copy would have read as a fix. Locations are RELATIVE.",
7
+ "Operator, 2026-10-06: a consumer's declared range is never changed by a story ('avoid package.json upgrades in this change'; a range may be locked for a reason); the provider ships the change on a release line inside each consumer's range."
8
+ ],
9
+ "description": "Appended to the criteria a diff reviewer judges against when the detective reported a shared fix site: what is shared, where the sibling working copies are, and what a correct shared change looks like in a diff.",
10
+ "id": "review-shared",
11
+ "seams": [],
12
+ "placeholders": [
13
+ "__SHARED_SITES__",
14
+ "__SIBLING_LOCATIONS__"
15
+ ],
16
+ "version": 2,
17
+ "body": "\nSHARED WITH OTHER CODELINES OF THIS STORY — the investigation reported:\n__SHARED_SITES__\nThe other codelines' working copies, relative to this codeline's root:\n__SIBLING_LOCATIONS__\nJudge the diff with this in mind. A change to this codeline's declared range of a shared package is a BLOCKER finding: the range belongs to this codeline's own upgrade decisions, never to this story — where the range does not take the change, the provider ships it on a release line inside the range. A dependency pointed at another codeline's working copy (a local path or link) that is SAVED in the diff is a defect: that is how the change is built and tested, not part of what ships. Where this codeline provides the package, judge whether each consumer above receives the change through the range it ALREADY declares, on every release line those ranges require; a consumer left out of reach is a blocker.\n"
18
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$why": [
3
+ "Moved out of run-agent-orchestration.sh on 2026-08-16.",
4
+ "",
5
+ "Decides whether a story that failed repeatedly should have its acceptance criteria restructured, and proposes the restructuring."
6
+ ],
7
+ "description": "Judges whether a repeatedly-failing story needs its acceptance criteria restructured.",
8
+ "seams": [
9
+ "ac-elaboration"
10
+ ],
11
+ "id": "story-recovery-analyst",
12
+ "version": 1,
13
+ "body": "You are the story-recovery-analyst. Story __STORY_ID__ timed out twice (watchdog) and was marked failed -- it never finished within its time budget. Diagnose whether the story's OWN scope is the root cause (too many acceptance criteria, ambiguous/contradictory requirements, an unbounded/open-ended task) as opposed to a transient model/infra hiccup.\n\nStory (full PRD entry):\n__STORY_JSON__\n\nTail of its execution log (what it was doing when it timed out):\n__LOG_TAIL__\n\nIf the story's scope is genuinely too large or ambiguous: respond with JSON {\"restructure\": true, \"new_acs\": [\"...\", \"...\"], \"reason\": \"...\"} -- new_acs must be a TRIMMED, CONCRETE, non-overlapping replacement for the story's current acceptanceCriteria array (same intent, narrower/clearer scope, each one independently verifiable).\nIf this looks like a transient issue (model hiccup, infra flake) rather than a scope problem: respond with JSON {\"restructure\": false, \"reason\": \"...\"}.\nRespond with ONLY the JSON object, nothing else.",
14
+ "placeholders": [
15
+ "__STORY_JSON__",
16
+ "__LOG_TAIL__",
17
+ "__STORY_ID__"
18
+ ]
19
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "id": "team-lead-review",
3
+ "layer": "project",
4
+ "version": 3,
5
+ "description": "The code reviewer: judges a story change against its criteria and emits a verdict.",
6
+ "$why": [
7
+ "MIGRATED OUT OF team-lead-review.sh ON 2026-08-13, byte-identical (3827 bytes).",
8
+ "This prompt was a 78-line double-quoted bash string, and prose inside one is live code: a raw quote closed the string so <the GIT DIFF above> parsed as a redirection, and two backticks EXECUTED as commands and spliced their empty output into what the model was sent. The reviewer produced no verdict for two days and the retry loop ran 701 cycles.",
9
+ "A prompt in a data file cannot do that.",
10
+ "NO PROJECT FACT BELONGS HERE. Under the target design a mint-time agent generates each project prompt using this template as its guide.",
11
+ "v2, 2026-08-13: the scan_secrets example named a CLIENT environment variable. A project fact in a generic template becomes wrong output for every other project once a mint-time agent generates project prompts from it.",
12
+ "2026-08-19: see code-review-cycle — unevidenced issues are discarded by code-review-json.py, so this seam must ask for evidence too.",
13
+ "2026-08-19: this seam is the ONLY reviewer the pipeline executes, and it received no prior verdict and no iteration number — zero occurrences of PRIOR or __ITERATION__ in team-lead-review.sh. A live brownfield run run 2 it approved a story whose blocker it had raised and which had not changed since; the engine detected that and called an escalation function that does not exist. It never changed its mind — it was never told it had a mind to change. The history was already append-only in code-reviews.jsonl."
14
+ ],
15
+ "placeholders": [
16
+ "__ESTIMATION_ASSESSMENT__",
17
+ "__BLOCKER_DISCIPLINE__",
18
+ "__CODEGRAPH_TOOL_BLOCK__",
19
+ "__FIX_ANALYSIS_BLOCK__",
20
+ "__LEARNED_RULES_BLOCK__",
21
+ "__PRIOR_REVIEW__",
22
+ "__PROJECT_ROOT__",
23
+ "__PROJECT_TOOLS_BLOCK__",
24
+ "__STORY_DESC__",
25
+ "__STORY_DIFF__",
26
+ "__STORY_FILES__",
27
+ "__STORY_ID__",
28
+ "__STORY_TITLE__",
29
+ "__TEST_FILES__",
30
+ "__TEST_OWNERSHIP__",
31
+ "__UNCOVERED_VC_BLOCK__",
32
+ "__VC_BLOCK__",
33
+ "__STORY_ACS__"
34
+ ],
35
+ "body": "REVIEW TASK: Story __STORY_ID__ — __STORY_TITLE__\n\nDESCRIPTION:\n__STORY_DESC__\n\n__STORY_ACS____VC_BLOCK____ESTIMATION_ASSESSMENT__\n__UNCOVERED_VC_BLOCK__\n__FIX_ANALYSIS_BLOCK__\n__BLOCKER_DISCIPLINE__\n\nRELEVANT FILES (fix files + the reproducing test the pipeline shipped — review BOTH): __STORY_FILES__ __TEST_FILES__\n\nGIT DIFF (recent changes):\n```diff\n__STORY_DIFF__\n```\n\nPROJECT ROOT: __PROJECT_ROOT__\n__CODEGRAPH_TOOL_BLOCK__\n\nCREDENTIALS. Call scan_secrets with the diff above before you finish. It reports values that\nhave been PASTED INTO the code — a quoted, long, high-entropy literal assigned to a\ncredential-shaped name. It deliberately does NOT report references (an identifier, a member\nexpression, a process.env read), because those are the correct practice: a pasted key is always\na literal, so nothing real is missed by ignoring them.\n\n scan_secrets(diff=\"<the GIT DIFF above>\")\n\nJudge what it returns; it does not block anything. A finding is a blocker — a credential in\nsource is not fixable after the fact once committed. An empty result is not proof of safety on\nits own, so if the diff introduces configuration you can still say so in your own words.\n\nThis check used to run at commit time and matched `name: value` on shape alone. On 2026-08-09\nit refused a correct commit for `management_token: SOME_SERVICE_API_TOKEN` — an\nenvironment-derived identifier — and had never caught a real leak. It is yours now because you\nhave the diff and can tell the two apart.\n\nReview the implementation against each acceptance criterion above.\nCheck: TypeScript strict compliance, error handling, security (OWASP).\n__TEST_OWNERSHIP__\n\nCONCISION & REUSE (blocker-level checks):\n- If the change addresses the symptom but NOT the prescribed root cause above (e.g. adds new code paths the bug never reaches), that is a 'blocker' — request changes.\n- If a MORE CONCISE change would achieve the SAME criteria with the SAME coverage, request changes and name it. A shorter change that leaves a criterion unmet or a behaviour untested is not more concise — it is incomplete.\n- If the diff hand-rolls logic that an EXISTING function/helper already provides (verify with the tool above), that is a 'blocker' — request changes and name the helper to reuse instead.\n- Do NOT approve an over-engineered fix just because it satisfies the AC wording.\n\nTEST COVERAGE VERIFICATION (grounded, not a visual skim — found live 2026-08-03,\nObserved live: the reviewer claimed 2 of 3 required test scenarios were missing,\nTWICE in a row, against a diff that unambiguously contained all 3 as clearly-\nnamed `it(...)` blocks — a real, reproducible failure to verify a claim the\ntools below could have confirmed in one call):\n- Before flagging ANY acceptance-criteria test scenario as missing or absent, use\n the search tool to grep the diff above (or read_file on the test file under\n PROJECT_ROOT) for an `it(`/`test(` block whose name or body plausibly covers\n that scenario. A large diff is easy to skim past a matching block — search for\n it, do not judge from a visual read alone.\n- If your search finds a plausible match, do NOT flag that scenario as missing —\n if the test's assertions are wrong or incomplete, say so specifically (quote\n the test name and what it fails to assert); 'missing' and 'inadequate' are\n different findings and must not be conflated.\n- If your search finds nothing, name the exact search you ran (tool + query) in\n the issue description, so a genuinely absent test is distinguishable from an\n unverified guess.\nDo NOT read from external URLs.\n__LEARNED_RULES_BLOCK__\n__PROJECT_TOOLS_BLOCK__\n\nRespond with ONLY a JSON object (no markdown fences):\n{\"verdict\":\"approved\",\"rating\":9,\"unmet\":[],\"issues\":[],\"summary\":\"...\"}\n OR\n{\"verdict\":\"changes_requested\",\"rating\":6,\"unmet\":[\"<each criterion not met, and what would meet it>\"],\"issues\":[{\"severity\":\"blocker|major|minor\",\"file\":\"...\",\"line\":0,\"description\":\"...\",\"suggestedFix\":\"...\"}],\"summary\":\"...\"}\n\nA 'blocker' issue MUST be fixed before merge. 'major' should be fixed. 'minor' is optional.\n\nEvery issue MUST carry an `evidence` field: the exact thing you READ that establishes it — `<path>:<line>` you opened, or the command you ran and what it returned. Not a restatement of the claim, not a plan reference: what you looked at. An issue without evidence is DISCARDED before it reaches anyone, so an unsupported finding is worse than no finding — it costs you the issue. If a claim is about a library's API, the evidence is that library's installed types or source in this repository, and the installed contract beats the plan.\n\n\n__PRIOR_REVIEW__\nCONSISTENCY WITH YOUR OWN PREVIOUS VERDICTS\n\nIf you raised a blocker on an earlier iteration and the code that caused it has not changed, that\nblocker still stands: raise it again, and do NOT approve. Dropping a finding is a decision that the\nfinding was wrong — if you now believe that, say so explicitly and give the evidence that changed\nyour mind. Silence is not resolution, and an approval that follows an unresolved blocker is the one\noutcome this pipeline cannot detect for itself.\n\nIf the code HAS changed, judge the change on its merits: a previous blocker is not a permanent one.\n\n\nACCOUNT FOR EVERY VERIFICATION CRITERION. Alongside the verdict, emit `vcAssessment`: one entry per criterion listed above, `{\"criterion\":\"<the criterion, restated>\",\"met\":true|false,\"evidence\":\"<path>:<line> you opened, or the command you ran and what it returned>\"}`. An approval that names no criterion cannot be checked by anyone afterwards. Do NOT approve while marking a criterion unmet — that contradiction is rejected automatically and costs you the review.",
36
+ "seams": [
37
+ "team-lead-review"
38
+ ],
39
+ "mayBeEmpty": [
40
+ "__ESTIMATION_ASSESSMENT__",
41
+ "__FIX_ANALYSIS_BLOCK__",
42
+ "__LEARNED_RULES_BLOCK__",
43
+ "__PRIOR_REVIEW__",
44
+ "__PROJECT_TOOLS_BLOCK__",
45
+ "__STORY_ACS__",
46
+ "__TEST_FILES__",
47
+ "__UNCOVERED_VC_BLOCK__",
48
+ "__VC_BLOCK__"
49
+ ],
50
+ "$whyEstimationAssessmentMayBeEmpty": "__ESTIMATION_ASSESSMENT__ (2026-09-28): the estimation application's assessment of this story's ticket (EPAM_ESTIMATION_MANIFEST, rendered by lib/handlers/estimation-manifest.js with this agent's framing from the engine template estimation-assessment). Absent is the ordinary state: a run with no manifest, or a ticket the manifest does not cover. It sits directly beside its neighbour with no separator of its own, so an absent assessment renders the prompt byte-for-byte as before.",
51
+ "$mayBeEmptyWhy": "Ordinary absences on a first cycle, each with a deliberate empty branch in its producer: the KB is cleared per run so nothing has been learned yet; the first cycle has no earlier review of its own; and every verification criterion being covered is the GOOD outcome, not an error. Undeclared, the renderer refused and the reviewer could not run AT ALL — it emitted a synthetic phase-level changes_requested, no per-story feedback existed, and the loop halted the run of 2026-08-28 with two correct one-line fixes sitting unreviewed on their branches. prior-reviews.py WARNS when it cannot read the log, so declaring this hides no failed lookup. __FIX_ANALYSIS_BLOCK__ joined them on 2026-08-29: a story with no root-cause analysis is ordinary — greenfield work, or one the detective did not analyse — and its producer is a deliberate empty (`[ -n \"$STORY_FIX_ANALYSIS\" ] && ... || true`). Undeclared, the reviewer refused to render for such a story and the phase could not proceed at all. The paid run of 2026-08-28 hid this because every story it carried HAD an analysis. __TEST_FILES__ and __PROJECT_TOOLS_BLOCK__ joined on 2026-08-29: a change that touches no test file is ordinary (the list comes from a git diff filtered for tests), and a project that declares no tools is ordinary (build_project_tools_block returns empty rather than failing). __STORY_DESC__ is deliberately NOT declared: an empty description means the reviewer would judge a story it cannot see, and that is far more likely a story-id mismatch than a genuinely description-free story. Refusing is the correct answer there.",
52
+ "_storyAcs": "__STORY_ACS__ RESTORED 2026-09-11 as a BLOCK, mayBeEmpty: the caller supplies the heading AND the numbered criteria together, or nothing. ea920ee7 removed the placeholder because brownfield never has acceptance criteria (it judges on VCs) and an empty heading is worse than none — that ruling stands: on brownfield the prompt carries no heading. It threw away the greenfield case: a greenfield PRD is authored WITH acceptance criteria and they are in scope (operator, 2026-09-11), so this seam sees them there. Absent is a real state; present is the greenfield contract.",
53
+ "$whyVcBlockMayBeEmpty": "__VC_BLOCK__ holds the brownfield verification criteria; a greenfield story judges on its acceptance criteria (__STORY_ACS__) and has none, so the block is legitimately empty — as code-review-cycle already declares. Undeclared, the reviewer could not render its prompt for any greenfield story and the phase halted after six no-verdict cycles (£0 greenfield harness, 2026-09-13)."
54
+ }
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+ /**
3
+ * THE MODEL THE WRITER'S REVIEW RUNS ON — the one reader of config/reviewer-policy.json (see that file). Both pipelines call it: the bash reviewer (team-lead-review.sh reviewer_model_for) and the Claude workflow's generator.
4
+ *
5
+ * reviewerModel({ writerModel, policyFile, setFile }) → the model name
6
+ * node reviewer-model.js <writer's rung model> → prints it (the active provider set's file, by EPAM_PROVIDER_SET)
7
+ *
8
+ * No policy file (a pipeline without this package's file) is the old rule: the writer's model. A policy it does not know, a file it cannot read, or a set with no highest ladder is a stated error — never a silent change of model.
9
+ */
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ const POLICIES = ['writer-rung', 'top-of-ladder'];
14
+
15
+ function reviewerModel({ writerModel, policyFile, setFile }) {
16
+ if (!policyFile || !fs.existsSync(policyFile)) return writerModel;
17
+ let policy;
18
+ try { policy = JSON.parse(fs.readFileSync(policyFile, 'utf8')).model; } catch (e) { throw new Error(`the reviewer policy ${policyFile} is not readable JSON: ${e.message}`); }
19
+ if (!POLICIES.includes(policy)) throw new Error(`the reviewer policy names '${policy}', which is not one of: ${POLICIES.join(', ')} (${policyFile})`);
20
+ if (policy === 'writer-rung') return writerModel;
21
+ if (!setFile) throw new Error('the reviewer policy is top-of-ladder and no provider set file was given to read the ladder from');
22
+ // read as the pipeline reads a set: a set may inherit a ladder from another set's file (readSetFile)
23
+ let set; try { set = require('./llm-settings-resolve.js').readSetFile(setFile); } catch { set = JSON.parse(fs.readFileSync(setFile, 'utf8')); }
24
+ const highest = set.ladders && set.ladders.highest;
25
+ const steps = (highest && highest.modelLadder) || [];
26
+ const top = steps.length ? steps[steps.length - 1].to : (highest && highest.startModel);
27
+ if (!top) throw new Error(`the provider set file ${setFile} has no highest ladder to take the reviewer's top model from`);
28
+ return top;
29
+ }
30
+
31
+ module.exports = { reviewerModel, POLICIES };
32
+
33
+ if (require.main === module) {
34
+ try {
35
+ const resolve = require('./llm-settings-resolve.js');
36
+ const policyFile = path.join(path.dirname(__dirname), '..', 'config', 'reviewer-policy.json');
37
+ const setFile = resolve.activeSetFile();
38
+ process.stdout.write(`${reviewerModel({ writerModel: process.argv[2] || '', policyFile, setFile })}\n`);
39
+ } catch (e) { process.stderr.write(`reviewer-model: ${e.message}\n`); process.exit(1); }
40
+ }
@@ -205,6 +205,20 @@ _provider_for_model() {
205
205
  if [ -n "$_prov" ]; then echo "$_prov"; else echo "${EPAM_ORCHESTRATION_PROVIDER:-}"; fi
206
206
  }
207
207
 
208
+ # reviewer_model_for <the writer's rung model> — the model the review runs on: the declared policy
209
+ # (config/reviewer-policy.json, read by lib/reviewer-model.js — the same reader the Claude workflow's generator uses, so the two
210
+ # pipelines cannot differ). 'writer-rung' is the model that produced the work; 'top-of-ladder' is the top of the active provider set's
211
+ # highest ladder. Run 11, 2026-10-21: on the writer's Sonnet rung the reviewer approved a thin and a rich delivery alike (9 and 9);
212
+ # on Opus it separated them (6 and 7.5) and sent both back. An unreadable policy is said, and the review stays on the writer's model.
213
+ reviewer_model_for() {
214
+ local _writer="$1" _m
215
+ if _m=$("${NODE_BIN:-node}" "$SCRIPT_DIR/lib/reviewer-model.js" "$_writer"); then
216
+ printf '%s\n' "$_m"
217
+ else
218
+ printf '%s\n' "$_writer"; return 1
219
+ fi
220
+ }
221
+
208
222
  # Invoke the review-agent LLM for a single story, with ladder escalation + a
209
223
  # tight iteration cap so it produces a real verdict instead of thrashing.
210
224
  # Outputs raw LLM text; caller extracts verdict JSON. On total failure it emits
@@ -869,7 +883,8 @@ $(render_engine_prompt story-diff-not-inlined "$_sdni_vals" excluded)"
869
883
  export EPAM_REASONING_EFFORT
870
884
  log " review-agent follows the writer's model for $story_id: model=$_rung_model provider=$_rung_provider effort=${EPAM_REASONING_EFFORT} temp=${EPAM_TEMPERATURE:-unset} (writer's rung: effort=${_rung_effort:-unset} temp=${_rung_temperature:-unset})"
871
885
 
872
- log " Invoking review-agent for $story_id... (model=$_rung_model provider=$_rung_provider)"
886
+ _review_model=$(reviewer_model_for "$_rung_model") || warning " the reviewer's model policy could not be read — reviewing on the writer's model $_rung_model"
887
+ log " Invoking review-agent for $story_id... (model=$_review_model provider=$_rung_provider; the writer ran $_rung_model)"
873
888
  REVIEW_OUTPUT_FILE="${LOG_DIR:-$AUTOMATION_DIR/logs}/review-agent-${story_id}.log"
874
889
  # B25 — the reviewer used to fail leaving NO evidence: `$(... | tee FILE)` never
875
890
  # creates FILE when the pipeline dies before producing stdout, so a filesystem
@@ -878,7 +893,7 @@ $(render_engine_prompt story-diff-not-inlined "$_sdni_vals" excluded)"
878
893
  # SOMETHING — a step that cannot explain its own failure gets re-diagnosed by
879
894
  # guesswork every time (three wrong mechanism guesses on 2026-07-24 alone).
880
895
  : > "$REVIEW_OUTPUT_FILE" 2>/dev/null || true
881
- REVIEW_OUTPUT=$(run_review_prompt "$REVIEW_PROMPT" "$_rung_model" "$_rung_provider" 2>&1 | tee -a "$REVIEW_OUTPUT_FILE")
896
+ REVIEW_OUTPUT=$(run_review_prompt "$REVIEW_PROMPT" "$_review_model" "$_rung_provider" 2>&1 | tee -a "$REVIEW_OUTPUT_FILE")
882
897
  _review_rc=${PIPESTATUS[0]}
883
898
  if [ -z "$(printf '%s' "$REVIEW_OUTPUT" | tr -d '[:space:]')" ]; then
884
899
  warning " review-agent produced NO OUTPUT AT ALL (rc=${_review_rc}, model=${_TLR_MODEL:-?}, provider=${EPAM_ORCHESTRATION_PROVIDER:-?})"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amsd-pipeline-shared",
3
- "version": "0.5.10",
3
+ "version": "0.5.11",
4
4
  "description": "The AMSD pipeline's shared components, one source for the bash pipeline and its Claude workflows: the model-call gateway (llm-handler.sh — retry, ladder, self-heal, cost) and the libraries it runs.",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {