mandrel 2.31.0 → 2.33.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 (267) hide show
  1. package/.agents/README.md +13 -17
  2. package/.agents/agents/acceptance-critic.md +1 -2
  3. package/.agents/docs/SDLC.md +12 -9
  4. package/.agents/docs/agentrc-reference.json +63 -58
  5. package/.agents/docs/configuration.md +275 -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/git-conventions-reference.md +27 -27
  10. package/.agents/rules/git-conventions.md +4 -2
  11. package/.agents/rules/known-tooling-behavior.md +66 -30
  12. package/.agents/rules/testing-standards.md +35 -71
  13. package/.agents/runtime-deps.json +2 -2
  14. package/.agents/schemas/agentrc.schema.json +1955 -1410
  15. package/.agents/schemas/lifecycle/README.md +21 -14
  16. package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
  17. package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
  18. package/.agents/scripts/README.md +16 -29
  19. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  20. package/.agents/scripts/audit-to-stories.js +160 -41
  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 +159 -0
  27. package/.agents/scripts/check-lifecycle-lint.js +108 -124
  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 +81 -35
  38. package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +185 -0
  39. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  40. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  41. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  42. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  43. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  44. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  45. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  46. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  47. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  48. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  49. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  50. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  51. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  52. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  53. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  54. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  55. package/.agents/scripts/lib/child-exec.js +193 -0
  56. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  57. package/.agents/scripts/lib/cli-args.js +64 -0
  58. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  59. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  60. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  61. package/.agents/scripts/lib/config/commands.js +3 -5
  62. package/.agents/scripts/lib/config/explain.js +5 -7
  63. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  64. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  65. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  66. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  67. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  68. package/.agents/scripts/lib/config/gates/index.js +5 -2
  69. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  70. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  71. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  72. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  73. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  74. package/.agents/scripts/lib/config/github.js +0 -1
  75. package/.agents/scripts/lib/config/limits.js +3 -48
  76. package/.agents/scripts/lib/config/qa.js +105 -0
  77. package/.agents/scripts/lib/config/runners.js +38 -16
  78. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  79. package/.agents/scripts/lib/config-settings-schema-delivery.js +245 -56
  80. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  81. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  82. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  83. package/.agents/scripts/lib/crap-utils.js +84 -520
  84. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  85. package/.agents/scripts/lib/degraded-mode.js +2 -2
  86. package/.agents/scripts/lib/dependency-parser.js +20 -7
  87. package/.agents/scripts/lib/doc-tiers.js +3 -3
  88. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  89. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  90. package/.agents/scripts/lib/findings/provenance-field.js +135 -0
  91. package/.agents/scripts/lib/findings/route-finding.js +57 -8
  92. package/.agents/scripts/lib/fs-walk.js +52 -0
  93. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  94. package/.agents/scripts/lib/git-utils.js +16 -36
  95. package/.agents/scripts/lib/knip-config-resolver.js +181 -0
  96. package/.agents/scripts/lib/knip-entry-sync.js +508 -0
  97. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  98. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  99. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  100. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  101. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  102. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  103. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  104. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  105. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  106. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  107. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  108. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  109. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  110. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  111. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  112. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  113. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +7 -26
  114. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +69 -5
  115. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +69 -13
  116. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +49 -0
  117. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  118. package/.agents/scripts/lib/orchestration/resolve-stories.js +77 -50
  119. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  120. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  121. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  122. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  123. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  124. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  125. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  126. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  127. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  128. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  129. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  130. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  131. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
  132. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  133. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  134. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  135. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  136. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  137. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  138. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +116 -1
  139. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  140. package/.agents/scripts/lib/orchestration/ticket-validator.js +38 -0
  141. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  142. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  143. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  144. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  145. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  146. package/.agents/scripts/lib/signals/index.js +8 -6
  147. package/.agents/scripts/lib/signals/schema.js +20 -25
  148. package/.agents/scripts/lib/signals/write.js +8 -8
  149. package/.agents/scripts/lib/story-body/footer-block.js +97 -0
  150. package/.agents/scripts/lib/story-body/story-body.js +18 -81
  151. package/.agents/scripts/lib/temp-retention.js +1 -1
  152. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  153. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  154. package/.agents/scripts/lib/wave-runner/footprint.js +306 -0
  155. package/.agents/scripts/lib/wave-runner/ready-set.js +198 -181
  156. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  157. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  158. package/.agents/scripts/lib/worktree-manager.js +2 -3
  159. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  160. package/.agents/scripts/pr-watch-with-update.js +7 -5
  161. package/.agents/scripts/providers/github/blocked-by-add.js +25 -10
  162. package/.agents/scripts/providers/github/cache.js +2 -2
  163. package/.agents/scripts/providers/github/comments.js +6 -28
  164. package/.agents/scripts/providers/github/compose.js +0 -15
  165. package/.agents/scripts/providers/github/errors.js +10 -27
  166. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  167. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  168. package/.agents/scripts/providers/github.js +4 -7
  169. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  170. package/.agents/scripts/resolve-stories.js +21 -7
  171. package/.agents/scripts/run-lint.js +61 -61
  172. package/.agents/scripts/run-test-profile.js +6 -6
  173. package/.agents/scripts/run-verify.js +48 -30
  174. package/.agents/scripts/single-story-close.js +20 -0
  175. package/.agents/scripts/single-story-init.js +12 -35
  176. package/.agents/scripts/stories-wave-tick.js +192 -9
  177. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  178. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  179. package/.agents/skills/skills.index.json +1 -11
  180. package/.agents/workflows/audit-documentation.md +5 -6
  181. package/.agents/workflows/audit-to-stories.md +28 -2
  182. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  183. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  184. package/.agents/workflows/helpers/code-review.md +3 -8
  185. package/.agents/workflows/helpers/deliver-reference.md +30 -2
  186. package/.agents/workflows/helpers/deliver-story-reference.md +84 -16
  187. package/.agents/workflows/helpers/plan-reference.md +76 -0
  188. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  189. package/.agents/workflows/mandrel-update.md +10 -10
  190. package/.agents/workflows/qa-assist.md +15 -20
  191. package/.agents/workflows/qa-explore.md +9 -8
  192. package/README.md +1 -1
  193. package/docs/CHANGELOG.md +58 -0
  194. package/lib/migrations/index.js +2 -0
  195. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  196. package/package.json +14 -5
  197. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  198. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  199. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  200. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  201. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  202. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  203. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  204. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  205. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  206. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  207. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  208. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  209. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  210. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  211. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  212. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  213. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  214. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  215. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  216. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  217. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  218. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  219. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  220. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  221. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  222. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  223. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  224. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  225. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  226. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  227. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  228. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  229. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  230. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  231. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  232. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  233. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  234. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  235. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  236. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  237. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  238. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  239. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  240. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  241. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  242. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  243. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  244. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  245. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  246. package/.agents/scripts/lib/planning-corpus.js +0 -37
  247. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  248. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  249. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  250. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  251. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  252. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  253. package/.agents/scripts/lib/signals/read.js +0 -268
  254. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  255. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  256. package/.agents/scripts/lib/story-plan.js +0 -379
  257. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  258. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  259. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  260. package/.agents/scripts/lint-baseline.js +0 -507
  261. package/.agents/scripts/providers/github/prs.js +0 -103
  262. package/.agents/scripts/signals-view.js +0 -309
  263. package/.agents/scripts/story-plan.js +0 -370
  264. package/.agents/scripts/sync-branch-from-base.js +0 -149
  265. package/.agents/scripts/validate-docs-freshness.js +0 -314
  266. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  267. package/.agents/workflows/helpers/signals.md +0 -112
