amsd-pipeline-shared 0.5.29 → 0.5.31
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/bin/amsd-pipeline-shared.js +14 -5
- package/docs/gateway-baseline-2.0.80.md5 +2 -0
- package/docs/gateway-files.txt +15 -0
- package/orchestrations/config/call-policy.json +4 -0
- package/orchestrations/config/finding-contracts.json +13 -0
- package/orchestrations/prompts/templates/project-prompt-generation.json +6 -4
- package/orchestrations/prompts/templates/prompt-review.json +6 -4
- package/orchestrations/prompts/templates/spec-codeline-share.json +22 -0
- package/orchestrations/prompts/templates/spec-coordinator-review.json +10 -6
- package/orchestrations/prompts/templates/spec-review-criteria.json +6 -4
- package/orchestrations/prompts/templates/spec-review-other-lanes.json +17 -0
- package/orchestrations/prompts/templates/spec-review-ticket.json +22 -0
- package/orchestrations/prompts/templates/spec-ticket-split.json +21 -0
- package/orchestrations/scripts/lib/call-attempts.js +32 -0
- package/orchestrations/scripts/lib/call-failed.sh +65 -0
- package/orchestrations/scripts/lib/codeline-lanes.sh +5 -0
- package/orchestrations/scripts/lib/doctor/agent.js +5 -4
- package/orchestrations/scripts/lib/doctor/handle.js +26 -2
- package/orchestrations/scripts/lib/engine-prompt.js +6 -0
- package/orchestrations/scripts/lib/failure-healing.sh +2344 -0
- package/orchestrations/scripts/lib/finding-check.js +70 -0
- package/orchestrations/scripts/lib/handlers/filtered-prd.js +4 -3
- package/orchestrations/scripts/lib/kb_schema.py +289 -0
- package/orchestrations/scripts/lib/project-prompt-builder.js +42 -6
- package/orchestrations/scripts/lib/project-prompt-contract.js +63 -1
- package/orchestrations/scripts/lib/prompt-build-calls.js +110 -0
- package/orchestrations/scripts/lib/spec-review-findings.js +151 -0
- package/orchestrations/scripts/lib/ticket-split.js +71 -0
- package/orchestrations/scripts/llm-handler.sh +3 -1
- package/orchestrations/scripts/mint-agents-step.js +1 -4
- package/orchestrations/scripts/refresh-project-prompts.js +1 -2
- package/orchestrations/scripts/spec-mode-runner.js +235 -175
- package/package.json +1 -1
|
@@ -64,12 +64,15 @@ if (cmd === 'verify') {
|
|
|
64
64
|
|
|
65
65
|
if (cmd === 'revert') {
|
|
66
66
|
if (!fs.existsSync(path.join(STATE, 'installed.json'))) fail(`nothing to revert: no amsd-pipeline-shared install recorded in ${STATE}`);
|
|
67
|
+
// a file the install did not have when this package went in (its own file, or one a pipeline release no longer carries): removed again
|
|
68
|
+
let ABSENT = []; try { ABSENT = JSON.parse(fs.readFileSync(path.join(STATE, 'installed.json'), 'utf8')).absent || []; } catch { ABSENT = []; }
|
|
69
|
+
const hadNone = (f) => !BASE[f] || ABSENT.includes(f);
|
|
67
70
|
for (const f of FILES) {
|
|
68
71
|
const b = path.join(BACKUP, f);
|
|
69
72
|
if (fs.existsSync(b)) fs.copyFileSync(b, path.join(ORCH, f));
|
|
70
|
-
else if (
|
|
73
|
+
else if (hadNone(f)) fs.rmSync(path.join(ORCH, f), { force: true });
|
|
71
74
|
}
|
|
72
|
-
const back = FILES.filter((f) => (
|
|
75
|
+
const back = FILES.filter((f) => (hadNone(f) ? fs.existsSync(path.join(ORCH, f)) : !known(f, md5(path.join(ORCH, f)))));
|
|
73
76
|
if (back.length) fail(`revert incomplete — not the released copy:\n ${back.join('\n ')}`);
|
|
74
77
|
fs.rmSync(STATE, { recursive: true, force: true });
|
|
75
78
|
process.stdout.write(`amsd-pipeline-shared: reverted ${FILES.length} file(s) to the released copies\n`);
|
|
@@ -82,8 +85,11 @@ let PREV = {}; try { PREV = JSON.parse(fs.readFileSync(path.join(STATE, 'install
|
|
|
82
85
|
const plan = FILES.map((f) => {
|
|
83
86
|
const now = md5(path.join(ORCH, f)); const ours = md5(path.join(ROOT, 'orchestrations', f));
|
|
84
87
|
if (now && now !== ours && PREV[f] === now) return { f, now, ours, state: 'previous' };
|
|
85
|
-
//
|
|
86
|
-
|
|
88
|
+
// ABSENT FROM THE INSTALL: installed — this package's own file, or one a pipeline release no longer carries because this package holds it
|
|
89
|
+
// (operator, 2026-10-23: "move it … and remove in bash pipeline" — one copy, in this package)
|
|
90
|
+
if (now === null) return { f, now, ours, state: 'new' };
|
|
91
|
+
// a file the release never had (this package's own, e.g. lib/agent-messages.js), present with other bytes
|
|
92
|
+
if (!BASE[f]) return { f, now, ours, state: now === ours ? 'current' : 'other' };
|
|
87
93
|
return { f, now, ours, state: now === ours ? 'current' : (known(f, now) ? 'released' : 'other') };
|
|
88
94
|
});
|
|
89
95
|
const other = plan.filter((p) => p.state === 'other');
|
|
@@ -103,7 +109,10 @@ for (const p of plan.filter((x) => ['released', 'new', 'previous'].includes(x.st
|
|
|
103
109
|
fs.chmodSync(dst, mode);
|
|
104
110
|
}
|
|
105
111
|
fs.mkdirSync(STATE, { recursive: true });
|
|
112
|
+
// what the install did not have before this package (kept across re-installs, so a revert removes exactly those)
|
|
113
|
+
let WAS_ABSENT = []; try { WAS_ABSENT = JSON.parse(fs.readFileSync(path.join(STATE, 'installed.json'), 'utf8')).absent || []; } catch { WAS_ABSENT = []; }
|
|
114
|
+
const absent = [...new Set([...WAS_ABSENT, ...plan.filter((p) => p.state === 'new').map((p) => p.f)])].sort();
|
|
106
115
|
fs.writeFileSync(path.join(STATE, 'installed.json'), `${JSON.stringify({ package: PKG.name, version: PKG.version,
|
|
107
|
-
files: Object.fromEntries(FILES.map((f) => [f, md5(path.join(ROOT, 'orchestrations', f))])) }, null, 2)}\n`);
|
|
116
|
+
files: Object.fromEntries(FILES.map((f) => [f, md5(path.join(ROOT, 'orchestrations', f))])), absent }, null, 2)}\n`);
|
|
108
117
|
const n = plan.filter((x) => ['released', 'new', 'previous'].includes(x.state)).length;
|
|
109
118
|
process.stdout.write(`amsd-pipeline-shared ${PKG.version} → ${ORCH}: ${n} file(s) replaced, ${FILES.length - n} already current\n`);
|
|
@@ -245,3 +245,5 @@ fac1b26443bb52ba45ba072f4d654920 scripts/lib/story-guards.sh
|
|
|
245
245
|
ada237373615a26d924226184ed7614b scripts/lib/prd-change-review.sh
|
|
246
246
|
c2dfd2381db0242fdbc487a948fd7feb scripts/lib/agent-si.js
|
|
247
247
|
03bde875977ab741c54c3b38a236a496 scripts/pre-run-reset.sh
|
|
248
|
+
7600f6665c5a9f24dde8969b4c013aa4 scripts/lib/failure-healing.sh
|
|
249
|
+
f5f3f2ffcf1da723455115e856dc173f scripts/lib/kb_schema.py
|
package/docs/gateway-files.txt
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
config/call-policy.json
|
|
1
2
|
config/doctor-heavy-jobs.json
|
|
2
3
|
config/doctor-signatures.json
|
|
4
|
+
config/finding-contracts.json
|
|
3
5
|
config/model-pricing.json
|
|
4
6
|
config/repo-artifacts.json
|
|
5
7
|
config/resume-preserves.json
|
|
@@ -111,13 +113,17 @@ prompts/templates/spec-agent-speckit-refine.json
|
|
|
111
113
|
prompts/templates/spec-agent-speckit.json
|
|
112
114
|
prompts/templates/spec-authoring-rules.json
|
|
113
115
|
prompts/templates/spec-brownfield-mode.json
|
|
116
|
+
prompts/templates/spec-codeline-share.json
|
|
114
117
|
prompts/templates/spec-context-fragments.json
|
|
115
118
|
prompts/templates/spec-coordinator-review.json
|
|
116
119
|
prompts/templates/spec-coordinator.json
|
|
117
120
|
prompts/templates/spec-model-review.json
|
|
118
121
|
prompts/templates/spec-review-criteria.json
|
|
122
|
+
prompts/templates/spec-review-other-lanes.json
|
|
123
|
+
prompts/templates/spec-review-ticket.json
|
|
119
124
|
prompts/templates/spec-shared-consumer.json
|
|
120
125
|
prompts/templates/spec-story-block.json
|
|
126
|
+
prompts/templates/spec-ticket-split.json
|
|
121
127
|
prompts/templates/speckit-split-rules.json
|
|
122
128
|
prompts/templates/squad-coder.json
|
|
123
129
|
prompts/templates/squad-leader.json
|
|
@@ -154,6 +160,8 @@ scripts/lib/agent-ladder.sh
|
|
|
154
160
|
scripts/lib/agent-messages.js
|
|
155
161
|
scripts/lib/agent-si.js
|
|
156
162
|
scripts/lib/agent-si.js
|
|
163
|
+
scripts/lib/call-attempts.js
|
|
164
|
+
scripts/lib/call-failed.sh
|
|
157
165
|
scripts/lib/checkpoints.sh
|
|
158
166
|
scripts/lib/codeline-lanes.sh
|
|
159
167
|
scripts/lib/codeline-packages.js
|
|
@@ -172,6 +180,7 @@ scripts/lib/doctor/cli.js
|
|
|
172
180
|
scripts/lib/doctor/crash-record.js
|
|
173
181
|
scripts/lib/doctor/handle.js
|
|
174
182
|
scripts/lib/doctor/profile.js
|
|
183
|
+
scripts/lib/doctor/release.js
|
|
175
184
|
scripts/lib/doctor/repair.js
|
|
176
185
|
scripts/lib/doctor/restarter.js
|
|
177
186
|
scripts/lib/doctor/state.js
|
|
@@ -180,7 +189,9 @@ scripts/lib/engine-paths.sh
|
|
|
180
189
|
scripts/lib/engine-prompt.js
|
|
181
190
|
scripts/lib/env-file.sh
|
|
182
191
|
scripts/lib/evidence-windows.js
|
|
192
|
+
scripts/lib/failure-healing.sh
|
|
183
193
|
scripts/lib/failure-signature.js
|
|
194
|
+
scripts/lib/finding-check.js
|
|
184
195
|
scripts/lib/flags.sh
|
|
185
196
|
scripts/lib/git-ops.sh
|
|
186
197
|
scripts/lib/halt-recovery.sh
|
|
@@ -212,6 +223,7 @@ scripts/lib/kb-arbitration.js
|
|
|
212
223
|
scripts/lib/kb-cli.js
|
|
213
224
|
scripts/lib/kb-store.js
|
|
214
225
|
scripts/lib/kb-synthesizer.js
|
|
226
|
+
scripts/lib/kb_schema.py
|
|
215
227
|
scripts/lib/langfuse-emit.js
|
|
216
228
|
scripts/lib/llm-settings-resolve.js
|
|
217
229
|
scripts/lib/mint-and-spec.sh
|
|
@@ -230,6 +242,7 @@ scripts/lib/prelaunch.js
|
|
|
230
242
|
scripts/lib/producers/fix-plan.js
|
|
231
243
|
scripts/lib/project-prompt-builder.js
|
|
232
244
|
scripts/lib/project-prompt-contract.js
|
|
245
|
+
scripts/lib/prompt-build-calls.js
|
|
233
246
|
scripts/lib/prompt-library.js
|
|
234
247
|
scripts/lib/prompt-variant.sh
|
|
235
248
|
scripts/lib/qa-gate-evidence.sh
|
|
@@ -244,12 +257,14 @@ scripts/lib/run-modes.sh
|
|
|
244
257
|
scripts/lib/seam-invocation.js
|
|
245
258
|
scripts/lib/seam-ladder.sh
|
|
246
259
|
scripts/lib/self-heal.js
|
|
260
|
+
scripts/lib/spec-review-findings.js
|
|
247
261
|
scripts/lib/stage-coverage-gate.sh
|
|
248
262
|
scripts/lib/story-acs-block.sh
|
|
249
263
|
scripts/lib/story-attempt.sh
|
|
250
264
|
scripts/lib/story-guards.sh
|
|
251
265
|
scripts/lib/story-outputs.sh
|
|
252
266
|
scripts/lib/story-retry-state.sh
|
|
267
|
+
scripts/lib/ticket-split.js
|
|
253
268
|
scripts/lib/tool-timeouts.js
|
|
254
269
|
scripts/lib/transcript-tool-calls.js
|
|
255
270
|
scripts/lib/worktree-rungs.sh
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "HOW MANY TIMES ONE AGENTIC CALL IS ASKED — one value, read by every gateway through scripts/lib/call-attempts.js: the bash hub (llm-handler.sh), the failed-call step (lib/call-failed.sh), the doctor's repair agent (lib/doctor/handle.js), codeline discovery's wall-clock bound, and the Claude workflow's gateway and workflow runner (amsd-pipeline-workflow generator/gateway.js callMaxAttempts). EPAM_CALL_MAX_ATTEMPTS, when a run sets it, wins (an operator's knob for one run, never a second default).",
|
|
3
|
+
"maxAttempts": 3
|
|
4
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "EVERY FINDING MUST LEAD TO AN ACTION (docs/FINDINGS-MUST-ACT.md). One entry per review point (amsd-pipeline-workflow docs/interaction-inventory.json): what the agent finds, the action that must follow, who checks again, which agent self-heal escalates, and how many correction rounds. Read by lib/finding-check.js (the checker and the rounds) — never restated in code.",
|
|
3
|
+
"points": {
|
|
4
|
+
"R9": {
|
|
5
|
+
"producer": "spec-coordinator (review)",
|
|
6
|
+
"finding": "the review's unmetCriteria entries and its blocking flags on a needs_review verdict (lib/spec-review-findings.js detectiveCorrectionNeeded)",
|
|
7
|
+
"action": "a detective correction, recorded on the story as specification.corrections[]",
|
|
8
|
+
"recheck": "the coordinator reviews the corrected story again",
|
|
9
|
+
"selfHeals": "code-graph-detective (its ladder advances each round)",
|
|
10
|
+
"rounds": 2
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -25,9 +25,10 @@
|
|
|
25
25
|
"2026-08-17: this template had NO layer field, and absent defaults to \"engine\" — so the provisioning filter dropped it and the builder then failed with \"'project-prompt-generation' is not installed. It must be declared in bootstrap.copyVerbatim\" while bootstrap DID declare it. It was declared and then filtered out.",
|
|
26
26
|
"",
|
|
27
27
|
"It is project-layer by construction: project-prompt-builder.js reads the PROJECT copy of this template (outDir) to generate every other project prompt — \"the generator's own prompt comes from the project copy just installed, not from an engine-side string\". The layer field now states what the code does.",
|
|
28
|
-
"2026-10-06, AMSD-2974: metrolinx's repro-test-writer was generated as \"write a spec file for a Toast component at cx-shared\" — the generator completed sentences its slots complete at run time, using the one ticket it was shown, and the reviewer passed it because every claim was true. The prompt serves every story and call; a slot's place is the pipeline's; narrowing and a displaced slot are findings."
|
|
28
|
+
"2026-10-06, AMSD-2974: metrolinx's repro-test-writer was generated as \"write a spec file for a Toast component at cx-shared\" — the generator completed sentences its slots complete at run time, using the one ticket it was shown, and the reviewer passed it because every claim was true. The prompt serves every story and call; a slot's place is the pipeline's; narrowing and a displaced slot are findings.",
|
|
29
|
+
"v4 (operator, 2026-10-24: \"when we generate prompt and profile we must gear to the current use case and strip out what is not needed and add net new\"): the generator could only reword — every segment came back and nothing could be added — so a project carried guidance for kinds of project it is not and lacked guidance it needs. It now leaves out a segment that cannot apply (reason recorded) and adds net-new guidance once, in the form project-prompt-contract.js formatUseCaseForm writes and parses; the kind of project and whether each codeline has code come in __GEN_PROJECT_CONTEXT__ (useCaseContext)."
|
|
29
30
|
],
|
|
30
|
-
"body": "You are specialising ONE generic agent prompt for ONE specific project. You are the same agent that just minted this project's roles, so you already know what this project is and who will work on it.\n\n## The template you must specialise\n\nIts id is __GEN_TEMPLATE_ID__ and its purpose is: __GEN_TEMPLATE_DESCRIPTION__\n\nEverything between the two markers below is the template, verbatim. The markers are not\npart of it. A code fence cannot delimit this: several templates contain fences of their\nown, so the first closing fence would truncate the body mid-instruction.\n\n-----BEGIN TEMPLATE BODY-----\n__GEN_TEMPLATE_BODY__\n-----END TEMPLATE BODY-----\n\n## What this project is\n\n__GEN_PROJECT_CONTEXT__\n\nCodelines in scope and their declared dependencies:\n__GEN_CODELINE_CONTEXT__\n\nThe roles you minted for this project:\n__GEN_MINTED_ROLES__\n\n## The one rule you cannot break\n\nYou are given the template as numbered SEGMENTS — the prose that sits BETWEEN its placeholders. You\nnever see the placeholders and you must never write one. The pipeline puts them back between your\nsegments, in their original positions, after you answer.\n\n- Return EXACTLY as many segments as you were given, in the same order.\n- Specialise the WORDING of each segment for this project. A segment may be reworded freely.\n- NEVER write a placeholder token (the SCREAMING_SNAKE form between double underscores). It would be substituted a second time, or read as literal\n text to the agent. The pipeline owns them; you own the prose.\n- An empty segment is legitimate — some segments are only the join between two adjacent slots.\n\nThis is why the rule is shaped this way: when the generator was asked to reproduce placeholders\ncharacter-for-character while rewriting the prose around them, it lost them. On 2026-09-08 that\nproduced 25 refusals in one run and aborted it at 37 of 39 prompts. You cannot drop a token you\nwere never given.\n\nFor context, the placeholders the pipeline will restore are: __GEN_TEMPLATE_PLACEHOLDERS__\n\n## The prompt serves every story and every call\n\nThis prompt is reused for every story and every call this project makes to the agent — not only for the ticket in scope now. Where one segment ends and the next begins, the pipeline inserts a value that changes with every call: the role for this turn, the file to write, the command to run, the diff, the story. A segment that ends mid-sentence is completed at run time by that value. Never complete it yourself, never move words across a segment break, and never write a value the pipeline supplies. Never make the ticket in scope, one story's component or one story's file the subject of the prompt: the tickets are named above only so you know what kind of work this project does.\n\n## The other rule you cannot break\n\nThe template's OUTPUT SHAPE is a contract too. Every field name the template states in its response\nformat is read by a consumer that will reject an answer without it — the agent would produce work\nnothing accepts, and the seam fails after the model has already been paid for.\n\n- Reproduce every field name EXACTLY, in the same shape the template states it.\n- NEVER drop one because it looks redundant, and NEVER rename one to something clearer.\n- NEVER add a field. A consumer reads by name and has no place to put an invention.\n\nThe output fields this template states are exactly: __GEN_TEMPLATE_OUTPUT_FIELDS__\n\n## Before you answer\n\nCheck your reply against both lists above: every output field must appear in what you are about to\nsend, and no placeholder token may. A specialisation that drops a field is refused and the work is\nredone, so the check costs you nothing and the omission costs an attempt.\n\n## What specialising means\n\nKeep the template's instructions, its output format, its prohibitions and its reasoning intact. They encode failures this pipeline has already paid for. You are not rewriting the prompt or improving its wording.\n\nAdd only what a competent engineer joining THIS project would need and could not infer from the generic text:\n- the conventions, layout and idioms this codebase actually uses\n- the commands this project runs to build, test and type-check\n- the traps this stack presents that a generic prompt would not warn about\n\nGround every addition in the project context above. If the context does not tell you something, say nothing about it — an invented convention is worse than a missing one, because the agent will follow it.\n\nDo not name a file, symbol, package or command that does not appear in the context you were given.\n\n## How to answer\n\nReturn the SEGMENT blocks back, in the same shape and order you were shown: one marker line per\nsegment, each followed by your wording for that segment, and close with the END SEGMENTS marker.\n\nPlain text between the markers. No JSON, no escaping, no markdown fences. A segment may be empty.\nNo placeholder tokens anywhere — the pipeline restores them between your segments.\n\n(The marker lines are not reproduced here on purpose: the only ones in this prompt are the real\nsegments you must answer, so there is never a second set to confuse them with.)\n__GEN_PREVIOUS_REFUSAL__\n\n# Do not invent a path\n\nEvery specific path, filename, directory or symbol you write will be checked against the\nrepository by a reviewer. A plausible-looking path you reasoned your way to — `src/routes/...`,\n`src/pages/api/...` — is the most common reason a generated prompt is rejected and regenerated,\nand each regeneration risks dropping the placeholders above.\n\nSo: state a specific path ONLY when it appears in the facts or the codeline block you were given\nabove — and never a path that belongs to one story's change: that reaches the agent at run time. You were handed those precisely so you would not have to guess.\n\nWhen you have no verified path for something, say the general thing instead — \"the checkout form\ncomponent\", \"the API route that handles checkout\" — which is true, useful to the agent, and cannot\nbe falsified. A prompt that is accurate and general survives review; one that is specific and\ninvented does not.\n\nNever guess a path to sound concrete.\n",
|
|
31
|
+
"body": "You are specialising ONE generic agent prompt for ONE specific project. You are the same agent that just minted this project's roles, so you already know what this project is and who will work on it.\n\n## The template you must specialise\n\nIts id is __GEN_TEMPLATE_ID__ and its purpose is: __GEN_TEMPLATE_DESCRIPTION__\n\nEverything between the two markers below is the template, verbatim. The markers are not\npart of it. A code fence cannot delimit this: several templates contain fences of their\nown, so the first closing fence would truncate the body mid-instruction.\n\n-----BEGIN TEMPLATE BODY-----\n__GEN_TEMPLATE_BODY__\n-----END TEMPLATE BODY-----\n\n## What this project is\n\n__GEN_PROJECT_CONTEXT__\n\nCodelines in scope and their declared dependencies:\n__GEN_CODELINE_CONTEXT__\n\nThe roles you minted for this project:\n__GEN_MINTED_ROLES__\n\n## The one rule you cannot break\n\nYou are given the template as numbered SEGMENTS — the prose that sits BETWEEN its placeholders. You\nnever see the placeholders and you must never write one. The pipeline puts them back between your\nsegments, in their original positions, after you answer.\n\n- Return EXACTLY as many segments as you were given, in the same order.\n- Specialise the WORDING of each segment for this project. A segment may be reworded freely.\n- NEVER write a placeholder token (the SCREAMING_SNAKE form between double underscores). It would be substituted a second time, or read as literal\n text to the agent. The pipeline owns them; you own the prose.\n- An empty segment is legitimate — some segments are only the join between two adjacent slots.\n\nThis is why the rule is shaped this way: when the generator was asked to reproduce placeholders\ncharacter-for-character while rewriting the prose around them, it lost them. On 2026-09-08 that\nproduced 25 refusals in one run and aborted it at 37 of 39 prompts. You cannot drop a token you\nwere never given.\n\nFor context, the placeholders the pipeline will restore are: __GEN_TEMPLATE_PLACEHOLDERS__\n\n## Gear it to this project's use case\n\nThe kind of project and its codelines are stated above. Specialise for THEM:\n- A segment whose guidance cannot apply to this project — it addresses a kind of project, a stack, or a situation this project\n does not have — is left out: answer it with the not-applicable line below, saying why. A segment that states what the answer\n must look like is never left out; reword it instead.\n- Guidance this agent needs on this project that the template does not give — from the facts about this project's codelines,\n stack and kind of work given above — is added once, in the additions block below. It must be true of this project (a reviewer\n checks it against the codelines), serve every story and every call, and contain no placeholder token and no response tag.\n- Leaving out and adding are both optional. Nothing is left out because it is long, and nothing is added that the template\n already says.\n\n__GEN_USE_CASE_FORM__\n\n## The prompt serves every story and every call\n\nThis prompt is reused for every story and every call this project makes to the agent — not only for the ticket in scope now. Where one segment ends and the next begins, the pipeline inserts a value that changes with every call: the role for this turn, the file to write, the command to run, the diff, the story. A segment that ends mid-sentence is completed at run time by that value. Never complete it yourself, never move words across a segment break, and never write a value the pipeline supplies. Never make the ticket in scope, one story's component or one story's file the subject of the prompt: the tickets are named above only so you know what kind of work this project does.\n\n## The other rule you cannot break\n\nThe template's OUTPUT SHAPE is a contract too. Every field name the template states in its response\nformat is read by a consumer that will reject an answer without it — the agent would produce work\nnothing accepts, and the seam fails after the model has already been paid for.\n\n- Reproduce every field name EXACTLY, in the same shape the template states it.\n- NEVER drop one because it looks redundant, and NEVER rename one to something clearer.\n- NEVER add a field. A consumer reads by name and has no place to put an invention.\n\nThe output fields this template states are exactly: __GEN_TEMPLATE_OUTPUT_FIELDS__\n\n## Before you answer\n\nCheck your reply against both lists above: every output field must appear in what you are about to\nsend, and no placeholder token may. A specialisation that drops a field is refused and the work is\nredone, so the check costs you nothing and the omission costs an attempt.\n\n## What specialising means\n\nKeep the template's instructions, its output format, its prohibitions and its reasoning intact. They encode failures this pipeline has already paid for. You are not rewriting the prompt or improving its wording.\n\nAdd only what a competent engineer joining THIS project would need and could not infer from the generic text:\n- the conventions, layout and idioms this codebase actually uses\n- the commands this project runs to build, test and type-check\n- the traps this stack presents that a generic prompt would not warn about\n\nGround every addition in the project context above. If the context does not tell you something, say nothing about it — an invented convention is worse than a missing one, because the agent will follow it.\n\nDo not name a file, symbol, package or command that does not appear in the context you were given.\n\n## How to answer\n\nReturn the SEGMENT blocks back, in the same shape and order you were shown: one marker line per\nsegment, each followed by your wording for that segment, and close with the END SEGMENTS marker.\n\nPlain text between the markers. No JSON, no escaping, no markdown fences. A segment may be empty.\nNo placeholder tokens anywhere — the pipeline restores them between your segments.\n\n(The marker lines are not reproduced here on purpose: the only ones in this prompt are the real\nsegments you must answer, so there is never a second set to confuse them with.)\n__GEN_PREVIOUS_REFUSAL__\n\n# Do not invent a path\n\nEvery specific path, filename, directory or symbol you write will be checked against the\nrepository by a reviewer. A plausible-looking path you reasoned your way to — `src/routes/...`,\n`src/pages/api/...` — is the most common reason a generated prompt is rejected and regenerated,\nand each regeneration risks dropping the placeholders above.\n\nSo: state a specific path ONLY when it appears in the facts or the codeline block you were given\nabove — and never a path that belongs to one story's change: that reaches the agent at run time. You were handed those precisely so you would not have to guess.\n\nWhen you have no verified path for something, say the general thing instead — \"the checkout form\ncomponent\", \"the API route that handles checkout\" — which is true, useful to the agent, and cannot\nbe falsified. A prompt that is accurate and general survives review; one that is specific and\ninvented does not.\n\nNever guess a path to sound concrete.\n",
|
|
31
32
|
"description": "Specialise one generic agent prompt for one project, preserving its placeholder contract.",
|
|
32
33
|
"id": "project-prompt-generation",
|
|
33
34
|
"placeholders": [
|
|
@@ -39,9 +40,10 @@
|
|
|
39
40
|
"__GEN_TEMPLATE_DESCRIPTION__",
|
|
40
41
|
"__GEN_TEMPLATE_ID__",
|
|
41
42
|
"__GEN_TEMPLATE_OUTPUT_FIELDS__",
|
|
42
|
-
"__GEN_TEMPLATE_PLACEHOLDERS__"
|
|
43
|
+
"__GEN_TEMPLATE_PLACEHOLDERS__",
|
|
44
|
+
"__GEN_USE_CASE_FORM__"
|
|
43
45
|
],
|
|
44
|
-
"version":
|
|
46
|
+
"version": 4,
|
|
45
47
|
"seams": [
|
|
46
48
|
"prompt-builder"
|
|
47
49
|
],
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"wording, not whether the specialisation was ambitious enough. A reviewer invited to improve",
|
|
20
20
|
"prose rewrites prompts that were correct, and the placeholder contract already covers",
|
|
21
21
|
"structure. Only a claim that can be checked against the repository is in scope.",
|
|
22
|
-
"2026-10-06, AMSD-2974: metrolinx's repro-test-writer was generated as \"write a spec file for a Toast component at cx-shared\" — the generator completed sentences its slots complete at run time, using the one ticket it was shown, and the reviewer passed it because every claim was true. The prompt serves every story and call; a slot's place is the pipeline's; narrowing and a displaced slot are findings."
|
|
22
|
+
"2026-10-06, AMSD-2974: metrolinx's repro-test-writer was generated as \"write a spec file for a Toast component at cx-shared\" — the generator completed sentences its slots complete at run time, using the one ticket it was shown, and the reviewer passed it because every claim was true. The prompt serves every story and call; a slot's place is the pipeline's; narrowing and a displaced slot are findings.",
|
|
23
|
+
"v4 (operator, 2026-10-24: gear each prompt to its use case — strip what is not needed, add net new): the generator may now leave out a segment that cannot apply to this project and add guidance the template lacks (project-prompt-contract.js); the reviewer is shown both (__USE_CASE_EDITS__) and judges them as it judges every other claim."
|
|
23
24
|
],
|
|
24
25
|
"description": "Falsifies what a generated project prompt claims about this project, before any agent inherits it.",
|
|
25
26
|
"seams": [
|
|
@@ -27,8 +28,8 @@
|
|
|
27
28
|
],
|
|
28
29
|
"consumer": "engine",
|
|
29
30
|
"id": "prompt-review",
|
|
30
|
-
"version":
|
|
31
|
-
"body": "You are reviewing a PROJECT PROMPT that was generated by specialising a generic template for this specific project. Another agent will receive it as its system prompt and act on every claim in it.\n\nTHE GENERIC TEMPLATE IT WAS DERIVED FROM:\n__TEMPLATE_BODY__\n\nTHE GENERATED PROJECT PROMPT UNDER REVIEW (id: __PROMPT_ID__):\n__GENERATED_BODY__\n\nTHE CODELINES THIS PROJECT WORKS ON, and what each one declares:\n__CODELINE_BLOCK__\n\nTHE WORK THE PROJECT WAS ASKED TO DO:\n__TICKET_BLOCK__\n\n__TOOL_LINE__\n\n# What is in scope\n\
|
|
31
|
+
"version": 4,
|
|
32
|
+
"body": "You are reviewing a PROJECT PROMPT that was generated by specialising a generic template for this specific project. Another agent will receive it as its system prompt and act on every claim in it.\n\nTHE GENERIC TEMPLATE IT WAS DERIVED FROM:\n__TEMPLATE_BODY__\n\nTHE GENERATED PROJECT PROMPT UNDER REVIEW (id: __PROMPT_ID__):\n__GENERATED_BODY__\n\nWHAT THE GENERATOR LEFT OUT OF THE TEMPLATE FOR THIS PROJECT, AND WHAT IT ADDED:\n__USE_CASE_EDITS__\n\nTHE CODELINES THIS PROJECT WORKS ON, and what each one declares:\n__CODELINE_BLOCK__\n\nTHE WORK THE PROJECT WAS ASKED TO DO:\n__TICKET_BLOCK__\n\n__TOOL_LINE__\n\n# What is in scope\n\nTHREE QUESTIONS ONLY:\n1. Does the generated prompt state anything about this project that is FALSE?\n2. Is the generated prompt NARROWED TO ONE STORY OR CALL, or does a slot NO LONGER FIT ITS SENTENCE? (See \"A prompt narrowed to one call\" below.)\n3. Was any part of the template LEFT OUT although it applies to this project, or was anything ADDED that is false of this project or serves only one story or call? A part left out is a finding when its reason is false of the codelines or the kind of project; quote the template's words that were left out as the claim.\n\nA claim is in scope when it can be checked against a repository — a path, a file, a directory, a dependency, a command, a framework, a convention, a symbol, an ownership statement ('X owns Y'). Open the codelines and check it.\n\nExamples of a finding:\n- it names a test directory, file suffix or framework this codeline does not use\n- it names a dependency, script or command the manifest does not declare\n- it asserts a file, module or symbol exists that does not\n- it attributes behaviour to a file that does not contain it\n- it describes a codeline this project does not have, or confuses two that it does\n- it instructs the agent to run something this codeline cannot run\n\n# A prompt narrowed to one call\n\nThe generated prompt is not written for the ticket in scope now. It is reused for every story and every call this project makes to the agent, and each call's specifics — the role for the turn, the file to write, the command to run, the diff, the story — arrive through the slots (the SCREAMING_SNAKE tokens between double underscores) at run time. Compare it with the template, slot by slot, and report as a finding:\n- a statement true only for one story or one call — this ticket's component, a file or path one story will write, a command aimed at one story's file — written where the template leaves that to a slot. It is a finding even when it is true today: the next story receives it as an instruction.\n- a slot that no longer fits its sentence: in the template the slot completes or sits inside a sentence; in the generated prompt the words around it were moved, completed by hand, or rewritten, so the run-time value would land in the wrong place or be said twice. Quote the template's words around that slot and the generated prompt's.\nFor these findings `claim` quotes the generated text, `checked` names the template slot you compared it with, and `found` says what the template has there. Give no `verification` field: this is read from the two texts, not from the repository.\n\n# Before you call anything absent: SEARCH for it\n\nA path in a generated prompt is frequently ABBREVIATED rather than wrong. Looking at the literal\nstring, finding nothing there, and reporting a false claim is the single most common way this\nreview does damage — it rejects a prompt that was RIGHT about the project, the generator rewrites\nthe whole thing to escape a finding that was not real, and in rewriting it drops the placeholders\nthe pipeline depends on. Three attempts of that aborts the run.\n\nSo, for every path, file, directory or symbol the prompt names:\n\n1. SEARCH the codeline for it by BASENAME before concluding it does not exist. `src/routes/checkouts/[checkoutId].ts`\n and `src/pages/api/checkouts/[checkoutId].ts` are both a SHORTENED form of\n `src/pages/api/v1/ecommerce/checkouts/[checkoutId].ts`, which exists — that is a near-miss path,\n NOT a false claim, and it is not a finding.\n2. An IMPRECISE path that RESOLVES to a real file is not a finding. Neither is a partial path, a\n path missing intermediate directories, or a file named without its directory.\n A NAME MAY BE A DIRECTORY. A file glob such as **/Name* matches files, not directories, and a\n text search matches contents, not folder names — neither can show you a directory. Before you\n call a component, module or folder absent, list its parent directory and look for it there.\n3. Report it ONLY when the thing genuinely does not exist anywhere in the codeline — no file of\n that name, no such dependency in the manifest, no such symbol in the sources.\n\nAbsence at one exact path is not absence from the repository. If you did not search, you do not\nknow, and \"I did not find it where the prompt said\" is not a finding you may report.\n\n# What is NOT in scope\n\nDo not review wording, tone, length, structure or ambition. Do not propose improvements. Do not object that the prompt is too close to, or too far from, the template — how far it may diverge is decided elsewhere, and the placeholder contract is already enforced mechanically. (A prompt narrowed to one call, or a slot that no longer fits its sentence, is not a question of distance — report it as above.)\n\nA prompt that says nothing false and serves every call is SOUND, however plain it is.\n\n# Evidence\n\nReturn every defect you can evidence, and nothing you cannot. For each finding, say what you CHECKED and what you FOUND, naming the file or manifest you read. A finding without a check is an opinion, and an opinion about a prompt is not a defect.\n\nWhere a finding turns on whether a named path or dependency is present, fill in the verification field as well as the prose. The pipeline re-runs that exact check against the repository and DISCARDS any finding the check refutes — so a careless reading costs nothing, and a correct one is confirmed independently of how convincingly you argued it.\n\nIf every claim checks out, return no findings. An empty finding list from a review that RAN is the correct answer for a sound prompt.\n\n# How to return the verdict\n\nEmit the verdict INLINE as the last thing you write, wrapped in these exact tags. Do not write it\nto a file, and do not use a tool to deliver it — a verdict delivered any other way is not read, and\nthe prompt is then installed unreviewed.\n\n<PROMPT_REVIEW>\n{\"falseClaims\": []}\n</PROMPT_REVIEW>\n\n`falseClaims` is a list of the defects you evidenced. Each entry is an object:\n\n- `claim` — the false statement, quoted from the generated prompt\n- `checked` — what you opened or ran to test it, naming the file or manifest\n- `found` — what was actually there\n- `verification` — when the finding turns on a named path or dependency being present or absent,\n an object stating what to re-check, so the pipeline can confirm it independently\n\nTHE ROLES THIS PROJECT MINTED, which is the ground truth for any claim naming an agent:\n\n__ROSTER_BLOCK__\n\nA prompt naming one of these is stating a FACT, not inventing one. Check the list before calling a\nrole false — an unverifiable claim is not a false claim, and rejecting one costs a regeneration and\nteaches the generator nothing.\n\nTHE BLOCK IS THE WHOLE ANSWER. Nothing you write outside it is read — the consumer matches the\ntags and discards everything else. Do not narrate your investigation, do not summarise what you\nchecked, and do not restate the prompt back. Put your evidence INSIDE `checked` and `found` on the\nfinding it belongs to, where it is kept; anywhere else it is thrown away.\n\nMeasured 2026-08-27: reviews averaged 3,366 output tokens to deliver a 13-token verdict. Every one\nof those tokens was billed and 99% of them were discarded unread.\n\nAn empty list is the correct verdict for a sound prompt. Emit the block either way: the block is\nhow a review that RAN is distinguished from one that did not run at all.\n",
|
|
32
33
|
"placeholders": [
|
|
33
34
|
"__CODELINE_BLOCK__",
|
|
34
35
|
"__GENERATED_BODY__",
|
|
@@ -36,7 +37,8 @@
|
|
|
36
37
|
"__ROSTER_BLOCK__",
|
|
37
38
|
"__TEMPLATE_BODY__",
|
|
38
39
|
"__TICKET_BLOCK__",
|
|
39
|
-
"__TOOL_LINE__"
|
|
40
|
+
"__TOOL_LINE__",
|
|
41
|
+
"__USE_CASE_EDITS__"
|
|
40
42
|
],
|
|
41
43
|
"mayBeEmpty": [
|
|
42
44
|
"__PROMPT_ID__"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "spec-codeline-share",
|
|
3
|
+
"version": 2,
|
|
4
|
+
"layer": "project",
|
|
5
|
+
"seams": [
|
|
6
|
+
"code-graph-detective",
|
|
7
|
+
"spec-agent",
|
|
8
|
+
"spec-coordinator"
|
|
9
|
+
],
|
|
10
|
+
"description": "A codeline's share of the whole-ticket split, given to that codeline's spec agents with the ticket as filed.",
|
|
11
|
+
"placeholders": [
|
|
12
|
+
"__CODELINE__",
|
|
13
|
+
"__SHARE__",
|
|
14
|
+
"__OTHERS__"
|
|
15
|
+
],
|
|
16
|
+
"$why": [
|
|
17
|
+
"v1 (operator, 2026-10-24: \"One lane spec ... keep writer split\"): a codeline's spec agents were given the lane's own restatement of the ticket (specTextPerCodeline) and narrowed it; they now work from the ticket as filed and the share of it the whole-ticket split gave this codeline.",
|
|
18
|
+
"layer project (operator, 2026-10-24: \"Prompts and profiles and plans must be agentically generated\"): the template is the generic parent; the project's copy is generated and reviewed by the pipeline's own prompt builder, and only that copy is executed (lib/prompt-library.js buildPrompt).",
|
|
19
|
+
"v2: the share and the others arrive as JSON data (lib/ticket-split.js shareBlock) — the words are the prompt's."
|
|
20
|
+
],
|
|
21
|
+
"body": "THIS CODELINE'S SHARE OF THE TICKET — codeline __CODELINE__. The whole ticket (above) was split over every codeline it runs\nacross before any codeline was specified. The requirements this codeline must deliver and what it must do for each, as JSON (`role` changes: the change to make; already-met: show it is met here);\nyour plan must give every one of them a change in this codeline, or show in code that it is already met here. A requirement\nlisted here is never deferred, excluded, sent to another ticket, or left waiting on input the ticket does not say it waits for.\n__SHARE__\n\nWhat the other codelines do for the same ticket, as JSON (theirs to plan, not yours — named so your plan fits theirs):\n__OTHERS__"
|
|
22
|
+
}
|
|
@@ -4,26 +4,30 @@
|
|
|
4
4
|
"",
|
|
5
5
|
"The reviewer is handed manifest evidence and the codeline scope as VALUES, because both",
|
|
6
6
|
"are computed from the run's own PRD — a template naming either would be wrong for every",
|
|
7
|
-
"other project."
|
|
7
|
+
"other project.",
|
|
8
|
+
"v2 — v1 (operator, 2026-10-23): run 13 — every lane's spec reviewer saw only its own lane and an agent-written plan, never the ticket as filed; a lane's spec had narrowed the ticket (\"would need its own ticket\", \"once design input lands\", neither in the ticket) and nothing compared it with the ticket."
|
|
8
9
|
],
|
|
9
|
-
"body": "You are the EPAM CLI specification coordinator reviewing the completed spec outputs for phase __PHASE__.\n\nEach story was processed by a sequential agent pipeline:\n 1. openspec elaborated requirements (AC refinement, story splits, technical depth)\n 2. speckit reviewed openspec's output (testability, security, edge cases, gap analysis)\n\n__REVIEW_CRITERIA__\n\nMANIFEST EVIDENCE (checked against the repository, not asserted):\n__MANIFEST_EVIDENCE__\n__CODELINE_SCOPE__\n\n__MANIFEST_GROUNDING__\n\nQUALITY SCORE — what the number has to mean. It is enforced: below 0.7 halts the run\nbefore implementation, so an unanchored guess stops real work.\n 0.9-1.0 the spec is ready to implement; criteria are observable and the file set fits.\n 0.7-0.89 ready to implement, with ordinary open questions a competent writer resolves.\n 0.5-0.69 NOT ready: something concrete is wrong or missing — criteria that cannot be\n observed, a file set that cannot deliver what is described, contradictions.\n below 0.5 the spec would send a writer somewhere useless.\nScore what IS in front of you. Do NOT lower the score because an agent could not read the\nsource files, or because a human \"might want to check\" — that is true of every ticket and\nis not a defect in this spec. If your notes cannot name something concretely wrong, the\nscore belongs at 0.7 or above.\n\nRespond with JSON between <SPEC_REVIEW> and </SPEC_REVIEW> using this schema:\n{\"items\": [\n {\"storyId\":\"REM-xxx\",\"verdict\":\"approved|needs_review\",\"reviewNotes\":\"coordinator observations\",\"qualityScore\":<0.0-1.0>,\"flags\":[{\"flag\":\"short-slug\",\"severity\":\"blocking|advisory\",\"why\":\"what you checked and what you found\"}],\"planAlignment\":\"aligned|justified_deviation|unexplained_mismatch|not_applicable\"}\n]}\n\nStories to review:\n__REVIEW_PAYLOAD__\n\n<SPEC_REVIEW>\n</SPEC_REVIEW>",
|
|
10
|
+
"body": "You are the EPAM CLI specification coordinator reviewing the completed spec outputs for phase __PHASE__.\n\nEach story was processed by a sequential agent pipeline:\n 1. openspec elaborated requirements (AC refinement, story splits, technical depth)\n 2. speckit reviewed openspec's output (testability, security, edge cases, gap analysis)\n\n__REVIEW_CRITERIA__\n\nMANIFEST EVIDENCE (checked against the repository, not asserted):\n__MANIFEST_EVIDENCE__\n__CODELINE_SCOPE__\n\n__MANIFEST_GROUNDING__\n\nQUALITY SCORE — what the number has to mean. It is enforced: below 0.7 halts the run\nbefore implementation, so an unanchored guess stops real work.\n 0.9-1.0 the spec is ready to implement; criteria are observable and the file set fits.\n 0.7-0.89 ready to implement, with ordinary open questions a competent writer resolves.\n 0.5-0.69 NOT ready: something concrete is wrong or missing — criteria that cannot be\n observed, a file set that cannot deliver what is described, contradictions.\n below 0.5 the spec would send a writer somewhere useless.\nScore what IS in front of you. Do NOT lower the score because an agent could not read the\nsource files, or because a human \"might want to check\" — that is true of every ticket and\nis not a defect in this spec. If your notes cannot name something concretely wrong, the\nscore belongs at 0.7 or above.\n\nRespond with JSON between <SPEC_REVIEW> and </SPEC_REVIEW> using this schema:\n{\"items\": [\n {\"storyId\":\"REM-xxx\",\"verdict\":\"approved|needs_review\",\"reviewNotes\":\"coordinator observations\",\"qualityScore\":<0.0-1.0>,\"flags\":[{\"flag\":\"short-slug\",\"severity\":\"blocking|advisory\",\"why\":\"what you checked and what you found\"}],\"planAlignment\":\"aligned|justified_deviation|unexplained_mismatch|not_applicable\"}\n]}\n\n__TICKET_AS_FILED__\n\n__OTHER_LANES_BLOCK__\n\nStories to review:\n__REVIEW_PAYLOAD__\n\n<SPEC_REVIEW>\n</SPEC_REVIEW>",
|
|
10
11
|
"description": "Review a completed specification pass against the manifest and codeline scope.",
|
|
11
12
|
"id": "spec-coordinator-review",
|
|
12
13
|
"placeholders": [
|
|
13
14
|
"__CODELINE_SCOPE__",
|
|
14
15
|
"__MANIFEST_EVIDENCE__",
|
|
15
16
|
"__MANIFEST_GROUNDING__",
|
|
17
|
+
"__OTHER_LANES_BLOCK__",
|
|
16
18
|
"__PHASE__",
|
|
17
19
|
"__REVIEW_CRITERIA__",
|
|
18
|
-
"__REVIEW_PAYLOAD__"
|
|
20
|
+
"__REVIEW_PAYLOAD__",
|
|
21
|
+
"__TICKET_AS_FILED__"
|
|
19
22
|
],
|
|
20
|
-
"version":
|
|
23
|
+
"version": 2,
|
|
21
24
|
"seams": [
|
|
22
25
|
"spec-coordinator"
|
|
23
26
|
],
|
|
24
27
|
"mayBeEmpty": [
|
|
25
|
-
"__CODELINE_SCOPE__"
|
|
28
|
+
"__CODELINE_SCOPE__",
|
|
29
|
+
"__OTHER_LANES_BLOCK__"
|
|
26
30
|
],
|
|
27
31
|
"$whyMayBeEmpty": "Declared 2026-08-27 by sweeping the CLASS rather than the instance, after three runs died on it one placeholder at a time. Every call site for these writes an explicit empty branch — the story is not being split, attempt one carries no forced-retry note, no documentation was fetched, the persona prefix is optional — so absence is a real state, which is the renderer's own test. Placeholders supplied by a lookup that may FAIL (`x || ''`) are deliberately NOT here: for those, empty means broken, and declaring them would hide the blank-brief defect the renderer exists to surface. Unanimity is required — one deliberate branch among several defensive lookups is not a licence.",
|
|
28
|
-
"$mayBeEmptyWhy": "__CODELINE_SCOPE__ is a note about the OTHER codelines a story reaches, and a story that reaches only one has none. codelineScopeBlock() returns '' in three deliberate branches — no lane, no story spanning more than one codeline, no other lane — so the empty value is the author's answer, not a failed lookup. Undeclared, the renderer refused it and killed the spec pass AFTER it had produced its verification criteria: mock3, whose two stories live one per codeline, lost a completed pass to a note it had no reason to write. "
|
|
32
|
+
"$mayBeEmptyWhy": "__CODELINE_SCOPE__ is a note about the OTHER codelines a story reaches, and a story that reaches only one has none. codelineScopeBlock() returns '' in three deliberate branches — no lane, no story spanning more than one codeline, no other lane — so the empty value is the author's answer, not a failed lookup. Undeclared, the renderer refused it and killed the spec pass AFTER it had produced its verification criteria: mock3, whose two stories live one per codeline, lost a completed pass to a note it had no reason to write. __OTHER_LANES_BLOCK__ is the other lanes' settled specs: the first lane, or a story in one lane, has none."
|
|
29
33
|
}
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"observable checks live in verificationCriteria. Asked anyway, the reviewer reported the",
|
|
9
9
|
"empty array as a hallucination or a persistence failure and lowered qualityScore for it,",
|
|
10
10
|
"which is a gate blocking on a policy it was not told about.",
|
|
11
|
-
"v2 (operator, 2026-10-22): plan and criteria are reconciled before pause 2 — run 11's gotransit plan said 'no change' while its own criterion asked for a toast longer than 5 s; nothing checked one against the other."
|
|
11
|
+
"v2 (operator, 2026-10-22): plan and criteria are reconciled before pause 2 — run 11's gotransit plan said 'no change' while its own criterion asked for a toast longer than 5 s; nothing checked one against the other.",
|
|
12
|
+
"v3 (operator, 2026-10-23): the unmet criteria are a required list in the answer (unmetCriteria) — live, 1 of 3 real reviews of run 13 flagged the same contrast gap as advisory under its own name, and nothing corrected it.",
|
|
13
|
+
"v4 — v1 (operator, 2026-10-23): run 13 — every lane's spec reviewer saw only its own lane and an agent-written plan, never the ticket as filed; a lane's spec had narrowed the ticket (\"would need its own ticket\", \"once design input lands\", neither in the ticket) and nothing compared it with the ticket. The review now accounts for every ticket requirement with quoted evidence and an owner (live TDD, 2026-10-24: test/live/spec-ticket-fidelity.live.test.js in amsd-pipeline-workflow)."
|
|
12
14
|
],
|
|
13
15
|
"description": "The criteria a spec-review coordinator judges a story's collaborative spec work against.",
|
|
14
16
|
"id": "spec-review-criteria",
|
|
@@ -16,9 +18,9 @@
|
|
|
16
18
|
"spec-coordinator"
|
|
17
19
|
],
|
|
18
20
|
"placeholders": [],
|
|
19
|
-
"version":
|
|
21
|
+
"version": 4,
|
|
20
22
|
"bodies": {
|
|
21
|
-
"brownfield": "For each story, evaluate the quality of the collaborative spec work:\n1. Did both agents add meaningful, non-overlapping value?\n2. Flag any story needing human review.\n3. PLAN ALIGNMENT — where planAlignmentEvidence is present, it tells you what the\n code-graph-detective SAID it would investigate (detectivePlan) and a deterministic,\n cheap signal (aligned: true/false) for whether the final fixSiteAnalysis shares any\n named symbol/file with that plan. This signal is NOT your verdict — it is evidence, the\n same as MANIFEST EVIDENCE below. A plan can legitimately turn out wrong once real\n exploration starts. Read detectivePlan and fixSiteAnalysis yourself and judge: did the\n detective explain WHY it moved from its plan to its final answer (a stated pivot,\n reasoning that connects the two), or did the answer just change with no explanation?\n The latter is a real defect (a fix has shipped that shared no term with the detective's\n own stated plan, with no explanation, more than once on the same ticket) — set\n planAlignment to justified_deviation when the detective justifies its pivot, or\n unexplained_mismatch when it does not, and say why in reviewNotes. aligned:true needs\n no action.\n\n(Brownfield tickets never split and their acceptance criteria are immutable —\ndo not evaluate split quality or AC completeness; there is nothing there for\neither agent to have changed.\n\nEXPECTED, NOT A DEFECT: an agent's notes may describe acceptance criteria it\nauthored while the story's acceptanceCriteria array is empty, and report\nacceptanceChanged=false. That is this pipeline REDACTING them on purpose: on a\nbrownfield ticket the ACs are the ticket's own, and a thin ticket legitimately\nhas none. The observable checks live in verificationCriteria — judge THOSE.\nDo not report the mismatch as a hallucination, a persistence failure or a\nmetadata inconsistency, and do not lower qualityScore for it. An empty\nacceptanceCriteria array on a brownfield story is correct by policy.)\n\nEVERY VERIFICATION CRITERION HAS A PLANNED CHANGE THAT MAKES IT TRUE. For each verification criterion, name what in the plan makes it observable in this codeline: a fix site whose changeRequired is true, this codeline's own change or update in overallPlanForThisCodeline, or — where this codeline consumes another's package — the provider's change it receives, which its Writer is tested against through the provider's build the pipeline places in this codeline. A criterion nothing planned makes true cannot be met by the writer: flag it as criterion-without-planned-change, severity blocking, with a why that quotes the criterion and names the change that would meet it, or says that the criterion asks for something this story does not change and should be dropped. Do not approve a spec with such a criterion.\n",
|
|
22
|
-
"greenfield": "For each story, evaluate the quality of the collaborative spec work:\n1. Did both agents add meaningful, non-overlapping value?\n2. Are the acceptance criteria complete, testable, and non-overlapping?\n3. Are story splits logical and properly scoped?\n4. Flag any story needing human review.\n\nEVERY VERIFICATION CRITERION HAS A PLANNED CHANGE THAT MAKES IT TRUE. For each verification criterion, name what in the plan makes it observable in this codeline: a fix site whose changeRequired is true, this codeline's own change or update in overallPlanForThisCodeline, or — where this codeline consumes another's package — the provider's change it receives, which its Writer is tested against through the provider's build the pipeline places in this codeline. A criterion nothing planned makes true cannot be met by the writer: flag it as criterion-without-planned-change, severity blocking, with a why that quotes the criterion and names the change that would meet it, or says that the criterion asks for something this story does not change and should be dropped. Do not approve a spec with such a criterion.\n"
|
|
23
|
+
"brownfield": "For each story, evaluate the quality of the collaborative spec work:\n1. Did both agents add meaningful, non-overlapping value?\n2. Flag any story needing human review.\n3. PLAN ALIGNMENT — where planAlignmentEvidence is present, it tells you what the\n code-graph-detective SAID it would investigate (detectivePlan) and a deterministic,\n cheap signal (aligned: true/false) for whether the final fixSiteAnalysis shares any\n named symbol/file with that plan. This signal is NOT your verdict — it is evidence, the\n same as MANIFEST EVIDENCE below. A plan can legitimately turn out wrong once real\n exploration starts. Read detectivePlan and fixSiteAnalysis yourself and judge: did the\n detective explain WHY it moved from its plan to its final answer (a stated pivot,\n reasoning that connects the two), or did the answer just change with no explanation?\n The latter is a real defect (a fix has shipped that shared no term with the detective's\n own stated plan, with no explanation, more than once on the same ticket) — set\n planAlignment to justified_deviation when the detective justifies its pivot, or\n unexplained_mismatch when it does not, and say why in reviewNotes. aligned:true needs\n no action.\n\n(Brownfield tickets never split and their acceptance criteria are immutable —\ndo not evaluate split quality or AC completeness; there is nothing there for\neither agent to have changed.\n\nEXPECTED, NOT A DEFECT: an agent's notes may describe acceptance criteria it\nauthored while the story's acceptanceCriteria array is empty, and report\nacceptanceChanged=false. That is this pipeline REDACTING them on purpose: on a\nbrownfield ticket the ACs are the ticket's own, and a thin ticket legitimately\nhas none. The observable checks live in verificationCriteria — judge THOSE.\nDo not report the mismatch as a hallucination, a persistence failure or a\nmetadata inconsistency, and do not lower qualityScore for it. An empty\nacceptanceCriteria array on a brownfield story is correct by policy.)\n\nEVERY VERIFICATION CRITERION HAS A PLANNED CHANGE THAT MAKES IT TRUE. For each verification criterion, name what in the plan makes it observable in this codeline: a fix site whose changeRequired is true, this codeline's own change or update in overallPlanForThisCodeline, or — where this codeline consumes another's package — the provider's change it receives, which its Writer is tested against through the provider's build the pipeline places in this codeline. A criterion nothing planned makes true cannot be met by the writer: flag it as criterion-without-planned-change, severity blocking, with a why that quotes the criterion and names the change that would meet it, or says that the criterion asks for something this story does not change and should be dropped. Do not approve a spec with such a criterion. List every such criterion in unmetCriteria — the criterion quoted, and why — for every story you review; an empty list states that every criterion has a planned change behind it.\n\n\nTHE TICKET, REQUIREMENT BY REQUIREMENT. List in ticketRequirements EVERY requirement the TICKET AS FILED states (its\ndescription, acceptance criteria and comments), quoted, each with its evidence from any lane, quoted: a requirement for BEHAVIOUR\nis met by a fix site that makes it true; a requirement for a CHECK (validation, testing, an audit) is met by a verification\ncriterion that performs that check. Give its coverage: \"full\" only when the evidence meets EVERY part of the requirement — every\nkind of thing it names, and the very change it asks for (to increase something is not met by making it configurable) — otherwise\n\"partial\" or \"none\". Only \"full\" is met: true. Partial or none is met: false, with lane: the lane whose code must change (or that\nmust add the check), and it is also listed in unmetCriteria with that \"owner\". Read every lane's fix sites and criteria before\ncalling anything unmet. A lane's own spec deferring or excluding a ticket requirement does not make it met.",
|
|
24
|
+
"greenfield": "For each story, evaluate the quality of the collaborative spec work:\n1. Did both agents add meaningful, non-overlapping value?\n2. Are the acceptance criteria complete, testable, and non-overlapping?\n3. Are story splits logical and properly scoped?\n4. Flag any story needing human review.\n\nEVERY VERIFICATION CRITERION HAS A PLANNED CHANGE THAT MAKES IT TRUE. For each verification criterion, name what in the plan makes it observable in this codeline: a fix site whose changeRequired is true, this codeline's own change or update in overallPlanForThisCodeline, or — where this codeline consumes another's package — the provider's change it receives, which its Writer is tested against through the provider's build the pipeline places in this codeline. A criterion nothing planned makes true cannot be met by the writer: flag it as criterion-without-planned-change, severity blocking, with a why that quotes the criterion and names the change that would meet it, or says that the criterion asks for something this story does not change and should be dropped. Do not approve a spec with such a criterion. List every such criterion in unmetCriteria — the criterion quoted, and why — for every story you review; an empty list states that every criterion has a planned change behind it.\n\n\nTHE TICKET, REQUIREMENT BY REQUIREMENT. List in ticketRequirements EVERY requirement the TICKET AS FILED states (its\ndescription, acceptance criteria and comments), quoted, each with its evidence from any lane, quoted: a requirement for BEHAVIOUR\nis met by a fix site that makes it true; a requirement for a CHECK (validation, testing, an audit) is met by a verification\ncriterion that performs that check. Give its coverage: \"full\" only when the evidence meets EVERY part of the requirement — every\nkind of thing it names, and the very change it asks for (to increase something is not met by making it configurable) — otherwise\n\"partial\" or \"none\". Only \"full\" is met: true. Partial or none is met: false, with lane: the lane whose code must change (or that\nmust add the check), and it is also listed in unmetCriteria with that \"owner\". Read every lane's fix sites and criteria before\ncalling anything unmet. A lane's own spec deferring or excluding a ticket requirement does not make it met."
|
|
23
25
|
}
|
|
24
26
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "spec-review-other-lanes",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"layer": "project",
|
|
5
|
+
"seams": [
|
|
6
|
+
"spec-coordinator"
|
|
7
|
+
],
|
|
8
|
+
"description": "The settled spec of every other lane of the story, so a review judges what another lane really plans, not a summary of it.",
|
|
9
|
+
"placeholders": [
|
|
10
|
+
"__OTHER_LANES__"
|
|
11
|
+
],
|
|
12
|
+
"$why": [
|
|
13
|
+
"v1 (operator, 2026-10-23): run 13 — every lane's spec reviewer saw only its own lane and an agent-written plan, never the ticket as filed; a lane's spec had narrowed the ticket (\"would need its own ticket\", \"once design input lands\", neither in the ticket) and nothing compared it with the ticket.",
|
|
14
|
+
"layer project (operator, 2026-10-24: \"Prompts and profiles and plans must be agentically generated\"): the template is the generic parent; the project's copy is generated and reviewed by the pipeline's own prompt builder, and only that copy is executed (lib/prompt-library.js buildPrompt)."
|
|
15
|
+
],
|
|
16
|
+
"body": "EVERY OTHER LANE OF THIS STORY SPECIFIED SO FAR — its settled spec (its verification criteria and the fix sites it plans) and\nthe bridge's contract from it. A requirement another lane's change must make true is met only when its fix sites or criteria\nBELOW meet it.\n__OTHER_LANES__"
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "spec-review-ticket",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"layer": "project",
|
|
5
|
+
"seams": [
|
|
6
|
+
"spec-coordinator",
|
|
7
|
+
"code-graph-detective"
|
|
8
|
+
],
|
|
9
|
+
"description": "The ticket as filed, given to the spec review and the detective's correction as the source of truth, apart from every agent's restatement.",
|
|
10
|
+
"placeholders": [
|
|
11
|
+
"__TICKET_KEY__",
|
|
12
|
+
"__TICKET_TITLE__",
|
|
13
|
+
"__TICKET_DESCRIPTION__",
|
|
14
|
+
"__TICKET_ACCEPTANCE__",
|
|
15
|
+
"__TICKET_COMMENTS__"
|
|
16
|
+
],
|
|
17
|
+
"$why": [
|
|
18
|
+
"v1 (operator, 2026-10-23): run 13 — every lane's spec reviewer saw only its own lane and an agent-written plan, never the ticket as filed; a lane's spec had narrowed the ticket (\"would need its own ticket\", \"once design input lands\", neither in the ticket) and nothing compared it with the ticket.",
|
|
19
|
+
"layer project (operator, 2026-10-24: \"Prompts and profiles and plans must be agentically generated\"): the template is the generic parent; the project's copy is generated and reviewed by the pipeline's own prompt builder, and only that copy is executed (lib/prompt-library.js buildPrompt)."
|
|
20
|
+
],
|
|
21
|
+
"body": "THE TICKET AS FILED — the source of truth for what this story must deliver. Everything else in this prompt that describes\nscope (a lane's spec, the plan, an agent's notes) is an AGENT'S RESTATEMENT of it, and may be wrong. A restatement that defers,\nexcludes, or calls for a separate ticket something the ticket asks for does not change what the ticket asks for.\nTicket __TICKET_KEY__: __TICKET_TITLE__\nDescription:\n__TICKET_DESCRIPTION__\nAcceptance criteria:\n__TICKET_ACCEPTANCE__\nComments:\n__TICKET_COMMENTS__"
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "spec-ticket-split",
|
|
3
|
+
"version": 4,
|
|
4
|
+
"layer": "project",
|
|
5
|
+
"seams": [
|
|
6
|
+
"spec-coordinator"
|
|
7
|
+
],
|
|
8
|
+
"description": "The whole ticket split over every codeline it runs across, by the spec coordinator, before any codeline is specified.",
|
|
9
|
+
"placeholders": [
|
|
10
|
+
"__TICKET_AS_FILED__",
|
|
11
|
+
"__CODELINES__"
|
|
12
|
+
],
|
|
13
|
+
"$why": [
|
|
14
|
+
"v1 (operator, 2026-10-24: \"One lane spec ... keep writer split\"): run 13 cut the ticket into one PRD per codeline BEFORE the spec (filtered-prd.js), so every lane specified a slice and a lane narrowed the ticket (cx-shared: contrast \"would need its own ticket\"); no agent owned the whole ticket. The spec now starts with one split of the WHOLE ticket over every codeline, which each codeline's spec then works from.",
|
|
15
|
+
"v2 (live test T1, iteration 1, 2026-10-24): the split chose owners per requirement, and gave a consumer a share while leaving another consumer of the same shared component off it (gotransit vs metrolinx/upexpress, pinned to different provider lines); a validation criterion and a comment's request were left unowned; 'what' diagnosed instead of prescribing; 'already met' rested on a sample of call sites; the ticket's 'designs needed' was not listed. Every requirement now answers for EVERY codeline.",
|
|
16
|
+
"v3 (operator, 2026-10-24: \"Needs to be generic for any user story any stack\" / \"Arch changes only\"): v2's wording carried this ticket's stack (stylesheets, call sites, package pins, designs). The fix is the STRUCTURE — every requirement answers for every codeline with a role and evidence (the answer's schema, splitRecord) — and the agent's own tool grant; the instruction states only the task, for any story on any stack.",
|
|
17
|
+
"layer project (operator, 2026-10-24: \"Prompts and profiles and plans must be agentically generated\"): the template is the generic parent; the project's copy is generated and reviewed by the pipeline's own prompt builder, and only that copy is executed (lib/prompt-library.js buildPrompt).",
|
|
18
|
+
"v4: the codelines arrive as JSON data (lib/ticket-split.js) — every word about them is the prompt's, none the engine's; an empty path is a codeline with no code yet (greenfield)."
|
|
19
|
+
],
|
|
20
|
+
"body": "__TICKET_AS_FILED__\n\nTHE CODELINES THIS TICKET RUNS ACROSS, as JSON: each one's name (`codeline`), its checkout (`path` — read it with Read, Glob and\nGrep before you assign it anything; an empty path means the codeline has no code yet and is built by this work), and why the\ncodeline discovery put it in the ticket (`reason`, `evidence`, `facts`):\n__CODELINES__\n\nYOUR TASK: SPLIT THE WHOLE TICKET OVER THESE CODELINES, before any codeline is specified. Every codeline's spec will start from\nits share of this split, so nothing the ticket asks may be left to no codeline, and no codeline may be given less than the ticket\nasks of it.\n\n1. List every requirement the ticket states — in its description, its acceptance criteria, and its comments — in the ticket's\n own words (`text`), with where it is (`source`). Nothing the ticket does not state.\n2. For each requirement, answer for EVERY codeline above, each decided from that codeline's own code, which you read: whether its\n code must change (`changes`, and `what` is the change to make), already delivers it in full (`already-met`, and `evidence`\n shows it everywhere it applies), or is not involved (`not-involved`, and why).\n3. Nothing the ticket asks is deferred, excluded, or left waiting on something the ticket does not say it waits for. What cannot\n be decided from the code is a `changes` with what must be found out in `why`.\n\nAnswer in the structured form you are given."
|
|
21
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* HOW MANY TIMES ONE AGENTIC CALL IS ASKED — the one reader of config/call-policy.json (see that file). Every gateway calls it.
|
|
4
|
+
*
|
|
5
|
+
* callMaxAttempts(env = process.env) → an integer >= 1: EPAM_CALL_MAX_ATTEMPTS when the environment sets it, else the policy's maxAttempts
|
|
6
|
+
* node call-attempts.js → prints it; exit 1 with the reason when it cannot be read
|
|
7
|
+
*
|
|
8
|
+
* A value that is not a whole number of at least one, or a policy file it cannot read, is a stated error — never a default of its own.
|
|
9
|
+
*/
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const POLICY = path.join(__dirname, '..', '..', 'config', 'call-policy.json');
|
|
14
|
+
|
|
15
|
+
function whole(v, where) {
|
|
16
|
+
const n = Number(v);
|
|
17
|
+
if (!Number.isInteger(n) || n < 1) throw new Error(`the call attempt count ${where} is '${v}', not a whole number of at least 1`);
|
|
18
|
+
return n;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function callMaxAttempts(env = process.env) {
|
|
22
|
+
if (env.EPAM_CALL_MAX_ATTEMPTS !== undefined && env.EPAM_CALL_MAX_ATTEMPTS !== '') return whole(env.EPAM_CALL_MAX_ATTEMPTS, 'in EPAM_CALL_MAX_ATTEMPTS');
|
|
23
|
+
let policy;
|
|
24
|
+
try { policy = JSON.parse(fs.readFileSync(POLICY, 'utf8')); } catch (e) { throw new Error(`the call policy ${POLICY} is not readable JSON: ${e.message}`); }
|
|
25
|
+
return whole(policy.maxAttempts, `in ${POLICY} (maxAttempts)`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (require.main === module) {
|
|
29
|
+
try { process.stdout.write(`${callMaxAttempts()}\n`); } catch (e) { process.stderr.write(`${e.message}\n`); process.exit(1); }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { callMaxAttempts, POLICY };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# THE FAILED-CALL STEP, ONCE, FOR EVERY AGENTIC CALL (operator, 2026-10-23: "All agentic calls - need self heal integration and retries - all - no
|
|
3
|
+
# exceptions"). An agent call that gave no usable answer, as the pipeline's gateway takes it (llm-handler.sh "UNIFORM CALL RESILIENCE"), by the
|
|
4
|
+
# pipeline's own functions: the failure on the agent's ladder and the next rung (lib/agent-ladder.sh, the chains exported by lib/model-ladders.sh
|
|
5
|
+
# from the project's settings), the previous-refusal block for the next prompt (engine template previous-refusal), and the KB episode
|
|
6
|
+
# (lib/kb-apply.sh) so self-heal learns from it. No AI.
|
|
7
|
+
# call-failed.sh env CALL_AGENT (the agent), CALL_MODEL (the model just used), CALL_STORY, CALL_REASON, CALL_ATTEMPT,
|
|
8
|
+
# CALL_STATE (the folder its ladder state and records live in), EPAM_PROJECT_CONFIG_DIR (the project's settings; optional)
|
|
9
|
+
# Prints {"model", "decision": "escalating|top|no-chain", "block", "episode": bool, "line"} and appends the same as one call-failed event to
|
|
10
|
+
# $CALL_STATE/events.jsonl; "line" is the hub's own attempt line, out of the call policy's count (lib/call-attempts.js). On a library that
|
|
11
|
+
# does not load, or a count it cannot read: {"error": true, "reason"}.
|
|
12
|
+
set -uo pipefail
|
|
13
|
+
S="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
14
|
+
: "${CALL_AGENT:?}" "${CALL_STATE:?}" "${CALL_MODEL:=}" "${CALL_STORY:=global}" "${CALL_REASON:=the agent produced no usable answer}" "${CALL_ATTEMPT:=1}"
|
|
15
|
+
export LOG_DIR="$CALL_STATE"; mkdir -p "$LOG_DIR"
|
|
16
|
+
export NODE_BIN="${NODE_BIN:-$(command -v node)}"
|
|
17
|
+
_log="$LOG_DIR/call-failed.log"
|
|
18
|
+
{
|
|
19
|
+
# shellcheck source=/dev/null
|
|
20
|
+
. "$S/lib/model-ladders.sh"
|
|
21
|
+
# the project's chains (EPAM_MODEL_LADDER_<TIER>); none when no project settings are named — the agent then keeps its model
|
|
22
|
+
if [ -n "${EPAM_PROJECT_CONFIG_DIR:-}" ] && [ -f "$EPAM_PROJECT_CONFIG_DIR/llm-settings.json" ]; then export_model_ladders "$EPAM_PROJECT_CONFIG_DIR/llm-settings.json"; fi
|
|
23
|
+
# shellcheck source=/dev/null
|
|
24
|
+
. "$S/lib/agent-ladder.sh"
|
|
25
|
+
# shellcheck source=/dev/null
|
|
26
|
+
. "$S/lib/kb-apply.sh"
|
|
27
|
+
} >>"$_log" 2>&1 || { jq -cn --rawfile m "$_log" '{error:true, reason:"the pipeline ladder/KB libraries did not load", message:$m}'; exit 0; }
|
|
28
|
+
declare -F agent_ladder_model >/dev/null || { jq -cn '{error:true, reason:"agent_ladder_model is not defined"}'; exit 0; }
|
|
29
|
+
max="$("$NODE_BIN" "$S/lib/call-attempts.js" 2>>"$_log")" || { jq -cn --rawfile m "$_log" '{error:true, reason:"no call attempt count", message:$m}'; exit 0; }
|
|
30
|
+
|
|
31
|
+
agent_ladder_record_failure "$CALL_AGENT" "$CALL_STORY"
|
|
32
|
+
next="$(agent_ladder_model "$CALL_AGENT" "$CALL_STORY" "$CALL_MODEL" 2>>"$_log")"
|
|
33
|
+
# after a recorded failure the ladder either names the next rung or none — its top, or no chain for the tier — and the same rung is retried;
|
|
34
|
+
# said as the hub says it (llm-handler.sh "UNIFORM CALL RESILIENCE")
|
|
35
|
+
n="$((CALL_ATTEMPT + 1))/${max}"
|
|
36
|
+
if [ -n "$next" ] && [ "$next" != "$CALL_MODEL" ]; then
|
|
37
|
+
decision=escalating; line="[ai-run] attempt ${n} for '${CALL_AGENT}' — escalating ${CALL_MODEL} -> ${next}"
|
|
38
|
+
elif agent_ladder_exhausted "$CALL_AGENT" "$CALL_STORY" "$CALL_MODEL" 2>>"$_log"; then
|
|
39
|
+
decision=top; next="$CALL_MODEL"; line="[ai-run] attempt ${n} for '${CALL_AGENT}' — at the top of its declared chain (${CALL_MODEL:-default}), retrying the same rung"
|
|
40
|
+
else
|
|
41
|
+
decision=no-chain; next="$CALL_MODEL"; line="[ai-run] attempt ${n} for '${CALL_AGENT}' — no chain declared for this agent's tier, retrying on ${CALL_MODEL:-default}"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# the hub's _ai_prompt_with_failure: never a credential in a prompt; bounded, the tail kept
|
|
45
|
+
why="$(printf '%s' "$CALL_REASON" | sed -E 's/(sk-[A-Za-z0-9_-]{8,}|ey[A-Za-z0-9_-]{20,}|[A-Za-z0-9_-]{32,})/[REDACTED]/g' | tail -c 1200)"
|
|
46
|
+
block="$("$NODE_BIN" -e '
|
|
47
|
+
try {
|
|
48
|
+
const { renderEngineTemplate } = require(process.argv[1] + "/lib/engine-prompt.js");
|
|
49
|
+
process.stdout.write(renderEngineTemplate("previous-refusal", { __REASON__: process.argv[2], __ARTEFACT__: process.argv[3] || "answer" }));
|
|
50
|
+
} catch (e) { process.stdout.write(""); }
|
|
51
|
+
' "$S" "$why" "$CALL_AGENT" 2>>"$_log")"
|
|
52
|
+
|
|
53
|
+
# a block that did not render is said, never sent silently empty (the renderer needs the install's seam registry beside these scripts)
|
|
54
|
+
[ -n "$block" ] || { jq -cn --arg agent "$CALL_AGENT" --rawfile m "$_log" '{error:true, reason:("the previous-refusal block did not render for " + $agent + " — is this the installed component, with its seam registry?"), message:$m}'; exit 0; }
|
|
55
|
+
# kb_record_episode never fails its caller (losing an episode must not fail a run); it records only when the KB tool is installed beside it —
|
|
56
|
+
# so that is what "episode" says (a write error inside the store is swallowed by the pipeline's own function, by its design)
|
|
57
|
+
printf '%s' "$CALL_REASON" | kb_record_episode "$CALL_STORY" "$CALL_AGENT" "model call produced no usable output" 2>>"$_log"
|
|
58
|
+
episode=$([ -f "$S/lib/kb-cli.js" ] && echo true || echo false)
|
|
59
|
+
printf '%s\n' "$line" >>"$_log"
|
|
60
|
+
jq -cn --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg agent "$CALL_AGENT" --arg story "$CALL_STORY" --argjson attempt "$CALL_ATTEMPT" \
|
|
61
|
+
--arg from "$CALL_MODEL" --arg to "$next" --arg decision "$decision" --argjson episode "$episode" --arg reason "$CALL_REASON" \
|
|
62
|
+
'{ts:$ts, kind:"call-failed", agent:$agent, story:$story, attempt:$attempt, from:$from, to:$to, decision:$decision, episode:$episode, reason:$reason}' \
|
|
63
|
+
>> "$LOG_DIR/events.jsonl"
|
|
64
|
+
jq -cn --arg model "$next" --arg decision "$decision" --arg block "$block" --argjson episode "$episode" --arg line "$line" \
|
|
65
|
+
'{model:$model, decision:$decision, block:$block, episode:$episode, line:$line}'
|
|
@@ -769,6 +769,11 @@ _lane_phase_args() {
|
|
|
769
769
|
|
|
770
770
|
_run_codeline_loop() {
|
|
771
771
|
local _prd_path="$1"
|
|
772
|
+
# THE STORY BEYOND A LANE (lib/spec-review-findings.js storyContext): every lane's spec review is shown the ticket as filed — the
|
|
773
|
+
# run's own PRD, before any lane rewrites a story — and the run's log folder, where the bridge leaves its contracts. Set once here,
|
|
774
|
+
# for every lane; the Claude workflow's driver sets the same two (driver/run.js).
|
|
775
|
+
export EPAM_RUN_PRD="${EPAM_RUN_PRD:-$_prd_path}"
|
|
776
|
+
export EPAM_RUN_LOG_DIR="${EPAM_RUN_LOG_DIR:-${LOG_DIR:-}}"
|
|
772
777
|
local _log_file="${2:-/tmp/orch-$(date +%Y%m%dT%H%M%S).log}"
|
|
773
778
|
# When the tier3 launcher (or any external phase-managing caller) drives the
|
|
774
779
|
# phase loop itself (calling us once per phase via --phase X), set this to
|