amsd-pipeline-shared 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -2
- package/bin/amsd-pipeline-shared.js +96 -0
- package/docs/CONSUMER-REVIEW-B56.md +68 -0
- package/docs/gateway-baseline-2.0.80.md5 +38 -0
- package/docs/gateway-files.txt +39 -0
- package/orchestrations/prompts/templates/codeline-discovery.json +40 -0
- package/orchestrations/scripts/agent-attempt-analyst.sh +287 -0
- package/orchestrations/scripts/lib/agent-contract.js +84 -0
- package/orchestrations/scripts/lib/agent-ladder.sh +182 -0
- package/orchestrations/scripts/lib/agent-messages.js +92 -0
- package/orchestrations/scripts/lib/agent-si.js +81 -0
- package/orchestrations/scripts/lib/constraint-compiler.js +109 -0
- package/orchestrations/scripts/lib/constraint-sanity.js +109 -0
- package/orchestrations/scripts/lib/content-retry.js +260 -0
- package/orchestrations/scripts/lib/cost-emitter.js +669 -0
- package/orchestrations/scripts/lib/cost-record.sh +127 -0
- package/orchestrations/scripts/lib/engine-prompt.js +393 -0
- package/orchestrations/scripts/lib/env-file.sh +149 -0
- package/orchestrations/scripts/lib/evidence-windows.js +41 -0
- package/orchestrations/scripts/lib/failure-signature.js +101 -0
- package/orchestrations/scripts/lib/handlers/emit-cost.js +50 -0
- package/orchestrations/scripts/lib/jq-vals.sh +111 -0
- package/orchestrations/scripts/lib/kb-apply.sh +169 -0
- package/orchestrations/scripts/lib/kb-arbitration.js +138 -0
- package/orchestrations/scripts/lib/kb-cli.js +219 -0
- package/orchestrations/scripts/lib/kb-store.js +249 -0
- package/orchestrations/scripts/lib/kb-synthesizer.js +253 -0
- package/orchestrations/scripts/lib/langfuse-emit.js +369 -0
- package/orchestrations/scripts/lib/llm-settings-resolve.js +342 -0
- package/orchestrations/scripts/lib/model-ladder.sh +1891 -0
- package/orchestrations/scripts/lib/model-ladders.sh +236 -0
- package/orchestrations/scripts/lib/pipeline-tools.js +60 -0
- package/orchestrations/scripts/lib/relative-paths.js +57 -0
- package/orchestrations/scripts/lib/render-prompt-section.js +100 -0
- package/orchestrations/scripts/lib/seam-invocation.js +825 -0
- package/orchestrations/scripts/lib/seam-ladder.sh +171 -0
- package/orchestrations/scripts/lib/self-heal.js +174 -0
- package/orchestrations/scripts/lib/stage-coverage-gate.sh +221 -0
- package/orchestrations/scripts/lib/tool-timeouts.js +46 -0
- package/orchestrations/scripts/lib/transcript-tool-calls.js +173 -0
- package/orchestrations/scripts/llm-handler.sh +1102 -0
- package/orchestrations/scripts/spec-mode-runner.js +11229 -0
- package/orchestrations/scripts/team-lead-review.sh +1140 -0
- package/orchestrations/scripts/update-monitor.sh +302 -0
- package/package.json +21 -4
package/README.md
CHANGED
|
@@ -1,3 +1,61 @@
|
|
|
1
|
-
#
|
|
1
|
+
# amsd-pipeline-shared
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The AMSD pipeline's shared components: one source of code for the bash pipeline (`amsd-pipeline`) and its Claude
|
|
4
|
+
workflows (`amsd-pipeline-workflow`). A fix lands here, once, and both run the same bytes.
|
|
5
|
+
|
|
6
|
+
## What is in it
|
|
7
|
+
|
|
8
|
+
| Component | Files | What it does |
|
|
9
|
+
|---|---|---|
|
|
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
|
+
| 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 Spec runner | `orchestrations/scripts/spec-mode-runner.js` | A failed mint re-proposal keeps the accepted roles and says so |
|
|
13
|
+
| The KB synthesiser | `orchestrations/scripts/lib/kb-synthesizer.js` | Runs on its own seam's model |
|
|
14
|
+
| 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 |
|
|
15
|
+
|
|
16
|
+
Every file sits at the path it has in a pipeline install. The first commit is the 35 gateway files exactly as released
|
|
17
|
+
in amsd-pipeline 2.0.80 (`docs/gateway-baseline-2.0.80.md5`); every change since is its own commit with its tests.
|
|
18
|
+
`docs/CONSUMER-REVIEW-B56.md` reads every caller of the gateway before and after the fix.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
After the pipeline, into the same install. The pipeline and its installer are not changed:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx amsd-pipeline@2.0.80 --dest <install> --ref v2.0.80 --no-docker
|
|
26
|
+
npx amsd-pipeline-shared@<version> install --into <install>
|
|
27
|
+
npx amsd-pipeline-shared@<version> verify --into <install> # every file is this package's
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`install` replaces a file only when the install holds the released 2.0.80 copy, or a copy an earlier version of this
|
|
31
|
+
package laid down (an upgrade). Anything else — a local edit, another release — is refused before a byte is written,
|
|
32
|
+
with the list. The replaced copies are kept in `<install>/.amsd-pipeline-shared/replaced/`, and
|
|
33
|
+
`amsd-pipeline-shared revert --into <install>` puts the released bytes back exactly.
|
|
34
|
+
|
|
35
|
+
Needs: Node 20+, bash, jq.
|
|
36
|
+
|
|
37
|
+
## Messages from the shell
|
|
38
|
+
|
|
39
|
+
For the bash pipeline (backlog B55), one JSON line out:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
amsd-pipeline-shared messages send --dir <logs/messages> --from <agent> --to <agent|router> --router <agent> --type info --subject S --text T
|
|
43
|
+
amsd-pipeline-shared messages receive --dir <logs/messages> --agent <agent> [--mark-read yes]
|
|
44
|
+
amsd-pipeline-shared messages list --dir <logs/messages>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The pipeline's own `receive-messages.sh` and `list-messages.sh` read what this writes.
|
|
48
|
+
|
|
49
|
+
## Tests
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm test # node --test, one file at a time; against a pipeline install (AMSD_INSTALL, AMSD_PLAIN_INSTALL)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The gateway, the reviewer, the mint re-proposal and the synthesiser each have a test written red first and
|
|
56
|
+
mutation-checked (md5 before and after). The install is tested on a copy of a 2.0.80 install: exactly the package's
|
|
57
|
+
files change, an edited file is refused, an upgrade replaces only its own copies, revert gives back the release.
|
|
58
|
+
|
|
59
|
+
## Version
|
|
60
|
+
|
|
61
|
+
`amsd-pipeline-shared version`. Released with a git tag `v<version>` and the npm publish of the same version.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// THE SHARED COMPONENT INTO A PIPELINE INSTALL — without changing the pipeline's installer (operator, 2026-10-18: no bash
|
|
4
|
+
// pipeline change outside the shared component). After `npx amsd-pipeline@<v> --dest <install>`:
|
|
5
|
+
// amsd-pipeline-shared install --into <install> lay this package's files into <install>/orchestrations
|
|
6
|
+
// amsd-pipeline-shared verify --into <install> every file is this package's (exit 1 and the list when not)
|
|
7
|
+
// amsd-pipeline-shared revert --into <install> put back the files this package replaced
|
|
8
|
+
// amsd-pipeline-shared messages send|receive|list --dir <bus> … the agent-to-agent bus (lib/agent-messages.js)
|
|
9
|
+
// amsd-pipeline-shared version
|
|
10
|
+
// A file is replaced only when the install holds the released copy this package was built from (docs/gateway-baseline-
|
|
11
|
+
// 2.0.80.md5) or this package's own copy (a re-install); anything else is refused, file by file, and nothing is written.
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const crypto = require('crypto');
|
|
15
|
+
|
|
16
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
17
|
+
const PKG = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
|
|
18
|
+
const FILES = fs.readFileSync(path.join(ROOT, 'docs', 'gateway-files.txt'), 'utf8').trim().split('\n').filter(Boolean);
|
|
19
|
+
const BASE = Object.fromEntries(fs.readFileSync(path.join(ROOT, 'docs', 'gateway-baseline-2.0.80.md5'), 'utf8').trim().split('\n')
|
|
20
|
+
.map((l) => l.trim().split(/\s+/)).map(([sum, f]) => [f, sum]));
|
|
21
|
+
const md5 = (f) => (fs.existsSync(f) ? crypto.createHash('md5').update(fs.readFileSync(f)).digest('hex') : null);
|
|
22
|
+
const fail = (m, code = 1) => { process.stderr.write(`amsd-pipeline-shared: ${m}\n`); process.exit(code); };
|
|
23
|
+
|
|
24
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
25
|
+
if (cmd === 'version') { process.stdout.write(`${PKG.version}\n`); process.exit(0); }
|
|
26
|
+
// THE MESSAGE BUS FOR SHELL CALLERS (the bash pipeline's adoption, backlog B55): one JSON line out
|
|
27
|
+
if (cmd === 'messages') {
|
|
28
|
+
const m = require(path.join(ROOT, 'orchestrations', 'scripts', 'lib', 'agent-messages.js'));
|
|
29
|
+
const [sub, ...o] = rest; const opt = {}; for (let i = 0; i < o.length; i += 2) opt[o[i].replace(/^--/, '').replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = o[i + 1];
|
|
30
|
+
try {
|
|
31
|
+
const out = sub === 'send' ? m.send({ ...opt, data: opt.data ? JSON.parse(opt.data) : {} })
|
|
32
|
+
: sub === 'receive' ? m.receive({ ...opt, unread: opt.unread !== 'no', markRead: opt.markRead === 'yes' })
|
|
33
|
+
: sub === 'list' ? m.list(opt) : (() => { throw new Error('messages send|receive|list --dir <bus> …'); })();
|
|
34
|
+
process.stdout.write(`${JSON.stringify(out)}\n`); process.exit(0);
|
|
35
|
+
} catch (e) { fail(String(e && e.message)); }
|
|
36
|
+
}
|
|
37
|
+
const at = rest.indexOf('--into');
|
|
38
|
+
if (!['install', 'verify', 'revert'].includes(cmd) || at < 0 || !rest[at + 1]) fail('usage: amsd-pipeline-shared install|verify|revert --into <pipeline install> | version', 64);
|
|
39
|
+
const INSTALL = path.resolve(rest[at + 1]);
|
|
40
|
+
const ORCH = path.join(INSTALL, 'orchestrations');
|
|
41
|
+
if (!fs.existsSync(path.join(ORCH, 'scripts'))) fail(`${INSTALL} is not a pipeline install (no orchestrations/scripts)`);
|
|
42
|
+
const STATE = path.join(INSTALL, '.amsd-pipeline-shared');
|
|
43
|
+
const BACKUP = path.join(STATE, 'replaced');
|
|
44
|
+
|
|
45
|
+
if (cmd === 'verify') {
|
|
46
|
+
const off = FILES.filter((f) => md5(path.join(ORCH, f)) !== md5(path.join(ROOT, 'orchestrations', f)));
|
|
47
|
+
if (off.length) fail(`${off.length} file(s) are not this package's (${PKG.version}):\n ${off.join('\n ')}`);
|
|
48
|
+
process.stdout.write(`amsd-pipeline-shared ${PKG.version}: all ${FILES.length} file(s) in ${ORCH} are this package's\n`);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (cmd === 'revert') {
|
|
53
|
+
if (!fs.existsSync(path.join(STATE, 'installed.json'))) fail(`nothing to revert: no amsd-pipeline-shared install recorded in ${STATE}`);
|
|
54
|
+
for (const f of FILES) {
|
|
55
|
+
const b = path.join(BACKUP, f);
|
|
56
|
+
if (fs.existsSync(b)) fs.copyFileSync(b, path.join(ORCH, f));
|
|
57
|
+
else if (!BASE[f]) fs.rmSync(path.join(ORCH, f), { force: true }); // this package's own file: the release has none
|
|
58
|
+
}
|
|
59
|
+
const back = FILES.filter((f) => (BASE[f] ? md5(path.join(ORCH, f)) !== BASE[f] : fs.existsSync(path.join(ORCH, f))));
|
|
60
|
+
if (back.length) fail(`revert incomplete — not the released copy:\n ${back.join('\n ')}`);
|
|
61
|
+
fs.rmSync(STATE, { recursive: true, force: true });
|
|
62
|
+
process.stdout.write(`amsd-pipeline-shared: reverted ${FILES.length} file(s) to the released copies\n`);
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// install: every target must be the released copy or this package's — checked for all files before any is written
|
|
67
|
+
// an earlier version of this package laid down: its copies are replaceable too (an upgrade), never anyone else's
|
|
68
|
+
let PREV = {}; try { PREV = JSON.parse(fs.readFileSync(path.join(STATE, 'installed.json'), 'utf8')).files || {}; } catch { PREV = {}; }
|
|
69
|
+
const plan = FILES.map((f) => {
|
|
70
|
+
const now = md5(path.join(ORCH, f)); const ours = md5(path.join(ROOT, 'orchestrations', f));
|
|
71
|
+
if (now && now !== ours && PREV[f] === now) return { f, now, ours, state: 'previous' };
|
|
72
|
+
// a file the release never had (this package's own, e.g. lib/agent-messages.js): installed where absent
|
|
73
|
+
if (!BASE[f]) return { f, now, ours, state: now === ours ? 'current' : (now === null ? 'new' : 'other') };
|
|
74
|
+
return { f, now, ours, state: now === ours ? 'current' : (now === BASE[f] ? 'released' : 'other') };
|
|
75
|
+
});
|
|
76
|
+
const other = plan.filter((p) => p.state === 'other');
|
|
77
|
+
if (other.length) {
|
|
78
|
+
fail(`refused — ${other.length} file(s) in the install are neither the released copy (2.0.80) nor this package's; nothing was written:\n `
|
|
79
|
+
+ other.map((p) => `${p.f} (md5 ${p.now || 'absent'})`).join('\n '));
|
|
80
|
+
}
|
|
81
|
+
for (const p of plan.filter((x) => x.state === 'released')) {
|
|
82
|
+
const b = path.join(BACKUP, p.f); fs.mkdirSync(path.dirname(b), { recursive: true });
|
|
83
|
+
if (!fs.existsSync(b)) fs.copyFileSync(path.join(ORCH, p.f), b);
|
|
84
|
+
}
|
|
85
|
+
for (const p of plan.filter((x) => ['released', 'new', 'previous'].includes(x.state))) {
|
|
86
|
+
const dst = path.join(ORCH, p.f);
|
|
87
|
+
const mode = p.state === 'new' ? fs.statSync(path.join(ROOT, 'orchestrations', p.f)).mode : fs.statSync(dst).mode;
|
|
88
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
89
|
+
fs.copyFileSync(path.join(ROOT, 'orchestrations', p.f), dst);
|
|
90
|
+
fs.chmodSync(dst, mode);
|
|
91
|
+
}
|
|
92
|
+
fs.mkdirSync(STATE, { recursive: true });
|
|
93
|
+
fs.writeFileSync(path.join(STATE, 'installed.json'), `${JSON.stringify({ package: PKG.name, version: PKG.version,
|
|
94
|
+
files: Object.fromEntries(FILES.map((f) => [f, md5(path.join(ROOT, 'orchestrations', f))])) }, null, 2)}\n`);
|
|
95
|
+
const n = plan.filter((x) => ['released', 'new', 'previous'].includes(x.state)).length;
|
|
96
|
+
process.stdout.write(`amsd-pipeline-shared ${PKG.version} → ${ORCH}: ${n} file(s) replaced, ${FILES.length - n} already current\n`);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# B56 consumer review — who receives a real failure instead of "success, empty answer"
|
|
2
|
+
|
|
3
|
+
The fix (`llm-handler.sh`, claude and codemie-claude arms): a call whose CLI fails is retried up to
|
|
4
|
+
`EPAM_CALL_MAX_ATTEMPTS` (3) with the ladder consulted, the previous-refusal block added and a KB episode recorded; if
|
|
5
|
+
every attempt fails, the hub exits 1. Before the fix every failed call exited 0 with an empty answer.
|
|
6
|
+
|
|
7
|
+
Read by hand against amsd-pipeline v2.0.80 (`git show v2.0.80:…`), 2026-10-18. Every caller of `ai-run.sh` /
|
|
8
|
+
`llm-handler.sh` / `AI_RUNNER_CMD` in `orchestrations/scripts` (tests and tools excluded).
|
|
9
|
+
|
|
10
|
+
## Same outcome as today, after the hub's retries (a failure handled as an empty answer)
|
|
11
|
+
| Caller | How it handles a non-zero exit |
|
|
12
|
+
|---|---|
|
|
13
|
+
| spec-mode-runner.js `runClaude` → `runAgentForJson` | rejects; each call site below catches |
|
|
14
|
+
| — spec coordinator (:1825), coordinator review (:2524), LLM decisions (:2704) | catch → default / no review, logged |
|
|
15
|
+
| — ticket links (:7614), spec agent (:7890), speckit review (:8223) | catch → "could not be read" / failed attempt |
|
|
16
|
+
| — mint proposals (:4339), survey review (:4874) | inside `retryUntilParsedAsync`: "the call itself failed" → next attempt |
|
|
17
|
+
| — roster review (:5476 via mint-agents-step :917), project roster review (:5249 via roster-seams :174) | catch → `review_failed` |
|
|
18
|
+
| lib/llm-call.js (discovery, ac-gate, cpa) | already rejects on non-zero exit AND on empty — unchanged |
|
|
19
|
+
| lib/kb-synthesizer.js | already handles `r.status !== 0` |
|
|
20
|
+
| lib/cli-commands.sh :261, story-attempt.sh :752 / :791, review-cycle.sh :43, vc-coverage-check.sh :158, brownfield-repro-test-writer.sh :867, retry-extension.sh :199, run-agent-orchestration.sh :4052–4132 | `\|\| echo ""` / `\|\| true` / `if … then … else` — the empty-answer path |
|
|
21
|
+
| prd-change-review.sh :216, :452 | `\|\| echo ""` / its else branch |
|
|
22
|
+
|
|
23
|
+
## Better — the failure is now classified as a failure
|
|
24
|
+
| Caller | Before | After |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| failure-healing.sh :1278 (failure analyst) | call "ok", answer unparseable | its call-failed branch; its own analyst retry/ladder loop runs |
|
|
27
|
+
| model-ladder.sh :1378 (ladder coordinator) | call "ok", no JSON | its call-failed branch |
|
|
28
|
+
| prd-change-review.sh :86 (PRD-change reviewer) | empty reply parsed | `verdict: fail — the change was NOT reviewed` |
|
|
29
|
+
| post-impl-tc-writer.sh :234 | "no criteria extracted" | "Agent failed (exit 1)" (TC_EXIT is passed on) |
|
|
30
|
+
| orch-prompt.sh :133 (the Writer, via claude.sh) | a crashed writer call looked like a finished attempt | `_rc` = 1 → the Writer's failure path (analyst, ladder) |
|
|
31
|
+
| agent-attempt-analyst.sh :240 | — | already treats non-zero as `self_heal_failed` (exit 2) |
|
|
32
|
+
| surveyEstate (:3724) | `not_investigated` | `failed` with the reason (catch) |
|
|
33
|
+
|
|
34
|
+
## Changes that need a decision
|
|
35
|
+
1. **Mint re-proposal (spec-mode-runner.js :4414) has no catch, nor has its caller (mint-agents-step.js :843 → the
|
|
36
|
+
top-level catch, exit 1).** Before: an empty re-proposal ended the loop and the mint kept the roles already accepted.
|
|
37
|
+
After: three failed attempts there fail the mint step.
|
|
38
|
+
**Fixed in this package** (operator: "yes move both in"): spec-mode-runner.js joined the component; the call is caught,
|
|
39
|
+
the loop ends as today and says so (test/mint-reproposal.test.js).
|
|
40
|
+
2. **The reviewer (team-lead-review.sh :326 / :877) ignores the call's exit status and reads `2>&1`.** A failed call's
|
|
41
|
+
output is the hub's error text, which passes the "no verdict" check and is parsed as the review → the fallback
|
|
42
|
+
`changes_requested` with a "could not parse" blocker. Fail-safe (never approves) — before and after the fix — but the
|
|
43
|
+
Writer is sent to fix an issue that does not exist instead of "review incomplete — reviewer unavailable".
|
|
44
|
+
**Fixed in this package**: team-lead-review.sh joined the component; a failed call is no verdict — retried up the
|
|
45
|
+
reviewer's ladder, then review-incomplete (test/reviewer.test.js).
|
|
46
|
+
|
|
47
|
+
## Cost
|
|
48
|
+
A persistent failure now costs up to 3 hub attempts per caller attempt (callers with their own retry loop multiply:
|
|
49
|
+
content-retry ×3, the failure analyst's ladder loop, the roster review ×2). Calls that succeed cost the same as before.
|
|
50
|
+
|
|
51
|
+
## Found while proving the analyst in the real project environment (2026-10-18)
|
|
52
|
+
- The analyst works there: `self-heal.js` → `agent-attempt-analyst.sh` diagnosed a planted no-json failure on
|
|
53
|
+
claude-sonnet-5 and marked it analysed. (It declined only in a stripped environment with no project config.)
|
|
54
|
+
- One analysis = 4 model calls by design: the analyst + its plan pass, then `kb-synthesizer` + its plan pass (and 12 free
|
|
55
|
+
`--help` capability probes).
|
|
56
|
+
- **`kb-synthesizer` runs with no model resolved** (AI_MODEL unset) — it falls back to the CLI's default model instead of
|
|
57
|
+
its seam's. An "appropriate LLM settings" defect. **Fixed in this package**: with no model given it runs on its seam's
|
|
58
|
+
start rung (seamInvocationEnv; claude-haiku-4-5 on the claude set); a named model still wins (test/kb-synthesizer-model.test.js).
|
|
59
|
+
- **On a fresh install the synthesiser cannot render its prompt**: `projects/<p>/prompts/kb-enforcement-synthesis.json`
|
|
60
|
+
is a minted project prompt, absent on wf-280 ("project-authority prompt missing … Mint it"). To check against how the
|
|
61
|
+
codeline cache supplies prompts before the next live leg; the self-heal listener must report a synthesis failure.
|
|
62
|
+
**Checked 2026-10-18: not a risk for a run with its codeline cache** — the run's project copy (run/project/prompts,
|
|
63
|
+
44 prompts from .prompt-cache/<codeline>/prompts) holds it. A project with no minted or cached prompt set lacks it.
|
|
64
|
+
- **The ladder position `mid` resolves to the middle tier, `high`** (seam-invocation.js resolveTierPosition: base/mid/top
|
|
65
|
+
over the declared order medium/high/highest), whose chain is haiku → sonnet-5 → opus-5-5. config/llm-defaults.claude.json
|
|
66
|
+
says of `medium`: "A `mid` seam never reaches Opus: 14 seams resolve here". 12 seams declare `mid` (ticket-links among
|
|
67
|
+
them) and, by the code, can reach Opus. The pipeline's code and its declaration disagree — for the operator to rule;
|
|
68
|
+
the gateway follows the code (agent_ladder_model), as the hub does.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
6b83e0d700392eebdd1bae1ec233ab26 prompts/templates/codeline-discovery.json
|
|
2
|
+
b2470a21b73b03abb95713f05acaa827 scripts/agent-attempt-analyst.sh
|
|
3
|
+
c45cda62ef52ee95c8de6acead3408f8 scripts/lib/agent-contract.js
|
|
4
|
+
f881c989bd12e76e13e017ff2d984041 scripts/lib/agent-ladder.sh
|
|
5
|
+
c2dfd2381db0242fdbc487a948fd7feb scripts/lib/agent-si.js
|
|
6
|
+
488870d661550b057077104147b54004 scripts/lib/constraint-compiler.js
|
|
7
|
+
639d21b8d2adb82d2ed98ceffc3c2175 scripts/lib/constraint-sanity.js
|
|
8
|
+
f31ac2a712f0e1f288606b0fdbb7d001 scripts/lib/content-retry.js
|
|
9
|
+
e24bfedd32af37785a42fa8c168ae52a scripts/lib/cost-emitter.js
|
|
10
|
+
d7fb52664b0f0528b6142bf0574c1f49 scripts/lib/cost-record.sh
|
|
11
|
+
f20fe173b49d25008e206f82ace56619 scripts/lib/engine-prompt.js
|
|
12
|
+
773a5b134875d61f988b15934a9d5fec scripts/lib/env-file.sh
|
|
13
|
+
66f728a2d4f3f4dac44bfc08775cc485 scripts/lib/evidence-windows.js
|
|
14
|
+
e80316d37f028cdf83f3a8fa43913509 scripts/lib/failure-signature.js
|
|
15
|
+
1ada64b928930ab3937c4b26269fb4ec scripts/lib/handlers/emit-cost.js
|
|
16
|
+
dccf359893d3c9a2c233ab19e0a64728 scripts/lib/jq-vals.sh
|
|
17
|
+
a2589a1ff6443fc1c18b43095d9d8872 scripts/lib/kb-apply.sh
|
|
18
|
+
740f541460c80aa0f3220656069f0838 scripts/lib/kb-arbitration.js
|
|
19
|
+
8d6e41fe34703497cf5e8b5d1320e3e7 scripts/lib/kb-cli.js
|
|
20
|
+
c0a3a88d1a895e0a86ea483cada15411 scripts/lib/kb-store.js
|
|
21
|
+
133c3a3df52044457822755a23f40bd2 scripts/lib/kb-synthesizer.js
|
|
22
|
+
84348b573adb9c666eb040fb8f7621b2 scripts/lib/langfuse-emit.js
|
|
23
|
+
991a2faba5aaa513e63fa64689761248 scripts/lib/llm-settings-resolve.js
|
|
24
|
+
ed15fbd317db2643b19a3fd7ecd1979a scripts/lib/model-ladder.sh
|
|
25
|
+
4d2dc2a636736b5b77711ad7736f9075 scripts/lib/model-ladders.sh
|
|
26
|
+
97d25b9108a01292d1b28da0d3c129e9 scripts/lib/pipeline-tools.js
|
|
27
|
+
6bfffd3ba6eb845f738d3157168bf056 scripts/lib/relative-paths.js
|
|
28
|
+
6894e248c7a86d07626b7e981d534354 scripts/lib/render-prompt-section.js
|
|
29
|
+
912511e457b0a6f4801c479400b1c3a3 scripts/lib/seam-invocation.js
|
|
30
|
+
010eaf91866f87040076c6889c6dc148 scripts/lib/seam-ladder.sh
|
|
31
|
+
49cafcda352d69ed32966950902d8e4d scripts/lib/self-heal.js
|
|
32
|
+
177515d856a573dad5f515ece454d835 scripts/lib/stage-coverage-gate.sh
|
|
33
|
+
be6d0a378fa9c3bc9da6240877aaeae3 scripts/lib/tool-timeouts.js
|
|
34
|
+
5574182fb27aaeabbfff261b3a8d6d39 scripts/lib/transcript-tool-calls.js
|
|
35
|
+
c4b96230c15b52a845cf20fd3266fe01 scripts/llm-handler.sh
|
|
36
|
+
f206025f304e8320b8417a6dd6606044 scripts/spec-mode-runner.js
|
|
37
|
+
dfa070b168a7c01fab8aab5a46d16192 scripts/team-lead-review.sh
|
|
38
|
+
40c65de8360d5a9f59902fca26d1d7a8 scripts/update-monitor.sh
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
prompts/templates/codeline-discovery.json
|
|
2
|
+
scripts/agent-attempt-analyst.sh
|
|
3
|
+
scripts/lib/agent-contract.js
|
|
4
|
+
scripts/lib/agent-ladder.sh
|
|
5
|
+
scripts/lib/agent-messages.js
|
|
6
|
+
scripts/lib/agent-si.js
|
|
7
|
+
scripts/lib/constraint-compiler.js
|
|
8
|
+
scripts/lib/constraint-sanity.js
|
|
9
|
+
scripts/lib/content-retry.js
|
|
10
|
+
scripts/lib/cost-emitter.js
|
|
11
|
+
scripts/lib/cost-record.sh
|
|
12
|
+
scripts/lib/engine-prompt.js
|
|
13
|
+
scripts/lib/env-file.sh
|
|
14
|
+
scripts/lib/evidence-windows.js
|
|
15
|
+
scripts/lib/failure-signature.js
|
|
16
|
+
scripts/lib/handlers/emit-cost.js
|
|
17
|
+
scripts/lib/jq-vals.sh
|
|
18
|
+
scripts/lib/kb-apply.sh
|
|
19
|
+
scripts/lib/kb-arbitration.js
|
|
20
|
+
scripts/lib/kb-cli.js
|
|
21
|
+
scripts/lib/kb-store.js
|
|
22
|
+
scripts/lib/kb-synthesizer.js
|
|
23
|
+
scripts/lib/langfuse-emit.js
|
|
24
|
+
scripts/lib/llm-settings-resolve.js
|
|
25
|
+
scripts/lib/model-ladder.sh
|
|
26
|
+
scripts/lib/model-ladders.sh
|
|
27
|
+
scripts/lib/pipeline-tools.js
|
|
28
|
+
scripts/lib/relative-paths.js
|
|
29
|
+
scripts/lib/render-prompt-section.js
|
|
30
|
+
scripts/lib/seam-invocation.js
|
|
31
|
+
scripts/lib/seam-ladder.sh
|
|
32
|
+
scripts/lib/self-heal.js
|
|
33
|
+
scripts/lib/stage-coverage-gate.sh
|
|
34
|
+
scripts/lib/tool-timeouts.js
|
|
35
|
+
scripts/lib/transcript-tool-calls.js
|
|
36
|
+
scripts/llm-handler.sh
|
|
37
|
+
scripts/spec-mode-runner.js
|
|
38
|
+
scripts/team-lead-review.sh
|
|
39
|
+
scripts/update-monitor.sh
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$why": [
|
|
3
|
+
"Moved out of lib/codeline-discovery.js buildDiscoveryPrompt on 2026-08-15 (2 of 20).",
|
|
4
|
+
"",
|
|
5
|
+
"This prompt decides which repositories a ticket belongs to, so its wording sets the scope",
|
|
6
|
+
"of the entire run. Extracted mechanically and pinned byte for byte by a golden capture",
|
|
7
|
+
"taken before the move.",
|
|
8
|
+
"",
|
|
9
|
+
"Requiring the module used to exit the requiring process: its argument check ran at import",
|
|
10
|
+
"time. Scoping that to direct invocation is what made the migration provable at all.",
|
|
11
|
+
"",
|
|
12
|
+
"Built from the golden OUTPUT, not from the source text. The first attempt extracted the",
|
|
13
|
+
"template literal verbatim and kept its JS escapes — a backslash-quote stayed two characters",
|
|
14
|
+
"instead of one — so the rendered prompt differed from the original by two bytes. The",
|
|
15
|
+
"byte-identity check caught it, which is the entire reason the golden is captured first.",
|
|
16
|
+
"",
|
|
17
|
+
"Facts added 2026-08-16. codeline-facts.json had no producer at all: every one in the repo was",
|
|
18
|
+
"typed by hand, so a new project had none and whatever a run learned died with it. Discovery",
|
|
19
|
+
"is the single producer because it is the only stage that opens every candidate repository.",
|
|
20
|
+
"",
|
|
21
|
+
"Rule 6 changed 2026-10-19 (version 3, amsd-pipeline-shared). The reason asked for \"what part of the ticket",
|
|
22
|
+
"it covers\", and the AC classifier reads each reason as that codeline's description (JIRA_CODELINE_DESC_*, ac-gate.js",
|
|
23
|
+
"codelineDesc). For a ticket naming several sites the reasons assigned work to each (\"the acceptance criteria apply to",
|
|
24
|
+
"GO\", \"must be wired here\"), and the classifier scoped the story to every site; where the reasons only described the",
|
|
25
|
+
"site (\"matching the ticket's GO component\") it chose the shared library. Same prompt, same model, both outcomes",
|
|
26
|
+
"(AMSD-2974, launch LIVE-AMSD-2974-8 vs the classifier probe; amsd-pipeline-workflow docs/probes/why-20261019). The",
|
|
27
|
+
"reason now describes the repository and its relation to the others; deciding the work stays with the classifier."
|
|
28
|
+
],
|
|
29
|
+
"body": "You are the codeline-discovery agent for a brownfield engineering pipeline.\n\nYour task: given a set of Jira tickets and a manifest of local code repositories,\nidentify which repository (or repositories) each ticket's changes belong to.\n\nJIRA TICKETS:\n__ISSUES_SUMMARY__\n\nREPOSITORY MANIFEST (git repos only):\n__MANIFEST_SUMMARY__\n\nTHE MANIFEST ABOVE IS EVERY REPOSITORY IN THE ESTATE, in no particular order. It is\nnot ranked, not filtered and not scored: nothing has judged relevance before you, so\nposition carries no information and the first entry is not a hint.\n\nYOU HAVE TOOLS, AND THE ANSWER IS USUALLY IN THE CODE RATHER THAN IN THIS LISTING.\nNames and READMEs describe what a repository calls itself; the code shows what it\nactually implements, and two sibling repositories in the same estate routinely\ndescribe themselves almost identically.\n\n codegraph_query search indexed symbols. Pass codeline=<name from the manifest>\n to choose which repository to search; without it, a query is\n ambiguous when several are in scope.\n search find text across a repository.\n read_file open a specific file.\n list_files see how a repository is laid out.\n\nSEARCH BEFORE YOU DECIDE. Look for the thing the ticket actually asks about — the\nsymbol, the component, the configuration it names — and see which repository\ncontains it. A repository that mentions the domain in prose and a repository that\nimplements it are indistinguishable from this listing alone, and only one of them\nis the place the work belongs.\n\nRules:\n1. Match each ticket to the repository whose name, packageName, or description\n best fits the ticket's domain. Use context clues: service names, domain terms,\n technology references, component names, file path mentions.\n2. RETURN THE REPOSITORIES THE EVIDENCE SUPPORTS. An empty result stops the run,\n which is the correct outcome when the work genuinely cannot be placed — but it\n is almost never the honest answer, because you have tools and the estate is in\n front of you. Search first. If you still cannot ground a part of the ticket,\n put that part in \"unsure\" rather than selecting a repository to fill the slot:\n a guess promoted to a selection cannot be spotted afterwards, and the run will\n modify whatever you name.\n\n2a. ONE TICKET MAY SPAN SEVERAL REPOSITORIES, and you MUST return one entry for\n each. This estate is maintained as many separate codelines, so a single\n ticket routinely covers work in more than one of them — a change with both\n front-end and back-end parts, or one applied across several product areas.\n Returning a single repository for such a ticket silently drops the rest of\n the work: those repositories are never touched, and the run reports success\n having done a fraction of the job.\n\n The strongest evidence is the ticket's own \"components\" field, which is the\n tracker's record of which product areas it touches. When a ticket names\n several distinct product areas, return the repository that owns each one.\n Do not collapse them because one candidate scored highest — the ranking\n measures textual similarity to a single repository, not how much of the\n ticket that repository covers.\n\n Decide the count from the ticket. Do not aim for any particular number.\n3. SELECT ON EVIDENCE, NOT ON A HUNCH. Every selected repository must be grounded\n in something concrete you can point at:\n - a ticket component, label or phrase that names that product area, or\n - something in the repository itself (its name, its manifest, its code)\n that matches what the ticket asks for.\n POSITION IN THE LIST IS NOT EVIDENCE — there is no ranking to appeal to. Nor is\n \"it is probably the backend for X\": if you have to say likely, probably, or may\n be, you do not have evidence, you have a hunch. What you found with a tool is\n evidence, and it is the strongest kind available here.\n If a part of the ticket clearly belongs SOMEWHERE but you cannot ground it in\n one of these repositories, DO NOT SELECT ONE ANYWAY. Put it in \"unsure\" with\n what you could not resolve. An unresolved component is real information and a\n human can settle it; a guess promoted to a selection cannot be spotted later.\n Do not omit a candidate you CAN ground merely because another also fits — a\n ticket spanning several product areas needs all of them (rule 2a).\n4. Assign each selected repo a short codeline identifier: 2-20 chars, lowercase,\n alphanumeric only, no dots or dashes. Derive it from the directory name by\n removing only decoration - a domain suffix, or an organisation or platform\n prefix - and keeping the part that actually names the product. Keep every\n remaining word: for a directory \"alpha-beta-gamma\" the identifier is\n \"alphabetagamma\", NOT \"gamma\". Dropping words produces an identifier that no\n longer identifies the repository.\n5. Return exactly one entry only when the ticket genuinely belongs to exactly one\n repository. \"One entry\" is the right answer for a self-contained change, and\n the wrong answer for a ticket spanning several product areas — see rule 2a.\n6. \"reason\" must be one sentence explaining why THIS repository was selected: what the\n repository is, and how it relates to the ticket and to the other repositories you\n selected — for example, that it owns a component the ticket names, or that it uses a\n component another selected repository owns. Describe the REPOSITORY, not the work:\n do not say what must change, be built, wired or verified in it. Which repositories\n the work belongs to is decided after you, from the acceptance criteria, and it reads\n your reason as the description of the repository. Every selected repository needs its own\n reason — a single justification covering the whole set makes a wrong\n selection impossible to spot afterwards.\n7. \"evidence\" must quote the CONCRETE thing that grounds the selection: the\n ticket component/label/phrase verbatim, or the file, directory, symbol or\n manifest entry you found in that repository — including what a tool returned. It is not a second reason and not a restatement —\n if you cannot fill it without hedging, the repository belongs in \"unsure\",\n not in \"codelines\".\n8. REPORT WHAT AN AGENT WORKING IN THAT REPOSITORY WOULD NEED TO KNOW and could not\n read off the source — its \"facts\". You are the only stage that opens every\n candidate repository, so this is the one place the estate is visible at once.\n Useful facts are things like: how its tests are actually run and what they\n need in order to pass, what a build or commit hook demands before it will\n let a change through, where the source and tests live when it is not obvious,\n and any setup without which a correct change still fails.\n FACTS ARE OBSERVED, NEVER GUESSED. Each one must quote its \"source\": the file,\n script or manifest entry you read it from, verbatim enough that someone can\n open it and check. A fact you cannot source is a guess, and a guess here is\n handed to every agent that works in that repository as though it were\n established — worse than saying nothing. If you observed nothing beyond what\n the source already says, return an empty list. An empty list is an honest\n answer and it is reported; an invented fact is not detectable later.\n A FACT'S SOURCE IS A FILE INSIDE THAT REPOSITORY — its code, manifest, config,\n hooks, scripts or README. Nothing the pipeline produced is a source: not a PRD,\n not a ticket or its description, not a previous run's output, not a log. Those\n describe a ticket, not the repository; a \"fact\" taken from them is the ticket\n restated, and every later story in that repository would inherit it as truth.\n Do not restate the ticket, and do not describe what the change should be —\n these describe the REPOSITORY, and they outlive this ticket.\n\nOutput format (strict JSON, no markdown fences, no preamble, no trailing text):\n{\n \"codelines\": [\n { \"name\": \"<short-identifier-derived-from-the-directory-name>\",\n \"path\": \"/absolute/path/to/repo\",\n \"reason\": \"what this repository is, and how it relates to the ticket and to the other selected repositories\",\n \"evidence\": \"ticket component \\\"<the-component-verbatim>\\\"\",\n \"facts\": [\n { \"text\": \"what an agent working here needs to know and cannot read off the source\",\n \"source\": \"the file, script or manifest entry you read it from\" }\n ] }\n ],\n \"unsure\": [\n { \"part\": \"the part of the ticket you could not place\",\n \"why\": \"what you would need in order to place it\" }\n ]\n}",
|
|
30
|
+
"description": "Match each ticket to the repository or repositories its changes belong to.",
|
|
31
|
+
"id": "codeline-discovery",
|
|
32
|
+
"placeholders": [
|
|
33
|
+
"__ISSUES_SUMMARY__",
|
|
34
|
+
"__MANIFEST_SUMMARY__"
|
|
35
|
+
],
|
|
36
|
+
"version": 3,
|
|
37
|
+
"seams": [
|
|
38
|
+
"codeline-discovery"
|
|
39
|
+
]
|
|
40
|
+
}
|