mandrel 2.30.0 → 2.32.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 (253) hide show
  1. package/.agents/README.md +13 -17
  2. package/.agents/agents/acceptance-critic.md +1 -2
  3. package/.agents/docs/SDLC.md +4 -4
  4. package/.agents/docs/agentrc-reference.json +61 -57
  5. package/.agents/docs/configuration.md +274 -227
  6. package/.agents/docs/execution-reference.md +13 -14
  7. package/.agents/docs/quality-gates.md +195 -23
  8. package/.agents/instructions.md +2 -5
  9. package/.agents/rules/ci-remediation.md +44 -1
  10. package/.agents/rules/git-conventions-reference.md +27 -27
  11. package/.agents/rules/git-conventions.md +4 -2
  12. package/.agents/rules/known-tooling-behavior.md +66 -30
  13. package/.agents/rules/testing-standards.md +35 -71
  14. package/.agents/runtime-deps.json +0 -1
  15. package/.agents/schemas/agentrc.schema.json +1939 -1400
  16. package/.agents/schemas/lifecycle/README.md +21 -14
  17. package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
  18. package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
  19. package/.agents/scripts/README.md +7 -29
  20. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  21. package/.agents/scripts/bootstrap.js +28 -26
  22. package/.agents/scripts/check-baseline-drift.js +73 -13
  23. package/.agents/scripts/check-baseline-scope.js +362 -0
  24. package/.agents/scripts/check-dead-exports.js +9 -1
  25. package/.agents/scripts/check-gherkin-corpus.js +508 -0
  26. package/.agents/scripts/check-knip-entries.js +136 -0
  27. package/.agents/scripts/check-lifecycle-lint.js +36 -112
  28. package/.agents/scripts/check-schema-references.js +1 -1
  29. package/.agents/scripts/diagnose-friction.js +7 -4
  30. package/.agents/scripts/generate-config-docs.js +263 -171
  31. package/.agents/scripts/install-matrix-assert.js +0 -1
  32. package/.agents/scripts/lib/ITicketingProvider.js +0 -58
  33. package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
  34. package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
  35. package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
  36. package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
  37. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
  38. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  39. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  40. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  41. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  42. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  43. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  44. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  45. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  46. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  47. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  48. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  49. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  50. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  51. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  52. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  53. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  54. package/.agents/scripts/lib/child-exec.js +193 -0
  55. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  56. package/.agents/scripts/lib/cli-args.js +64 -0
  57. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  58. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  59. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  60. package/.agents/scripts/lib/config/commands.js +3 -5
  61. package/.agents/scripts/lib/config/explain.js +5 -7
  62. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  63. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  64. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  65. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  66. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  67. package/.agents/scripts/lib/config/gates/index.js +5 -2
  68. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  69. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  70. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  71. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  72. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  73. package/.agents/scripts/lib/config/github.js +0 -1
  74. package/.agents/scripts/lib/config/limits.js +3 -48
  75. package/.agents/scripts/lib/config/qa.js +105 -0
  76. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  77. package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
  78. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  79. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  80. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  81. package/.agents/scripts/lib/crap-utils.js +84 -520
  82. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  83. package/.agents/scripts/lib/degraded-mode.js +2 -2
  84. package/.agents/scripts/lib/doc-tiers.js +3 -3
  85. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  86. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  87. package/.agents/scripts/lib/fs-walk.js +52 -0
  88. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  89. package/.agents/scripts/lib/git-utils.js +16 -36
  90. package/.agents/scripts/lib/knip-entry-sync.js +469 -0
  91. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  92. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  93. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  94. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  95. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  96. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  97. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  98. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  99. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  100. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  101. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  102. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  103. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  104. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  105. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  106. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  107. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
  108. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
  109. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  110. package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
  111. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  112. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  113. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  116. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  117. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  118. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  119. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  120. package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +17 -2
  121. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  122. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  123. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  124. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +65 -25
  125. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  126. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  127. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  128. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  129. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  130. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  131. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  132. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  133. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  134. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  135. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  136. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  137. package/.agents/scripts/lib/signals/index.js +8 -6
  138. package/.agents/scripts/lib/signals/schema.js +20 -25
  139. package/.agents/scripts/lib/signals/write.js +8 -8
  140. package/.agents/scripts/lib/story-body/story-body.js +12 -59
  141. package/.agents/scripts/lib/temp-retention.js +1 -1
  142. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  143. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  144. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  145. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  146. package/.agents/scripts/lib/worktree-manager.js +2 -3
  147. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  148. package/.agents/scripts/pr-watch-with-update.js +7 -5
  149. package/.agents/scripts/providers/github/cache.js +2 -2
  150. package/.agents/scripts/providers/github/comments.js +6 -28
  151. package/.agents/scripts/providers/github/compose.js +0 -15
  152. package/.agents/scripts/providers/github/errors.js +10 -27
  153. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  154. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  155. package/.agents/scripts/providers/github.js +4 -7
  156. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  157. package/.agents/scripts/resolve-stories.js +0 -2
  158. package/.agents/scripts/run-lint.js +61 -61
  159. package/.agents/scripts/run-test-profile.js +6 -6
  160. package/.agents/scripts/run-verify.js +48 -30
  161. package/.agents/scripts/single-story-close.js +20 -0
  162. package/.agents/scripts/single-story-init.js +12 -35
  163. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  164. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  165. package/.agents/skills/skills.index.json +2 -12
  166. package/.agents/skills/stack/qa/playwright/SKILL.md +48 -0
  167. package/.agents/workflows/audit-documentation.md +5 -6
  168. package/.agents/workflows/audit-to-stories.md +2 -2
  169. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  170. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  171. package/.agents/workflows/helpers/code-review.md +3 -8
  172. package/.agents/workflows/helpers/deliver-reference.md +2 -1
  173. package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
  174. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  175. package/.agents/workflows/mandrel-update.md +10 -10
  176. package/.agents/workflows/qa-assist.md +15 -20
  177. package/.agents/workflows/qa-explore.md +9 -8
  178. package/README.md +1 -1
  179. package/docs/CHANGELOG.md +49 -0
  180. package/lib/migrations/index.js +2 -0
  181. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  182. package/package.json +12 -3
  183. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  184. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  185. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  186. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  187. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  188. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  189. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  190. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  191. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  192. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  193. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  194. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  195. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  196. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  197. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  198. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  199. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  200. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  201. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  202. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  203. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  204. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  205. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  206. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  207. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  208. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  209. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  210. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  211. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  212. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  213. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  214. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  215. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  216. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  217. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  218. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  219. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  220. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  221. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  222. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  223. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  224. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  225. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  226. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  227. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  228. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  229. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  230. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  231. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  232. package/.agents/scripts/lib/planning-corpus.js +0 -37
  233. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  234. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  235. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  236. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  237. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  238. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  239. package/.agents/scripts/lib/signals/read.js +0 -268
  240. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  241. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  242. package/.agents/scripts/lib/story-plan.js +0 -379
  243. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  244. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  245. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  246. package/.agents/scripts/lint-baseline.js +0 -507
  247. package/.agents/scripts/providers/github/prs.js +0 -103
  248. package/.agents/scripts/signals-view.js +0 -309
  249. package/.agents/scripts/story-plan.js +0 -370
  250. package/.agents/scripts/sync-branch-from-base.js +0 -149
  251. package/.agents/scripts/validate-docs-freshness.js +0 -314
  252. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  253. package/.agents/workflows/helpers/signals.md +0 -112
