amsd-pipeline-shared 0.5.38 → 0.5.40

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "set-fidelity-check",
3
- "version": 1,
3
+ "version": 2,
4
4
  "seams": [],
5
5
  "layer": "engine",
6
6
  "description": "A judge reads a codeline's minted roster and reusable prompts against the ticket they were minted for and reports what is geared to that ticket rather than to the codeline.",
@@ -8,11 +8,11 @@
8
8
  "__CODELINE__",
9
9
  "__TICKET_KEY__",
10
10
  "__TICKET_TITLE__",
11
- "__ROSTER__",
12
- "__PROMPTS__"
11
+ "__ROSTER__"
13
12
  ],
14
13
  "$why": [
15
- "v1 (operator, 2026-10-24: \"We need some post pause 1 checkpoints to ensure no contamination in codeline level prompts and profiles\"): run 13's roster named every agent after the ticket's subject and ~half its reusable prompts described that one ticket; the sets are cached per codeline and reused for every later story. Judged by an agent, not a word list; run by the pre-launch of the leg after pause 1 and reported at pause 1."
14
+ "v1 (operator, 2026-10-24: \"We need some post pause 1 checkpoints to ensure no contamination in codeline level prompts and profiles\"): run 13's roster named every agent after the ticket's subject and ~half its reusable prompts described that one ticket; the sets are cached per codeline and reused for every later story. Judged by an agent, not a word list; run by the pre-launch of the leg after pause 1 and reported at pause 1.",
15
+ "v2 (live, 2026-10-24): a codeline's whole prompt set in one call was 908 KB and the judge answered about its own context limit; the prompts are judged one by one by the pipeline's own reviewer as they are built (each copy records its judges), so this judge reads the roster only."
16
16
  ],
17
- "body": "You judge what a pipeline minted for ONE codeline and will REUSE for every later story on it: its agent roster (names and briefs) and\nits reusable agent prompts. They were minted while this ticket was in scope:\n\nTicket __TICKET_KEY__: __TICKET_TITLE__\nCodeline: __CODELINE__\n\nTHE ROSTER (each agent: name, kind, brief):\n__ROSTER__\n\nTHE PROMPTS (each: id, then its body):\n__PROMPTS__\n\nReport every place where the roster or a prompt is geared to THIS ticket rather than to the codeline: an agent named after the\nticket's subject, component or key; a brief or prompt that takes this ticket's subject, components, files or requirements as its\nsubject or as standing instruction, so that an implementer on a DIFFERENT story of this codeline would be misled. A mention of the\nticket's subject or files only as a marked example of the codeline's conventions is not geared. Quote the words. Report nothing you\ncannot quote. Answer in the structured form you are given; `geared` is empty when nothing is geared."
17
+ "body": "You judge the agent roster a pipeline minted for ONE codeline and will REUSE for every later story on it: its agents' names and\nbriefs. It was minted while this ticket was in scope:\n\nTicket __TICKET_KEY__: __TICKET_TITLE__\nCodeline: __CODELINE__\n\nTHE ROSTER (each agent: name, kind, brief):\n__ROSTER__\n\nReport every agent that is geared to THIS ticket rather than to the codeline: named after the ticket's subject, component or key, or\nbriefed so that it takes this ticket's subject, components, files or requirements as its subject or as standing instruction — an\nimplementer on a DIFFERENT story of this codeline would be misled. A mention of the ticket's subject or files only as a marked example\nof the codeline's conventions is not geared. Quote the words. Report nothing you cannot quote. Answer in the structured form you are\ngiven; `geared` is empty when nothing is geared."
18
18
  }
