mandrel 2.58.0 → 2.60.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.
Files changed (124) hide show
  1. package/.agents/README.md +17 -12
  2. package/.agents/agents/acceptance-critic.md +24 -43
  3. package/.agents/agents/story-worker.md +18 -19
  4. package/.agents/docs/SDLC.md +12 -13
  5. package/.agents/docs/agentrc-reference.json +1 -2
  6. package/.agents/docs/configuration.md +29 -46
  7. package/.agents/docs/quality-gates.md +9 -5
  8. package/.agents/docs/workflows.md +1 -1
  9. package/.agents/instructions.md +5 -7
  10. package/.agents/rules/ci-remediation.md +41 -8
  11. package/.agents/rules/known-tooling-behavior.md +65 -15
  12. package/.agents/runtime-deps.json +7 -2
  13. package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
  14. package/.agents/schemas/agentrc.schema.json +6 -11
  15. package/.agents/schemas/crap-baseline.schema.json +1 -1
  16. package/.agents/schemas/crap-report.schema.json +1 -1
  17. package/.agents/schemas/story-deliver-terminal.schema.json +3 -3
  18. package/.agents/scripts/README.md +11 -1
  19. package/.agents/scripts/acceptance-eval.js +25 -27
  20. package/.agents/scripts/ceremony-derive.js +15 -10
  21. package/.agents/scripts/check-context-budget.js +148 -228
  22. package/.agents/scripts/check-schema-references.js +5 -3
  23. package/.agents/scripts/check-workflow-citations.js +33 -147
  24. package/.agents/scripts/coverage-capture.js +7 -4
  25. package/.agents/scripts/deliver-light.js +41 -100
  26. package/.agents/scripts/deliver-run.js +631 -0
  27. package/.agents/scripts/file-ci-gap.js +59 -11
  28. package/.agents/scripts/install-matrix-assert.js +48 -3
  29. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +51 -33
  30. package/.agents/scripts/lib/baselines/crap-preview-incremental.js +6 -2
  31. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +0 -8
  32. package/.agents/scripts/lib/baselines/kinds/crap.js +35 -18
  33. package/.agents/scripts/lib/changed-files.js +30 -0
  34. package/.agents/scripts/lib/config/delivery-routing.js +5 -4
  35. package/.agents/scripts/lib/config/explain.js +1 -3
  36. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +1 -1
  37. package/.agents/scripts/lib/config-resolver.js +1 -0
  38. package/.agents/scripts/lib/config-settings-schema-delivery.js +28 -21
  39. package/.agents/scripts/lib/coverage-capture-fullscope.js +10 -2
  40. package/.agents/scripts/lib/coverage-capture-incremental.js +3 -2
  41. package/.agents/scripts/lib/coverage-capture-usage.js +4 -1
  42. package/.agents/scripts/lib/crap-engine.js +2 -2
  43. package/.agents/scripts/lib/crap-utils.js +21 -5
  44. package/.agents/scripts/lib/doc-tiers.js +4 -2
  45. package/.agents/scripts/lib/escomplex-ast-compat.js +39 -17
  46. package/.agents/scripts/lib/escomplex-kernel.js +298 -0
  47. package/.agents/scripts/lib/feedback-loop/graduator-core.js +7 -6
  48. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +7 -5
  49. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  50. package/.agents/scripts/lib/gh-exec.js +160 -0
  51. package/.agents/scripts/lib/maintainability-engine.js +3 -3
  52. package/.agents/scripts/lib/observability/source-classifier.js +1 -0
  53. package/.agents/scripts/lib/orchestration/ceremony-routing.js +74 -132
  54. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +123 -12
  55. package/.agents/scripts/lib/orchestration/complexity-gate.js +180 -352
  56. package/.agents/scripts/lib/orchestration/light-suitability.js +71 -136
  57. package/.agents/scripts/lib/orchestration/plan-context.js +44 -50
  58. package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +8 -6
  59. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +104 -119
  60. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +41 -25
  61. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +11 -11
  62. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +63 -29
  63. package/.agents/scripts/lib/orchestration/plan-persist/wave-collision-gate.js +107 -0
  64. package/.agents/scripts/lib/orchestration/review-depth.js +14 -11
  65. package/.agents/scripts/lib/orchestration/run-epilogue.js +260 -182
  66. package/.agents/scripts/lib/orchestration/run-scoped-config.js +63 -99
  67. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +3 -3
  68. package/.agents/scripts/lib/orchestration/single-story-close/phases/graphql-preflight.js +137 -0
  69. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +105 -18
  70. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +4 -3
  71. package/.agents/scripts/lib/orchestration/story-follow-ups.js +156 -39
  72. package/.agents/scripts/lib/orchestration/story-init-envelope.js +71 -0
  73. package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -17
  74. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +25 -209
  75. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +8 -5
  76. package/.agents/scripts/lib/orchestration/ticket-validator.js +44 -183
  77. package/.agents/scripts/lib/orchestration/ticketing/reads.js +14 -25
  78. package/.agents/scripts/lib/runtime-deps/dep-resolution.js +155 -0
  79. package/.agents/scripts/lib/runtime-deps/ensure-installed.js +44 -9
  80. package/.agents/scripts/lib/runtime-deps/parser-major.js +110 -0
  81. package/.agents/scripts/lib/runtime-deps/preflight.js +6 -25
  82. package/.agents/scripts/lib/runtime-deps/scan-imports.js +46 -1
  83. package/.agents/scripts/lib/skills/walk-skill-files.js +1 -1
  84. package/.agents/scripts/lib/story-body/body-format-lints.js +58 -12
  85. package/.agents/scripts/lib/story-body/story-body.js +83 -29
  86. package/.agents/scripts/lib/templates/decomposer-prompts.js +28 -33
  87. package/.agents/scripts/lib/wave-runner/live-probe.js +31 -5
  88. package/.agents/scripts/merge-baseline.js +4 -5
  89. package/.agents/scripts/plan-context.js +117 -28
  90. package/.agents/scripts/plan-persist.js +79 -39
  91. package/.agents/scripts/plan-run-epilogue.js +11 -8
  92. package/.agents/scripts/pr-watch-with-update.js +9 -2
  93. package/.agents/scripts/run-verify.js +13 -6
  94. package/.agents/scripts/single-story-init.js +7 -57
  95. package/.agents/scripts/stories-wave-tick.js +160 -26
  96. package/.agents/skills/core/gates-and-baselines/reference.md +0 -1
  97. package/.agents/skills/skills.index.json +2 -12
  98. package/.agents/skills/stack/qa/playwright/SKILL.md +26 -0
  99. package/.agents/workflows/audit-to-stories.md +14 -11
  100. package/.agents/workflows/helpers/acceptance-self-eval.md +84 -157
  101. package/.agents/workflows/helpers/code-review.md +4 -2
  102. package/.agents/workflows/helpers/deliver-digest.md +31 -24
  103. package/.agents/workflows/helpers/deliver-light.md +92 -101
  104. package/.agents/workflows/helpers/deliver-reference.md +116 -100
  105. package/.agents/workflows/helpers/deliver-story-reference.md +58 -124
  106. package/.agents/workflows/helpers/deliver-story.md +17 -18
  107. package/.agents/workflows/helpers/plan-reference.md +82 -60
  108. package/.agents/workflows/mandrel-deliver.md +47 -31
  109. package/.agents/workflows/mandrel-plan.md +32 -30
  110. package/.agents/workflows/mandrel-update.md +36 -21
  111. package/README.md +3 -3
  112. package/docs/CHANGELOG.md +43 -0
  113. package/lib/cli/registry.js +45 -25
  114. package/lib/cli/update.js +376 -17
  115. package/lib/migrations/index.js +2 -0
  116. package/lib/migrations/steps/2.60.0-retire-audit-results-autofile.js +40 -0
  117. package/package.json +8 -2
  118. package/.agents/schemas/model-attribution.schema.json +0 -53
  119. package/.agents/scripts/lib/orchestration/model-attribution.js +0 -418
  120. package/.agents/scripts/lib/orchestration/split-policy-validator.js +0 -188
  121. package/.agents/scripts/lib/orchestration/story-plan-state.js +0 -33
  122. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +0 -67
  123. package/.agents/scripts/lib/templates/spec-author-prompts.js +0 -76
  124. package/.agents/skills/core/scope-triage/SKILL.md +0 -48