@@ -21,6 +21,32 @@ import process from 'node:process';
21
21
  // can spawn directly.
22
22
  const useShell = process.platform === 'win32';
23
23
 
24
+ /**
25
+ * A Node gate spelled `node .agents/scripts/<script>` — the shape six of the
26
+ * eight tasks share. Spelling it once keeps this list readable as what it
27
+ * actually is (a fan-out roster) instead of a wall of spawn tuples, and keeps
28
+ * adding a gate from costing the module a maintainability regression for the
29
+ * boilerplate rather than the behaviour. Mirrors `run-verify.js`'s `gate()`.
30
+ *
31
+ * Use bare `node` (PATH-resolved) rather than `process.execPath`:
32
+ * `process.execPath` on Windows often expands to `C:\Program
33
+ * Files\nodejs\node.exe`, and spawn(..., { shell: true }) does not quote the
34
+ * executable, so the space breaks invocation.
35
+ *
36
+ * The path is passed WHOLE rather than assembled from a basename. Composing
37
+ * it (`` `.agents/scripts/${script}` ``) reads better and is wrong:
38
+ * `check-knip-entries.js` derives which CLIs something actually invokes by
39
+ * scanning this file's source text for literal script paths, and four of the
40
+ * gates below are invoked from nowhere else. Interpolating the path made them
41
+ * invisible to that derivation, so knip called them dead and the ratchet
42
+ * offered to record live gates as expected-dead — the Story #5012 trap.
43
+ *
44
+ * @param {string} name reported in this driver's own error prefix
45
+ * @param {string} script repo-relative path, spelled as one literal
46
+ * @returns {{ name: string, cmd: string, args: string[] }}
47
+ */
48
+ const nodeGate = (name, script) => ({ name, cmd: 'node', args: [script] });
49
+
24
50
  const tasks = [
25
51
  {
26
52
  name: 'biome',
@@ -47,67 +73,41 @@ const tasks = [
47
73
  '!.worktrees/**',
48
74
  ],
49
75
  },
50
- {
51
- // Custom Node-based lint for the lifecycle bus surface (Story #2227).
52
- // Enforces:
53
- // 1. No `Promise.all` over listener arrays under
54
- // `.agents/scripts/lib/orchestration/lifecycle/**`.
55
- // 2. Wildcard-observer firewall: no state-mutating imports in
56
- // listener modules that register on `'*'`.
57
- // Both rules are mandated by Tech Spec #2189 and have no biome
58
- // equivalent.
59
- //
60
- // Use bare `node` (PATH-resolved) rather than `process.execPath`:
61
- // `process.execPath` on Windows often expands to `C:\Program
62
- // Files\nodejs\node.exe`, and spawn(..., { shell: true }) does not
63
- // quote the executable, so the space breaks invocation.
64
- name: 'lifecycle-lint',
65
- cmd: 'node',
66
- args: ['.agents/scripts/check-lifecycle-lint.js'],
67
- },
68
- {
69
- // Custom Node-based lint for the workflow prose surface (Epic #4474
70
- // PR5). Enforces: no workflow may instruct calling an exported
71
- // library function that has no CLI entrypoint the measured
72
- // shim-writing failure mode the /plan collapse killed. See
73
- // check-workflow-cli-lint.js for the paragraph-level heuristic.
74
- name: 'workflow-cli-lint',
75
- cmd: 'node',
76
- args: ['.agents/scripts/check-workflow-cli-lint.js'],
77
- },
78
- {
79
- // Custom Node-based lint for label-vocabulary citations in
80
- // `.agents/docs/SDLC.md` and `.agents/workflows/**/*.md` (Story #2892,
81
- // Tech Spec F9 under Epic #2880). Greps inline backtick code
82
- // spans for axis-shaped tokens (`type/epic`, etc.) and asserts
83
- // only the canonical `<axis>::<value>` separator from
84
- // `lib/label-constants.js` appears. Closes the drift gap that
85
- // let the original `type/epic` typo land in a since-retired
86
- // planning workflow helper.
87
- name: 'label-vocabulary',
88
- cmd: 'node',
89
- args: ['.agents/scripts/lint-label-vocabulary.js'],
90
- },
91
- {
92
- // GitHub Actions job-timeout gate (Story #4936). Enumerates every
93
- // `jobs.<id>` key across `.github/workflows/*.yml` and fails when one
94
- // sets no `timeout-minutes` (inheriting GitHub's 360-minute default) or
95
- // sets one above the ceiling. A deadlocked Windows job burned 44 minutes
96
- // of a runner and withheld the failing required check's logs for the
97
- // whole time; nothing but this check would have caught the gap.
98
- name: 'workflow-timeouts',
99
- cmd: 'node',
100
- args: ['.agents/scripts/check-workflow-timeouts.js'],
101
- },
102
- {
103
- // Architecture cycle ratchet (Story #3991). Detects directed import
104
- // cycles under `.agents/scripts/` and fails on any cycle not in the
105
- // committed allowlist (`baselines/arch-cycles.json`). Mirrors the
106
- // ratchet-down semantics of `check-dead-exports.js`.
107
- name: 'arch-cycles',
108
- cmd: 'node',
109
- args: ['.agents/scripts/check-arch-cycles.js'],
110
- },
76
+ // Lifecycle surface (Story #2227). Enforces (1) no `Promise.all` under
77
+ // `.agents/scripts/lib/orchestration/lifecycle/**` the ledger is
78
+ // append-only and a parallel write interleaves records; and (2) the
79
+ // auto-merge lockout: the `gh pr merge` literal appears only in
80
+ // `single-story-close/phases/auto-merge.js`. Neither has a biome
81
+ // equivalent. Story #5024 dropped the third rule (the wildcard-observer
82
+ // firewall) with the bus and the `listeners/` directory its predicate
83
+ // required.
84
+ nodeGate('lifecycle-lint', '.agents/scripts/check-lifecycle-lint.js'),
85
+ // Workflow prose surface (Epic #4474 PR5). No workflow may instruct calling
86
+ // an exported library function that has no CLI entrypoint — the measured
87
+ // shim-writing failure mode the /plan collapse killed. See
88
+ // check-workflow-cli-lint.js for the paragraph-level heuristic.
89
+ nodeGate('workflow-cli-lint', '.agents/scripts/check-workflow-cli-lint.js'),
90
+ // Label-vocabulary citations in `.agents/docs/SDLC.md` and
91
+ // `.agents/workflows/**/*.md` (Story #2892). Greps inline backtick code
92
+ // spans for axis-shaped tokens (`type/epic`, etc.) and asserts only the
93
+ // canonical `<axis>::<value>` separator from `lib/label-constants.js`
94
+ // appears. Closes the drift gap that let the original `type/epic` typo land.
95
+ nodeGate('label-vocabulary', '.agents/scripts/lint-label-vocabulary.js'),
96
+ // GitHub Actions job-timeout gate (Story #4936). Fails a `jobs.<id>` that
97
+ // sets no `timeout-minutes` (inheriting GitHub's 360-minute default) or sets
98
+ // one above the ceiling. A deadlocked Windows job burned 44 minutes of a
99
+ // runner and withheld the failing required check's logs for the whole time.
100
+ nodeGate('workflow-timeouts', '.agents/scripts/check-workflow-timeouts.js'),
101
+ // Architecture cycle ratchet (Story #3991). Detects directed import cycles
102
+ // under `.agents/scripts/` and fails on any cycle not in the committed
103
+ // allowlist (`baselines/arch-cycles.json`).
104
+ nodeGate('arch-cycles', '.agents/scripts/check-arch-cycles.js'),
105
+ // Static Gherkin corpus gate (Story #5013): must-compile with the real
106
+ // parser, then must-bind scoped per step root. Opt-in behind
107
+ // `qa.gherkinLint`, so it exits 0 as "not configured" in this repo. Here
108
+ // rather than in run-verify.js so it reaches CI through the same `lint`
109
+ // required check as arch-cycles; listing it in both would double-pay it.
110
+ nodeGate('gherkin-corpus', '.agents/scripts/check-gherkin-corpus.js'),
111
111
  ];
