mandrel 2.0.0 → 2.1.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 (311) hide show
  1. package/.agents/README.md +59 -28
  2. package/.agents/agents/acceptance-critic.md +9 -7
  3. package/.agents/agents/story-worker.md +41 -46
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +51 -44
  6. package/.agents/docs/agentrc-reference.json +8 -13
  7. package/.agents/docs/configuration.md +32 -56
  8. package/.agents/docs/execution-reference.md +39 -10
  9. package/.agents/docs/quality-gates.md +14 -16
  10. package/.agents/docs/workflows.md +6 -6
  11. package/.agents/instructions.md +64 -79
  12. package/.agents/rules/ci-remediation.md +3 -3
  13. package/.agents/rules/git-conventions-reference.md +42 -51
  14. package/.agents/schemas/agentrc.schema.json +34 -45
  15. package/.agents/schemas/audit-rules.json +59 -1
  16. package/.agents/schemas/audit-rules.schema.json +33 -1
  17. package/.agents/schemas/lifecycle/README.md +1 -2
  18. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
  20. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
  21. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  22. package/.agents/schemas/signal-event.schema.json +3 -3
  23. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  24. package/.agents/schemas/validation-evidence.schema.json +1 -1
  25. package/.agents/scripts/acceptance-eval.js +22 -66
  26. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  27. package/.agents/scripts/bootstrap.js +3 -3
  28. package/.agents/scripts/check-dead-exports.js +43 -104
  29. package/.agents/scripts/check-doc-links.js +2 -2
  30. package/.agents/scripts/check-lifecycle-lint.js +1 -1
  31. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  32. package/.agents/scripts/deliver-recover.js +122 -0
  33. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  34. package/.agents/scripts/evidence-gate.js +20 -50
  35. package/.agents/scripts/generate-skills-index.js +17 -1
  36. package/.agents/scripts/generate-workflows-doc.js +4 -4
  37. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  38. package/.agents/scripts/lib/audit-suite/selector.js +323 -23
  39. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
  40. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  41. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  42. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  43. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  44. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
  45. package/.agents/scripts/lib/checks/index.js +1 -1
  46. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  47. package/.agents/scripts/lib/checks/state.js +17 -248
  48. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
  49. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  50. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  51. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  52. package/.agents/scripts/lib/cli-args.js +23 -2
  53. package/.agents/scripts/lib/close-validation/gates.js +13 -13
  54. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  55. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  56. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  57. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  58. package/.agents/scripts/lib/config/delivery-routing.js +7 -6
  59. package/.agents/scripts/lib/config/explain.js +10 -16
  60. package/.agents/scripts/lib/config/github.js +7 -5
  61. package/.agents/scripts/lib/config/limits.js +15 -25
  62. package/.agents/scripts/lib/config/quality.js +11 -14
  63. package/.agents/scripts/lib/config/runners.js +8 -21
  64. package/.agents/scripts/lib/config/temp-paths.js +18 -56
  65. package/.agents/scripts/lib/config-settings-schema-delivery.js +31 -13
  66. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
  67. package/.agents/scripts/lib/config-settings-schema.js +48 -22
  68. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  69. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  70. package/.agents/scripts/lib/duplicate-search.js +38 -7
  71. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  72. package/.agents/scripts/lib/format-generated-json.js +97 -0
  73. package/.agents/scripts/lib/framework-version.js +19 -189
  74. package/.agents/scripts/lib/gh-exec.js +8 -0
  75. package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
  76. package/.agents/scripts/lib/git-utils.js +0 -14
  77. package/.agents/scripts/lib/json-utils.js +1 -2
  78. package/.agents/scripts/lib/label-constants.js +0 -15
  79. package/.agents/scripts/lib/label-taxonomy.js +1 -12
  80. package/.agents/scripts/lib/observability/active-story-env.js +42 -163
  81. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  82. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  83. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  84. package/.agents/scripts/lib/observability/source-classifier.js +2 -2
  85. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  86. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  87. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  88. package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
  89. package/.agents/scripts/lib/orchestration/code-review.js +58 -168
  90. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
  91. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  92. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  93. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  94. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  95. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  96. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
  97. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  98. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  99. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
  100. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  101. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  102. package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
  103. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  104. package/.agents/scripts/lib/orchestration/plan-context.js +114 -24
  105. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
  106. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +3 -7
  107. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  108. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
  109. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
  110. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  111. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -75
  112. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
  113. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
  114. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  115. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
  116. package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
  117. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  118. package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
  119. package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
  120. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  121. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  122. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  123. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  124. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  125. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  126. package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
  127. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
  128. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  129. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  130. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +3 -12
  131. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  132. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
  133. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  134. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  135. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
  136. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  137. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  138. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
  139. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  140. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  141. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -32
  142. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  143. package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
  144. package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
  145. package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
  146. package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
  147. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  148. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
  149. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
  150. package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
  151. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
  152. package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
  153. package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
  154. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  155. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  156. package/.agents/scripts/lib/planning-corpus.js +12 -286
  157. package/.agents/scripts/lib/preflight-runner.js +2 -2
  158. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  159. package/.agents/scripts/lib/signals/index.js +4 -17
  160. package/.agents/scripts/lib/signals/read.js +35 -35
  161. package/.agents/scripts/lib/signals/schema.js +8 -11
  162. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  163. package/.agents/scripts/lib/signals/write.js +0 -1
  164. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  165. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  166. package/.agents/scripts/lib/story-adjacency.js +8 -7
  167. package/.agents/scripts/lib/story-body/story-body.js +6 -5
  168. package/.agents/scripts/lib/templates/decomposer-prompts.js +7 -3
  169. package/.agents/scripts/lib/test-env.js +14 -1
  170. package/.agents/scripts/lib/test-tiers.js +0 -3
  171. package/.agents/scripts/lib/ticket-body-sections.js +0 -14
  172. package/.agents/scripts/lib/validation-evidence.js +31 -59
  173. package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
  174. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  175. package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
  176. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  177. package/.agents/scripts/plan-context.js +38 -6
  178. package/.agents/scripts/plan-persist.js +145 -35
  179. package/.agents/scripts/plan-run-epilogue.js +83 -38
  180. package/.agents/scripts/post-structured-comment.js +0 -38
  181. package/.agents/scripts/pr-watch-with-update.js +43 -22
  182. package/.agents/scripts/providers/github/compose.js +0 -1
  183. package/.agents/scripts/providers/github/errors.js +0 -19
  184. package/.agents/scripts/providers/github/issues.js +1 -11
  185. package/.agents/scripts/providers/github/mappers.js +5 -0
  186. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  187. package/.agents/scripts/providers/github/tickets.js +33 -153
  188. package/.agents/scripts/providers/github.js +17 -6
  189. package/.agents/scripts/resolve-stories.js +236 -0
  190. package/.agents/scripts/run-coverage.js +4 -1
  191. package/.agents/scripts/run-lint.js +2 -2
  192. package/.agents/scripts/run-verify.js +31 -2
  193. package/.agents/scripts/signals-view.js +9 -10
  194. package/.agents/scripts/single-story-close.js +173 -18
  195. package/.agents/scripts/single-story-confirm-merge.js +288 -15
  196. package/.agents/scripts/single-story-init.js +6 -10
  197. package/.agents/scripts/stories-wave-tick.js +79 -4
  198. package/.agents/scripts/story-plan.js +3 -3
  199. package/.agents/scripts/update-ticket-state.js +8 -50
  200. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  201. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  202. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  203. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  204. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  205. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  206. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  207. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  208. package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
  209. package/.agents/skills/core/scope-triage/SKILL.md +3 -0
  210. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  211. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  212. package/.agents/skills/skills.index.json +2 -12
  213. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  214. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  215. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  216. package/.agents/workflows/audit-architecture.md +3 -4
  217. package/.agents/workflows/audit-clean-code.md +4 -4
  218. package/.agents/workflows/audit-documentation.md +4 -5
  219. package/.agents/workflows/audit-lighthouse.md +8 -0
  220. package/.agents/workflows/audit-navigability.md +10 -0
  221. package/.agents/workflows/audit-performance.md +2 -3
  222. package/.agents/workflows/audit-quality.md +8 -9
  223. package/.agents/workflows/audit-security.md +1 -2
  224. package/.agents/workflows/audit-seo.md +10 -0
  225. package/.agents/workflows/audit-ux-ui.md +7 -0
  226. package/.agents/workflows/deliver.md +98 -45
  227. package/.agents/workflows/git-cleanup.md +2 -2
  228. package/.agents/workflows/git-deliver.md +1 -1
  229. package/.agents/workflows/helpers/acceptance-self-eval.md +21 -13
  230. package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
  231. package/.agents/workflows/helpers/code-review.md +12 -10
  232. package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
  233. package/.agents/workflows/helpers/deliver-story.md +193 -118
  234. package/.agents/workflows/helpers/parallel-tooling.md +2 -2
  235. package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
  236. package/.agents/workflows/plan.md +184 -19
  237. package/.agents/workflows/qa-assist.md +6 -6
  238. package/.agents/workflows/qa-explore.md +3 -3
  239. package/.agents/workflows/qa-run.md +1 -5
  240. package/bin/mandrel.js +12 -1
  241. package/docs/CHANGELOG.md +40 -0
  242. package/lib/cli/registry.js +262 -19
  243. package/lib/cli/sync-agents.js +157 -0
  244. package/lib/cli/sync-commands.js +115 -6
  245. package/lib/cli/sync.js +168 -6
  246. package/lib/cli/update.js +105 -8
  247. package/lib/cli/version-helpers.js +131 -0
  248. package/lib/migrations/README.md +7 -5
  249. package/lib/migrations/index.js +12 -9
  250. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  251. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  252. package/package.json +1 -1
  253. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  254. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  255. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  256. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  257. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  258. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  259. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  260. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  261. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  262. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  263. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  264. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  265. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  266. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  267. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  268. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  269. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  270. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  271. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  272. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  273. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  274. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  275. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  276. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  277. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  278. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  279. package/.agents/schemas/risk-verdict.schema.json +0 -53
  280. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  281. package/.agents/scripts/analyze-execution.js +0 -444
  282. package/.agents/scripts/check-prepush-recovery.js +0 -90
  283. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  284. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  285. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
  286. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  287. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  288. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  289. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
  290. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
  291. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  292. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  293. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  294. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  295. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  296. package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
  297. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  298. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  299. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  300. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
  301. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  302. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  303. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  304. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
  305. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  306. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
  307. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  308. package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
  309. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
  310. package/.agents/scripts/resolve-plan-run.js +0 -117
  311. package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