@@ -1,5 +1,5 @@
1
1
  /**
2
- * CLI: ratchet-down gate on provenance citations in workflow prose.
2
+ * CLI: provenance-citation **report** for workflow prose.
3
3
  *
4
4
  * Workflow documents ride resident in an executing agent's context. A
5
5
  * `(Story #1234)` aside costs the same tokens as instruction and teaches
@@ -7,28 +7,24 @@
7
7
  * citations that survive are the ones a reader must follow to act; the
8
8
  * rest belong in the commit trail and `docs/decisions.md`.
9
9
  *
10
- * Nothing stops the tax re-accumulating one well-meaning aside at a time,
11
- * so this gate holds the line: count every issue-shaped reference across
12
- * `.agents/workflows/**\/*.md` and fail when the total rises above the
13
- * committed baseline at `baselines/workflow-citations.json`.
10
+ * This command counts every issue-shaped reference across
11
+ * `.agents/workflows/**\/*.md` and prints the per-file tally. It **always
12
+ * exits 0**: Story #5340 demoted it from a ratchet to a report and deleted
13
+ * `baselines/workflow-citations.json` with it. The ratchet failed a rise
14
+ * above a committed total, so a prose fix that added one pointer had to be
15
+ * paid for with an unrelated trim in the same commit — and that is how three
16
+ * reference sections came to describe mechanisms the code had already
17
+ * retired. The count is still worth seeing on every change, so it stays a
18
+ * report. See `docs/decisions.md`, ADR 20260917-5340.
14
19
  *
15
20
  * The counted token is the bare `#NNNN` form rather than the
16
21
  * `(Story|Epic|issue|refs) #NNNN` phrasing, because the same citation is
17
- * written both ways — `(Story #4593)` and a bare `(#4593)` — and a gate
18
- * that only saw the prefixed form would wave the other one through.
19
- *
20
- * Ratchet semantics mirror `check-arch-cycles.js`:
21
- * - Total above the baseline → exit 1, with the files that grew.
22
- * - Total below the baseline → printed as shrinkage, exit 0. Refresh the
23
- * baseline with `--update` to lock the reduction in.
24
- * - Equal → exit 0.
22
+ * written both ways — `(Story #4593)` and a bare `(#4593)` — and a report
23
+ * that only saw the prefixed form would undercount the other one.
25
24
  *
26
25
  * Flags:
27
- * --baseline <path> override the baseline path (default
28
- * `baselines/workflow-citations.json`, resolved from cwd)
29
26
  * --root <path> scan a different workflow root (default
30
27
  * `.agents/workflows`)
31
- * --update rewrite the baseline from the live count
32
28
  * --json write the structured envelope to stdout
33
29
  */