112
112
 
113
113
  function runTask({ name, cmd, args }) {
@@ -8,10 +8,10 @@
8
8
  * runner only — set SKIP_PREFLIGHT=0 to include preflight if desired.
9
9
  */
10
10
 
11
- import { spawnSync } from 'node:child_process';
12
11
  import fs from 'node:fs';
13
12
  import path from 'node:path';
14
13
  import { fileURLToPath } from 'node:url';
14
+ import { spawnChild } from './lib/child-exec.js';
15
15
  import { runAsCli } from './lib/cli-utils.js';
16
16
  import { buildWebhookSafeTestEnv } from './lib/test-env.js';
17
17
  import { parseTapOutput } from './lib/test-profile/parse-tap.js';
@@ -56,13 +56,14 @@ export function parseProfileArgv(argv) {
56
56
  * @param {object} [opts]
57
57
  * @param {string[]} [opts.argv]
58
58
  * @param {string} [opts.cwd]
59
- * @param {typeof spawnSync} [opts.spawn]
59
+ * @param {Function} [opts.spawn] - Injected child runner; defaults to the
60
+ * shared surface's real `spawnSync`.
60
61
  * @param {typeof fs} [opts.fs]
61
62
  */
62
63
  export function runTestProfile({
63
64
  argv = process.argv.slice(2),
64
65
  cwd = ROOT,
65
- spawn = spawnSync,
66
+ spawn,
66
67
  fs: fsLike = fs,
67
68
  } = {}) {
68
69
  const { outDir, topN, testArgv } = parseProfileArgv(argv);
@@ -75,7 +76,8 @@ export function runTestProfile({
75
76
  ];
76
77
 
77
78
  const started = Date.now();
78
- const result = spawn(process.execPath, nodeArgs, {
79
+ const result = spawnChild(process.execPath, nodeArgs, {
80
+ run: spawn,
79
81
  cwd,
80
82
  encoding: 'utf8',
81
83
  env: {
@@ -84,8 +86,6 @@ export function runTestProfile({
84
86
  FORCE_COLOR: '0',
85
87
  NO_COLOR: '1',
86
88
  },
87
- maxBuffer: 64 * 1024 * 1024,
88
- shell: false,
89
89
  });
90
90
  const wallDurationMs = Date.now() - started;
91
91
 
@@ -6,8 +6,8 @@
6
6
  * locally, without epic-scoped MI projection or push semantics.
7
7
  *
8
8
  * Order: audit (SCA) → lint (includes docs:check + the arch-cycles ratchet) →
9
- * full test suite → unified baselines → the dead-exports and context-budget
10
- * ratchets.
9
+ * full test suite → unified baselines → the standalone ratchets
10
+ * (dead-exports ×2, context-budget, cyclomatic, schema-references).
11
11
  *
12
12
  * The `audit` step runs `npm audit --audit-level=high`, matching CI's
13
13
  * "Dependency Vulnerability Audit (SCA)" gate so a local green no longer hides
@@ -27,42 +27,60 @@
27
27
  * full-tree scan in check-dead-exports.js) to a command that already carries
28
28
  * the full test suite.
29
29
  *
30
- * A handful of CI gates cannot be reproduced by this command (action pinning,
31
- * TruffleHog secret scan, the BASELINE_SCOPE=full push-scoped maintainability
32
- * run) those are catalogued in docs/ci-contract.md.
30
+ * Story #5004 closed the last two mirror gaps that were pure omission.
31
+ * `check-cyclomatic.js` (#4923) and `check-schema-references.js` (#4938) were
32
+ * each added to the `baselines` job's standalone-ratchet slot without ever
33
+ * being added here, so a green `verify` still hid both. Like their neighbours
34
+ * they are pure-Node and cost milliseconds.
35
+ *
36
+ * Still NOT mirrored: `check-workflow-citations.js` and
37
+ * `check-baseline-scope.js` run in CI's `baselines` job only —
38
+ * `.agents/rules/known-tooling-behavior.md` entry 2 carries the current
39
+ * coverage table. (`prune-baseline-orphans.js --check` used to sit in that
40
+ * list; it no longer runs in CI at all — the un-attributed duplicate of the
41
+ * scope gate's `extra` direction reds every open PR on inherited rows.) Nor are the CI gates this command structurally cannot
42
+ * reproduce (action pinning, TruffleHog secret scan) — those are catalogued
43
+ * in docs/ci-contract.md. The nightly full-scope re-score
44
+ * (.github/workflows/baseline-drift.yml) is deliberately outside this
45
+ * mirror too: it re-scores the whole tree, which is the cost `verify` exists
46
+ * to avoid paying on every run.
33
47
  */
34
48
 
35
49
  import { spawnSync } from 'node:child_process';
36
50
  import { runAsCli } from './lib/cli-utils.js';
37
51
 
52
+ /**
53
+ * A gate step: `node .agents/scripts/<script>` plus any extra args. Seven of
54
+ * the ten steps share exactly that shape, so spelling it once leaves the list
55
+ * below readable as what it actually is — a gate *order* — instead of a wall
56
+ * of spawn tuples.
57
+ *
58
+ * @param {string} label reported as `failedStep` when the gate exits non-zero
59
+ * @param {string} script basename under `.agents/scripts/`
60
+ * @param {...string} args extra CLI args
61
+ * @returns {{ label: string, cmd: string, args: string[] }}
62
+ */
63
+ const gate = (label, script, ...args) => ({
64
+ label,
65
+ cmd: 'node',
66
+ args: [`.agents/scripts/${script}`, ...args],
67
+ });
68
+
38
69
  const STEPS = [
39
- {
40
- label: 'audit',
41
- cmd: 'npm',
42
- args: ['audit', '--audit-level=high'],
43
- },
70
+ { label: 'audit', cmd: 'npm', args: ['audit', '--audit-level=high'] },
44
71
  { label: 'lint', cmd: 'npm', args: ['run', 'lint'] },
45
72
  { label: 'test', cmd: 'npm', args: ['test'] },
46
- {
47
- label: 'baselines',
48
- cmd: 'node',
49
- args: ['.agents/scripts/check-baselines.js'],
50
- },
51
- {
52
- label: 'dead-exports',
53
- cmd: 'node',
54
- args: ['.agents/scripts/check-dead-exports.js'],
55
- },
56
- {
57
- label: 'dead-exports-production',
58
- cmd: 'node',
59
- args: ['.agents/scripts/check-dead-exports.js', '--production'],
60
- },
61
- {
62
- label: 'context-budget',
63
- cmd: 'node',
64
- args: ['.agents/scripts/check-context-budget.js'],
65
- },
73
+ gate('baselines', 'check-baselines.js'),
74
+ // Ordered ahead of the dead-exports pair deliberately. When a new CLI is
75
+ // missing from knip.json's entry list, both gates fail — but only this one
76
+ // names the cause. Seeing the ratchet's whole-file diff first is what made
77
+ // "accept the diff" look like the fix during Story #5012.
78
+ gate('knip-entries', 'check-knip-entries.js'),
79
+ gate('dead-exports', 'check-dead-exports.js'),
80
+ gate('dead-exports-production', 'check-dead-exports.js', '--production'),
81
+ gate('context-budget', 'check-context-budget.js'),
82
+ gate('cyclomatic', 'check-cyclomatic.js'),
83
+ gate('schema-references', 'check-schema-references.js'),
66
84
  ];
67
85
 
68
86
  export function runVerifySteps({
@@ -44,6 +44,18 @@
44
44
  * [--no-auto-merge]
45
45
  * [--wait-merge | --no-wait-merge]
46
46
  * [--merge-watch-mode <sync|async>]
47
+ * [--override-review-block <reason>]
48
+ *
49
+ * `--override-review-block <reason>` is the one sanctioned way
50
+ * past a code-review CRITICAL blocker. A critical finding halts this script
51
+ * before auto-merge, and until this flag existed there was no override at all —
52
+ * so an operator who had read a finding and judged it wrong could only land by
53
+ * running `gh pr merge` themselves, bypassing the gate with nothing written
54
+ * down. The flag does not weaken the gate; it moves that escape hatch into a
55
+ * mandatory-reason audit trail (Story comment + PR comment + a
56
+ * `review-block-overridden` friction signal) and reports
57
+ * `gates.codeReview: "overridden"` on the terminal envelope. A bare or
58
+ * too-short reason fails during option parsing, before any phase runs.
47
59
  *
48
60
  * `--merge-watch-mode` (Story #4949) overrides `delivery.mergeWatch.mode` for
49
61
  * one invocation, on the same explicit-wins-over-config precedence
@@ -217,6 +229,14 @@ runAsCli(import.meta.url, main, {
217
229
  '--merge-watch-mode <sync|async>',
218
230
  'Override delivery.mergeWatch.mode for this invocation only. `async` caps the merge wait to a short probe window and returns the resumable `pending` terminal instead of holding the foreground slot — pass it on every close of a multi-Story run. An invalid value exits non-zero before any phase runs.',
219
231
  ],
232
+ [
233
+ '--override-review-block <reason>',
234
+ // Deliberately does not spell the merge CLI invocation: the
235
+ // merge-lockout rule in `check-lifecycle-lint.js` forbids that literal
236
+ // in any string outside `phases/auto-merge.js`, and it is right to —
237
+ // the point of this flag is that arming stays on the one code path.
238
+ 'Land despite a Story-scope code-review CRITICAL blocker you have reviewed and judged wrong. The reason is mandatory (≥12 chars) and is recorded on the Story, on the PR, and as a `review-block-overridden` friction signal; the terminal envelope reports `gates.codeReview: "overridden"`. Use this instead of merging the PR by hand with the GitHub CLI — a hand-merge bypasses the gate with no record at all.',
239
+ ],
220
240
  ['--no-evidence', 'Do not reuse or write gate evidence stamps.'],
221
241
  ['--dry-run', 'Report the plan; mutate nothing.'],
222
242
  ],
@@ -54,7 +54,6 @@ import {
54
54
  import { getStoryBranch, gitSpawn, gitSync } from './lib/git-utils.js';
55
55
  import { Logger } from './lib/Logger.js';
56
56
  import { TYPE_LABELS } from './lib/label-constants.js';
57
- import { setActiveStoryEnv } from './lib/observability/active-story-env.js';
58
57
  import { emitTerseResult } from './lib/observability/terse-result.js';
59
58
  import {
60
59
  executeFastForward,
@@ -492,10 +491,9 @@ export function seedStoryBranch({ cwd, storyBranch, baseBranch, progress }) {
492
491
  }
493
492
 
494
493
  /**
495
- * Provision a worktree (or check out the branch in single-tree mode), then
496
- * record the active-story environment markers. Returns the resolved
497
- * `workCwd`, `worktreeCreated`, and `installStatus`. Exported for testing
498
- * (owns the worktree/single-tree routing + setActiveStoryEnv call).
494
+ * Provision a worktree (or check out the branch in single-tree mode).
495
+ * Returns the resolved `workCwd`, `worktreeCreated`, and `installStatus`.
496
+ * Exported for testing (owns the worktree/single-tree routing).
499
497
  *
500
498
  * @param {object} opts
501
499
  * @param {object} opts.runtime
@@ -541,23 +539,6 @@ export async function provisionWorktree({
541
539
  gitSync(cwd, 'checkout', storyBranch);
542
540
  }
543
541
 
544
- try {
545
- // v2 Stories are standalone — no parent Epic. The helper omits
546
- // CC_EPIC_ID from env + file; the trace hook keys its standalone-trace
547
- // branch on CC_EPIC_ID being absent.
548
- setActiveStoryEnv({
549
- storyId,
550
- workCwd,
551
- logger: {
552
- warn: (m) => progress('ENV', `⚠️ ${m}`),
553
- },
554
- });
555
- } catch (err) {
556
- Logger.error(
557
- `[single-story-init] ⚠️ Failed to set active-Story env: ${err?.message ?? err}`,
558
- );
559
- }
560
-
561
542
  return { workCwd, worktreeCreated, installStatus };
562
543
  }
563
544
 
@@ -572,13 +553,11 @@ export async function runSingleStoryInit({
572
553
  injectedConfig,
573
554
  injectedSweep,
574
555
  // Story #3483: lets tests drive the lease preflight deterministically.
575
- // `injectedAcquireLease` swaps the guard; `leaseNow` injects the clock the
576
- // fail-closed liveness check evaluates against (audit #3513). `steal`
577
- // forcibly transfers a foreign claim — the standalone path has no Epic
578
- // heartbeat ledger, so a foreign assignee blocks unless stolen.
556
+ // `injectedAcquireLease` swaps the guard. `steal` forcibly transfers a
557
+ // foreign claim the lease fails closed (audit #3513), so a foreign
558
+ // assignee blocks unless stolen.
579
559
  injectedAcquireLease,
580
560
  steal = false,
581
- leaseNow,
582
561
  injectedVerifyRemote,
583
562
  // Story #4620: swap the git-touching provisioning steps so the
584
563
  // early-flip-then-rollback ordering is unit-testable without a real worktree.
@@ -652,13 +631,12 @@ export async function runSingleStoryInit({
652
631
  `Standalone Story: "${story.title}" → branch ${storyBranch} from ${baseBranch}.`,
653
632
  );
654
633
 
655
- // Story #3483 — lease preflight. Take an exclusive, time-bounded claim on
656
- // the Story ticket before any git mutation so two concurrent standalone
657
- // runs cannot both drive the same Story. The standalone path has no Epic
658
- // heartbeat ledger, so the guard fails closed (audit #3513): a foreign
659
- // assignee is treated as a live claim and aborts init (naming the current
660
- // owner) unless --steal forcibly transfers it. Unclaimed / self-held claims
661
- // proceed. Skipped under --dry-run (no assignee mutation).
634
+ // Story #3483 — lease preflight. Take an exclusive claim on the Story
635
+ // ticket before any git mutation so two concurrent standalone runs cannot
636
+ // both drive the same Story. The guard fails closed (audit #3513): a
637
+ // foreign assignee aborts init (naming the current owner) unless --steal
638
+ // forcibly transfers it. Unclaimed / self-held claims proceed. Skipped
639
+ // under --dry-run (no assignee mutation).
662
640
  let workCwd = cwd;
663
641
  let worktreeCreated = false;
664
642
  let installStatus = { status: 'skipped', reason: 'dry-run' };
@@ -670,7 +648,6 @@ export async function runSingleStoryInit({
670
648
  storyId,
671
649
  config,
672
650
  steal: stealRequested,
673
- now: leaseNow,
674
651
  });
675
652
  progress(
676
653
  'LEASE',