@@ -2,30 +2,37 @@
2
2
  /* node:coverage ignore file */
3
3
 
4
4
  /**
5
- * check-lifecycle-lint.js — enforce the three Tech Spec lint rules for
6
- * the lifecycle bus surface that biome's stock ruleset cannot express.
5
+ * check-lifecycle-lint.js — enforce the lifecycle lint rules that biome's
6
+ * stock ruleset cannot express.
7
7
  *
8
- * Rule 1 — "No Promise.all over listener arrays".
9
- * Files under `.agents/scripts/lib/orchestration/lifecycle/**` (the bus
10
- * + listeners surface) MUST NOT contain `Promise.all(`. The bus is a
11
- * strictly sequential mediator; parallelizing listeners breaks
12
- * repeatability and idempotency by definition. Tests under
13
- * `tests/lifecycle/**` are exempt — fixtures that prove the rule
8
+ * Rule 1 — "No Promise.all over the lifecycle surface".
9
+ * Files under `.agents/scripts/lib/orchestration/lifecycle/**` MUST NOT
10
+ * contain `Promise.all(`. The surface is the append-only ledger writer, and
11
+ * record ordering is the whole point of an append-only trail: a parallel
12
+ * write interleaves records and breaks the ordering a reader relies on.
13
+ * Tests under `tests/lifecycle/**` are exempt — fixtures that prove the rule
14
14
  * bites need to carry the pattern.
15
15
  *
16
- * Rule 2 "Wildcard-observer firewall".
17
- * Any module under `.agents/scripts/lib/orchestration/lifecycle/listeners/**`
18
- * that calls `bus.on('*', …)` MUST NOT import a side-effecting module.
19
- * The static blocklist is small (the modules that mutate GitHub state,
20
- * the worktree, or write outside `temp/run-<id>/`); we match by module
21
- * specifier suffix to keep the rule simple and stable.
16
+ * (Story #5024 narrowed the rationale. It was "the bus is a strictly
17
+ * sequential mediator; parallelizing listeners breaks repeatability", which
18
+ * stopped being the reason when the bus was deleted. The rule still guards a
19
+ * real property of what remains.)
20
+ *
21
+ * The old Rule 2 "wildcard-observer firewall", gating any module under
22
+ * `lifecycle/listeners/**` that called `bus.on('*', …)` — was deleted by
23
+ * Story #5024 along with the bus. Both halves of its predicate became
24
+ * unsatisfiable in the same commit (the directory is gone and there is no
25
+ * `bus` to call `.on` on), so it returned zero violations by construction
26
+ * rather than by verification: a gate that cannot fire, of exactly the class
27
+ * Story #5004 retired `check-gherkin-placeholders.js` for.
22
28
  *
23
29
  * Rule 3 — "Auto-merge lockout" (Story #2253 / Task #2255, Epic #2172
24
30
  * review High-1).
25
31
  * String literals containing the substring `gh pr merge` MUST NOT
26
32
  * appear in any file under `.agents/scripts/**` EXCEPT
27
- * `.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js`
28
- * (Wave 7, Story #2256). The original safety hole was an
33
+ * `lib/orchestration/single-story-close/phases/auto-merge.js` — the v2
34
+ * close path that replaced the Epic-era `AutomergeArmer` listener the rule
35
+ * was originally written against. The original safety hole was an
29
36
  * unconditional `gh pr merge <pr> --auto --squash --delete-branch`
30
37
  * call in `epic-deliver-finalize.js` that armed GitHub's native
31
38
  * auto-merge BEFORE the framework's automerge predicate evaluated
@@ -39,6 +46,23 @@
39
46
  * The exempt path is matched by suffix so it bites even before the
40
47
  * armer file lands (Wave 7); pre-existence is not required.
41
48
  *
49
+ * Scan root:
50
+ * --root <dir> Scan <dir> instead of this checkout. Both surfaces are
51
+ * derived from it, so one flag moves the whole scan.
52
+ * Defaults to the repository this script ships in, which is
53
+ * what `npm run lint` gets by passing nothing.
54
+ *
55
+ * The seam exists for tests (Story #5052). The CLI test that proves a
56
+ * violation is *caught* has to plant one, and it used to plant into the
57
+ * live `.agents/` tree — which `tests/e2e/sync-prune.integration.test.js`
58
+ * copies with the real binary, so the two raced: the sync either lost the
59
+ * file between enumeration and `copyfile` (ENOENT) or copied it once and
60
+ * pruned it on the second pass, tripping an idempotence assertion. Pointing
61
+ * the planting test at a temp root keeps what that test was written for —
62
+ * discovery is still the CLI's own walk, not an injected file list — while
63
+ * leaving the shared tree untouched. Mirrors the `--root` seam
64
+ * `check-test-temp-hygiene.js` already ships for the same reason.
65
+ *
42
66
  * Exit codes:
43
67
  * 0 — clean.
44
68
  * 1 — at least one violation; offending file + line printed to stderr.
@@ -48,25 +72,32 @@
48
72
  * biome + markdownlint; a custom rule fits cleanly alongside.
49
73
  */