@@ -330,9 +330,13 @@ function checklist({ env, results }) {
330
330
  /**
331
331
  * NOTHING MINTED FOR A CODELINE IS GEARED TO THE TICKET (operator, 2026-10-24: "post pause 1 checkpoints to ensure no contamination in
332
332
  * codeline level prompts and profiles"). After pause 1 the Agents phase has minted each codeline's roster and prompt set, which are kept
333
- * and reused for every later story on that codeline. A judge agent (template set-fidelity-check, the given `judge` call) reads each
334
- * codeline's roster and prompts against the ticket and quotes what is geared to it; anything quoted is RED, naming the codeline, the
335
- * file and the words. Not run when the launch gives no judge (a £0 leg) — then NOT-YET, never GREEN by silence.
333
+ * and reused for every later story on that codeline. Two parts, each what it needs to be:
334
+ * - the ROSTER: a judge agent (template set-fidelity-check, the given `judge` call) reads each codeline's agents against the ticket and
335
+ * quotes what is geared to it;
336
+ * - the PROMPTS: every recorded copy must carry the record of the reviewer that judged it (judgedWith — the pipeline's own prompt review,
337
+ * whose rule is a prompt narrowed to one story); a copy with no record is the exact gap that let run 13's ticket-geared prompts through,
338
+ * and is RED by name. (A whole set in one judge call was 908 KB and the judge answered about its context, not the prompts.)
339
+ * Anything quoted or unjudged is RED, naming the codeline and the words or files. No judge given (a £0 leg): RED, never green by silence.
336
340
  */
337
341
  const SET_FIDELITY_SCHEMA = { type: 'object', required: ['geared'], properties: { geared: { type: 'array', items: { type: 'object',
338
342
  required: ['where', 'quote', 'why'], properties: { where: { type: 'string' }, quote: { type: 'string' }, why: { type: 'string' } } } } } };
@@ -343,26 +347,33 @@ async function setFidelity({ install, projectConfigDir, ticket, codelines = [],
343
347
  const rows = []; let examined = 0;
344
348
  for (const codeline of codelines) {
345
349
  const rp = b.recordedPromptsDir(projectConfigDir, codeline); const mi = b.mintedSnapshotDir(projectConfigDir, codeline);
346
- const prompts = fs.existsSync(rp) ? fs.readdirSync(rp).filter((n) => n.endsWith('.json')).sort().map((n) => { const d = readJson(path.join(rp, n), {}); return `=== ${n} ===\n${d.body || Object.values(d.bodies || {}).join('\n')}`; }) : [];
350
+ const promptFiles = fs.existsSync(rp) ? fs.readdirSync(rp).filter((n) => n.endsWith('.json')).sort() : [];
351
+ // a generated copy (authority project) with no judging record; a verbatim copy (authority template / copied) carries none by design
352
+ const unjudged = promptFiles.filter((n) => { const d = readJson(path.join(rp, n), {}); return d && d.authority === 'project' && !d.judgedWith; });
347
353
  let roster = '(none recorded)';
348
354
  try {
349
355
  const ag = readJson(path.join(mi, 'roster.json'), {}).agents || {}; const pr = (readJson(path.join(mi, 'agent-profiles.json'), {}).profiles) || {};
350
356
  const names = Object.keys(ag);
351
357
  if (names.length) roster = names.map((n) => `- ${n} [${(ag[n] || {}).kind || ''}]\n${String(pr[n] || (ag[n] || {}).persona || '(no brief)')}`).join('\n\n');
352
358
  } catch { /* judged as recorded */ }
353
- if (!prompts.length && roster === '(none recorded)') { rows.push({ codeline, examined: false, geared: [] }); continue; }
354
- const prompt = renderEngineTemplate('set-fidelity-check', { __CODELINE__: codeline, __TICKET_KEY__: ticket.key || '', __TICKET_TITLE__: ticket.title || '',
355
- __ROSTER__: roster, __PROMPTS__: prompts.join('\n\n') || '(none recorded)' });
356
- const out = await judge({ prompt, schema: SET_FIDELITY_SCHEMA, codeline });
359
+ if (!promptFiles.length && roster === '(none recorded)') { rows.push({ codeline, examined: false, geared: [], unjudged: [] }); continue; }
360
+ let geared = [];
361
+ if (roster !== '(none recorded)') {
362
+ const prompt = renderEngineTemplate('set-fidelity-check', { __CODELINE__: codeline, __TICKET_KEY__: ticket.key || '', __TICKET_TITLE__: ticket.title || '', __ROSTER__: roster });
363
+ const out = await judge({ prompt, schema: SET_FIDELITY_SCHEMA, codeline });
364
+ geared = (out && Array.isArray(out.geared)) ? out.geared : null;
365
+ }
357
366
  examined += 1;
358
- rows.push({ codeline, examined: true, geared: (out && Array.isArray(out.geared)) ? out.geared : null });
367
+ rows.push({ codeline, examined: true, geared, unjudged });
359
368
  }
360
369
  const unjudged = rows.filter((r) => r.examined && r.geared === null).map((r) => r.codeline);
361
370
  const geared = rows.filter((r) => r.examined && Array.isArray(r.geared) && r.geared.length);
362
- const ok = examined > 0 && !unjudged.length && !geared.length;
371
+ const unjudgedPrompts = rows.filter((r) => r.examined && r.unjudged.length);
372
+ const ok = examined > 0 && !unjudged.length && !geared.length && !unjudgedPrompts.length;
363
373
  const why = [geared.length && geared.map((r) => `${r.codeline}: ${r.geared.length} geared — ${r.geared.slice(0, 3).map((g) => `${g.where}: "${String(g.quote).slice(0, 80)}"`).join('; ')}`).join('; '),
374
+ unjudgedPrompts.length && unjudgedPrompts.map((r) => `${r.codeline}: ${r.unjudged.length} prompt copy(ies) no reviewer is recorded to have judged: ${r.unjudged.slice(0, 4).join(', ')}`).join('; '),
364
375
  unjudged.length && `no verdict for ${unjudged.join(', ')}`, !examined && 'nothing minted to examine'].filter(Boolean).join('; ');
365
- return res('set-fidelity', ok, ok ? `${examined} codeline set(s) judged against ${ticket.key}: nothing geared to the ticket` : why, { rows });
376
+ return res('set-fidelity', ok, ok ? `${examined} codeline set(s) judged against ${ticket.key}: no agent geared to the ticket, every prompt copy judged by the reviewer` : why, { rows });
366
377
  }
367
378
 
368
379
  /** The set-fidelity judge a LIVE launch gives setFidelity: the prompt-review seam's own model, ladder and tools (seam-invocation), through the
@@ -375,7 +386,8 @@ function setFidelityJudge({ install, env = process.env, logDir }) {
375
386
  return async ({ prompt, schema, codeline }) => {
376
387
  const seamEnv = seamInvocationEnv('prompt-review', path.join(orchOf(install), 'agents'));
377
388
  if (seamEnv.EPAM_ALLOWED_TOOLS) seamEnv.AI_GATE_ALLOW_TOOLS = '1';
378
- seamEnv.EPAM_AGENT_NAME = 'set-fidelity';
389
+ // the call runs AS the prompt-review seam (llm-handler resolves the seam by agent name); the ledger names the check
390
+ seamEnv.EPAM_AGENT_NAME = 'prompt-review';
379
391
  seamEnv.EPAM_RESPONSE_SCHEMA = spec.schemaEnv({ name: 'set_fidelity', description: 'what is geared to the ticket', parameters: schema });
380
392
  const text = await spec.runClaude(promptExec, prompt, path.join(logDir || os.tmpdir(), `set-fidelity-${codeline}.log`), seamEnv, { costAgent: 'set-fidelity' });
381
393
  const m = String(text || '').match(/\{[\s\S]*\}/);
@@ -4470,13 +4470,29 @@ const TOOL_ROSTER_REVIEW = {
4470
4470
  'checked with your tools. Do not answer in prose.',
4471
4471
  parameters: {
4472
4472
  type: 'object',
4473
- required: ['verdict', 'findings'],
4473
+ required: ['verdict', 'findings', 'roles'],
4474
4474
  properties: {
4475
4475
  verdict: {
4476
4476
  type: 'string',
4477
4477
  enum: ['sound', 'defects_found', 'nothing_to_review'],
4478
4478
  description: 'sound = every checkable claim held. defects_found = at least one did not.',
4479
4479
  },
4480
+ // A ROLE OUTLIVES THE TICKET (operator, 2026-10-24): the roster is kept with its codelines and reused for every later story, so
4481
+ // every minted role is answered for, one by one — the rule in prose alone was answered "sound" on a roster whose every agent was
4482
+ // named after the ticket. A role that does not outlive the ticket becomes a blocking finding (rolesOutliveTicketFindings).
4483
+ roles: {
4484
+ type: 'array',
4485
+ description: 'One entry for EVERY minted role reviewed.',
4486
+ items: {
4487
+ type: 'object',
4488
+ required: ['name', 'outlivesTicket', 'why'],
4489
+ properties: {
4490
+ name: { type: 'string', description: 'The minted role, named exactly as in the roster.' },
4491
+ outlivesTicket: { type: 'boolean', description: 'true only when this role — its name and its brief — would serve a story on this codeline about something OTHER than the current ticket: named for the kind of engineer/investigator and the codeline, briefed on the codeline, not on this ticket\'s subject, components or files (except as marked examples).' },
4492
+ why: { type: 'string', description: 'Quote the words that gear the role to the ticket, or say what makes it the codeline\'s.' },
4493
+ },
4494
+ },
4495
+ },
4480
4496
  findings: {
4481
4497
  type: 'array',
4482
4498
  items: {
@@ -4885,6 +4901,19 @@ function _readJsonOrNull(f) {
4885
4901
  * Extracted and exported because it could not be tested otherwise: it sat inside the async function
4886
4902
  * that also does the batching and makes the model calls, so exercising it meant paying for a run.
4887
4903
  */
4904
+ /** A ROLE THAT DOES NOT OUTLIVE THE TICKET IS A BLOCKING FINDING (operator, 2026-10-24): the review answers for every minted role
4905
+ * (TOOL_ROSTER_REVIEW.roles); one it marks outlivesTicket:false — named or briefed for the ticket in scope rather than the codeline —
4906
+ * is turned into the finding the mint's correction loop acts on. The reviewer's own words are the claim. */
4907
+ function rolesOutliveTicketFindings(part) {
4908
+ const roles = part && Array.isArray(part.roles) ? part.roles : [];
4909
+ return roles.filter((r) => r && r.name && r.outlivesTicket === false).map((r) => ({
4910
+ agent: String(r.name), severity: 'blocking', claim: String(r.why || '(the reviewer gave no words)'),
4911
+ checked: 'whether this role, as named and briefed, would serve a story on its codeline about something other than the current ticket',
4912
+ found: 'minted for the ticket in scope, not for the codeline it is kept with',
4913
+ remedy: 'a role named for its kind of work and its codeline, briefed on the codeline — its stack, conventions and how to verify there — naming the current ticket only as a marked example',
4914
+ }));
4915
+ }
4916
+
4888
4917
  function aggregateRosterReview(results, legalVerdicts) {
4889
4918
  const legal = Array.isArray(legalVerdicts) ? legalVerdicts : [];
4890
4919
  const rows = Array.isArray(results) ? results : [];
@@ -4892,7 +4921,9 @@ function aggregateRosterReview(results, legalVerdicts) {
4892
4921
  const unexamined = rows.filter((r) => !r || !r.part || typeof r.part !== 'object'
4893
4922
  || r.part.verdict === 'nothing_to_review' || !r.part.verdict || illegal(r.part.verdict));
4894
4923
  const examined = rows.filter((r) => !unexamined.includes(r));
4895
- const findings = rows.flatMap((r) => (r && r.part && Array.isArray(r.part.findings) ? r.part.findings : []));
4924
+ // a role an examined batch says does not outlive the ticket is a finding of that batch, whatever verdict it gave
4925
+ const roleFindings = examined.flatMap((r) => rolesOutliveTicketFindings(r.part));
4926
+ const findings = rows.flatMap((r) => (r && r.part && Array.isArray(r.part.findings) ? r.part.findings : [])).concat(roleFindings);
4896
4927
 
4897
4928
  if (!examined.length) {
4898
4929
  return {
@@ -4903,7 +4934,7 @@ function aggregateRosterReview(results, legalVerdicts) {
4903
4934
  };
4904
4935
  }
4905
4936
 
4906
- const verdict = examined.some((r) => r.part.verdict === 'defects_found') ? 'defects_found' : 'sound';
4937
+ const verdict = (examined.some((r) => r.part.verdict === 'defects_found') || roleFindings.length) ? 'defects_found' : 'sound';
4907
4938
  return {
4908
4939
  verdict,
4909
4940
  findings,
@@ -11107,7 +11138,7 @@ module.exports = {
11107
11138
  storyAcsBlock,
11108
11139
  specAgentContract,
11109
11140
  rosterReviewVerdict,
11110
- aggregateRosterReview,
11141
+ aggregateRosterReview, rolesOutliveTicketFindings,
11111
11142
  reviewOutcomeKeepsChange,
11112
11143
  // Exported so the brief lookup can be asserted without a run.
11113
11144
  profilesWithProjectBriefs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amsd-pipeline-shared",
3
- "version": "0.5.38",
3
+ "version": "0.5.40",
4
4
  "description": "The AMSD pipeline's shared components, one source for the bash pipeline and its Claude workflows: the model-call gateway (llm-handler.sh — retry, ladder, self-heal, cost) and the libraries it runs.",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {