mandrel 2.56.0 → 2.58.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 (114) hide show
  1. package/.agents/agents/plan-critic.md +13 -18
  2. package/.agents/agents/story-worker.md +25 -33
  3. package/.agents/docs/agentrc-reference.json +0 -30
  4. package/.agents/docs/configuration.md +8 -28
  5. package/.agents/docs/execution-reference.md +5 -5
  6. package/.agents/docs/quality-gates.md +8 -7
  7. package/.agents/instructions.md +9 -10
  8. package/.agents/schemas/agentrc.schema.json +9 -185
  9. package/.agents/schemas/story-deliver-terminal.schema.json +1 -1
  10. package/.agents/scripts/acceptance-eval.js +107 -17
  11. package/.agents/scripts/ceremony-derive.js +191 -0
  12. package/.agents/scripts/check-context-budget.js +28 -33
  13. package/.agents/scripts/check-cyclomatic.js +4 -3
  14. package/.agents/scripts/deliver-light.js +31 -94
  15. package/.agents/scripts/evidence-gate.js +17 -1
  16. package/.agents/scripts/lib/audit-suite/checklist-threading.js +15 -2
  17. package/.agents/scripts/lib/baselines/coverage-updater-cli.js +110 -0
  18. package/.agents/scripts/lib/baselines/crap-preview-scan.js +25 -0
  19. package/.agents/scripts/lib/baselines/crap-updater-cli.js +223 -0
  20. package/.agents/scripts/lib/bdd-scenario-budget.js +21 -3
  21. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -1
  22. package/.agents/scripts/lib/close-validation/gates.js +52 -1
  23. package/.agents/scripts/lib/config/acceptance-eval.js +25 -57
  24. package/.agents/scripts/lib/config/delivery-routing.js +7 -33
  25. package/.agents/scripts/lib/config/explain.js +0 -19
  26. package/.agents/scripts/lib/config/limits.js +18 -78
  27. package/.agents/scripts/lib/config/quality.js +6 -3
  28. package/.agents/scripts/lib/config/runners.js +3 -2
  29. package/.agents/scripts/lib/config-settings-schema-delivery.js +15 -68
  30. package/.agents/scripts/lib/config-settings-schema-quality.js +0 -14
  31. package/.agents/scripts/lib/config-settings-schema.js +16 -143
  32. package/.agents/scripts/lib/crap-engine.js +35 -4
  33. package/.agents/scripts/lib/crap-utils.js +17 -1
  34. package/.agents/scripts/lib/cyclomatic-ceiling.js +19 -7
  35. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  36. package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
  37. package/.agents/scripts/lib/observability/source-classifier.js +1 -0
  38. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +5 -4
  39. package/.agents/scripts/lib/orchestration/ceremony-routing.js +19 -73
  40. package/.agents/scripts/lib/orchestration/code-review.js +7 -3
  41. package/.agents/scripts/lib/orchestration/complexity-gate.js +46 -212
  42. package/.agents/scripts/lib/orchestration/file-assumptions.js +32 -17
  43. package/.agents/scripts/lib/orchestration/light-escalation.js +3 -3
  44. package/.agents/scripts/lib/orchestration/light-suitability.js +66 -233
  45. package/.agents/scripts/lib/orchestration/pinned-identifier-lint.js +137 -0
  46. package/.agents/scripts/lib/orchestration/plan-context.js +189 -387
  47. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +42 -153
  48. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +14 -70
  49. package/.agents/scripts/lib/orchestration/plan-persist/acceptance-handle-repair.js +107 -0
  50. package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +305 -0
  51. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +138 -170
  52. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +128 -297
  53. package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +55 -0
  54. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +16 -65
  55. package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +22 -35
  56. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +36 -135
  57. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
  58. package/.agents/scripts/lib/orchestration/review-base-ref.js +138 -0
  59. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
  60. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +37 -5
  61. package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
  62. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +6 -1
  63. package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +213 -0
  64. package/.agents/scripts/lib/orchestration/task-body-validator.js +10 -63
  65. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +33 -539
  66. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +21 -414
  67. package/.agents/scripts/lib/orchestration/ticket-validator.js +54 -118
  68. package/.agents/scripts/lib/orchestration/verify-credit.js +69 -24
  69. package/.agents/scripts/lib/story-body/body-format-lints.js +15 -85
  70. package/.agents/scripts/lib/story-body/story-body.js +54 -240
  71. package/.agents/scripts/lib/templates/decomposer-prompts.js +133 -121
  72. package/.agents/scripts/lib/test-isolate/cli-options.js +93 -0
  73. package/.agents/scripts/lib/test-isolate/progress-log.js +45 -0
  74. package/.agents/scripts/lib/test-isolate/render-report.js +97 -0
  75. package/.agents/scripts/lib/test-isolate/run-isolate.js +87 -0
  76. package/.agents/scripts/lib/test-run-credit.js +277 -0
  77. package/.agents/scripts/lib/wave-runner/footprint.js +48 -358
  78. package/.agents/scripts/lib/wave-runner/ready-set.js +6 -5
  79. package/.agents/scripts/lib/workers/crap-worker.js +32 -41
  80. package/.agents/scripts/plan-context.js +7 -9
  81. package/.agents/scripts/plan-critics.js +28 -54
  82. package/.agents/scripts/plan-persist.js +25 -68
  83. package/.agents/scripts/quality-preview.js +51 -0
  84. package/.agents/scripts/run-tests.js +12 -0
  85. package/.agents/scripts/stories-wave-tick.js +23 -45
  86. package/.agents/scripts/test-isolate.js +13 -180
  87. package/.agents/scripts/update-coverage-baseline.js +25 -70
  88. package/.agents/scripts/update-crap-baseline.js +19 -123
  89. package/.agents/skills/core/scope-triage/SKILL.md +3 -3
  90. package/.agents/workflows/audit-clean-code.md +4 -3
  91. package/.agents/workflows/helpers/acceptance-self-eval.md +41 -41
  92. package/.agents/workflows/helpers/code-quality-guardrails.md +4 -4
  93. package/.agents/workflows/helpers/code-review.md +2 -3
  94. package/.agents/workflows/helpers/deliver-digest.md +46 -55
  95. package/.agents/workflows/helpers/deliver-light.md +40 -105
  96. package/.agents/workflows/helpers/deliver-reference.md +1 -1
  97. package/.agents/workflows/helpers/deliver-story-reference.md +54 -55
  98. package/.agents/workflows/helpers/deliver-story.md +10 -13
  99. package/.agents/workflows/helpers/plan-reference.md +163 -221
  100. package/.agents/workflows/mandrel-plan.md +31 -40
  101. package/.agents/workflows/memory-consolidate.md +9 -13
  102. package/docs/CHANGELOG.md +36 -0
  103. package/lib/cli/registry.js +98 -2
  104. package/lib/migrations/index.js +4 -0
  105. package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +45 -0
  106. package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +59 -0
  107. package/package.json +1 -1
  108. package/.agents/scripts/lib/framework-version.js +0 -39
  109. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +0 -223
  110. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +0 -97
  111. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +0 -26
  112. package/.agents/scripts/lib/orchestration/spec-budget.js +0 -89
  113. package/.agents/scripts/lib/orchestration/spec-spill.js +0 -74
  114. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +0 -107
@@ -18,6 +18,9 @@
18
18
  * maintainability rows this branch improved on files it touched, as a
19
19
  * `baseline-refresh:` commit, so the committed baseline stops falling
20
20
  * behind the tree in the upward direction.
21
+ * 3. **Context-budget write-back** (Story #5313) — persist the lower
22
+ * documentation-tier totals this branch measured, so a trimmed tier's
23
+ * gain locks in without `check-context-budget.js` ever going red on it.
21
24
  *
22
25
  * Extracted from `close-validation.js` when the second step landed. The phase
23
26
  * module's job is the gate chain, the evidence keyspace and the gate-log sink;
@@ -29,6 +32,7 @@
29
32
 
30
33
  import { Logger } from '../../../Logger.js';
31
34
  import { runBaselineUpwardWriteback as defaultRunBaselineUpwardWriteback } from '../../story-close/baseline-upward-writeback.js';
35
+ import { runContextBudgetWriteback as defaultRunContextBudgetWriteback } from '../../story-close/context-budget-writeback.js';
32
36
  import { runScopedFormatAutofix as defaultRunScopedFormatAutofix } from '../../story-close/format-autofix.js';
33
37
 
34
38
  /**
@@ -69,22 +73,22 @@ function formatAutofixStep({
69
73
  }
70
74
 
71
75
  /**
72
- * Run the upward maintainability write-back.
76
+ * Run one of the two baseline write-backs and report it on one progress line.
77
+ *
78
+ * Both steps (Story #5224's maintainability rows, Story #5313's context-budget
79
+ * totals) take the same context and answer in the same shape — `committed`
80
+ * with a `sha`, or a named `reason` — so one wrapper serves both; `describe`
81
+ * renders the committed outcome in the step's own words.
73
82
  *
74
83
  * @param {object} ctx the shared step context (see {@link runPreGateSteps})
84
+ * @param {{ tag: string, run: Function, describe: (w: object) => string, noun: string }} step
75
85
  * @returns {Promise<void>}
76
86
  */
77
- async function baselineWritebackStep({
78
- cwd,
79
- worktreePath,
80
- storyId,
81
- baseBranch,
82
- storyBranch,
83
- config,
84
- progress,
85
- runBaselineUpwardWriteback,
86
- }) {
87
- const writeback = await runBaselineUpwardWriteback({
87
+ async function writebackStep(
88
+ { cwd, worktreePath, storyId, baseBranch, storyBranch, config, progress },
89
+ { tag, run, describe, noun },
90
+ ) {
91
+ const writeback = await run({
88
92
  cwd,
89
93
  worktreePath,
90
94
  storyId,
@@ -94,10 +98,10 @@ async function baselineWritebackStep({
94
98
  logger: Logger,
95
99
  });
96
100
  progress(
97
- 'BASELINE',
101
+ tag,
98
102
  writeback?.committed
99
- ? `✅ Wrote back ${writeback.improvedPaths?.length ?? 0} improved maintainability row(s) as ${writeback.sha} on ${storyBranch}.`
100
- : `⏭ No baseline write-back (${writeback?.reason ?? 'nothing to write'}).`,
103
+ ? `✅ ${describe(writeback)} as ${writeback.sha} on ${storyBranch}.`
104
+ : `⏭ No ${noun} (${writeback?.reason ?? 'nothing to write'}).`,
101
105
  );
102
106
  }
103
107
 
@@ -142,18 +146,24 @@ async function bestEffort({ tag, label, progress, run }) {
142
146
  * progress: (tag: string, msg: string) => void,
143
147
  * runScopedFormatAutofix?: typeof defaultRunScopedFormatAutofix,
144
148
  * runBaselineUpwardWriteback?: typeof defaultRunBaselineUpwardWriteback,
149
+ * runContextBudgetWriteback?: typeof defaultRunContextBudgetWriteback,
145
150
  * }} args
146
151
  * @returns {Promise<void>}
147
152
  */
148
153
  export async function runPreGateSteps({
149
154
  runScopedFormatAutofix = defaultRunScopedFormatAutofix,
150
155
  runBaselineUpwardWriteback = defaultRunBaselineUpwardWriteback,
156
+ runContextBudgetWriteback = defaultRunContextBudgetWriteback,
151
157
  ...ctx
152
158
  }) {
153
159
  const { storyBranch, progress } = ctx;
154
160
  if (!storyBranch) {
155
161
  progress('FORMAT', '⏭ Skipped scoped format-autofix (no story branch).');
156
162
  progress('BASELINE', '⏭ Skipped baseline write-back (no story branch).');
163
+ progress(
164
+ 'BUDGET',
165
+ '⏭ Skipped context-budget write-back (no story branch).',
166
+ );
157
167
  return;
158
168
  }
159
169
  await bestEffort({
@@ -166,6 +176,26 @@ export async function runPreGateSteps({
166
176
  tag: 'BASELINE',
167
177
  label: 'baseline write-back',
168
178
  progress,
169
- run: () => baselineWritebackStep({ ...ctx, runBaselineUpwardWriteback }),
179
+ run: () =>
180
+ writebackStep(ctx, {
181
+ tag: 'BASELINE',
182
+ run: runBaselineUpwardWriteback,
183
+ noun: 'baseline write-back',
184
+ describe: (w) =>
185
+ `Wrote back ${w.improvedPaths?.length ?? 0} improved maintainability row(s)`,
186
+ }),
187
+ });
188
+ await bestEffort({
189
+ tag: 'BUDGET',
190
+ label: 'context-budget write-back',
191
+ progress,
192
+ run: () =>
193
+ writebackStep(ctx, {
194
+ tag: 'BUDGET',
195
+ run: runContextBudgetWriteback,
196
+ noun: 'context-budget write-back',
197
+ describe: (w) =>
198
+ `Wrote back lower context-budget totals (${w.tiers?.join(', ')})`,
199
+ }),
170
200
  });
171
201
  }
@@ -7,7 +7,7 @@ import {
7
7
  import { runCloseValidation } from '../../close-validation/runner.js';
8
8
  import { getCiDelivery } from '../../config/ci.js';
9
9
  import { resolveConfig } from '../../config-resolver.js';
10
- import { getStoryBranch, gitSync } from '../../git-utils.js';
10
+ import { getStoryBranch, gitSpawn, gitSync } from '../../git-utils.js';
11
11
  import { Logger } from '../../Logger.js';
12
12
  import { emitTerminalFriction } from '../../observability/runtime-friction.js';
13
13
  import { emitTerseResult } from '../../observability/terse-result.js';
@@ -318,6 +318,11 @@ async function openAndReviewPr({
318
318
  prNumber,
319
319
  provider,
320
320
  runCodeReviewFn: injectedRunCodeReview ?? runCodeReviewDefault,
321
+ // The runner owns the git seam it hands its phases (same as `gitSync` to
322
+ // `pushStoryBranch`). The review needs it to resolve `origin/<base>` —
323
+ // the ref base-sync merged from — before it will score anything
324
+ // (Story #5325).
325
+ gitSpawnFn: gitSpawn,
321
326
  progress,
322
327
  });
323
328
  if (reviewOutcome.halted) {
@@ -0,0 +1,213 @@
1
+ /**
2
+ * context-budget-writeback.js — lock a context-budget gain in on the branch
3
+ * that earned it (Story #5313).
4
+ *
5
+ * `check-context-budget.js` used to fail a gated documentation tier that came
6
+ * in **under** its recorded total (Story #4872), so a Story that trimmed prose
7
+ * paid for the trim twice: once in the edit and once in the red gate whose
8
+ * only remedy was a hand-run `--update`. Story #5313 reverses that rule —
9
+ * shrinkage exits 0 and is reported — and this module answers the concern
10
+ * the rule existed for: a stale recorded total silently absorbs the next
11
+ * growth. When the tree the close is scoring measures under its recorded
12
+ * totals, the lower totals are written back into
13
+ * `baselines/context-budget.json` and folded into one `baseline-refresh:`
14
+ * commit on the Story branch, so the gain lands in the branch's own PR.
15
+ *
16
+ * ## Where it runs, and why not post-land
17
+ *
18
+ * The Story names the post-land tail. This step runs from the close's
19
+ * **pre-gate write-back seam** (`phases/pre-gate-steps.js`, beside the
20
+ * maintainability write-back of Story #5224) instead: the only sanctioned
21
+ * landing is the Story branch → PR → `main`, so a post-land write would have
22
+ * to commit straight to the base branch. Writing on the branch ahead of the
23
+ * gates keeps the row inside the PR the gates score and lets
24
+ * `refresh-ack.js` vouch for it through the same `baseline-refresh:` marker.
25
+ *
26
+ * Constraints, each a "must not":
27
+ *
28
+ * 1. **Only a downward move is written.** Growth past tolerance and an
29
+ * unbacked recorded row must still fail `check-context-budget.js` exactly
30
+ * as before; when either is present this step skips and the gate speaks.
31
+ * 2. **Idempotent and silent.** No shrink, no write, no commit.
32
+ * 3. **Never fails the close.** Every failure is a named skip.
33
+ */
34
+
35
+ import fs from 'node:fs';
36
+ import path from 'node:path';
37
+
38
+ import {
39
+ DEFAULT_TOLERANCE_BYTES,
40
+ buildBaseline as defaultBuildBaseline,
41
+ diffBudget as defaultDiffBudget,
42
+ loadBaseline as defaultLoadBaseline,
43
+ } from '../../../check-context-budget.js';
44
+ import { resolveDocTiers as defaultResolveDocTiers } from '../../doc-tiers.js';
45
+ import { gitSync as defaultGitSync } from '../../git-utils.js';
46
+ import { Logger as DefaultLogger } from '../../Logger.js';
47
+
48
+ const TAG = '[context-budget-writeback]';
49
+
50
+ /** Repo-relative location of the committed budget. */
51
+ const BASELINE_REL_PATH = 'baselines/context-budget.json';
52
+
53
+ /**
54
+ * Build the commit subject — conventional, carrying the `baseline-refresh:`
55
+ * marker `refresh-ack.js` recognises, fixed-length but for the Story id.
56
+ *
57
+ * @param {number|string} storyId
58
+ * @returns {string}
59
+ */
60
+ function buildCommitSubject(storyId) {
61
+ return `chore(baselines): baseline-refresh: lower context-budget totals (story #${storyId})`;
62
+ }
63
+
64
+ /**
65
+ * One line per tier that moved down, before → after.
66
+ *
67
+ * @param {Array<{ tier: string, current: number, baseline: number }>} shrunk
68
+ * @returns {string}
69
+ */
70
+ function buildCommitBody(shrunk) {
71
+ return [
72
+ 'Documentation tiers this branch trimmed under their recorded totals,',
73
+ 'written back so the budget stops holding slack the tree no longer',
74
+ 'spends. Growth and unbacked rows are never rewritten here.',
75
+ '',
76
+ ...shrunk.map((s) => `- ${s.tier}: ${s.baseline} -> ${s.current} bytes`),
77
+ ].join('\n');
78
+ }
79
+
80
+ /**
81
+ * Report a no-op by name.
82
+ *
83
+ * @param {object} logger
84
+ * @param {string} reason
85
+ */
86
+ function skip(logger, reason) {
87
+ logger.info?.(`${TAG} no write-back (${reason}).`);
88
+ return { ran: false, committed: false, reason };
89
+ }
90
+
91
+ /**
92
+ * Everything that must hold before a write. Returns a skip reason or `null`.
93
+ *
94
+ * @param {{ workTree: string, storyBranch: string, git: Function, relPath: string }} ctx
95
+ * @returns {string|null}
96
+ */
97
+ function precheck({ workTree, storyBranch, git, relPath }) {
98
+ const onBranch = git(['rev-parse', '--abbrev-ref', 'HEAD'], {
99
+ cwd: workTree,
100
+ });
101
+ if (onBranch !== storyBranch) return 'wrong-branch';
102
+ const status = git(['status', '--porcelain', '--', relPath], {
103
+ cwd: workTree,
104
+ });
105
+ if (status.length > 0) return 'dirty-tree';
106
+ return null;
107
+ }
108
+
109
+ /**
110
+ * Write the envelope and fold it into one commit. On a rejected commit the
111
+ * file is restored so the gates score the tree the close found.
112
+ *
113
+ * @param {{ workTree: string, git: Function, relPath: string, envelope: object, storyId: number|string, shrunk: object[] }} args
114
+ * @returns {{ sha: string }}
115
+ */
116
+ function commitBaseline({ workTree, git, relPath, envelope, storyId, shrunk }) {
117
+ fs.writeFileSync(
118
+ path.join(workTree, relPath),
119
+ `${JSON.stringify(envelope, null, 2)}\n`,
120
+ );
121
+ git(['add', '--', relPath], { cwd: workTree });
122
+ try {
123
+ git(
124
+ [
125
+ 'commit',
126
+ '-m',
127
+ buildCommitSubject(storyId),
128
+ '-m',
129
+ buildCommitBody(shrunk),
130
+ ],
131
+ { cwd: workTree },
132
+ );
133
+ } catch (err) {
134
+ git(['restore', '--staged', '--worktree', '--', relPath], {
135
+ cwd: workTree,
136
+ });
137
+ throw err;
138
+ }
139
+ return { sha: git(['rev-parse', '--short', 'HEAD'], { cwd: workTree }) };
140
+ }
141
+
142
+ /**
143
+ * Write the lower context-budget totals back on the Story branch.
144
+ *
145
+ * Total: a throw anywhere is a named skip, never a failed close.
146
+ *
147
+ * @param {{
148
+ * cwd: string,
149
+ * worktreePath?: string|null,
150
+ * storyId: number|string,
151
+ * storyBranch?: string,
152
+ * config: object,
153
+ * logger?: object,
154
+ * gitSync?: (cwd: string, ...args: string[]) => string,
155
+ * resolveDocTiersImpl?: typeof defaultResolveDocTiers,
156
+ * loadBaselineImpl?: typeof defaultLoadBaseline,
157
+ * diffBudgetImpl?: typeof defaultDiffBudget,
158
+ * buildBaselineImpl?: typeof defaultBuildBaseline,
159
+ * }} opts
160
+ * @returns {{ ran: boolean, committed: boolean, sha?: string, tiers?: string[], reason?: string }}
161
+ */
162
+ export function runContextBudgetWriteback({
163
+ cwd,
164
+ worktreePath,
165
+ storyId,
166
+ storyBranch,
167
+ config,
168
+ logger = DefaultLogger,
169
+ gitSync = defaultGitSync,
170
+ resolveDocTiersImpl = defaultResolveDocTiers,
171
+ loadBaselineImpl = defaultLoadBaseline,
172
+ diffBudgetImpl = defaultDiffBudget,
173
+ buildBaselineImpl = defaultBuildBaseline,
174
+ } = {}) {
175
+ if (!cwd || !storyBranch) return skip(logger, 'missing-context');
176
+ const workTree = worktreePath || cwd;
177
+ const git = (args, opts = {}) => gitSync(opts.cwd ?? workTree, ...args);
178
+ try {
179
+ const blocked = precheck({
180
+ workTree,
181
+ storyBranch,
182
+ git,
183
+ relPath: BASELINE_REL_PATH,
184
+ });
185
+ if (blocked) return skip(logger, blocked);
186
+ const baseline = loadBaselineImpl(path.join(workTree, BASELINE_REL_PATH));
187
+ if (!baseline) return skip(logger, 'no-baseline');
188
+ const tierMap = resolveDocTiersImpl(config, { root: workTree });
189
+ const diff = diffBudgetImpl(tierMap, baseline);
190
+ if (diff.grown.length > 0 || diff.absent.length > 0) {
191
+ return skip(logger, 'drift-not-downward');
192
+ }
193
+ if (diff.shrunk.length === 0) return skip(logger, 'no-shrink');
194
+ const tolerance = Number.isFinite(baseline.toleranceBytes)
195
+ ? baseline.toleranceBytes
196
+ : DEFAULT_TOLERANCE_BYTES;
197
+ const { sha } = commitBaseline({
198
+ workTree,
199
+ git,
200
+ relPath: BASELINE_REL_PATH,
201
+ envelope: buildBaselineImpl(tierMap, tolerance),
202
+ storyId,
203
+ shrunk: diff.shrunk,
204
+ });
205
+ const tiers = diff.shrunk.map((s) => s.tier);
206
+ logger.warn?.(
207
+ `${TAG} wrote back lower totals for ${tiers.join(', ')} on story #${storyId}; committed as ${sha}.`,
208
+ );
209
+ return { ran: true, committed: true, sha, tiers };
210
+ } catch (err) {
211
+ return skip(logger, `failed: ${err?.message ?? err}`);
212
+ }
213
+ }
@@ -10,9 +10,8 @@
10
10
  * object. To make the rules below actually fire on canonical plans, a string
11
11
  * body is **parsed** back into its structured form via `parseStoryBody`
12
12
  * before the section checks run (Story #3906 — previously the validator
13
- * `shouldSkipTicket`-skipped every string body, so the verify-tier suffix
14
- * rule, vague-verb check, and non-empty-goal check never ran on any real
15
- * decomposition). A still-structured object body (e.g. a caller that passes
13
+ * `shouldSkipTicket`-skipped every string body, so the vague-verb check and
14
+ * non-empty-goal check never ran on any real decomposition). A still-structured object body (e.g. a caller that passes
16
15
  * the pre-serialize shape directly) is validated as-is.
17
16
  *
18
17
  * Only `type: 'story'` tickets are validated; Feature/Epic tickets and
@@ -42,44 +41,23 @@
42
41
  * array uses the same object shape and is the home for paths the Story
43
42
  * reads but does not modify (test fixtures, sibling modules, etc.).
44
43
  *
45
- * `body.verify` entries must either name a testing tier in parentheses
46
- * drawn from `VERIFY_TIER_VALUES` (e.g. `npm run test (unit)`) or be the
47
- * literal `manual:<reason>` escape hatch when the Story is genuinely
48
- * unverifiable in isolation. `verify-tier-repair.js#normalizeVerifyTiers` runs
49
- * first on the persist path (Story #5005) and **repairs** the entries whose
50
- * tier `suggestVerifyFix` can infer, so the hard error below is reserved for
51
- * the entries only the author can resolve.
44
+ * `body.verify` entries are commands, nothing more: Story #5312 deleted the
45
+ * `(<tier>)` suffix, the `manual:<reason>` escape and the repair pass that
46
+ * appended the suffix for the author. The only verify rule left is that the
47
+ * list is non-empty.
52
48
  *
53
49
  * The errors are batched and surfaced as a single thrown Error so the
54
50
  * planner can see every offending slug in one pass instead of fixing one
55
51
  * at a time.
56
52
  */
57
53
 
58
- import {
59
- suggestPathEntryFix,
60
- suggestVerifyFix,
61
- } from '../story-body/body-format-lints.js';
54
+ import { suggestPathEntryFix } from '../story-body/body-format-lints.js';
62
55
  import {
63
56
  parse as parseStoryBody,
64
57
  StoryBodyParseError,
65
58
  } from '../story-body/story-body.js';
66
59
  import { FILE_ASSUMPTION_VALUES } from './file-assumption-enum.js';
67
60
 
68
- /**
69
- * Canonical testing-tier labels that a `verify[]` entry must name (in
70
- * parentheses) to pass plan-time validation. Mirrors the verify-rules contract
71
- * in `.agents/scripts/lib/templates/decomposer-prompts.js`.
72
- *
73
- * Entries that do not end with `(<tier>)` and are not `manual:<reason>` are
74
- * rejected by `collectVerifyErrors`.
75
- */
76
- export const VERIFY_TIER_VALUES = Object.freeze([
77
- 'unit',
78
- 'contract',
79
- 'e2e',
80
- 'validate',
81
- ]);
82
-
83
61
  /**
84
62
  * Predicate: should the validator skip this ticket entirely? Skip when:
85
63
  * - it is not a Story (only `type: 'story'` tickets are validated here),
@@ -91,7 +69,7 @@ export const VERIFY_TIER_VALUES = Object.freeze([
91
69
  * Story body to a markdown string, so a *string* body is NOT skipped here
92
70
  * (Story #3906) — `validateTaskBodyShape` parses it back into structured
93
71
  * form via `parseStoryBody` before applying the section rules. This is what
94
- * makes the verify-tier / vague-verb / non-empty-goal checks actually fire
72
+ * makes the non-empty-verify / vague-verb / non-empty-goal checks actually fire
95
73
  * on real plans. Features (and everything else) use narrative string bodies
96
74
  * and are skipped by the `type !== 'story'` guard.
97
75
  *
@@ -207,7 +185,6 @@ export function validateTaskBodyShape(ticket) {
207
185
  }
208
186
  errors.push(...collectChangesErrors(prefix, body.changes));
209
187
  errors.push(...collectAcceptanceErrors(prefix, body.acceptance));
210
- // Tier-suffix validation is always enforced on Story bodies (2-tier world).
211
188
  errors.push(...collectVerifyErrors(prefix, body.verify));
212
189
  errors.push(...collectReferencesErrors(prefix, body.references));
213
190
  return errors;
@@ -324,15 +301,6 @@ function collectAcceptanceErrors(prefix, rawAcceptance) {
324
301
  return [];
325
302
  }
326
303
 
327
- /**
328
- * Regex that matches a valid tier suffix at the end of a verify entry:
329
- * a parenthesised word drawn from `VERIFY_TIER_VALUES` (e.g. `(unit)`).
330
- * Whitespace before the opening paren is tolerated.
331
- */
332
- const VERIFY_TIER_RE = new RegExp(
333
- `\\((?:${VERIFY_TIER_VALUES.join('|')})\\)\\s*$`,
334
- );
335
-
336
304
  /**
337
305
  * @param {string} prefix
338
306
  * @param {unknown} rawVerify
@@ -342,31 +310,10 @@ function collectVerifyErrors(prefix, rawVerify) {
342
310
  const verify = Array.isArray(rawVerify) ? rawVerify : [];
343
311
  if (verify.length === 0) {
344
312
  return [
345
- `${prefix}: verify must list at least one entry — author it at the ticket's top level (preferred) or in the body's ## Verify section. Use "manual:<reason>" only when truly unverifiable in isolation.`,
313
+ `${prefix}: verify must list at least one entry — author it at the ticket's top level (preferred) or in the body's ## Verify section.`,
346
314
  ];
347
315
  }
348
- const errors = [];
349
- for (const v of verify) {
350
- if (typeof v !== 'string') continue;
351
- if (v.startsWith('manual:')) {
352
- const reason = v.slice('manual:'.length).trim();
353
- if (reason === '') {
354
- errors.push(
355
- `${prefix}: body.verify "manual:" entry has no reason after the colon.`,
356
- );
357
- }
358
- // manual: entries are exempt from the tier-suffix check.
359
- continue;
360
- }
361
- if (!VERIFY_TIER_RE.test(v)) {
362
- const fix = suggestVerifyFix(v);
363
- const fixIt = fix === null ? '' : ` Suggested fix: "${fix}".`;
364
- errors.push(
365
- `${prefix}: body.verify entry must end with a tier in parentheses — one of (${VERIFY_TIER_VALUES.join('|')}). Got: "${v}".${fixIt}`,
366
- );
367
- }
368
- }
369
- return errors;
316
+ return [];
370
317
  }
371
318
 
372
319
  /**