mandrel 2.59.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.
- package/.agents/README.md +11 -9
- package/.agents/agents/acceptance-critic.md +24 -43
- package/.agents/agents/story-worker.md +18 -19
- package/.agents/docs/SDLC.md +6 -6
- package/.agents/docs/agentrc-reference.json +1 -2
- package/.agents/docs/configuration.md +29 -46
- package/.agents/docs/quality-gates.md +8 -4
- package/.agents/docs/workflows.md +1 -1
- package/.agents/instructions.md +4 -5
- package/.agents/rules/ci-remediation.md +41 -8
- package/.agents/rules/known-tooling-behavior.md +65 -15
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +6 -11
- package/.agents/schemas/story-deliver-terminal.schema.json +3 -3
- package/.agents/scripts/README.md +11 -1
- package/.agents/scripts/acceptance-eval.js +25 -27
- package/.agents/scripts/ceremony-derive.js +15 -10
- package/.agents/scripts/check-context-budget.js +148 -228
- package/.agents/scripts/check-schema-references.js +5 -3
- package/.agents/scripts/check-workflow-citations.js +33 -147
- package/.agents/scripts/coverage-capture.js +7 -4
- package/.agents/scripts/deliver-light.js +41 -100
- package/.agents/scripts/deliver-run.js +631 -0
- package/.agents/scripts/file-ci-gap.js +59 -11
- package/.agents/scripts/lib/baselines/crap-preview-incremental.js +6 -2
- package/.agents/scripts/lib/changed-files.js +30 -0
- package/.agents/scripts/lib/config/delivery-routing.js +5 -4
- package/.agents/scripts/lib/config/explain.js +1 -3
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +1 -1
- package/.agents/scripts/lib/config-resolver.js +1 -0
- package/.agents/scripts/lib/config-settings-schema-delivery.js +28 -21
- package/.agents/scripts/lib/coverage-capture-fullscope.js +10 -2
- package/.agents/scripts/lib/coverage-capture-incremental.js +3 -2
- package/.agents/scripts/lib/coverage-capture-usage.js +4 -1
- package/.agents/scripts/lib/doc-tiers.js +4 -2
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +7 -6
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +7 -5
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/gh-exec.js +160 -0
- package/.agents/scripts/lib/observability/source-classifier.js +1 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +74 -132
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +123 -12
- package/.agents/scripts/lib/orchestration/complexity-gate.js +180 -352
- package/.agents/scripts/lib/orchestration/light-suitability.js +71 -136
- package/.agents/scripts/lib/orchestration/plan-context.js +13 -25
- package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +8 -6
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +76 -95
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +35 -18
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +11 -11
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +63 -29
- package/.agents/scripts/lib/orchestration/review-depth.js +14 -11
- package/.agents/scripts/lib/orchestration/run-epilogue.js +260 -182
- package/.agents/scripts/lib/orchestration/run-scoped-config.js +63 -99
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +3 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/graphql-preflight.js +137 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +105 -18
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +4 -3
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +156 -39
- package/.agents/scripts/lib/orchestration/story-init-envelope.js +71 -0
- package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -17
- package/.agents/scripts/lib/orchestration/ticket-validator.js +44 -183
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +14 -25
- package/.agents/scripts/lib/story-body/body-format-lints.js +58 -12
- package/.agents/scripts/lib/story-body/story-body.js +83 -29
- package/.agents/scripts/lib/templates/decomposer-prompts.js +7 -15
- package/.agents/scripts/lib/wave-runner/live-probe.js +31 -5
- package/.agents/scripts/merge-baseline.js +4 -5
- package/.agents/scripts/plan-context.js +117 -28
- package/.agents/scripts/plan-persist.js +79 -28
- package/.agents/scripts/plan-run-epilogue.js +11 -8
- package/.agents/scripts/pr-watch-with-update.js +9 -2
- package/.agents/scripts/run-verify.js +13 -6
- package/.agents/scripts/single-story-init.js +7 -57
- package/.agents/scripts/stories-wave-tick.js +160 -26
- package/.agents/skills/core/gates-and-baselines/reference.md +0 -1
- package/.agents/skills/skills.index.json +2 -2
- package/.agents/skills/stack/qa/playwright/SKILL.md +26 -0
- package/.agents/workflows/helpers/acceptance-self-eval.md +84 -157
- package/.agents/workflows/helpers/code-review.md +4 -2
- package/.agents/workflows/helpers/deliver-digest.md +31 -24
- package/.agents/workflows/helpers/deliver-light.md +92 -101
- package/.agents/workflows/helpers/deliver-reference.md +116 -100
- package/.agents/workflows/helpers/deliver-story-reference.md +58 -124
- package/.agents/workflows/helpers/deliver-story.md +17 -18
- package/.agents/workflows/helpers/plan-reference.md +65 -54
- package/.agents/workflows/mandrel-deliver.md +47 -31
- package/.agents/workflows/mandrel-plan.md +22 -21
- package/.agents/workflows/mandrel-update.md +36 -21
- package/docs/CHANGELOG.md +35 -0
- package/lib/cli/update.js +376 -17
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.60.0-retire-audit-results-autofile.js +40 -0
- package/package.json +2 -1
- package/.agents/schemas/model-attribution.schema.json +0 -53
- package/.agents/scripts/lib/orchestration/model-attribution.js +0 -418
- package/.agents/scripts/lib/orchestration/story-plan-state.js +0 -33
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +0 -67
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI:
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
|
18
|
-
* that only saw the prefixed form would
|
|
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 {{
|
|
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
|
-
|
|
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
|
|
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:
|
|
154
|
-
*
|
|
155
|
-
*
|
|
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
|
|
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}
|
|
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
|
|
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 {
|
|
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
|
-
|
|
287
|
-
exitCode,
|
|
183
|
+
exitCode: 0,
|
|
288
184
|
},
|
|
289
185
|
null,
|
|
290
186
|
2,
|
|
291
187
|
)}\n`,
|
|
292
188
|
);
|
|
293
|
-
return
|
|
189
|
+
return 0;
|
|
294
190
|
}
|
|
295
191
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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 [--
|
|
207
|
+
'node .agents/scripts/check-workflow-citations.js [--root <dir>] [--json]',
|
|
317
208
|
summary:
|
|
318
|
-
'
|
|
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
|
-
['--
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
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
|
|
35
|
+
* ## Escalation is terminal for THIS path, not for the session (Story #5344)
|
|
36
36
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
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
|
|
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 --
|
|
51
|
-
* node .agents/scripts/deliver-light.js --prompt "<text>" --amends '#123' --
|
|
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
|
-
|
|
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
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
--
|
|
103
|
-
|
|
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 `
|
|
191
|
-
*
|
|
192
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
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 —
|
|
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 },
|