mandrel 2.55.0 → 2.57.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/agents/plan-critic.md +13 -18
- package/.agents/agents/story-worker.md +25 -34
- package/.agents/docs/agentrc-reference.json +4 -30
- package/.agents/docs/configuration.md +11 -28
- package/.agents/docs/execution-reference.md +5 -5
- package/.agents/docs/quality-gates.md +8 -7
- package/.agents/instructions.md +9 -10
- package/.agents/rules/ci-remediation.md +39 -21
- package/.agents/schemas/agentrc.schema.json +28 -185
- package/.agents/schemas/story-deliver-terminal.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +107 -17
- package/.agents/scripts/audit-to-stories.js +222 -75
- package/.agents/scripts/ceremony-derive.js +191 -0
- package/.agents/scripts/check-context-budget.js +28 -33
- package/.agents/scripts/check-cyclomatic.js +4 -3
- package/.agents/scripts/deliver-light.js +31 -94
- package/.agents/scripts/file-ci-gap.js +306 -0
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +15 -2
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +25 -1
- package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +40 -52
- package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +5 -1
- package/.agents/scripts/lib/audit-to-stories/issue-corpus.js +162 -0
- package/.agents/scripts/lib/audit-to-stories/issues-file.js +121 -0
- package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/ledger-record.js +126 -0
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +11 -0
- package/.agents/scripts/lib/baselines/coverage-updater-cli.js +110 -0
- package/.agents/scripts/lib/baselines/crap-preview-scan.js +25 -0
- package/.agents/scripts/lib/baselines/crap-updater-cli.js +223 -0
- package/.agents/scripts/lib/bdd-scenario-budget.js +21 -3
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -1
- package/.agents/scripts/lib/close-validation/gates.js +52 -1
- package/.agents/scripts/lib/config/acceptance-eval.js +25 -57
- package/.agents/scripts/lib/config/delivery-routing.js +7 -33
- package/.agents/scripts/lib/config/explain.js +0 -19
- package/.agents/scripts/lib/config/limits.js +18 -78
- package/.agents/scripts/lib/config/quality.js +6 -3
- package/.agents/scripts/lib/config/runners.js +3 -2
- package/.agents/scripts/lib/config-settings-schema-delivery.js +15 -68
- package/.agents/scripts/lib/config-settings-schema-quality.js +0 -14
- package/.agents/scripts/lib/config-settings-schema.js +49 -143
- package/.agents/scripts/lib/crap-engine.js +35 -4
- package/.agents/scripts/lib/crap-utils.js +17 -1
- package/.agents/scripts/lib/cyclomatic-ceiling.js +19 -7
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +53 -13
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +71 -25
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +18 -25
- package/.agents/scripts/lib/{audit-to-stories/ledger.js → findings/audit-ledger.js} +131 -24
- package/.agents/scripts/lib/findings/route-finding.js +38 -0
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/github/framework-repo.js +148 -2
- package/.agents/scripts/lib/label-constants.js +6 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
- package/.agents/scripts/lib/observability/source-classifier.js +2 -0
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +5 -4
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +19 -73
- package/.agents/scripts/lib/orchestration/ci-gap-intake.js +605 -0
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +13 -8
- package/.agents/scripts/lib/orchestration/complexity-gate.js +46 -212
- package/.agents/scripts/lib/orchestration/file-assumptions.js +32 -17
- package/.agents/scripts/lib/orchestration/light-escalation.js +3 -3
- package/.agents/scripts/lib/orchestration/light-suitability.js +66 -233
- package/.agents/scripts/lib/orchestration/plan-context.js +181 -387
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +42 -153
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +14 -70
- package/.agents/scripts/lib/orchestration/plan-persist/audit-provenance.js +197 -0
- package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +300 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +131 -168
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +133 -299
- package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +55 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +16 -65
- package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +22 -35
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +30 -139
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
- package/.agents/scripts/lib/orchestration/run-epilogue.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
- package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +213 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +32 -20
- package/.agents/scripts/lib/orchestration/task-body-validator.js +10 -63
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +33 -539
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +21 -414
- package/.agents/scripts/lib/orchestration/ticket-validator.js +54 -118
- package/.agents/scripts/lib/orchestration/verify-credit.js +69 -24
- package/.agents/scripts/lib/story-body/body-format-lints.js +15 -85
- package/.agents/scripts/lib/story-body/story-body.js +17 -237
- package/.agents/scripts/lib/templates/decomposer-prompts.js +84 -121
- package/.agents/scripts/lib/test-isolate/cli-options.js +93 -0
- package/.agents/scripts/lib/test-isolate/progress-log.js +45 -0
- package/.agents/scripts/lib/test-isolate/render-report.js +97 -0
- package/.agents/scripts/lib/test-isolate/run-isolate.js +87 -0
- package/.agents/scripts/lib/test-run-credit.js +266 -0
- package/.agents/scripts/lib/wave-runner/footprint.js +48 -358
- package/.agents/scripts/lib/wave-runner/ready-set.js +6 -5
- package/.agents/scripts/lib/workers/crap-worker.js +32 -41
- package/.agents/scripts/plan-context.js +7 -9
- package/.agents/scripts/plan-critics.js +28 -54
- package/.agents/scripts/plan-persist.js +25 -68
- package/.agents/scripts/pr-watch-with-update.js +3 -2
- package/.agents/scripts/quality-preview.js +51 -0
- package/.agents/scripts/run-tests.js +12 -0
- package/.agents/scripts/stories-wave-tick.js +23 -45
- package/.agents/scripts/test-isolate.js +13 -180
- package/.agents/scripts/update-coverage-baseline.js +25 -70
- package/.agents/scripts/update-crap-baseline.js +19 -123
- package/.agents/skills/core/scope-triage/SKILL.md +3 -3
- package/.agents/workflows/audit-clean-code.md +4 -3
- package/.agents/workflows/audit-to-stories.md +63 -27
- package/.agents/workflows/helpers/acceptance-self-eval.md +41 -41
- package/.agents/workflows/helpers/code-quality-guardrails.md +4 -4
- package/.agents/workflows/helpers/code-review.md +2 -3
- package/.agents/workflows/helpers/deliver-digest.md +41 -57
- package/.agents/workflows/helpers/deliver-light.md +40 -105
- package/.agents/workflows/helpers/deliver-reference.md +1 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +56 -62
- package/.agents/workflows/helpers/deliver-story.md +9 -13
- package/.agents/workflows/helpers/plan-reference.md +132 -196
- package/.agents/workflows/mandrel-plan.md +28 -41
- package/.agents/workflows/memory-consolidate.md +9 -13
- package/docs/CHANGELOG.md +33 -0
- package/lib/migrations/index.js +4 -0
- package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +45 -0
- package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +59 -0
- package/package.json +1 -1
- package/.agents/scripts/lib/framework-version.js +0 -39
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +0 -223
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +0 -97
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +0 -26
- package/.agents/scripts/lib/orchestration/spec-budget.js +0 -89
- package/.agents/scripts/lib/orchestration/spec-spill.js +0 -74
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +0 -107
|
@@ -47,15 +47,17 @@
|
|
|
47
47
|
* Ratchet semantics (mirroring the sibling ratchets):
|
|
48
48
|
* - A gated tier grows beyond `baseline.tiers.<tier>.totalBytes +
|
|
49
49
|
* baseline.toleranceBytes` → exit 1, naming the tier and its delta.
|
|
50
|
-
* - A gated tier shrinks below its baseline total → exit
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
50
|
+
* - A gated tier shrinks below its baseline total → **exit 0**, reported as
|
|
51
|
+
* an informational `-` line (Story #5313). This deliberately reverses
|
|
52
|
+
* Story #4872's "shrink fails" rule: that rule made every trim a red gate
|
|
53
|
+
* whose only remedy was a hand-run `--update`, so the gain was paid for
|
|
54
|
+
* twice. The concern it answered — a stale total silently absorbing the
|
|
55
|
+
* next growth — is now met by the close's write-back seam
|
|
56
|
+
* (`story-close/context-budget-writeback.js`), which rewrites the lower
|
|
57
|
+
* total into `baselines/context-budget.json` on the Story branch so the
|
|
58
|
+
* gain locks in without a failing gate. Shrinkage stays zero-tolerance
|
|
59
|
+
* in the *report* (every byte under the total is listed) so a sub-
|
|
60
|
+
* tolerance gain is never discarded by the write-back either.
|
|
59
61
|
* - A recorded row naming a path the measured tier no longer contains →
|
|
60
62
|
* exit 1. The row describes a file that has been deleted or de-listed, so
|
|
61
63
|
* the bytes it contributes to the recorded total are fiction.
|
|
@@ -341,9 +343,9 @@ function absentRows(tier, files, baseTier) {
|
|
|
341
343
|
/**
|
|
342
344
|
* Pure diff: compare the current tier map against the committed baseline. A
|
|
343
345
|
* gated tier with no current files is skipped; a tier absent from the baseline
|
|
344
|
-
* is skipped. `grown
|
|
345
|
-
*
|
|
346
|
-
*
|
|
346
|
+
* is skipped. `grown` and `absent` entries fail the gate; `shrunk` entries are
|
|
347
|
+
* reported and written back by the close (Story #5313) — see the ratchet
|
|
348
|
+
* semantics in the module header.
|
|
347
349
|
*
|
|
348
350
|
* @param {{ tiers: Record<string, Array<{ path: string, bytes: number }>> }} tierMap
|
|
349
351
|
* @param {{ toleranceBytes?: number, tiers?: Record<string, { totalBytes: number }> }} baseline
|
|
@@ -384,9 +386,9 @@ export function diffBudget(tierMap, baseline) {
|
|
|
384
386
|
delta: current - baselineBytes,
|
|
385
387
|
});
|
|
386
388
|
} else if (current < baselineBytes) {
|
|
387
|
-
// Deliberately zero-tolerance: `tolerance` guards against
|
|
388
|
-
// trivial *addition*; mirroring it downward would
|
|
389
|
-
//
|
|
389
|
+
// Deliberately zero-tolerance in the report: `tolerance` guards against
|
|
390
|
+
// churn from a trivial *addition*; mirroring it downward would hide a
|
|
391
|
+
// gain from the write-back that locks it in (Story #5313).
|
|
390
392
|
shrunk.push({
|
|
391
393
|
tier,
|
|
392
394
|
current,
|
|
@@ -400,26 +402,24 @@ export function diffBudget(tierMap, baseline) {
|
|
|
400
402
|
}
|
|
401
403
|
|
|
402
404
|
/**
|
|
403
|
-
* Count the drift entries that fail the gate
|
|
404
|
-
*
|
|
405
|
-
* the
|
|
405
|
+
* Count the drift entries that fail the gate: growth past tolerance and a
|
|
406
|
+
* recorded row the tree no longer backs. Shrinkage is not in the set (Story
|
|
407
|
+
* #5313 — the close writes it back instead). This is the one place the
|
|
408
|
+
* failure set is defined and both the summary tag and the exit code read it.
|
|
406
409
|
*
|
|
407
410
|
* @param {ReturnType<typeof diffBudget>} diff
|
|
408
411
|
* @returns {number}
|
|
409
412
|
*/
|
|
410
413
|
export function budgetFailureCount(diff) {
|
|
411
|
-
return (
|
|
412
|
-
(diff?.grown?.length ?? 0) +
|
|
413
|
-
(diff?.shrunk?.length ?? 0) +
|
|
414
|
-
(diff?.absent?.length ?? 0)
|
|
415
|
-
);
|
|
414
|
+
return (diff?.grown?.length ?? 0) + (diff?.absent?.length ?? 0);
|
|
416
415
|
}
|
|
417
416
|
|
|
418
417
|
/**
|
|
419
418
|
* Render the human-readable diff. `+` lines are tiers that grew beyond
|
|
420
|
-
* tolerance; `-` lines are tiers that shrank below their recorded total
|
|
421
|
-
*
|
|
422
|
-
* one-line summary
|
|
419
|
+
* tolerance; `-` lines are tiers that shrank below their recorded total
|
|
420
|
+
* (informational — the close writes the lower total back) or rows naming a
|
|
421
|
+
* path the tree no longer carries (a gate failure). A one-line summary
|
|
422
|
+
* always follows.
|
|
423
423
|
*
|
|
424
424
|
* @param {ReturnType<typeof diffBudget>} diff
|
|
425
425
|
* @returns {string}
|
|
@@ -433,7 +433,7 @@ export function renderDiff(diff) {
|
|
|
433
433
|
}
|
|
434
434
|
for (const s of diff.shrunk) {
|
|
435
435
|
lines.push(
|
|
436
|
-
`- ${s.tier}: ${s.current} bytes is under the recorded ${s.baseline} (delta -${s.delta}) — the
|
|
436
|
+
`- ${s.tier}: ${s.current} bytes is under the recorded ${s.baseline} (delta -${s.delta}) — the close writes the lower total back to baselines/context-budget.json when this branch lands`,
|
|
437
437
|
);
|
|
438
438
|
}
|
|
439
439
|
for (const a of diff.absent ?? []) {
|
|
@@ -479,7 +479,7 @@ export function renderReachable(tierMap, baseline) {
|
|
|
479
479
|
* stderr?: { write: (s: string) => void },
|
|
480
480
|
* }} [opts]
|
|
481
481
|
* @returns {Promise<number>} 0 = clean / within tolerance / shrink-only / no-op;
|
|
482
|
-
* 1 = a gated tier grew beyond tolerance
|
|
482
|
+
* 1 = a gated tier grew beyond tolerance or a recorded row is unbacked
|
|
483
483
|
*/
|
|
484
484
|
/**
|
|
485
485
|
* Write a fresh budget, preserving the recorded tolerance so `--update` never
|
|
@@ -621,11 +621,6 @@ function renderFailureDiagnostics({ report, stderr }) {
|
|
|
621
621
|
`[context-budget] ❌ a documentation tier grew beyond tolerance — refresh the budget consciously with \`node .agents/scripts/check-context-budget.js --update\` once the growth is intentional\n`,
|
|
622
622
|
);
|
|
623
623
|
}
|
|
624
|
-
if (diff.shrunk.length > 0) {
|
|
625
|
-
stderr.write(
|
|
626
|
-
`[context-budget] ❌ a documentation tier came in under its recorded total — the ratchet is holding slack the tree no longer spends, so the next growth would be absorbed silently. Lock the gain in with \`node .agents/scripts/check-context-budget.js --update\`\n`,
|
|
627
|
-
);
|
|
628
|
-
}
|
|
629
624
|
if (diff.absent.length > 0) {
|
|
630
625
|
stderr.write(
|
|
631
626
|
`[context-budget] ❌ a recorded row names a path the measured tier no longer contains — its bytes inflate the recorded total against nothing. Refresh with \`node .agents/scripts/check-context-budget.js --update\`\n`,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI: ratchet on cyclomatic complexity against
|
|
3
|
-
*
|
|
3
|
+
* the fixed cyclomatic ceiling of 12 (Story #4923; the `cyclomaticMustFix`
|
|
4
|
+
* config key was retired in Story #5313).
|
|
4
5
|
*
|
|
5
6
|
* The must-fix ceiling was documented as blocking (`code-quality-guardrails.md`
|
|
6
7
|
* promises "the close-validation chain refuses the merge") while being read by
|
|
@@ -158,7 +159,7 @@ function warnAboutBaseline({ baseline, mustFix, baselinePath, stderr }) {
|
|
|
158
159
|
}
|
|
159
160
|
if (typeof baseline.ceiling === 'number' && baseline.ceiling !== mustFix) {
|
|
160
161
|
stderr.write(
|
|
161
|
-
`[cyclomatic] ⚠ baseline was recorded at ceiling c=${baseline.ceiling} but the
|
|
162
|
+
`[cyclomatic] ⚠ baseline was recorded at ceiling c=${baseline.ceiling} but the enforced ceiling is c=${mustFix} — re-run with --update\n`,
|
|
162
163
|
);
|
|
163
164
|
}
|
|
164
165
|
}
|
|
@@ -265,7 +266,7 @@ runAsCli(import.meta.url, main, {
|
|
|
265
266
|
invocation:
|
|
266
267
|
'node .agents/scripts/check-cyclomatic.js [--baseline <path>] [--update] [--json]',
|
|
267
268
|
summary:
|
|
268
|
-
'Ratchet on cyclomatic complexity: fail when a file gains a function above
|
|
269
|
+
'Ratchet on cyclomatic complexity: fail when a file gains a function above the fixed ceiling of 12, or when its worst function gets worse than the recorded baseline.',
|
|
269
270
|
flags: [
|
|
270
271
|
[
|
|
271
272
|
'--baseline <path>',
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
*
|
|
23
23
|
* - **gate** (default) — judge a prompt's predicted footprint. 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,
|
|
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.
|
|
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.
|
|
@@ -52,8 +52,7 @@
|
|
|
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
|
|
55
|
-
* not proceed light (
|
|
56
|
-
* backstop blocked.
|
|
55
|
+
* not proceed light (an `escalated` terminal), 3 the diff backstop blocked.
|
|
57
56
|
*/
|
|
58
57
|
|
|
59
58
|
import { parseArgs } from 'node:util';
|
|
@@ -84,15 +83,17 @@ Usage:
|
|
|
84
83
|
deliver-light.js --prompt <text> [--creates csv] [--refactors csv]
|
|
85
84
|
[--acceptance n] [--kinds csv] [--magnitude m]
|
|
86
85
|
[--uncertainty u] [--route lite|full] [--reason <text>]
|
|
87
|
-
[--amends '#id'] [--
|
|
86
|
+
[--amends '#id'] [--yes]
|
|
88
87
|
deliver-light.js --backstop --story <id>
|
|
89
88
|
|
|
90
89
|
The thin /deliver-light entry point: suitability gate → inline receipt Story →
|
|
91
90
|
the same single-story-init.js / single-story-close.js engine /mandrel-deliver uses.
|
|
92
91
|
|
|
93
92
|
The gate judges EFFORT and RISK, not artifact counts: N instances of one
|
|
94
|
-
mechanical edit is one kind at N sites.
|
|
95
|
-
|
|
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.
|
|
96
97
|
|
|
97
98
|
Gate options:
|
|
98
99
|
--prompt <text> Operator prompt describing the change. Required for the gate.
|
|
@@ -108,17 +109,9 @@ Gate options:
|
|
|
108
109
|
--route <r> Ledgered model verdict route: lite | full.
|
|
109
110
|
--reason <text> Recorded reason for a lite verdict (required for lite).
|
|
110
111
|
--amends <#id> Mark this as an amendment of an existing issue.
|
|
111
|
-
--
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
refused with --yes. Waives a coarse SIZE prediction
|
|
115
|
-
(change kinds, magnitude, uncertainty, deployable span)
|
|
116
|
-
only — sensitivity, migration span, and an unknown
|
|
117
|
-
footprint stay non-negotiable, the ledgered --route lite
|
|
118
|
-
verdict is still required, and the --backstop pass still
|
|
119
|
-
bounds the actual diff. Recorded in the receipt Story.
|
|
120
|
-
--yes Unattended: over-scope emits an escalated terminal
|
|
121
|
-
envelope and ENDS the session (no prompt, no fallback).
|
|
112
|
+
--yes Unattended marker. Escalation is terminal either way;
|
|
113
|
+
the flag is accepted so unattended callers keep their
|
|
114
|
+
invocation shape.
|
|
122
115
|
|
|
123
116
|
Backstop options:
|
|
124
117
|
--backstop Re-check the ACTUAL diff after implementation. Bounds the
|
|
@@ -132,9 +125,6 @@ Backstop options:
|
|
|
132
125
|
--help Show this help.
|
|
133
126
|
`;
|
|
134
127
|
|
|
135
|
-
/** Exit code when the gate did not resolve to proceed-light. */
|
|
136
|
-
const EXIT_NOT_PROCEED = 2;
|
|
137
|
-
|
|
138
128
|
/**
|
|
139
129
|
* Split a comma-separated path list into trimmed, non-empty entries.
|
|
140
130
|
*
|
|
@@ -196,14 +186,10 @@ export function synthesizeAcceptance(count) {
|
|
|
196
186
|
* uncertainty?: string,
|
|
197
187
|
* route?: string,
|
|
198
188
|
* reason?: string,
|
|
199
|
-
* operatorProceedLight?: string,
|
|
200
|
-
* yes?: boolean,
|
|
201
189
|
* injectedRules?: object,
|
|
202
190
|
* }} args `kinds` / `magnitude` / `uncertainty` are the declared effort-and-risk
|
|
203
191
|
* axes the gate judges (Story #4764); omitting them declares no signal, not a
|
|
204
|
-
* small one — an unrecognized bucket
|
|
205
|
-
* carries the operator's recorded answer to an `ask-operator` outcome
|
|
206
|
-
* (Story #4815) and is adjudicated inside the gate, never applied here.
|
|
192
|
+
* small one — an unrecognized bucket is reported as a warning (Story #5313).
|
|
207
193
|
* @returns {{ action: string, suitability: object, outcome: object }}
|
|
208
194
|
*/
|
|
209
195
|
export function runLightGate({
|
|
@@ -215,8 +201,6 @@ export function runLightGate({
|
|
|
215
201
|
uncertainty,
|
|
216
202
|
route,
|
|
217
203
|
reason,
|
|
218
|
-
operatorProceedLight,
|
|
219
|
-
yes = false,
|
|
220
204
|
injectedRules,
|
|
221
205
|
} = {}) {
|
|
222
206
|
const predictedChanges = buildPredictedChanges({ creates, refactors });
|
|
@@ -229,11 +213,7 @@ export function runLightGate({
|
|
|
229
213
|
verdict: { route, reason },
|
|
230
214
|
injectedRules,
|
|
231
215
|
});
|
|
232
|
-
const outcome = resolveLightGateOutcome({
|
|
233
|
-
suitability,
|
|
234
|
-
yes,
|
|
235
|
-
operatorOverride: operatorProceedLight,
|
|
236
|
-
});
|
|
216
|
+
const outcome = resolveLightGateOutcome({ suitability });
|
|
237
217
|
return { action: outcome.action, suitability, outcome };
|
|
238
218
|
}
|
|
239
219
|
|
|
@@ -246,11 +226,9 @@ export function runLightGate({
|
|
|
246
226
|
* prompt: string,
|
|
247
227
|
* changedFiles?: string[],
|
|
248
228
|
* amends?: string|number|null,
|
|
249
|
-
* override?: object|null,
|
|
250
229
|
* assembleFn?: typeof assemblePlanStories,
|
|
251
230
|
* createFn?: typeof createStoryIssues,
|
|
252
|
-
* }} args
|
|
253
|
-
* recorded in the receipt body so the decision is auditable from the ticket.
|
|
231
|
+
* }} args
|
|
254
232
|
* @returns {Promise<{ storyId: number, url: string|undefined, title: string }>}
|
|
255
233
|
*/
|
|
256
234
|
export async function createLightReceipt({
|
|
@@ -258,7 +236,6 @@ export async function createLightReceipt({
|
|
|
258
236
|
prompt,
|
|
259
237
|
changedFiles = [],
|
|
260
238
|
amends = null,
|
|
261
|
-
override = null,
|
|
262
239
|
assembleFn = assemblePlanStories,
|
|
263
240
|
createFn = createStoryIssues,
|
|
264
241
|
} = {}) {
|
|
@@ -266,7 +243,6 @@ export async function createLightReceipt({
|
|
|
266
243
|
prompt,
|
|
267
244
|
changedFiles,
|
|
268
245
|
amends,
|
|
269
|
-
override,
|
|
270
246
|
});
|
|
271
247
|
const { stories } = assembleFn([ticket]);
|
|
272
248
|
const { created } = await createFn({ provider, stories });
|
|
@@ -294,19 +270,6 @@ export function buildNextCommands(storyId) {
|
|
|
294
270
|
};
|
|
295
271
|
}
|
|
296
272
|
|
|
297
|
-
/**
|
|
298
|
-
* Was a non-blank `--operator-proceed-light` supplied? The gate core decides
|
|
299
|
-
* whether it *applies*; this only asks whether the operator typed one, so the
|
|
300
|
-
* attended-only refusal can fire before any adjudication.
|
|
301
|
-
*
|
|
302
|
-
* @param {{ 'operator-proceed-light'?: unknown }} values Parsed CLI values.
|
|
303
|
-
* @returns {boolean}
|
|
304
|
-
*/
|
|
305
|
-
export function hasOperatorOverride(values = {}) {
|
|
306
|
-
const raw = values['operator-proceed-light'];
|
|
307
|
-
return typeof raw === 'string' && raw.trim() !== '';
|
|
308
|
-
}
|
|
309
|
-
|
|
310
273
|
/**
|
|
311
274
|
* Emit a JSON envelope on stdout (the machine surface) so a headless caller can
|
|
312
275
|
* branch on it. Human-readable log lines stay on stderr.
|
|
@@ -355,12 +318,12 @@ async function runBackstopMode(values, deps = {}) {
|
|
|
355
318
|
* envelope** and stops (Story #4746). It is placed **first**, above every
|
|
356
319
|
* creation call site, so "nothing was started" is a property of the
|
|
357
320
|
* control flow rather than a claim the envelope makes about itself.
|
|
358
|
-
* - **`ask-operator`** is unchanged: the plain gate envelope and exit 2. It
|
|
359
|
-
* is not terminal — the operator has a choice to make, and manufacturing a
|
|
360
|
-
* terminal for it would end a session that is supposed to be waiting. The
|
|
361
|
-
* operator's proceed answer comes back as `--operator-proceed-light`.
|
|
362
321
|
* - **`proceed-light`** authors the receipt Story and prints the hand-off,
|
|
363
|
-
* carrying
|
|
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.
|
|
364
327
|
*
|
|
365
328
|
* The injectable seams exist so the no-side-effect guarantee is testable
|
|
366
329
|
* without a network: a test asserts the escalate path never reaches them.
|
|
@@ -390,17 +353,6 @@ export async function runGateMode(values, deps = {}) {
|
|
|
390
353
|
throw new Error('[deliver-light] --prompt <text> is required for the gate');
|
|
391
354
|
}
|
|
392
355
|
|
|
393
|
-
// Attended-only, enforced loudly (Story #4815). Silently ignoring the flag
|
|
394
|
-
// under --yes would let an automated caller pass it as a hopeful no-op and
|
|
395
|
-
// read the resulting escalation as a bug; a usage error says which of the
|
|
396
|
-
// two the caller has to give up.
|
|
397
|
-
if (values.yes === true && hasOperatorOverride(values)) {
|
|
398
|
-
process.stderr.write(HELP);
|
|
399
|
-
throw new Error(
|
|
400
|
-
'[deliver-light] --operator-proceed-light is attended-only and cannot be combined with --yes: an unattended run has no operator whose answer this is, and over-scope must fail closed to /mandrel-plan',
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
356
|
const gate = runLightGate({
|
|
405
357
|
creates: parseCsvPaths(values.creates),
|
|
406
358
|
refactors: parseCsvPaths(values.refactors),
|
|
@@ -412,35 +364,24 @@ export async function runGateMode(values, deps = {}) {
|
|
|
412
364
|
uncertainty: values.uncertainty,
|
|
413
365
|
route: values.route,
|
|
414
366
|
reason: values.reason,
|
|
415
|
-
operatorProceedLight: values['operator-proceed-light'],
|
|
416
|
-
yes: values.yes === true,
|
|
417
367
|
});
|
|
418
368
|
|
|
419
|
-
if (gate.action
|
|
369
|
+
if (gate.action !== 'proceed-light') {
|
|
420
370
|
const envelope = buildEscalationTerminal({
|
|
421
371
|
prompt: String(values.prompt),
|
|
422
372
|
reasons: gate.outcome.reasons,
|
|
423
373
|
});
|
|
424
374
|
emitTerminalFn(envelope);
|
|
375
|
+
// The refusal is still telemetered (Story #4856): the ceilings stay
|
|
376
|
+
// recalibratable from evidence even now that only risk rules refuse.
|
|
377
|
+
await recordRefusalFn({ gate, amends: values.amends });
|
|
425
378
|
Logger.warn(
|
|
426
379
|
`[deliver-light] ESCALATED to /mandrel-plan — this session ENDS here; run ${envelope.nextCommand} in a FRESH session: ${gate.outcome.reasons.join('; ')}`,
|
|
427
380
|
);
|
|
428
381
|
return exitCodeForTerminal(envelope);
|
|
429
382
|
}
|
|
430
383
|
|
|
431
|
-
|
|
432
|
-
emitFn(
|
|
433
|
-
{ mode: 'gate', action: gate.action, outcome: gate.outcome },
|
|
434
|
-
values.pretty,
|
|
435
|
-
);
|
|
436
|
-
await recordRefusalFn({ gate, amends: values.amends });
|
|
437
|
-
Logger.warn(
|
|
438
|
-
`[deliver-light] gate did not proceed light (${gate.action}): ${gate.outcome.reasons.join('; ')}`,
|
|
439
|
-
);
|
|
440
|
-
return EXIT_NOT_PROCEED;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const override = gate.outcome.override ?? null;
|
|
384
|
+
const warnings = gate.outcome.warnings ?? [];
|
|
444
385
|
const provider = createProviderFn(resolveConfigFn());
|
|
445
386
|
const receipt = await createReceiptFn({
|
|
446
387
|
provider,
|
|
@@ -450,7 +391,6 @@ export async function runGateMode(values, deps = {}) {
|
|
|
450
391
|
...parseCsvPaths(values.refactors),
|
|
451
392
|
],
|
|
452
393
|
amends: values.amends ?? null,
|
|
453
|
-
override,
|
|
454
394
|
});
|
|
455
395
|
emitFn(
|
|
456
396
|
{
|
|
@@ -458,16 +398,14 @@ export async function runGateMode(values, deps = {}) {
|
|
|
458
398
|
action: 'proceed-light',
|
|
459
399
|
storyId: receipt.storyId,
|
|
460
400
|
url: receipt.url,
|
|
461
|
-
|
|
401
|
+
warnings,
|
|
462
402
|
nextCommands: buildNextCommands(receipt.storyId),
|
|
463
403
|
outcome: gate.outcome,
|
|
464
404
|
},
|
|
465
405
|
values.pretty,
|
|
466
406
|
);
|
|
467
|
-
|
|
468
|
-
Logger.warn(
|
|
469
|
-
`[deliver-light] operator scope override recorded on Story #${receipt.storyId}: waived "${override.overriddenCode}" — ${override.recordedReason}`,
|
|
470
|
-
);
|
|
407
|
+
for (const warning of warnings) {
|
|
408
|
+
Logger.warn(`[deliver-light] ⚠ ${warning}`);
|
|
471
409
|
}
|
|
472
410
|
Logger.info(
|
|
473
411
|
`[deliver-light] receipt Story #${receipt.storyId} created — hand off to single-story-init.js.`,
|
|
@@ -488,7 +426,6 @@ async function main() {
|
|
|
488
426
|
route: { type: 'string' },
|
|
489
427
|
reason: { type: 'string' },
|
|
490
428
|
amends: { type: 'string' },
|
|
491
|
-
'operator-proceed-light': { type: 'string' },
|
|
492
429
|
yes: { type: 'boolean', default: false },
|
|
493
430
|
backstop: { type: 'boolean', default: false },
|
|
494
431
|
story: { type: 'string' },
|