50
74
 
51
- import { readdirSync, readFileSync } from 'node:fs';
75
+ import { readFileSync } from 'node:fs';
52
76
  import path from 'node:path';
53
77
  import { fileURLToPath } from 'node:url';
54
78
 
55
79
  import { runAsCli } from './lib/cli-utils.js';
80
+ import { walkFilesByExtension } from './lib/fs-walk.js';
56
81
 
57
82
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
58
83
  const REPO_ROOT = path.resolve(__dirname, '..', '..');
59
84
 
60
- const LIFECYCLE_DIR = path.join(
61
- REPO_ROOT,
62
- '.agents',
63
- 'scripts',
64
- 'lib',
65
- 'orchestration',
66
- 'lifecycle',
67
- );
68
- const LISTENERS_SUBDIR = path.join(LIFECYCLE_DIR, 'listeners');
69
- const SCRIPTS_DIR = path.join(REPO_ROOT, '.agents', 'scripts');
85
+ /** Rule 1's scan surface for a given repository root. */
86
+ function lifecycleDirFor(root) {
87
+ return path.join(
88
+ root,
89
+ '.agents',
90
+ 'scripts',
91
+ 'lib',
92
+ 'orchestration',
93
+ 'lifecycle',
94
+ );
95
+ }
96
+
97
+ /** Rule 3's scan surface for a given repository root. */
98
+ function scriptsDirFor(root) {
99
+ return path.join(root, '.agents', 'scripts');
100
+ }
70
101
 