34
30
 
@@ -40,9 +36,6 @@ import { runAsCli } from './lib/cli-utils.js';
40
36
  /** Default workflow root, relative to the repository root. */
41
37
  const DEFAULT_ROOT = path.join('.agents', 'workflows');
42
38
 
43
- /** Default baseline path, relative to the repository root. */
44
- const DEFAULT_BASELINE = path.join('baselines', 'workflow-citations.json');
45
-
46
39
  /**
47
40
  * Issue-shaped reference: a `#` followed by 3–5 digits. Narrow enough to
48
41
  * skip markdown headings and anchor links, wide enough to catch both the
@@ -54,27 +47,16 @@ const CITATION_RE = /#\d{3,5}\b/g;
54
47
  * Parse argv. Exported so unit tests can pin the parser.
55
48
  *
56
49
  * @param {string[]} argv
57
- * @returns {{ baselinePath: string | null, rootPath: string | null, update: boolean, json: boolean }}
50
+ * @returns {{ rootPath: string | null, json: boolean }}
58
51
  */
59
52
  export function parseArgv(argv = []) {
60
- const out = {
61
- baselinePath: null,
62
- rootPath: null,
63
- update: false,
64
- json: false,
65
- };
53
+ const out = { rootPath: null, json: false };
66
54
  for (let i = 0; i < argv.length; i += 1) {
67
55
  const flag = argv[i];
68
56
  const next = argv[i + 1];
69
- const takesValue = next && !next.startsWith('--');
70
- if (flag === '--baseline' && takesValue) {
71
- out.baselinePath = next;
72
- i += 1;
73
- } else if (flag === '--root' && takesValue) {
57
+ if (flag === '--root' && next && !next.startsWith('--')) {
74
58
  out.rootPath = next;
75
59
  i += 1;
76
- } else if (flag === '--update') {
77
- out.update = true;
78
60
  } else if (flag === '--json') {
79
61
  out.json = true;
80
62
  }
@@ -121,7 +103,7 @@ export function countCitations(source) {
121
103
  /**
122
104
  * Count citations across a file set, relativizing paths against `cwd` so
123
105
  * the report serializes identically on every platform. Files with zero
124
- * citations are omitted — the baseline records where the tax lives.
106
+ * citations are omitted — the report names where the tax lives.
125
107
  *
126
108
  * @param {string[]} files absolute paths
127
109
  * @param {string} cwd
@@ -150,72 +132,17 @@ export function tallyCitations(files, cwd, { readFile } = {}) {
150
132
  }
151
133
 
152
134
  /**
153
- * Pure helper: read the baseline envelope. Returns `null` when the file is
154
- * missing or unparseable — the baseline is not distributed to consumers, so
155
- * its absence degrades to "no ceiling" rather than a false failure.
156
- *
157
- * @param {string} baselinePath
158
- * @returns {{ total?: number, files?: Array<{ path: string, count: number }> } | null}
159
- */
160
- export function loadBaseline(baselinePath) {
161
- try {
162
- const parsed = JSON.parse(fs.readFileSync(baselinePath, 'utf-8'));
163
- return parsed && typeof parsed === 'object' ? parsed : null;
164
- } catch {
165
- return null;
166
- }
167
- }
168
-
169
- /**
170
- * Pure helper: diff the live tally against the baseline. `grew` names the
171
- * files whose per-file count rose, so a failure points at the edit rather
172
- * than only at the total.
135
+ * Pure helper: render the human-readable report — one line per file that
136
+ * carries a citation, then the total. No `+` / `-` ratchet vocabulary: there
137
+ * is nothing to regress against.
173
138
  *
174
- * @param {{ total?: number, files?: Array<{ path: string, count: number }> } | null} baseline
175
139
  * @param {{ total: number, files: Array<{ path: string, count: number }> }} tally
176
- * @returns {{ baselineTotal: number | null, delta: number | null, grew: Array<{ path: string, from: number, to: number }> }}
177
- */
178
- export function diffTally(baseline, tally) {
179
- const baselineTotal =
180
- typeof baseline?.total === 'number' ? baseline.total : null;
181
- const prior = new Map(
182
- (baseline?.files ?? []).map((row) => [row.path, row.count]),
183
- );
184
- const grew = tally.files
185
- .map((row) => ({
186
- path: row.path,
187
- from: prior.get(row.path) ?? 0,
188
- to: row.count,
189
- }))
190
- .filter((row) => row.to > row.from);
191
- return {
192
- baselineTotal,
193
- delta: baselineTotal === null ? null : tally.total - baselineTotal,
194
- grew,
195
- };
196
- }
197
-
198
- /**
199
- * Pure helper: render the human-readable diff, mirroring the `+` / `-`
200
- * vocabulary of the sibling ratchets.
201
- *
202
- * @param {{ baselineTotal: number | null, delta: number | null, grew: Array<{ path: string, from: number, to: number }> }} diff
203
- * @param {{ total: number }} tally
204
140
  * @returns {string}
205
141
  */
206
- export function renderDiff(diff, tally) {
207
- const lines = [];
208
- for (const row of diff.grew) {
209
- lines.push(`+ ${row.path}: ${row.from} -> ${row.to}`);
210
- }
211
- if (diff.delta !== null && diff.delta < 0) {
212
- lines.push(
213
- `[workflow-citations] ⚠ ${-diff.delta} citation(s) below baseline — refresh with --update to lock the reduction in`,
214
- );
215
- }
216
- const tag = diff.delta !== null && diff.delta > 0 ? '(gate fail)' : '(ok)';
142
+ export function renderReport(tally) {
143
+ const lines = tally.files.map((row) => ` ${row.path}: ${row.count}`);
217
144
  lines.push(
218
- `[workflow-citations] total=${tally.total} baseline=${diff.baselineTotal ?? 'none'} ${tag}`,
145
+ `[workflow-citations] total=${tally.total} across ${tally.files.length} file(s) — report only, never gated`,
219
146
  );
220
147
  return lines.join('\n');
221
148
  }
@@ -230,77 +157,41 @@ export function renderDiff(diff, tally) {
230
157
  * stdout?: { write: (s: string) => void },
231
158
  * stderr?: { write: (s: string) => void },
232
159
  * }} [opts]
233
- * @returns {Promise<number>} 0 = at or below baseline; 1 = regression
160
+ * @returns {Promise<number>} always 0 — this is a report, not a gate
234
161
  */
235
162
  export async function runCli({
236
163
  argv = process.argv.slice(2),
237
164
  cwd = process.cwd(),
238
165
  stdout = process.stdout,
239
- stderr = process.stderr,
240
166
  } = {}) {
241
- const { baselinePath, rootPath, update, json } = parseArgv(argv);
167
+ const { rootPath, json } = parseArgv(argv);
242
168
  const root = path.resolve(cwd, rootPath ?? DEFAULT_ROOT);
243
- const resolvedBaselinePath = path.resolve(
244
- cwd,
245
- baselinePath ?? DEFAULT_BASELINE,
246
- );
247
169
  if (!fs.existsSync(root)) {
248
170
  throw new Error(`[workflow-citations] workflow root not found: ${root}`);
249
171
  }
250
172
 
251
173
  const tally = tallyCitations(collectMarkdownFiles(root), cwd);
252
174
 
253
- if (update) {
254
- const envelope = {
255
- $schema: 'https://mandrel.dev/baselines/workflow-citations.schema.json',
256
- generatedAt: new Date().toISOString(),
257
- total: tally.total,
258
- files: tally.files,
259
- };
260
- fs.mkdirSync(path.dirname(resolvedBaselinePath), { recursive: true });
261
- fs.writeFileSync(
262
- resolvedBaselinePath,
263
- `${JSON.stringify(envelope, null, 2)}\n`,
264
- );
265
- stdout.write(
266
- `[workflow-citations] baseline written: ${resolvedBaselinePath} (total=${tally.total})\n`,
267
- );
268
- return 0;
269
- }
270
-
271
- const baseline = loadBaseline(resolvedBaselinePath);
272
- const diff = diffTally(baseline, tally);
273
- const exitCode = diff.delta !== null && diff.delta > 0 ? 1 : 0;
274
-
275
175
  if (json) {
276
176
  stdout.write(
277
177
  `${JSON.stringify(
278
178
  {
279
179
  kind: 'workflow-citations-report',
280
180
  root,
281
- baselinePath: resolvedBaselinePath,
282
181
  total: tally.total,
283
- baselineTotal: diff.baselineTotal,
284
- delta: diff.delta,
285
182
  files: tally.files,
286
- grew: diff.grew,
287
- exitCode,
183
+ exitCode: 0,
288
184
  },
289
185
  null,
290
186
  2,
291
187
  )}\n`,
292
188
  );
293
- return exitCode;
189
+ return 0;
294
190
  }
295
191
 
296
- if (!baseline) {
297
- stderr.write(
298
- `[workflow-citations] ⚠ baseline not found at ${resolvedBaselinePath} — no ceiling enforced\n`,
299
- );
300
- }
301
- stdout.write(`\n--- workflow-citations preview ---\n`);
302
- stdout.write(`${renderDiff(diff, tally)}\n`);
303
- return exitCode;
192
+ stdout.write(`\n--- workflow-citations report ---\n`);
193
+ stdout.write(`${renderReport(tally)}\n`);
194
+ return 0;
304
195
  }
305
196
 
306
197
  async function main() {
@@ -313,20 +204,15 @@ runAsCli(import.meta.url, main, {
313
204
  errorPrefix: '[workflow-citations] ❌ Fatal error',
314
205
  usage: {
315
206
  invocation:
316
- 'node .agents/scripts/check-workflow-citations.js [--baseline <path>] [--root <dir>] [--update] [--json]',
207
+ 'node .agents/scripts/check-workflow-citations.js [--root <dir>] [--json]',
317
208
  summary:
318
- 'Ratchet on provenance citations in workflow prose: count issue-shaped references across .agents/workflows/** and fail when the total rises above the recorded baseline.',
209
+ 'Report provenance citations in workflow prose: count issue-shaped references across .agents/workflows/** and print the per-file tally. Never fails.',
319
210
  flags: [
320
- [
321
- '--baseline <path>',
322
- 'Baseline file (default: baselines/workflow-citations.json).',
323
- ],
324
211
  ['--root <dir>', 'Workflow root to scan (default: .agents/workflows).'],
325
- ['--update', 'Rewrite the baseline from the live count.'],
326
- ['--json', 'Emit the comparison envelope as JSON.'],
212
+ ['--json', 'Emit the report envelope as JSON.'],
327
213
  ],
328
214
  notes: [
329
- 'Exit codes:\n 0 at or below the baseline\n 1 the citation total regressed above the baseline',
215
+ 'Exit codes:\n 0 always — this is a report, not a gate (Story #5340)',
330
216
  ],
331
217
  },
332
218
  });
@@ -31,8 +31,8 @@
31
31
  *
32
32
  * Step 3 is preceded by the changed-file skip when
33
33
  * `delivery.quality.gates.crap.incrementalCoverage.skipWhenUnchanged` is on
34
- * (the default): no changed file under `crap.targetDirs` versus `baseRef`
35
- * means no capture at all.
34
+ * (the default): no changed file under `crap.targetDirs` versus the ref
35
+ * `resolveChangedFilesRef` resolves means no capture at all (Story #5365).
36
36
  *
37
37
  * Exit codes:
38
38
  * 0 — coverage is fresh (or capture skipped/succeeded).
@@ -64,15 +64,18 @@ import { hasNpmScript, readPackageScripts } from './lib/npm-scripts.js';
64
64
  /**
65
65
  * Parse the full `process.argv` (index 2 onward) into the capture options.
66
66
  *
67
+ * A `ref` of `null` means the caller named none; `resolveChangedFilesRef` owns
68
+ * the fallback (Story #5365).
69
+ *
67
70
  * @param {string[]} argv
68
- * @returns {{ skipWhenNoCrapFiles: boolean, requireCredited: boolean, ref: string, cwd: string }}
71
+ * @returns {{ skipWhenNoCrapFiles: boolean, requireCredited: boolean, ref: string | null, cwd: string }}
69
72
  */
70
73
  export function parseArgs(argv) {
71
74
  const out = {
72
75
  skipWhenNoCrapFiles: false,
73
76
  // Story #5278 — an ARGUMENT, never a config read. See `runCoverageCapture`.
74
77
  requireCredited: false,
75
- ref: 'main',
78
+ ref: null,
76
79
  cwd: process.cwd(),
77
80
  };
78
81
  for (let i = 2; i < argv.length; i += 1) {
@@ -20,35 +20,35 @@
20
20
  *
21
21
  * Two modes:
22
22
  *
23
- * - **gate** (default) — judge a prompt's predicted footprint. On
23
+ * - **gate** (default) — judge a prompt's predicted footprint for RISK. On
24
24
  * `proceed-light` it authors the receipt Story (via the plan-persist
25
- * `createStoryIssues` surface) and prints the init/close hand-off,
26
- * carrying any predicted-shape `warnings[]` (Story #5313: an over-ceiling
27
- * prediction warns, it no longer stops). An un-ledgered verdict or an
28
- * un-waivable risk rule emits an `escalated` terminal envelope, never
29
- * landing silently. The former attended stop-and-ask outcome and the
30
- * flag that answered it went with the gate they answered.
25
+ * `createStoryIssues` surface) and prints the init/close hand-off. An
26
+ * un-ledgered verdict or an un-waivable risk rule emits an `escalated`
27
+ * terminal envelope, never landing silently. Story #5313 demoted the
28
+ * predicted-shape ceilings to warnings and Story #5344 deleted them: a
29
+ * size bucket the caller declares about its own request is not evidence,
30
+ * and the backstop below measures the real thing.
31
31
  * - **backstop** (`--backstop --story <id>`) — re-check the ACTUAL diff of
32
32
  * the Story branch after implementation; exit non-zero when it exceeds the
33
33
  * light ceilings, so an over-scope diff is blocked rather than landed.
34
34
  *
35
- * ## Escalation is terminal, not advisory (Story #4746)
35
+ * ## Escalation is terminal for THIS path, not for the session (Story #5344)
36
36
  *
37
- * Over-scope under `--yes` emits a schema-validated `story-deliver-terminal`
38
- * envelope with status `escalated` and **ends the session**. Before that it was
39
- * an ordinary gate envelope plus exit 2 — a warning a caller could walk past,
40
- * and one mandrel-bench 2.13.0 light-arm run did exactly that: it read the
41
- * escalation, invoked `/mandrel-plan` in the same session, and delivered. In-session
42
- * planning under-decomposed (ONE Story against the scenario's 3-5 contract,
43
- * where a fresh `/mandrel-plan` session on the identical seed authored four), so
44
- * escalation silently produced the outcome the guard exists to prevent.
37
+ * A refused gate emits a schema-validated `story-deliver-terminal` envelope
38
+ * with status `escalated`: nothing was created, and there is no smaller version
39
+ * of the light path to attempt. Story #4746 additionally required the SESSION
40
+ * to end, on one mandrel-bench 2.13.0 observation where an in-session
41
+ * `/mandrel-plan` under-decomposed. Story #5344 relaxes that half — the
42
+ * escalation still ends the light path, and `/mandrel-plan` may now be seeded
43
+ * with `escalation.reasons` in the same session (see
44
+ * `helpers/deliver-light.md`).
45
45
  * {@link module:lib/orchestration/story-deliver-terminal.buildEscalationTerminal}
46
- * carries the guarantees the schema then enforces.
46
+ * carries the guarantees the schema enforces either way.
47
47
  *
48
48
  * Usage:
49
49
  * node .agents/scripts/deliver-light.js --prompt "<text>" \
50
- * --creates path,path --acceptance 1 --route lite --reason "<why>"
51
- * node .agents/scripts/deliver-light.js --prompt "<text>" --amends '#123' --route lite --reason "<why>"
50
+ * --creates path,path --reason "<why>"
51
+ * node .agents/scripts/deliver-light.js --prompt "<text>" --amends '#123' --reason "<why>"
52
52
  * node .agents/scripts/deliver-light.js --backstop --story 4741
53
53
  *
54
54
  * Exit codes: 0 ok (proceed / clean backstop), 1 usage error, 2 the gate did
@@ -81,37 +81,26 @@ import { createProvider } from './lib/provider-factory.js';
81
81
  const HELP = `\
82
82
  Usage:
83
83
  deliver-light.js --prompt <text> [--creates csv] [--refactors csv]
84
- [--acceptance n] [--kinds csv] [--magnitude m]
85
- [--uncertainty u] [--route lite|full] [--reason <text>]
86
- [--amends '#id'] [--yes]
84
+ --reason <text> [--amends '#id']
87
85
  deliver-light.js --backstop --story <id>
88
86
 
89
87
  The thin /deliver-light entry point: suitability gate → inline receipt Story →
90
88
  the same single-story-init.js / single-story-close.js engine /mandrel-deliver uses.
91
89
 
92
- The gate judges EFFORT and RISK, not artifact counts: N instances of one
93
- mechanical edit is one kind at N sites. A predicted shape past a light ceiling
94
- is a WARNING on the envelope, not a refusal (Story #5313); only an un-ledgered
95
- verdict or an un-waivable risk rule (sensitive path, migration span) escalates.
96
- The --backstop pass enforces size against the actual diff.
90
+ The gate judges RISK, not size. Only two things refuse: a verdict with no
91
+ recorded reason, and an un-waivable risk rule the predicted PATHS trip (a
92
+ sensitive-path class, a migration paired with its consumers). The predicted-
93
+ shape ceilings were self-declared buckets and are gone (Story #5344, after
94
+ Story #5313 had already demoted them to warnings). The --backstop pass measures
95
+ the ACTUAL diff and is the only size block.
97
96
 
98
97
  Gate options:
99
98
  --prompt <text> Operator prompt describing the change. Required for the gate.
100
99
  --creates <csv> Predicted NEW file paths (comma-separated).
101
100
  --refactors <csv> Predicted edited/existing file paths (comma-separated).
102
- --acceptance <n> Predicted acceptance-criteria count (default 1). Not capped.
103
- --kinds <csv> Distinct KINDS of change (default: one per assumption, so
104
- N same-shaped edits count once).
105
- --magnitude <m> Coarse effort bucket: trivial | moderate | substantial
106
- (default moderate; substantial routes to /mandrel-plan).
107
- --uncertainty <u> determined (the request fixes the shape) | needs-design
108
- (default determined; needs-design routes to /mandrel-plan).
109
- --route <r> Ledgered model verdict route: lite | full.
110
- --reason <text> Recorded reason for a lite verdict (required for lite).
101
+ --reason <text> Recorded reason for taking the light path. Required: an
102
+ un-ledgered verdict escalates.
111
103
  --amends <#id> Mark this as an amendment of an existing issue.
112
- --yes Unattended marker. Escalation is terminal either way;
113
- the flag is accepted so unattended callers keep their
114
- invocation shape.
115
104
 
116
105
  Backstop options:
117
106
  --backstop Re-check the ACTUAL diff after implementation. Bounds the
@@ -153,23 +142,6 @@ export function buildPredictedChanges({ creates = [], refactors = [] } = {}) {
153
142
  ];
154
143
  }
155
144
 
156
- /**
157
- * Synthesize a predicted-acceptance array of the requested length — the shape
158
- * gate reads the count, not the text, so placeholder strings suffice. A count
159
- * below 1 yields a single-item array (a Story with no contract cannot be judged
160
- * trivial, and the shape derivation rejects a zero-length acceptance anyway).
161
- *
162
- * @param {unknown} count
163
- * @returns {string[]}
164
- */
165
- export function synthesizeAcceptance(count) {
166
- const n =
167
- typeof count === 'number' && Number.isFinite(count) && count >= 1
168
- ? Math.floor(count)
169
- : 1;
170
- return Array.from({ length: n }, (_v, i) => `AC-${i + 1}`);
171
- }
172
-
173
145
  /**
174
146
  * Run the suitability gate purely — no I/O. Returns the outcome envelope the
175
147
  * CLI serializes. The prompt text and `--amends` target are deliberately **not**
@@ -180,37 +152,25 @@ export function synthesizeAcceptance(count) {
180
152
  * @param {{
181
153
  * creates?: string[],
182
154
  * refactors?: string[],
183
- * acceptance?: number,
184
- * kinds?: string[],
185
- * magnitude?: string,
186
- * uncertainty?: string,
187
- * route?: string,
188
155
  * reason?: string,
189
156
  * injectedRules?: object,
190
- * }} args `kinds` / `magnitude` / `uncertainty` are the declared effort-and-risk
191
- * axes the gate judges (Story #4764); omitting them declares no signal, not a
192
- * small one — an unrecognized bucket is reported as a warning (Story #5313).
157
+ * }} args `reason` is the ledgered verdict (Story #5344: the `--route` half is
158
+ * gone, and so are the declared effort axes it sat beside — the recorded
159
+ * reason and the predicted paths are what the gate reads. Story #5366
160
+ * removed the last of them, `--acceptance`, whose value the gate clamped to
161
+ * a floor of one before reading it).
193
162
  * @returns {{ action: string, suitability: object, outcome: object }}
194
163
  */
195
164
  export function runLightGate({
196
165
  creates = [],
197
166
  refactors = [],
198
- acceptance,
199
- kinds,
200
- magnitude,
201
- uncertainty,
202
- route,
203
167
  reason,
204
168
  injectedRules,
205
169
  } = {}) {
206
170
  const predictedChanges = buildPredictedChanges({ creates, refactors });
207
171
  const suitability = deriveLightSuitability({
208
172
  predictedChanges,
209
- predictedAcceptance: synthesizeAcceptance(acceptance),
210
- predictedKinds: kinds,
211
- predictedMagnitude: magnitude,
212
- predictedUncertainty: uncertainty,
213
- verdict: { route, reason },
173
+ verdict: { reason },
214
174
  injectedRules,
215
175
  });
216
176
  const outcome = resolveLightGateOutcome({ suitability });
@@ -312,18 +272,17 @@ async function runBackstopMode(values, deps = {}) {
312
272
  /**
313
273
  * Gate mode — judge the prompt and, on proceed, author the receipt Story.
314
274
  *
315
- * The three outcomes are deliberately asymmetric in what they emit:
275
+ * The two outcomes are deliberately asymmetric in what they emit:
316
276
  *
317
277
  * - **`escalate-plan`** returns a schema-validated `escalated` **terminal
318
278
  * envelope** and stops (Story #4746). It is placed **first**, above every
319
279
  * creation call site, so "nothing was started" is a property of the
320
280
  * control flow rather than a claim the envelope makes about itself.
321
- * - **`proceed-light`** authors the receipt Story and prints the hand-off,
322
- * carrying the predicted-shape `warnings[]` (Story #5313) on the envelope
323
- * and on stderr, so an over-ceiling prediction is stated rather than
324
- * silently waved through. The former attended stop-and-ask outcome is
325
- * gone: the gate never had a question a human could answer that the diff
326
- * backstop does not answer better.
281
+ * - **`proceed-light`** authors the receipt Story and prints the hand-off.
282
+ * The former attended stop-and-ask outcome is gone, and so are the
283
+ * predicted-shape warnings Story #5313 left behind (Story #5344): the gate
284
+ * never had a question a human could answer that the diff backstop does
285
+ * not answer better.
327
286
  *
328
287
  * The injectable seams exist so the no-side-effect guarantee is testable
329
288
  * without a network: a test asserts the escalate path never reaches them.
@@ -356,13 +315,6 @@ export async function runGateMode(values, deps = {}) {
356
315
  const gate = runLightGate({
357
316
  creates: parseCsvPaths(values.creates),
358
317
  refactors: parseCsvPaths(values.refactors),
359
- acceptance: values.acceptance
360
- ? Number.parseInt(String(values.acceptance), 10)
361
- : 1,
362
- kinds: parseCsvPaths(values.kinds),
363
- magnitude: values.magnitude,
364
- uncertainty: values.uncertainty,
365
- route: values.route,
366
318
  reason: values.reason,
367
319
  });
368
320
 
@@ -376,12 +328,11 @@ export async function runGateMode(values, deps = {}) {
376
328
  // recalibratable from evidence even now that only risk rules refuse.
377
329
  await recordRefusalFn({ gate, amends: values.amends });
378
330
  Logger.warn(
379
- `[deliver-light] ESCALATED to /mandrel-plan — this session ENDS here; run ${envelope.nextCommand} in a FRESH session: ${gate.outcome.reasons.join('; ')}`,
331
+ `[deliver-light] ESCALATED to /mandrel-plan — the light path ENDS here; run ${envelope.nextCommand}, seeded with these reasons: ${gate.outcome.reasons.join('; ')}`,
380
332
  );
381
333
  return exitCodeForTerminal(envelope);
382
334
  }
383
335
 
384
- const warnings = gate.outcome.warnings ?? [];
385
336
  const provider = createProviderFn(resolveConfigFn());
386
337
  const receipt = await createReceiptFn({
387
338
  provider,
@@ -398,15 +349,11 @@ export async function runGateMode(values, deps = {}) {
398
349
  action: 'proceed-light',
399
350
  storyId: receipt.storyId,
400
351
  url: receipt.url,
401
- warnings,
402
352
  nextCommands: buildNextCommands(receipt.storyId),
403
353
  outcome: gate.outcome,
404
354
  },
405
355
  values.pretty,
406
356
  );
407
- for (const warning of warnings) {
408
- Logger.warn(`[deliver-light] ⚠ ${warning}`);
409
- }
410
357
  Logger.info(
411
358
  `[deliver-light] receipt Story #${receipt.storyId} created — hand off to single-story-init.js.`,
412
359
  );
@@ -419,14 +366,8 @@ async function main() {
419
366
  prompt: { type: 'string' },
420
367
  creates: { type: 'string' },
421
368
  refactors: { type: 'string' },
422
- acceptance: { type: 'string' },
423
- kinds: { type: 'string' },
424
- magnitude: { type: 'string' },
425
- uncertainty: { type: 'string' },
426
- route: { type: 'string' },
427
369
  reason: { type: 'string' },
428
370
  amends: { type: 'string' },
429
- yes: { type: 'boolean', default: false },
430
371
  backstop: { type: 'boolean', default: false },
431
372
  story: { type: 'string' },
432
373
  pretty: { type: 'boolean', default: false },