@@ -10,8 +10,8 @@
10
10
  * + `schema`; `buildSpanTree` was a throwing placeholder.
11
11
  * - Epic #1181 / Story #1440 / Task #1461 — placeholder replaced by
12
12
  * the real export from `./span-tree.js`.
13
- * - Story #1476 — `appendSignal` / `appendTrace` / `appendEpicSignal`
14
- * re-exported here via `./write.js` so the gate scripts (and any
13
+ * - Story #1476 — `appendSignal` / `appendTrace` re-exported here
14
+ * via `./write.js` so the gate scripts (and any
15
15
  * new code) converge on `lib/signals/` instead of importing the
16
16
  * writer directly from `lib/observability/`.
17
17
  *
@@ -21,19 +21,6 @@
21
21
  import { read } from './read.js';
22
22
  import * as schema from './schema.js';
23
23
  import { buildSpanTree } from './span-tree.js';
24
- import {
25
- appendEpicSignal,
26
- appendSignal,
27
- appendTrace,
28
- forEachLine,
29
- } from './write.js';
24
+ import { appendSignal, appendTrace, forEachLine } from './write.js';
30
25
 
31
- export {
32
- appendEpicSignal,
33
- appendSignal,
34
- appendTrace,
35
- buildSpanTree,
36
- forEachLine,
37
- read,
38
- schema,
39
- };
26
+ export { appendSignal, appendTrace, buildSpanTree, forEachLine, read, schema };
@@ -1,18 +1,18 @@
1
1
  /**
2
2
  * Streaming signals reader (Epic #1181 / Story #1438 / Task #1459).
3
3
  *
4
- * Provides one async-iterator entry point — `read({ epic, story?, kind?,
4
+ * Provides one async-iterator entry point — `read({ run, story?, kind?,
5
5
  * config? })` — that consumers use instead of opening the NDJSON file
6
6
  * themselves. The reader streams line-by-line so callers never load the
7
7
  * full file into memory (50MB+ traces stay under file size in RSS).
8
8
  *
9
9
  * On-disk layout (resolved via `lib/config/temp-paths.js`):
10
10
  *
11
- * <tempRoot>/epic-<epic>/story-<story>/signals.ndjson
11
+ * <tempRoot>/run-<run>/story-<story>/signals.ndjson
12
12
  *
13
13
  * When `story` is omitted, the reader fans out across every
14
- * `story-<id>/signals.ndjson` under `<tempRoot>/epic-<epic>/`. When the
15
- * Epic directory is missing, the iterator yields nothing (consumers
14
+ * `story-<id>/signals.ndjson` under `<tempRoot>/run-<run>/`. When the
15
+ * run directory is missing, the iterator yields nothing (consumers
16
16
  * treat absence as "no signals yet").
17
17
  *
18
18
  * ## Filter semantics
@@ -21,7 +21,7 @@
21
21
  * argument are yielded. Filtering happens after the per-line JSON
22
22
  * parse + envelope guard (see `lib/signals/schema.js`).
23
23
  * - `story` — narrows to a single Story's stream; otherwise we walk
24
- * every Story directory under the Epic.
24
+ * every Story directory under the run.
25
25
  *
26
26
  * ## Warn-once policy (AC #3)
27
27
  *
@@ -46,8 +46,8 @@ import fs from 'node:fs/promises';
46
46
  import path from 'node:path';
47
47
  import { createInterface } from 'node:readline';
48
48
  import {
49
- epicArtifactPath,
50
- epicTempDir,
49
+ runArtifactPath,
50
+ runTempDir,
51
51
  signalsFile,
52
52
  storyTempDir,
53
53
  } from '../config/temp-paths.js';
@@ -145,43 +145,43 @@ async function* streamFile(target, kindFilter) {
145
145
  }
146
146
 
147
147
  /**
148
- * List every `stories/story-<id>/signals.ndjson` path under the Epic's
149
- * `<tempRoot>/epic-<epic>/` directory. Returns an empty array when the
150
- * Epic directory is missing.
148
+ * List every `stories/story-<id>/signals.ndjson` path under the run's
149
+ * `<tempRoot>/run-<run>/` directory. Returns an empty array when the
150
+ * run directory is missing.
151
151
  *
152
152
  * The returned paths are sorted by Story ID ascending so the iterator's
153
153
  * output is stable across runs.
154
154
  *
155
155
  * Story #2940 nested per-Story directories under a `stories/` segment.
156
- * Epic-level signals continue to live at the Epic root.
156
+ * Run-level signals continue to live at the run root.
157
157
  *
158
- * @param {number} epic
158
+ * @param {number} run
159
159
  * @param {object | undefined} config
160
160
  * @returns {Promise<string[]>}
161
161
  */
162
- async function listEpicStorySignalsFiles(epic, config) {
163
- const epicDir = epicTempDir(epic, config);
164
- let epicEntries;
162
+ async function listRunStorySignalsFiles(run, config) {
163
+ const runDir = runTempDir(run, config);
164
+ let runEntries;
165
165
  try {
166
- epicEntries = await fs.readdir(epicDir, { withFileTypes: true });
166
+ runEntries = await fs.readdir(runDir, { withFileTypes: true });
167
167
  } catch {
168
168
  return [];
169
169
  }
170
- let hasEpicLevelSignals = false;
170
+ let hasRunLevelSignals = false;
171
171
  let hasStoriesDir = false;
172
- for (const ent of epicEntries) {
172
+ for (const ent of runEntries) {
173
173
  if (ent.isDirectory() && ent.name === 'stories') {
174
174
  hasStoriesDir = true;
175
175
  } else if (ent.isFile() && ent.name === 'signals.ndjson') {
176
176
  // Story #1430 — wave-runner lifecycle signals land here.
177
- hasEpicLevelSignals = true;
177
+ hasRunLevelSignals = true;
178
178
  }
179
179
  }
180
180
  const storyIds = [];
181
181
  if (hasStoriesDir) {
182
182
  let storyEntries;
183
183
  try {
184
- storyEntries = await fs.readdir(path.join(epicDir, 'stories'), {
184
+ storyEntries = await fs.readdir(path.join(runDir, 'stories'), {
185
185
  withFileTypes: true,
186
186
  });
187
187
  } catch {
@@ -195,43 +195,43 @@ async function listEpicStorySignalsFiles(epic, config) {
195
195
  }
196
196
  }
197
197
  storyIds.sort((a, b) => a - b);
198
- // Yield epic-level signals first (wave-start precedes per-Story friction),
198
+ // Yield run-level signals first (wave-start precedes per-Story friction),
199
199
  // then walk the per-Story streams in ascending ID order.
200
- const targets = hasEpicLevelSignals
201
- ? [epicArtifactPath(epic, 'signals.ndjson', config)]
200
+ const targets = hasRunLevelSignals
201
+ ? [runArtifactPath(run, 'signals.ndjson', config)]
202
202
  : [];
203
203
  for (const sid of storyIds) {
204
- targets.push(path.join(storyTempDir(epic, sid, config), 'signals.ndjson'));
204
+ targets.push(path.join(storyTempDir(run, sid, config), 'signals.ndjson'));
205
205
  }
206
206
  return targets;
207
207
  }
208
208
 
209
209
  /**
210
- * Stream every event matching `{ epic, story?, kind? }` from the
211
- * configured `tempRoot`'s `epic-<epic>/[story-<story>/]signals.ndjson`
210
+ * Stream every event matching `{ run, story?, kind? }` from the
211
+ * configured `tempRoot`'s `run-<run>/[story-<story>/]signals.ndjson`
212
212
  * file(s).
213
213
  *
214
214
  * Returns an async iterable so callers can `for await` over it without
215
215
  * buffering. The reader is **streaming** — peak memory stays below the
216
216
  * file size for any input.
217
217
  *
218
- * @param {{ epic: number, story?: number, kind?: string, config?: object }} args
218
+ * @param {{ run: number, story?: number, kind?: string, config?: object }} args
219
219
  * @returns {AsyncGenerator<object>}
220
220
  *
221
221
  * @example
222
- * for await (const evt of read({ epic: 1181 })) { ... }
223
- * for await (const evt of read({ epic: 1181, story: 1438, kind: 'friction' })) { ... }
222
+ * for await (const evt of read({ run: 1181 })) { ... }
223
+ * for await (const evt of read({ run: 1181, story: 1438, kind: 'friction' })) { ... }
224
224
  */
225
225
  export async function* read(args) {
226
226
  if (args == null || typeof args !== 'object') {
227
227
  throw new TypeError(
228
- `signals/read: args must be an object with at minimum { epic }; got ${args}`,
228
+ `signals/read: args must be an object with at minimum { run }; got ${args}`,
229
229
  );
230
230
  }
231
- const { epic, story, kind, config } = args;
232
- if (!isPositiveInt(epic)) {
231
+ const { run, story, kind, config } = args;
232
+ if (!isPositiveInt(run)) {
233
233
  throw new RangeError(
234
- `signals/read: epic must be a positive integer (got ${epic})`,
234
+ `signals/read: run must be a positive integer (got ${run})`,
235
235
  );
236
236
  }
237
237
  if (story !== undefined && !isPositiveInt(story)) {
@@ -259,8 +259,8 @@ export async function* read(args) {
259
259
  const kindFilter = kind ?? null;
260
260
  const targets =
261
261
  story !== undefined
262
- ? [signalsFile(epic, story, config)]
263
- : await listEpicStorySignalsFiles(epic, config);
262
+ ? [signalsFile(run, story, config)]
263
+ : await listRunStorySignalsFiles(run, config);
264
264
 
265
265
  for (const target of targets) {
266
266
  yield* streamFile(target, kindFilter);
@@ -3,7 +3,7 @@
3
3
  * shape guards (Epic #1181 / Story #1438 / Task #1458).
4
4
  *
5
5
  * Both writers (`lib/observability/signals-writer.js` callers) and
6
- * readers (`lib/signals/read.js`, `lib/observability/perf-aggregator.js`)
6
+ * readers (`lib/signals/read.js`, the retro's gather-signals phase)
7
7
  * import their field names from this module so the on-disk NDJSON shape
8
8
  * stays under one schema.
9
9
  *
@@ -19,15 +19,12 @@
19
19
  *
20
20
  * Signals-writer `appendSignal` call sites:
21
21
  * - `friction` — quality-gate / runtime friction (diagnose-friction.js,
22
- * lib/gates/friction.js, auto-refresh-runner.js,
23
- * worktree-reap.js, lifecycle-emit.js)
22
+ * lib/gates/friction.js)
24
23
  * - `acceptance-eval` — acceptance-eval.js per-criterion terminus signal.
25
- * - `wave-start` / `wave-complete` — `lib/wave-runner/tick.js` (read by
26
- * perf-aggregator's `waveParallelism` report;
27
- * `wave-start` also anchors span-tree Story spans)
24
+ * - `wave-start` / `wave-complete` — wave-window anchors; `wave-start` also
25
+ * anchors span-tree Story spans
28
26
  * - `wave-end` — span-tree pairing anchor (retained for span-tree)
29
- * - `state-transition` — notification-derived window anchor read by the
30
- * waveParallelism bucketer.
27
+ * - `state-transition` — notification-derived window anchor.
31
28
  *
32
29
  * The `dispatched` kind was deleted in the Epic #4406 signal-contract
33
30
  * cutover — it had no live emitter and no consumer.
@@ -80,9 +77,9 @@ export const EVENT_KINDS = Object.freeze({
80
77
  FRICTION: 'friction',
81
78
  TRACE: 'trace',
82
79
  // Wave-window forensics signals: `wave-start` / `wave-end` anchor the
83
- // span-tree's Story spans, and the perf-aggregator brackets each wave's
84
- // wall-clock from `wave-start` → `wave-complete` (the `waveParallelism`
85
- // report). These are READ they survive.
80
+ // span-tree's Story spans. Story #4545 deleted the `waveParallelism`
81
+ // consumer (perf-aggregator) with the execution-analysis surface; these
82
+ // kinds stay as the span-tree's anchors.
86
83
  WAVE_START: 'wave-start',
87
84
  WAVE_END: 'wave-end',
88
85
  WAVE_COMPLETE: 'wave-complete',
@@ -192,15 +192,15 @@ function bootstrapStoryWindow(storyNode, ts) {
192
192
  * from `buildSpanTree` so the outer function's CRAP score stays under
193
193
  * the new-method ceiling.
194
194
  *
195
- * @param {object} state — mutable accumulator (stories, tasksByStory, epic)
195
+ * @param {object} state — mutable accumulator (stories, tasksByStory, run)
196
196
  * @param {unknown} evt
197
197
  * @returns {void}
198
198
  */
199
199
  function ingestEvent(state, evt) {
200
200
  if (evt == null || typeof evt !== 'object') return;
201
201
 
202
- const eEpic = epicOf(evt);
203
- if (state.epic == null && eEpic != null) state.epic = eEpic;
202
+ const eRun = epicOf(evt);
203
+ if (state.run == null && eRun != null) state.run = eRun;
204
204
 
205
205
  const ts = tsOf(evt);
206
206
  const sid = storyOf(evt);
@@ -258,11 +258,11 @@ function finalizeStory(state, story) {
258
258
  * Build the span tree from an async iterable of signal events.
259
259
  *
260
260
  * @param {AsyncIterable<object> | Iterable<object>} iter
261
- * @returns {Promise<{ epic: number | null, stories: Array<object> }>}
261
+ * @returns {Promise<{ run: number | null, stories: Array<object> }>}
262
262
  *
263
263
  * @example
264
264
  * import { read, buildSpanTree } from './lib/signals/index.js';
265
- * const tree = await buildSpanTree(read({ epic: 1181 }));
265
+ * const tree = await buildSpanTree(read({ run: 1181 }));
266
266
  */
267
267
  export async function buildSpanTree(iter) {
268
268
  if (iter == null || typeof iter !== 'object') {
@@ -274,7 +274,7 @@ export async function buildSpanTree(iter) {
274
274
  // Accumulator state. `stories` keys are `storyKeyOf(sid)` strings;
275
275
  // `tasksByStory` mirrors that and holds per-task sub-maps.
276
276
  const state = {
277
- epic: null,
277
+ run: null,
278
278
  stories: new Map(),
279
279
  tasksByStory: new Map(),
280
280
  };
@@ -287,5 +287,5 @@ export async function buildSpanTree(iter) {
287
287
  storyEntries.sort(compareNodeIds);
288
288
  for (const story of storyEntries) finalizeStory(state, story);
289
289
 
290
- return { epic: state.epic, stories: storyEntries };
290
+ return { run: state.run, stories: storyEntries };
291
291
  }
@@ -12,7 +12,6 @@
12
12
  */
13
13
 
14
14
  export {
15
- appendEpicSignal,
16
15
  appendSignal,
17
16
  appendTrace,
18
17
  forEachLine,
@@ -41,7 +41,13 @@ export async function flipLabelAndNotify({
41
41
  config,
42
42
  progress,
43
43
  }) {
44
- const labelFlipped = await flipLabel(provider, storyId, story, progress);
44
+ const labelFlipped = await flipLabel(
45
+ provider,
46
+ storyId,
47
+ story,
48
+ progress,
49
+ config,
50
+ );
45
51
  if (!labelFlipped) return;
46
52
  await fireStoryClosingNotify({
47
53
  notifyFn: notifyFn ?? defaultNotify,
@@ -55,7 +61,7 @@ export async function flipLabelAndNotify({
55
61
  });
56
62
  }
57
63
 
58
- async function flipLabel(provider, storyId, story, progress) {
64
+ async function flipLabel(provider, storyId, story, progress, config) {
59
65
  try {
60
66
  // Story #3385 — flip to `agent::closing`, NOT `agent::done`. The
61
67
  // canonical mutator (`transitionTicketState`) only closes the GitHub
@@ -81,8 +87,13 @@ async function flipLabel(provider, storyId, story, progress) {
81
87
  // notification that `transitionTicketState` would dispatch is
82
88
  // redundant with the typed `story-closing` event that
83
89
  // `fireStoryClosingNotify` emits immediately afterwards.
90
+ //
91
+ // `config` is threaded so the on-disk board-metadata cache (issue #4555)
92
+ // lands under the project's configured `tempRoot` rather than the
93
+ // framework default, matching every other transition call site.
84
94
  await transitionTicketState(provider, storyId, STATE_LABELS.CLOSING, {
85
95
  ticketSnapshot: story,
96
+ config,
86
97
  });
87
98
  progress?.('LABELS', `🏷️ Story #${storyId} → agent::closing`);
88
99
  return true;
@@ -30,6 +30,8 @@ const FRONTMATTER_DELIMITER = '---';
30
30
  const POLICY_HEADING_RE = /^## Policy Capsule\s*$/;
31
31
  const ANY_H2_RE = /^## /;
32
32
  const BULLET_RE = /^- /;
33
+ /** An indented (wrapped) continuation of the bullet above it. */
34
+ const CONTINUATION_RE = /^\s+\S/;
33
35
 
34
36
  /**
35
37
  * Repo root resolver. Walks up from the SKILL.md path until we find a
@@ -85,8 +87,14 @@ function extractFrontmatterBlock(lines, skillPath) {
85
87
  /**
86
88
  * Scan the body for the '## Policy Capsule' heading and count contiguous
87
89
  * top-level '- ' bullets that follow it, stopping at the next '## ' heading
88
- * or end-of-file. Blank lines inside the bullet run do not reset the count;
89
- * a non-bullet, non-blank line does.
90
+ * or end-of-file. Blank lines inside the bullet run do not reset the count.
91
+ *
92
+ * An **indented** continuation line belongs to the bullet above it — capsule
93
+ * bullets routinely wrap at the document's prose width — so it neither counts
94
+ * as a new bullet nor terminates the run. Only a **flush-left**, non-blank,
95
+ * non-bullet line after the run terminates it (Story #4546: treating any
96
+ * non-bullet line as a terminator counted a wrapped 5-bullet capsule as 1 and
97
+ * tripped the floor in validate-skills.js).
90
98
  */
91
99
  function findPolicyCapsule(lines, bodyStart) {
92
100
  let headingIndex = -1;
@@ -114,8 +122,13 @@ function findPolicyCapsule(lines, bodyStart) {
114
122
  // Blank lines inside or before the bullet run are tolerated.
115
123
  continue;
116
124
  }
125
+ if (sawBulletRun && CONTINUATION_RE.test(line)) {
126
+ // Indented continuation of the bullet above — same bullet, not a
127
+ // terminator. Wrapped capsule bullets are the norm, not an error.
128
+ continue;
129
+ }
117
130
  if (sawBulletRun) {
118
- // Non-blank, non-bullet line after the bullet run terminates it.
131
+ // Flush-left, non-blank, non-bullet line after the run terminates it.
119
132
  break;
120
133
  }
121
134
  // Leading prose between heading and the first bullet is allowed.
@@ -42,16 +42,17 @@ import { parseBlockedBy } from './dependency-parser.js';
42
42
  * Story records (live ticket payloads, fixture tickets, or operator
43
43
  * DAG nodes).
44
44
  * @param {object} [opts]
45
- * @param {boolean} [opts.dropForeign=true] When true (the default,
46
- * matching pre-v2 Epic-scoped wrappers), edges pointing at ids outside
45
+ * @param {boolean} [opts.dropForeign=false] When `false` (the v2 default,
46
+ * matching the `/deliver` path `stories-wave-tick.js` and the
47
+ * `selectReadySet` core), the operator-DAG contract is preserved: a
48
+ * dependency on an id absent from the input is treated as not-yet-done
49
+ * and withholds the dependent until it completes. When `true` (the
50
+ * pre-v2 Epic-scoped-wrapper semantics), edges pointing at ids outside
47
51
  * the supplied story set are dropped so the DAG stays closed over the
48
- * scheduled set. The v2 `/deliver` path (`stories-wave-tick.js` and the
49
- * `selectReadySet` core) passes `false` to preserve the operator-DAG
50
- * contract, where a dependency on an id absent from the input is treated
51
- * as not-yet-done and withholds the dependent until it completes.
52
+ * scheduled set.
52
53
  * @returns {Map<number, number[]>}
53
54
  */
54
- export function buildStoryAdjacency(stories, { dropForeign = true } = {}) {
55
+ export function buildStoryAdjacency(stories, { dropForeign = false } = {}) {
55
56
  const records = Array.isArray(stories) ? stories : [];
56
57
  const storyIds = new Set(records.map((s) => Number(s?.id ?? s?.number)));
57
58
  const adjacency = new Map();
@@ -105,10 +105,9 @@ import { FILE_ASSUMPTION_VALUES } from '../orchestration/file-assumption-enum.js
105
105
 
106
106
  /**
107
107
  * @typedef {object} SerializeOptions
108
- * @property {boolean} [includeFooter=false] - Include `---\nparent/epic/blocked-by` footer.
108
+ * @property {boolean} [includeFooter=false] - Include `---\nparent/blocked-by` footer.
109
109
  * @property {object} [footer] - Footer fields when `includeFooter` is true.
110
110
  * @property {number} [footer.parent] - Parent feature issue number.
111
- * @property {number} [footer.epic] - Epic issue number.
112
111
  */
113
112
 
114
113
  // ---------------------------------------------------------------------------
@@ -967,8 +966,8 @@ function serializeAuthoredMarker(body) {
967
966
  }
968
967
 
969
968
  /**
970
- * Build the optional `---` footer block (`parent` / `Epic` / `blocked by`
971
- * lines). Returns the empty string when `opts.includeFooter` is falsy.
969
+ * Build the optional `---` footer block (`parent` / `blocked by` lines).
970
+ * Returns the empty string when `opts.includeFooter` is falsy.
972
971
  *
973
972
  * @param {StoryBody} body
974
973
  * @param {SerializeOptions} opts
@@ -978,7 +977,9 @@ function serializeFooter(body, opts) {
978
977
  if (!opts.includeFooter) return '';
979
978
  const footerLines = ['---'];
980
979
  if (opts.footer?.parent) footerLines.push(`parent: #${opts.footer.parent}`);
981
- if (opts.footer?.epic) footerLines.push(`Epic: #${opts.footer.epic}`);
980
+ // Story #4545 — no `Epic: #N` branch. `pr-base-guard.js` hard-refuses a
981
+ // Story body carrying that footer, so composing one here would let the
982
+ // framework generate work it would then reject at delivery.
982
983
  if (Array.isArray(body.depends_on)) {
983
984
  for (const dep of body.depends_on) {
984
985
  footerLines.push(`blocked by ${dep}`);
@@ -82,7 +82,9 @@ Your job is to turn a plan seed / Tech Spec into a Story ticket array for an AI
82
82
  - Thematic grouping is prose in the Story's folded \`## Spec\` / \`## Slicing\`, never sibling tickets for coupled work.
83
83
 
84
84
  ### LABEL CONVENTIONS:
85
- - Every ticket must have the \`type::story\` label. No other type label is allowed the retired Feature and Task tiers have no labels under this hierarchy.
85
+ - \`type::story\` is applied automatically by persist — you do not need to emit it, and no other type label is allowed (the retired Feature and Task tiers have no labels under this hierarchy).
86
+ - \`labels[]\` is **optional**. Emit it only to request an *additional* label; persist sanitizes the list before applying it.
87
+ - Do **not** emit \`agent::*\` labels — lifecycle state is runtime-owned, and persist applies \`agent::ready\` itself once every checkpoint is on the ticket.
86
88
  - Do **not** emit \`persona::*\` labels — the behavioral persona concept (and its label axis) was removed in v2.
87
89
 
88
90
  ### OUTPUT FORMAT:
@@ -97,7 +99,7 @@ You MUST respond ONLY with a valid JSON array of objects. No prose, no markdown
97
99
  "body": <string — see STORY BODY SCHEMA below>,
98
100
  "acceptance": ["<testable, observable criterion>", ...],
99
101
  "verify": ["<exact command or test path> (<tier>)", ...],
100
- "labels": ["type::story"],
102
+ "labels": ["<extra-label>"] (optional — type::story is applied automatically; omit this field unless you need an additional label),
101
103
  "depends_on": ["slug-of-blocking-dependency"] (optional array of Story slugs that block execution)
102
104
  }
103
105
  ]
@@ -107,7 +109,9 @@ You MUST respond ONLY with a valid JSON array of objects. No prose, no markdown
107
109
  ### STORY BODY SCHEMA (REQUIRED FOR EVERY STORY):
108
110
  \`body\` MUST be a **string** — the serialized markdown produced by \`serialize()\` from \`lib/story-body/story-body.js\`. Do NOT emit \`body\` as a JSON object: an object body throws \`StoryBodyParseError\` in the reconciler (Story #3302) and is discarded by the GitHub provider, producing an empty issue body. Stories are consumed by non-interactive sub-agents that must self-verify from the Story ticket alone — so the ticket must carry everything an agent needs to execute and self-verify.
109
111
 
110
- The \`acceptance[]\` and \`verify[]\` arrays live at the **top level** of the Story ticket object — that is the machine contract the validator reads. Persist syncs those arrays into the body's \`## Acceptance\` / \`## Verify\` sections so the GitHub issue stays a complete executable document. Prefer authoring the lists **once at top-level** and omitting those sections from the authored \`body\` string (or mirroring the exact same items). Do **not** invent a second criteria list inside \`## Spec\`, and do not author a separate Acceptance Spec / PRD artifact.
112
+ The \`acceptance[]\` and \`verify[]\` arrays live at the **top level** of the Story ticket object — that is the machine contract the validator reads. Author each list **once, at top level**, and **omit** the \`## Acceptance\` / \`## Verify\` sections from the authored \`body\` string: persist syncs the top-level arrays into those sections so the GitHub issue stays a complete executable document. The validator resolves both fields from the top level, so an omitted section is the expected shape, not a violation.
113
+
114
+ If you do write those sections into the \`body\` string anyway, they must mirror the top-level arrays **item for item** — persist fails closed on a disagreement rather than guessing which list is authoritative. Do **not** invent a second criteria list inside \`## Spec\`, and do not author a separate Acceptance Spec / PRD artifact.
111
115
 
112
116
  The serialized \`body\` string renders these markdown sections (in order):
113
117
 
@@ -17,12 +17,25 @@
17
17
  * the NODE_ENV band-aid was removed in Story #3342. Tests that need to
18
18
  * exercise the webhook POST inject `opts.fetchImpl` instead, so the
19
19
  * request never reaches the real network even if a URL resolves.)
20
+ * - Every `GIT_*` variable is dropped. When the suite runs inside a git
21
+ * hook (husky pre-push via the coverage-capture path), the parent git
22
+ * invocation exports `GIT_DIR` — from a linked worktree, the absolute
23
+ * `<main>/.git/worktrees/<name>` path. A test fixture's `git init`
24
+ * under that env re-initializes the shared gitdir and writes
25
+ * `core.bare=true` into the MAIN checkout's `.git/config`, breaking
26
+ * every worktree at once (#4580). Production git call sites are
27
+ * covered by `cleanGitEnv` in `git-utils.js`; this is the same scrub
28
+ * for test child processes, which may spawn git directly. Tests that
29
+ * need a `GIT_*` variable set it explicitly on their own spawn.
20
30
  *
21
31
  * @param {NodeJS.ProcessEnv} baseEnv
22
32
  * @returns {NodeJS.ProcessEnv}
23
33
  */
24
34
  export function buildWebhookSafeTestEnv(baseEnv = process.env) {
25
- const env = { ...baseEnv, NODE_ENV: baseEnv.NODE_ENV ?? 'test' };
35
+ const env = Object.fromEntries(
36
+ Object.entries(baseEnv).filter(([k]) => !k.startsWith('GIT_')),
37
+ );
38
+ env.NODE_ENV = baseEnv.NODE_ENV ?? 'test';
26
39
  if (env.MANDREL_ALLOW_TEST_WEBHOOKS !== '1') {
27
40
  delete env.NOTIFICATION_WEBHOOK_URL;
28
41
  }
@@ -12,13 +12,10 @@ import picomatch from 'picomatch';
12
12
  */
13
13
  export const INTEGRATION_INCLUDE = [
14
14
  'tests/**/*.integration.test.js',
15
- 'tests/epic-execute/epic-execute-record-wave.test.js',
16
15
  'tests/hook-chain-reflog-invariant.test.js',
17
- 'tests/push-epic-retry.test.js',
18
16
  'tests/contract/check-baselines-regression.test.js',
19
17
  'tests/contract/check-baselines-kernel-mismatch.test.js',
20
18
  'tests/integration-prime-after-sweep.test.js',
21
- 'tests/concurrency-wiring.test.js',
22
19
  'tests/scripts/git-cleanup.test.js',
23
20
  'tests/lib/checks/runner-integration.test.js',
24
21
  'tests/single-story-close-sync.test.js',
@@ -107,20 +107,6 @@ export function hasTicketSection(body, kind) {
107
107
  return locate(body, kind) !== null;
108
108
  }
109
109
 
110
- /**
111
- * Extract a managed region's content (between the markers, trimmed).
112
- * Returns `null` when the region is absent.
113
- *
114
- * @param {string} body
115
- * @param {'techSpec'|'acceptanceTable'} kind
116
- * @returns {string|null}
117
- */
118
- export function extractTicketSection(body, kind) {
119
- const loc = locate(body, kind);
120
- if (!loc) return null;
121
- return body.slice(loc.contentStart, loc.contentEnd).trim();
122
- }
123
-
124
110
  /**
125
111
  * Insert or replace a managed region, preserving every byte outside it.
126
112
  *