71
102
  /**
72
103
  * Files exempt from the merge-lockout rule. The path is matched by
@@ -102,52 +133,6 @@ const MERGE_LOCKOUT_INFRASTRUCTURE_SUFFIXES = Object.freeze([
102
133
  path.join('.agents', 'scripts', 'check-lifecycle-lint.js'),
103
134
  ]);
104
135
 
105
- /**
106
- * Static blocklist of modules that mutate state under orchestration.
107
- * Matched by `import … from '<spec>'` specifier suffix so both
108
- * relative and package imports are caught. The list is small by
109
- * intent — wildcard observers should not need ANY of these.
110
- *
111
- * Maintainers: when a future module joins the "mutates real state"
112
- * club, add it here. The lint rule is the wildcard-firewall contract;
113
- * the listeners SHOULD NOT bypass it.
114
- */
115
- const STATE_MUTATING_MODULES = Object.freeze([
116
- // GitHub state writers
117
- 'update-ticket-state.js',
118
- 'post-structured-comment.js',
119
- 'lib/orchestration/ticketing/state.js',
120
- 'lib/orchestration/ticketing/bulk.js',
121
- // git / worktree mutators
122
- 'lib/git-utils.js',
123
- 'lib/orchestration/worktree-manager.js',
124
- // notification writers
125
- 'notify.js',
126
- ]);
127
-
128
- /**
129
- * Walk a directory tree synchronously, yielding absolute paths of files
130
- * matching `.js`. The lifecycle surface is small (< 50 files in the
131
- * worst case); a streaming walker is unnecessary.
132
- */
133
- function* walkJs(dir) {
134
- let entries;
135
- try {
136
- entries = readdirSync(dir, { withFileTypes: true });
137
- } catch (err) {
138
- if (err.code === 'ENOENT') return;
139
- throw err;
140
- }
141
- for (const entry of entries) {
142
- const p = path.join(dir, entry.name);
143
- if (entry.isDirectory()) {
144
- yield* walkJs(p);
145
- } else if (entry.isFile() && entry.name.endsWith('.js')) {
146
- yield p;
147
- }
148
- }
149
- }
150
-
151
136
  /**
152
137
  * Rule 1 enforcement. Returns an array of `{ file, line, hint }`
153
138
  * violations. Inline disable comments (`// lint-lifecycle-disable`) on
@@ -158,7 +143,7 @@ export function findPromiseAllViolations(
158
143
  { read = readFileSync } = {},
159
144
  ) {
160
145
  const violations = [];
161
- for (const file of walkJs(rootDir)) {
146
+ for (const file of walkFilesByExtension(rootDir, '.js')) {
162
147
  const text = read(file, 'utf8');
163
148
  const lines = text.split('\n');
164
149
  for (let i = 0; i < lines.length; i += 1) {
@@ -169,7 +154,7 @@ export function findPromiseAllViolations(
169
154
  violations.push({
170
155
  file,
171
156
  line: i + 1,
172
- hint: 'Promise.all over listener arrays breaks bus repeatability. Listeners must run sequentially with await.',
157
+ hint: 'Promise.all on the lifecycle surface interleaves ledger writes. The ledger is append-only; writes must stay sequential.',
173
158
  });
174
159
  }
175
160
  }
@@ -177,45 +162,6 @@ export function findPromiseAllViolations(
177
162
  return violations;
178
163
  }
179
164
 
180
- /**
181
- * Rule 2 enforcement. Returns violations for any file under
182
- * `listenersDir` that BOTH (a) registers a wildcard observer
183
- * (`bus.on('*', …)`) AND (b) imports a state-mutating module.
184
- *
185
- * Files that don't register a wildcard observer are not gated; files
186
- * that wildcard-observe but only import safe modules are not gated.
187
- */
188
- export function findWildcardObserverFirewallViolations(
189
- listenersDir,
190
- { read = readFileSync, blocklist = STATE_MUTATING_MODULES } = {},
191
- ) {
192
- const violations = [];
193
- for (const file of walkJs(listenersDir)) {
194
- const text = read(file, 'utf8');
195
- const hasWildcard = /\bbus\s*\.\s*on\s*\(\s*['"`]\*['"`]/.test(text);
196
- if (!hasWildcard) continue;
197
- // Extract imported module specifiers — robust enough for ES module
198
- // imports without parsing the full AST.
199
- // `matchAll` returns an iterator of regex match arrays; using it
200
- // sidesteps the assignment-in-`while` pattern biome flags as
201
- // confusing.
202
- for (const match of text.matchAll(/\bfrom\s+['"]([^'"]+)['"]/g)) {
203
- const spec = match[1];
204
- for (const banned of blocklist) {
205
- if (spec === banned || spec.endsWith(`/${banned}`)) {
206
- violations.push({
207
- file,
208
- spec,
209
- hint: `Wildcard observers must not import state-mutating modules. Saw '${spec}'.`,
210
- });
211
- break;
212
- }
213
- }
214
- }
215
- }
216
- return violations;
217
- }
218
-
219
165
  /**
220
166
  * Strip block (`/* … *​/`) and line (`// …`) comments from a source
221
167
  * string. Pure — exported for tests so the comment-stripping contract
@@ -310,7 +256,7 @@ export function findMergeLockoutViolations(
310
256
  // CLI, only the space-delimited form is.
311
257
  const FORBIDDEN = 'gh pr merge';
312
258
  const lineRe = /(['"`])((?:\\.|(?!\1).)*)\1/g;
313
- for (const file of walkJs(rootDir)) {
259
+ for (const file of walkFilesByExtension(rootDir, '.js')) {
314
260
  // Skip the armer (intentional carrier) and the lint infrastructure.
315
261
  const allExempt = [...allowSuffixes, ...infrastructureSuffixes];
316
262
  if (allExempt.some((suffix) => file.endsWith(suffix))) continue;
@@ -347,25 +293,48 @@ export function findMergeLockoutViolations(
347
293
  return violations;
348
294
  }
349
295
 
296
+ /**
297
+ * Parse the argument vector. The only option is the scan root; anything
298
+ * else is ignored so an extra flag can never silently narrow the scan.
299
+ *
300
+ * @param {string[]} argv Arguments without the node/script entries.
301
+ * @returns {{ root: string }}
302
+ */
303
+ function parseArgv(argv) {
304
+ let root = REPO_ROOT;
305
+ for (let i = 0; i < argv.length; i += 1) {
306
+ if (argv[i] === '--root') {
307
+ i += 1;
308
+ root = path.resolve(String(argv[i] ?? '.'));
309
+ }
310
+ }
311
+ return { root };
312
+ }
313
+
350
314
  async function main() {
351
- // Per-rule discovery.
352
- const v1 = findPromiseAllViolations(LIFECYCLE_DIR);
353
- const v2 = findWildcardObserverFirewallViolations(LISTENERS_SUBDIR);
354
- const v3 = findMergeLockoutViolations(SCRIPTS_DIR);
315
+ const { root } = parseArgv(process.argv.slice(2));
316
+ // Per-rule discovery. Both rules' exemptions match by absolute-path
317
+ // SUFFIX, which is what lets an injected root keep the same allow-list
318
+ // semantics as the live tree — do not re-anchor them to `root`.
319
+ const v1 = findPromiseAllViolations(lifecycleDirFor(root));
320
+ const v3 = findMergeLockoutViolations(scriptsDirFor(root));
355
321
  const all = [
356
- ...v1.map((v) => ({ rule: 'no-promise-all-listeners', ...v })),
357
- ...v2.map((v) => ({ rule: 'wildcard-observer-firewall', ...v })),
322
+ ...v1.map((v) => ({ rule: 'no-promise-all-lifecycle', ...v })),
358
323
  ...v3.map((v) => ({ rule: 'merge-lockout', ...v })),
359
324
  ];
360
325
  if (all.length === 0) {
361
326
  process.stdout.write(
362
- '[lifecycle-lint] clean: no Promise.all over listeners; no wildcard-firewall breaches; no merge-lockout violations.\n',
327
+ '[lifecycle-lint] clean: no Promise.all on the lifecycle surface; no merge-lockout violations.\n',
363
328
  );
364
329
  return 0;
365
330
  }
366
331
  for (const v of all) {
367
- const loc = v.line ? `${v.file}:${v.line}` : v.file;
368
- process.stderr.write(`[lifecycle-lint][${v.rule}] ${loc}\n ${v.hint}\n`);
332
+ // Both finders always stamp a 1-based `line`, so there is no file-only
333
+ // fallback to render — the ternary that used to guard this was an
334
+ // unreachable branch (Story #5024).
335
+ process.stderr.write(
336
+ `[lifecycle-lint][${v.rule}] ${v.file}:${v.line}\n ${v.hint}\n`,
337
+ );
369
338
  }
370
339
  return 1;
371
340
  }
@@ -373,4 +342,19 @@ async function main() {
373
342
  await runAsCli(import.meta.url, main, {
374
343
  source: 'check-lifecycle-lint',
375
344
  propagateExitCode: true,
345
+ usage: {
346
+ invocation: 'node .agents/scripts/check-lifecycle-lint.js [--root <dir>]',
347
+ summary:
348
+ 'Enforce the two lifecycle lint rules biome cannot express: no Promise.all on the append-only lifecycle surface, and the auto-merge lockout on string literals under .agents/scripts/.',
349
+ flags: [
350
+ [
351
+ '--root <dir>',
352
+ 'Repository root to scan (default: the checkout this script ships in). Both rule surfaces derive from it; used by tests so a planted violation never touches the shared tree.',
353
+ ],
354
+ ],
355
+ notes: [
356
+ 'Ships as part of `npm run lint`, which invokes it with no arguments.',
357
+ 'Exit codes:\n 0 clean\n 1 at least one violation; offending file and line printed to stderr',
358
+ ],
359
+ },
376
360
  });
@@ -36,7 +36,7 @@
36
36
  * `` `${key}.schema.json` `` template), AND the
37
37
  * schema's stem appears literally in a JS source as the
38
38
  * key that would produce it. `lifecycle/` is loaded this
39
- * way by `lib/orchestration/lifecycle/bus.js`, and
39
+ * way by `lifecycle/emit-ledger-event.js`, and
40
40
  * `baselines/` by `lib/baselines/envelope.js`.
41
41
  *
42
42
  * BOTH halves are required. The directory evidence alone
@@ -36,6 +36,7 @@
36
36
  import { spawnSync } from 'node:child_process';
37
37
  import crypto from 'node:crypto';
38
38
  import { constants as osConstants } from 'node:os';
39
+ import { INTERCEPTOR_MAX_BUFFER_BYTES } from './lib/child-exec.js';
39
40
  import { getLimits, resolveConfig } from './lib/config-resolver.js';
40
41
  import { Logger } from './lib/Logger.js';
41
42
  import { appendSignal } from './lib/observability/signals-writer.js';
@@ -291,10 +292,12 @@ export async function main(args = process.argv.slice(2)) {
291
292
  const config = resolveConfig();
292
293
  const limits = getLimits(config);
293
294
  const executionTimeoutMs = limits.executionTimeoutMs;
294
- // Hardcoded post-reshape (Epic #1720 Story #1739). Node `spawnSync`
295
- // buffer ceiling 10 MiB is the framework-wide value used by every
296
- // child-process spawn site. An OOM symptom, not a domain knob.
297
- const executionMaxBuffer = 10485760;
295
+ // The interceptor's bound is deliberately *below* the framework-wide
296
+ // `MAX_BUFFER_BYTES` ceiling: it is a reported policy bound whose value the
297
+ // emitted friction row carries (`details.executionMaxBuffer`), not an
298
+ // overflow guard. Story #5009 moved its definition to the shared
299
+ // child-process surface so it is no longer a hand-copied literal here.
300
+ const executionMaxBuffer = INTERCEPTOR_MAX_BUFFER_BYTES;
298
301
 
299
302
  const commandStr = cmdArgs.join(' ');
300
303
  Logger.error(`[Diagnostic Interceptor] Executing: ${commandStr}`);