mandrel 2.0.0 → 2.2.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 (323) hide show
  1. package/.agents/README.md +59 -28
  2. package/.agents/agents/acceptance-critic.md +20 -9
  3. package/.agents/agents/story-worker.md +45 -48
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +60 -46
  6. package/.agents/docs/agentrc-reference.json +8 -13
  7. package/.agents/docs/configuration.md +33 -57
  8. package/.agents/docs/execution-reference.md +39 -10
  9. package/.agents/docs/quality-gates.md +17 -19
  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/gherkin-standards.md +10 -0
  14. package/.agents/rules/git-conventions-reference.md +42 -51
  15. package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
  16. package/.agents/schemas/agentrc.schema.json +35 -46
  17. package/.agents/schemas/audit-rules.json +59 -1
  18. package/.agents/schemas/audit-rules.schema.json +33 -1
  19. package/.agents/schemas/lifecycle/README.md +1 -2
  20. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  21. package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
  22. package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
  23. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  24. package/.agents/schemas/signal-event.schema.json +3 -3
  25. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  26. package/.agents/schemas/validation-evidence.schema.json +1 -1
  27. package/.agents/scripts/acceptance-eval.js +24 -68
  28. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  29. package/.agents/scripts/bootstrap.js +3 -3
  30. package/.agents/scripts/check-dead-exports.js +43 -104
  31. package/.agents/scripts/check-doc-links.js +2 -2
  32. package/.agents/scripts/check-lifecycle-lint.js +1 -1
  33. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  34. package/.agents/scripts/deliver-recover.js +122 -0
  35. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  36. package/.agents/scripts/evidence-gate.js +20 -50
  37. package/.agents/scripts/generate-skills-index.js +17 -1
  38. package/.agents/scripts/generate-workflows-doc.js +4 -4
  39. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  40. package/.agents/scripts/lib/audit-suite/selector.js +323 -23
  41. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
  42. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  43. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  44. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  45. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  46. package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
  47. package/.agents/scripts/lib/checks/index.js +1 -1
  48. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  49. package/.agents/scripts/lib/checks/state.js +17 -248
  50. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
  51. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  52. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  53. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  54. package/.agents/scripts/lib/cli-args.js +23 -2
  55. package/.agents/scripts/lib/close-validation/gates.js +13 -13
  56. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  57. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  58. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  59. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  60. package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
  61. package/.agents/scripts/lib/config/delivery-routing.js +7 -6
  62. package/.agents/scripts/lib/config/explain.js +10 -16
  63. package/.agents/scripts/lib/config/github.js +7 -5
  64. package/.agents/scripts/lib/config/limits.js +15 -25
  65. package/.agents/scripts/lib/config/quality.js +11 -14
  66. package/.agents/scripts/lib/config/runners.js +8 -21
  67. package/.agents/scripts/lib/config/temp-paths.js +18 -56
  68. package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
  69. package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
  70. package/.agents/scripts/lib/config-settings-schema.js +48 -22
  71. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  72. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  73. package/.agents/scripts/lib/duplicate-search.js +38 -7
  74. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  75. package/.agents/scripts/lib/format-generated-json.js +97 -0
  76. package/.agents/scripts/lib/framework-version.js +19 -189
  77. package/.agents/scripts/lib/gh-exec.js +8 -0
  78. package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
  79. package/.agents/scripts/lib/git-utils.js +0 -14
  80. package/.agents/scripts/lib/json-utils.js +1 -2
  81. package/.agents/scripts/lib/label-constants.js +0 -15
  82. package/.agents/scripts/lib/label-taxonomy.js +1 -12
  83. package/.agents/scripts/lib/observability/active-story-env.js +42 -163
  84. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  85. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  86. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  87. package/.agents/scripts/lib/observability/source-classifier.js +2 -2
  88. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  89. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  90. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  91. package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
  92. package/.agents/scripts/lib/orchestration/change-set.js +103 -0
  93. package/.agents/scripts/lib/orchestration/code-review.js +70 -191
  94. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
  95. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  96. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  97. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  98. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  99. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  100. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
  101. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  102. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  103. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
  104. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  105. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  106. package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
  107. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  108. package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
  109. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
  110. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
  111. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  112. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
  113. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
  114. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  115. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
  116. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
  117. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
  118. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  119. package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
  120. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
  121. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
  122. package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
  123. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  124. package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
  125. package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
  126. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  127. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  128. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  129. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  130. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  131. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  132. package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
  133. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
  134. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  135. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  136. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
  137. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  138. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
  139. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  140. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  141. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
  142. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  143. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  144. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
  145. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  146. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  147. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
  148. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
  149. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
  150. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  151. package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
  152. package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
  153. package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
  154. package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
  155. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  156. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
  157. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
  158. package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
  159. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
  160. package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
  161. package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
  162. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  163. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  164. package/.agents/scripts/lib/planning-corpus.js +12 -286
  165. package/.agents/scripts/lib/preflight-runner.js +2 -2
  166. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  167. package/.agents/scripts/lib/signals/index.js +4 -17
  168. package/.agents/scripts/lib/signals/read.js +35 -35
  169. package/.agents/scripts/lib/signals/schema.js +8 -11
  170. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  171. package/.agents/scripts/lib/signals/write.js +0 -1
  172. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  173. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  174. package/.agents/scripts/lib/story-adjacency.js +8 -7
  175. package/.agents/scripts/lib/story-body/story-body.js +81 -13
  176. package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
  177. package/.agents/scripts/lib/test-env.js +14 -1
  178. package/.agents/scripts/lib/test-tiers.js +0 -3
  179. package/.agents/scripts/lib/ticket-body-sections.js +0 -14
  180. package/.agents/scripts/lib/validation-evidence.js +31 -59
  181. package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
  182. package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
  183. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  184. package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
  185. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  186. package/.agents/scripts/plan-context.js +38 -7
  187. package/.agents/scripts/plan-critics.js +203 -0
  188. package/.agents/scripts/plan-persist.js +145 -35
  189. package/.agents/scripts/plan-run-epilogue.js +83 -38
  190. package/.agents/scripts/post-structured-comment.js +0 -38
  191. package/.agents/scripts/pr-watch-with-update.js +43 -22
  192. package/.agents/scripts/providers/github/compose.js +0 -1
  193. package/.agents/scripts/providers/github/errors.js +0 -19
  194. package/.agents/scripts/providers/github/issues.js +1 -11
  195. package/.agents/scripts/providers/github/mappers.js +5 -0
  196. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  197. package/.agents/scripts/providers/github/tickets.js +33 -153
  198. package/.agents/scripts/providers/github.js +17 -6
  199. package/.agents/scripts/quality-preview.js +13 -6
  200. package/.agents/scripts/resolve-stories.js +236 -0
  201. package/.agents/scripts/run-coverage.js +4 -1
  202. package/.agents/scripts/run-lint.js +2 -2
  203. package/.agents/scripts/run-verify.js +31 -2
  204. package/.agents/scripts/signals-view.js +9 -10
  205. package/.agents/scripts/single-story-close.js +173 -18
  206. package/.agents/scripts/single-story-confirm-merge.js +288 -15
  207. package/.agents/scripts/single-story-init.js +6 -10
  208. package/.agents/scripts/stories-wave-tick.js +380 -53
  209. package/.agents/scripts/story-plan.js +3 -3
  210. package/.agents/scripts/update-ticket-state.js +8 -50
  211. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  212. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  213. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  214. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  215. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  216. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  217. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  218. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  219. package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
  220. package/.agents/skills/core/scope-triage/SKILL.md +3 -0
  221. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  222. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  223. package/.agents/skills/skills.index.json +2 -12
  224. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  225. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  226. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  227. package/.agents/workflows/audit-architecture.md +3 -4
  228. package/.agents/workflows/audit-clean-code.md +4 -4
  229. package/.agents/workflows/audit-documentation.md +4 -5
  230. package/.agents/workflows/audit-lighthouse.md +8 -0
  231. package/.agents/workflows/audit-navigability.md +10 -0
  232. package/.agents/workflows/audit-performance.md +2 -3
  233. package/.agents/workflows/audit-quality.md +8 -9
  234. package/.agents/workflows/audit-security.md +1 -2
  235. package/.agents/workflows/audit-seo.md +10 -0
  236. package/.agents/workflows/audit-ux-ui.md +7 -0
  237. package/.agents/workflows/deliver.md +133 -45
  238. package/.agents/workflows/git-cleanup.md +2 -2
  239. package/.agents/workflows/git-deliver.md +1 -1
  240. package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
  241. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
  242. package/.agents/workflows/helpers/code-review.md +14 -12
  243. package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
  244. package/.agents/workflows/helpers/deliver-story.md +209 -118
  245. package/.agents/workflows/helpers/parallel-tooling.md +2 -2
  246. package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
  247. package/.agents/workflows/plan.md +239 -19
  248. package/.agents/workflows/qa-assist.md +6 -6
  249. package/.agents/workflows/qa-explore.md +3 -3
  250. package/.agents/workflows/qa-run.md +1 -5
  251. package/bin/mandrel.js +12 -1
  252. package/docs/CHANGELOG.md +62 -0
  253. package/lib/cli/registry.js +262 -19
  254. package/lib/cli/sync-agents.js +157 -0
  255. package/lib/cli/sync-commands.js +115 -6
  256. package/lib/cli/sync.js +168 -6
  257. package/lib/cli/update.js +105 -8
  258. package/lib/cli/version-helpers.js +131 -0
  259. package/lib/migrations/README.md +7 -5
  260. package/lib/migrations/index.js +17 -9
  261. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  262. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  263. package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
  264. package/package.json +2 -2
  265. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  266. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  267. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  268. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  269. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  270. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  271. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  272. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  273. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  274. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  275. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  276. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  277. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  278. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  279. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  280. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  281. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  282. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  283. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  284. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  285. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  286. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  287. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  288. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  289. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  290. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  291. package/.agents/schemas/risk-verdict.schema.json +0 -53
  292. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  293. package/.agents/scripts/analyze-execution.js +0 -444
  294. package/.agents/scripts/check-prepush-recovery.js +0 -90
  295. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  296. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  297. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
  298. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  299. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  300. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  301. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
  302. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
  303. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  304. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  305. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  306. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  307. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  308. package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
  309. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  310. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  311. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  312. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
  313. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  314. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  315. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  316. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
  317. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  318. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
  319. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  320. package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
  321. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
  322. package/.agents/scripts/resolve-plan-run.js +0 -117
  323. package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
@@ -199,90 +199,6 @@ export function seedStoryBranchRef({
199
199
  }
200
200
  }
201
201
 
202
- /**
203
- * Ensure an Epic branch exists and is published to `origin`. Handles all
204
- * four states of the (local, remote) matrix.
205
- *
206
- * @param {string} epicBranch
207
- * @param {string} baseBranch
208
- * @param {string} cwd
209
- * @param {{ progress?: (phase: string, message: string) => void }} [opts]
210
- */
211
- export async function ensureEpicBranch(epicBranch, baseBranch, cwd, opts = {}) {
212
- assertBranchSafe(epicBranch, baseBranch);
213
- const progress = opts.progress ?? (() => {});
214
-
215
- // Short-circuit: if we're already on the epic branch, just sync with remote.
216
- // This avoids redundant checkout calls and prevents the edge case where
217
- // branchExistsLocally returns false while we're on the branch (detached HEAD,
218
- // worktree race), which would route into the create path and fail on -b.
219
- const onBranch = currentBranch(cwd);
220
- if (onBranch === epicBranch) {
221
- const remote = branchExistsRemotely(epicBranch, cwd);
222
- if (remote) {
223
- progress('GIT', `Already on ${epicBranch}. Syncing with remote.`);
224
- await gitPullWithRetry(cwd, 'origin', epicBranch);
225
- } else {
226
- progress('GIT', `Already on ${epicBranch}. Publishing to remote.`);
227
- gitSync(cwd, 'push', '--no-verify', '-u', 'origin', epicBranch);
228
- }
229
- return;
230
- }
231
-
232
- const local = branchExistsLocally(epicBranch, cwd);
233
- const remote = branchExistsRemotely(epicBranch, cwd);
234
-
235
- if (!local && !remote) {
236
- progress('GIT', `Creating Epic branch: ${epicBranch} (from ${baseBranch})`);
237
- gitSync(cwd, 'checkout', baseBranch);
238
- await gitPullWithRetry(cwd, 'origin', baseBranch);
239
- gitSync(cwd, 'checkout', '-b', epicBranch);
240
- gitSync(cwd, 'push', '--no-verify', '-u', 'origin', epicBranch);
241
- _assertOnBranch(cwd, epicBranch);
242
- return;
243
- }
244
-
245
- if (local && !remote) {
246
- progress(
247
- 'GIT',
248
- `Epic branch exists locally only: ${epicBranch}. Publishing.`,
249
- );
250
- gitSync(cwd, 'checkout', epicBranch);
251
- gitSync(cwd, 'push', '--no-verify', '-u', 'origin', epicBranch);
252
- _assertOnBranch(cwd, epicBranch);
253
- return;
254
- }
255
-
256
- if (!local && remote) {
257
- progress('GIT', `Tracking remote Epic branch: ${epicBranch}`);
258
- gitSync(cwd, 'checkout', '-b', epicBranch, `origin/${epicBranch}`);
259
- await gitPullWithRetry(cwd, 'origin', epicBranch);
260
- _assertOnBranch(cwd, epicBranch);
261
- return;
262
- }
263
-
264
- progress('GIT', `Epic branch exists. Syncing: ${epicBranch}`);
265
- gitSync(cwd, 'checkout', epicBranch);
266
- await gitPullWithRetry(cwd, 'origin', epicBranch);
267
- _assertOnBranch(cwd, epicBranch);
268
- }
269
-
270
- /**
271
- * Post-operation assertion: verify HEAD is on the expected branch.
272
- * Guards against TOCTOU races where a parallel agent switches branches
273
- * between our checkout and pull.
274
- */
275
- function _assertOnBranch(cwd, expected) {
276
- const actual = currentBranch(cwd);
277
- if (actual !== expected) {
278
- throw new Error(
279
- `[git-branch-lifecycle] Branch assertion failed after checkout. ` +
280
- `Expected HEAD on '${expected}', found '${actual}'. ` +
281
- `A concurrent process may have switched branches.`,
282
- );
283
- }
284
- }
285
-
286
202
  /**
287
203
  * Check out a Story branch, creating it from `epicBranch` if neither local
288
204
  * nor remote exists. Non-destructive: if the branch already exists, this
@@ -337,80 +253,6 @@ export async function checkoutStoryBranch(
337
253
  gitSync(cwd, 'checkout', '-b', storyBranch, epicBranch);
338
254
  }
339
255
 
340
- /**
341
- * Ensure an Epic branch ref exists locally and is published to `origin`,
342
- * **without moving HEAD**. Designed for the worktree bootstrap path where
343
- * the main checkout must not switch branches (a parallel agent may be
344
- * working there, or the tree may be dirty).
345
- *
346
- * Uses `git branch` (not `checkout -b`) to create refs, and `git push` to
347
- * publish. Callers that need HEAD on the epic branch should use
348
- * `ensureEpicBranch()` instead.
349
- *
350
- * @param {string} epicBranch
351
- * @param {string} baseBranch
352
- * @param {string} cwd
353
- * @param {{ progress?: (phase: string, message: string) => void }} [opts]
354
- */
355
- /**
356
- * Pure: choose the action to take for `ensureEpicBranchRef` given whether
357
- * the branch exists locally and/or remotely. One of:
358
- * - `'noop'` — both refs exist; nothing to do.
359
- * - `'fetch'` — only remote exists; fetch into local.
360
- * - `'publish-existing'` — only local exists; push it.
361
- * - `'create-and-publish'` — neither exists; create from base then push.
362
- *
363
- * @param {boolean} local
364
- * @param {boolean} remote
365
- * @returns {'noop' | 'fetch' | 'publish-existing' | 'create-and-publish'}
366
- */
367
- export function planEnsureEpicBranchRefAction(local, remote) {
368
- if (local) return remote ? 'noop' : 'publish-existing';
369
- return remote ? 'fetch' : 'create-and-publish';
370
- }
371
-
372
- export function ensureEpicBranchRef(epicBranch, baseBranch, cwd, opts = {}) {
373
- assertBranchSafe(epicBranch, baseBranch);
374
- const progress = opts.progress ?? (() => {});
375
-
376
- // `ensureEpicBranchRef` is always called after a `git fetch origin` (via
377
- // `cachedGitFetch` / `fetchMainRefs` in `bootstrapWorktree`). The
378
- // remote-tracking refs are therefore authoritative, so we check
379
- // `refs/remotes/origin/<branch>` locally rather than issuing a second
380
- // network `ls-remote` call.
381
- const action = planEnsureEpicBranchRefAction(
382
- branchExistsLocally(epicBranch, cwd),
383
- branchExistsViaTrackingRef(epicBranch, cwd),
384
- );
385
-
386
- if (action === 'noop') {
387
- progress('GIT', `Epic branch ref exists (local+remote): ${epicBranch}`);
388
- return;
389
- }
390
-
391
- if (action === 'fetch') {
392
- progress('GIT', `Fetching remote Epic branch ref: ${epicBranch}`);
393
- const res = gitSpawn(cwd, 'fetch', 'origin', `${epicBranch}:${epicBranch}`);
394
- if (res.status !== 0) {
395
- throw new Error(
396
- `ensureEpicBranchRef: failed to fetch ${epicBranch}: ${res.stderr}`,
397
- );
398
- }
399
- return;
400
- }
401
-
402
- if (action === 'create-and-publish') {
403
- progress(
404
- 'GIT',
405
- `Creating Epic branch ref: ${epicBranch} (from ${baseBranch})`,
406
- );
407
- gitSync(cwd, 'branch', epicBranch, baseBranch);
408
- }
409
-
410
- progress('GIT', `Publishing Epic branch: ${epicBranch}`);
411
- gitSync(cwd, 'push', '--no-verify', '-u', 'origin', epicBranch);
412
- }
413
-
414
256
  /**
415
257
  * Ensure an arbitrary branch exists locally, creating from `baseBranch` if
416
258
  * missing. Used by the dispatcher's task-dispatch path where the expected
@@ -315,20 +315,6 @@ export function gitPullWithRetry(cwd, ...args) {
315
315
  return gitWithContentionRetry(cwd, ['pull', '--rebase'], args);
316
316
  }
317
317
 
318
- /**
319
- * Resolves the canonical branch name for an Epic.
320
- * v5 Standard: epic/[EPIC_ID]
321
- * @param {string|number} epicId
322
- * @returns {string}
323
- */
324
- export function getEpicBranch(epicId) {
325
- const id = typeof epicId === 'number' ? epicId : Number.parseInt(epicId, 10);
326
- if (!Number.isFinite(id) || id <= 0) {
327
- throw new Error(`getEpicBranch: invalid epicId: ${epicId}`);
328
- }
329
- return `epic/${id}`;
330
- }
331
-
332
318
  /**
333
319
  * Sanitize a string into a URL/branch-safe slug.
334
320
  * Lowercases, replaces non-alphanumeric characters with hyphens,
@@ -2,8 +2,7 @@
2
2
  * json-utils.js — shared JSON-shape helpers.
3
3
  *
4
4
  * Hoisted out of five callers (`lib/baselines/writer.js`,
5
- * `lib/config/defaults.js`, `lib/observability/baseline-refresh-rate.js`,
6
- * `lib/observability/perf-aggregator.js`, `lib/signals/schema.js`) which
5
+ * `lib/config/defaults.js`, `lib/signals/schema.js`) which
7
6
  * shipped functionally-equivalent copies of these predicates. See Story
8
7
  * #2464.
9
8
  *
@@ -135,20 +135,6 @@ export const PLANNING_LABELS = {
135
135
  */
136
136
  export const PLANNING_HEALTHCHECK_WAIVED = 'planning::healthcheck-waived';
137
137
 
138
- /**
139
- * Delivery-routing labels (Epic #4474 PR4 — design §2 mode matrix).
140
- * `delivery::single` marks an Epic whose plan was persisted in the
141
- * spec-only single-delivery mode: no Story tree exists and the Delivery
142
- * Slicing table of the Epic body's Tech Spec is the audit trail.
143
- * `plan-persist.js` applies it when the risk verdict declares
144
- * `deliveryShape: "single"`; a fan-out re-persist over the same Epic
145
- * removes it. The marker is **inert until #4475** lands the deliver-side
146
- * reader — nothing in the deliver path consumes it yet.
147
- */
148
- export const DELIVERY_LABELS = {
149
- SINGLE: 'delivery::single',
150
- };
151
-
152
138
  /** Palette for the taxonomy; consumed by label-taxonomy.js. */
153
139
  export const LABEL_COLORS = {
154
140
  TYPE: '#7057FF',
@@ -156,5 +142,4 @@ export const LABEL_COLORS = {
156
142
  STATUS_BLOCKED: '#D93F0B',
157
143
  ACCEPTANCE: '#FBCA04',
158
144
  PLANNING: '#FEF2C0',
159
- DELIVERY: '#BFD4F2',
160
145
  };
@@ -14,7 +14,6 @@
14
14
  import {
15
15
  ACCEPTANCE_LABELS,
16
16
  AGENT_LABELS,
17
- DELIVERY_LABELS,
18
17
  LABEL_COLORS,
19
18
  PLANNING_LABELS,
20
19
  STATUS_LABELS,
@@ -51,7 +50,7 @@ export const LABEL_TAXONOMY = [
51
50
  {
52
51
  name: AGENT_LABELS.CLOSING,
53
52
  color: LABEL_COLORS.AGENT,
54
- description: 'Close preflight passed; awaiting merge into the Epic branch',
53
+ description: 'Close preflight passed; awaiting merge into the base branch',
55
54
  },
56
55
  {
57
56
  name: AGENT_LABELS.DONE,
@@ -84,16 +83,6 @@ export const LABEL_TAXONOMY = [
84
83
  description:
85
84
  'Historical operator override for the retired post-plan healthcheck',
86
85
  },
87
-
88
- // Delivery routing — applied by `plan-persist.js` when the risk verdict
89
- // declares `deliveryShape: "single"` (Epic #4474 PR4). Inert until #4475
90
- // lands the deliver-side reader.
91
- {
92
- name: DELIVERY_LABELS.SINGLE,
93
- color: LABEL_COLORS.DELIVERY,
94
- description:
95
- 'Single-delivery plan — no Story tree; the Delivery Slicing table is the audit trail (#4474/#4475)',
96
- },
97
86
  ];
98
87
 
99
88
  /** @type {Array<{ name: string, type: 'single_select', options?: string[] }>} */
@@ -2,14 +2,14 @@
2
2
  * Active-Story env-var propagation (Epic #1030 Story #1043 / Task #1061).
3
3
  *
4
4
  * The PreToolUse / PostToolUse trace hook in
5
- * `lib/observability/tool-trace-hook.js` resolves the active Epic +
6
- * Story from `process.env.CC_EPIC_ID` and `process.env.CC_STORY_ID`.
7
- * This module is the single writer/clearer of those vars:
5
+ * `lib/observability/tool-trace-hook.js` resolves the active Story from
6
+ * `process.env.CC_STORY_ID`. This module is the single writer/clearer of
7
+ * that var:
8
8
  *
9
- * - `setActiveStoryEnv({ epicId, storyId, workCwd })` is called from
9
+ * - `setActiveStoryEnv({ storyId, workCwd })` is called from
10
10
  * `single-story-init.js` after the worktree is materialised. It sets the
11
- * vars on the current `process.env` (so any child commands the
12
- * orchestrator spawns inherit them) and exports them to a sibling
11
+ * var on the current `process.env` (so any child commands the
12
+ * orchestrator spawns inherit it) and exports it to a sibling
13
13
  * `.env.local` inside the worktree. The harness re-spawns the
14
14
  * agent with that file's contents loaded, so the trace hook fires
15
15
  * with the right ids on the *next* tool call after init returns.
@@ -37,96 +37,76 @@ const ENV_LOCAL_BASENAME = '.env.local';
37
37
  /**
38
38
  * Names of the env vars we own. Keeping the list central makes the
39
39
  * round-trip (set on init, clear on close) trivially auditable —
40
- * grep `CC_EPIC_ID` / `CC_STORY_ID` / `CC_SLICE_ID` to find every read site.
41
- *
42
- * Epic #4476 (M5) added `CC_SLICE_ID` (the single-delivery analogue of
43
- * `CC_STORY_ID` set on the ONE long guarded session so the PostToolUse
44
- * hook can emit `slice.heartbeat` off the token stream) and `CC_OPERATOR`
45
- * (the resolved lease-owner handle, stamped onto hook-emitted heartbeats so
46
- * `latestHeartbeatForOwner` keeps resolving a live claim). The clear path
47
- * wipes all four so no stale context leaks past close.
40
+ * grep `CC_STORY_ID` / `CC_EPIC_ID` to find every read site.
41
+ *
42
+ * `CC_STORY_ID` is the one var this module writes. `CC_EPIC_ID` is retained
43
+ * on the CLEAR path only: v2 has no Epics and nothing here sets it, but
44
+ * `tool-trace-hook.js`'s `resolveActiveStory` still reads it, so wiping it at
45
+ * close keeps the trace path's "no stale context past close" contract honest
46
+ * against a value that leaked in from outside (a pre-upgrade `.env.local` the
47
+ * harness already loaded, or an operator's shell).
48
+ *
49
+ * `CC_SLICE_ID` / `CC_OPERATOR` were removed with the heartbeat substrate:
50
+ * their only writer was the never-called `setActiveSliceEnv` and their only
51
+ * reader the deleted `hook-heartbeat.js`.
48
52
  */
49
- export const ACTIVE_STORY_ENV_KEYS = [
50
- 'CC_EPIC_ID',
51
- 'CC_STORY_ID',
52
- 'CC_SLICE_ID',
53
- 'CC_OPERATOR',
54
- ];
53
+ export const ACTIVE_STORY_ENV_KEYS = ['CC_EPIC_ID', 'CC_STORY_ID'];
55
54
 
56
55
  /**
57
56
  * Render the `.env.local` body. One `KEY=value` line per var, LF
58
57
  * line endings (the harness's dotenv parser tolerates CRLF too but LF
59
58
  * keeps the file deterministic across Windows / macOS / Linux).
60
59
  *
61
- * Story #2874 when `epicId === null` (standalone Story, no parent
62
- * Epic), the `CC_EPIC_ID=` line is omitted entirely. The trace
63
- * hook's no-op contract reads "var absent from env" as the signal,
64
- * so emitting an empty `CC_EPIC_ID=` line would set the var to the
65
- * empty string and change the contract.
60
+ * Only `CC_STORY_ID` is emitted: v2 Stories are standalone, so there is no
61
+ * parent Epic to key. The trace hook's no-op contract reads "var absent from
62
+ * env" as the signal, so no `CC_EPIC_ID=` line is written at all an empty
63
+ * one would set the var to the empty string and change the contract.
66
64
  *
67
65
  * Exported for testing.
68
66
  *
69
- * @param {{ epicId: number|null, storyId: number }} input
67
+ * @param {{ storyId: number }} input
70
68
  * @returns {string}
71
69
  */
72
- export function renderActiveStoryEnvFile({ epicId, storyId }) {
73
- const lines = [
70
+ export function renderActiveStoryEnvFile({ storyId }) {
71
+ return [
74
72
  '# Auto-managed by .agents/scripts/lib/observability/active-story-env.js',
75
73
  '# Re-generated on every story-init; deleted on story-close.',
76
- ];
77
- if (epicId !== null) lines.push(`CC_EPIC_ID=${epicId}`);
78
- lines.push(`CC_STORY_ID=${storyId}`, '');
79
- return lines.join('\n');
74
+ `CC_STORY_ID=${storyId}`,
75
+ '',
76
+ ].join('\n');
80
77
  }
81
78
 
82
79
  /**
83
- * Set `CC_EPIC_ID` / `CC_STORY_ID` on the current process and (when
84
- * `workCwd` is provided) export them to `<workCwd>/.env.local`.
80
+ * Set `CC_STORY_ID` on the current process and (when `workCwd` is provided)
81
+ * export it to `<workCwd>/.env.local`.
85
82
  *
86
- * Idempotent: re-running with the same ids is a no-op on disk; with
87
- * different ids the `.env.local` is overwritten.
83
+ * Idempotent: re-running with the same id is a no-op on disk; with a
84
+ * different id the `.env.local` is overwritten.
88
85
  *
89
- * Story #2874`epicId: null` is the standalone-Story sentinel.
90
- * When passed:
91
- * - `CC_EPIC_ID` is removed from `env` (the var MUST NOT be set
92
- * to an empty string the trace hook's `resolveActiveStory`
93
- * no-op contract is keyed on the var's absence).
94
- * - The rendered `.env.local` omits the `CC_EPIC_ID=` line.
95
- * - `CC_STORY_ID` behaviour is unchanged.
96
- * All other invalid `epicId` values (`0`, negative, NaN, non-integer)
97
- * still throw — `null` is the only standalone signal.
86
+ * v2 Stories are standalone there is no parent Epic — so `CC_EPIC_ID` is
87
+ * never set, and any pre-existing value is removed: the trace hook's
88
+ * `resolveActiveStory` no-op contract is keyed on the var's absence, and a
89
+ * stale epic id would key this Story's traces to a foreign Epic directory.
98
90
  *
99
- * @param {{ epicId: number|null, storyId: number, workCwd?: string,
91
+ * @param {{ storyId: number, workCwd?: string,
100
92
  * env?: NodeJS.ProcessEnv, fs?: typeof nodeFs,
101
93
  * logger?: { warn?: (m: string) => void } }} args
102
94
  * @returns {{ envSet: boolean, fileWritten: boolean, filePath: string|null }}
103
95
  */
104
96
  export function setActiveStoryEnv({
105
- epicId,
106
97
  storyId,
107
98
  workCwd,
108
99
  env = process.env,
109
100
  fs = nodeFs,
110
101
  logger,
111
102
  } = {}) {
112
- if (epicId !== null && (!Number.isInteger(epicId) || epicId <= 0)) {
113
- throw new Error(
114
- `[active-story-env] epicId must be a positive integer or null; got ${epicId}`,
115
- );
116
- }
117
103
  if (!Number.isInteger(storyId) || storyId <= 0) {
118
104
  throw new Error(
119
105
  `[active-story-env] storyId must be a positive integer; got ${storyId}`,
120
106
  );
121
107
  }
122
108
 
123
- if (epicId === null) {
124
- // Standalone Story — ensure CC_EPIC_ID is absent so the trace
125
- // hook's no-op contract sees "no epic" rather than "epic=''".
126
- if ('CC_EPIC_ID' in env) delete env.CC_EPIC_ID;
127
- } else {
128
- env.CC_EPIC_ID = String(epicId);
129
- }
109
+ if ('CC_EPIC_ID' in env) delete env.CC_EPIC_ID;
130
110
  env.CC_STORY_ID = String(storyId);
131
111
 
132
112
  let fileWritten = false;
@@ -134,13 +114,9 @@ export function setActiveStoryEnv({
134
114
  if (typeof workCwd === 'string' && workCwd.length > 0) {
135
115
  filePath = nodePath.join(workCwd, ENV_LOCAL_BASENAME);
136
116
  try {
137
- fs.writeFileSync(
138
- filePath,
139
- renderActiveStoryEnvFile({ epicId, storyId }),
140
- {
141
- encoding: 'utf8',
142
- },
143
- );
117
+ fs.writeFileSync(filePath, renderActiveStoryEnvFile({ storyId }), {
118
+ encoding: 'utf8',
119
+ });
144
120
  fileWritten = true;
145
121
  } catch (err) {
146
122
  logger?.warn?.(
@@ -192,100 +168,3 @@ export function clearActiveStoryEnv({
192
168
  }
193
169
  return { envCleared: true, fileRemoved, filePath };
194
170
  }
195
-
196
- /**
197
- * Render the single-delivery `.env.local` body. The single-delivery executor
198
- * (`deliver-epic-single.md`) walks the Delivery Slicing table inside ONE long
199
- * session; there is no Story fan-out and thus no `CC_STORY_ID`. Instead the
200
- * "current slice" is exported as `CC_SLICE_ID` so the PostToolUse hook emits
201
- * `slice.heartbeat` — the watchdog's forward-progress signal for the single
202
- * session — as a free byproduct of tool activity.
203
- *
204
- * `CC_STORY_ID` MUST be absent (its presence would make the hook emit
205
- * `story.heartbeat` and the trace path key off a non-existent Story). The
206
- * optional `operator` handle is emitted as `CC_OPERATOR` only when supplied.
207
- *
208
- * Exported for testing.
209
- *
210
- * @param {{ epicId: number, sliceId: string, operator?: string }} input
211
- * @returns {string}
212
- */
213
- export function renderActiveSliceEnvFile({ epicId, sliceId, operator }) {
214
- const lines = [
215
- '# Auto-managed by .agents/scripts/lib/observability/active-story-env.js',
216
- '# Re-generated on every slice-start; deleted on epic-close.',
217
- `CC_EPIC_ID=${epicId}`,
218
- `CC_SLICE_ID=${sliceId}`,
219
- ];
220
- if (typeof operator === 'string' && operator.length > 0) {
221
- lines.push(`CC_OPERATOR=${operator}`);
222
- }
223
- lines.push('');
224
- return lines.join('\n');
225
- }
226
-
227
- /**
228
- * Set `CC_EPIC_ID` / `CC_SLICE_ID` (+ optional `CC_OPERATOR`) on the current
229
- * process and, when `workCwd` is provided, export them to
230
- * `<workCwd>/.env.local` for the harness to reload on the next tool call. The
231
- * single-delivery analogue of {@link setActiveStoryEnv}; called at each
232
- * `slice.start` boundary so the hook's throttled `slice.heartbeat` is keyed to
233
- * the slice currently being implemented.
234
- *
235
- * `CC_STORY_ID` is explicitly removed so a prior Story context (if any ever
236
- * leaked in) cannot make the hook emit the wrong heartbeat shape.
237
- *
238
- * @param {{ epicId: number, sliceId: string, operator?: string|null,
239
- * workCwd?: string, env?: NodeJS.ProcessEnv, fs?: typeof nodeFs,
240
- * logger?: { warn?: (m: string) => void } }} args
241
- * @returns {{ envSet: boolean, fileWritten: boolean, filePath: string|null }}
242
- */
243
- export function setActiveSliceEnv({
244
- epicId,
245
- sliceId,
246
- operator,
247
- workCwd,
248
- env = process.env,
249
- fs = nodeFs,
250
- logger,
251
- } = {}) {
252
- if (!Number.isInteger(epicId) || epicId <= 0) {
253
- throw new Error(
254
- `[active-story-env] epicId must be a positive integer; got ${epicId}`,
255
- );
256
- }
257
- if (typeof sliceId !== 'string' || sliceId.length === 0) {
258
- throw new Error(
259
- `[active-story-env] sliceId must be a non-empty string; got ${sliceId}`,
260
- );
261
- }
262
- const normOperator =
263
- typeof operator === 'string' && operator.length > 0 ? operator : undefined;
264
-
265
- if ('CC_STORY_ID' in env) delete env.CC_STORY_ID;
266
- env.CC_EPIC_ID = String(epicId);
267
- env.CC_SLICE_ID = sliceId;
268
- if (normOperator) env.CC_OPERATOR = normOperator;
269
- else if ('CC_OPERATOR' in env) delete env.CC_OPERATOR;
270
-
271
- let fileWritten = false;
272
- let filePath = null;
273
- if (typeof workCwd === 'string' && workCwd.length > 0) {
274
- filePath = nodePath.join(workCwd, ENV_LOCAL_BASENAME);
275
- try {
276
- fs.writeFileSync(
277
- filePath,
278
- renderActiveSliceEnvFile({ epicId, sliceId, operator: normOperator }),
279
- { encoding: 'utf8' },
280
- );
281
- fileWritten = true;
282
- } catch (err) {
283
- logger?.warn?.(
284
- `[active-story-env] Failed to write ${filePath}: ${
285
- err instanceof Error ? err.message : String(err)
286
- }`,
287
- );
288
- }
289
- }
290
- return { envSet: true, fileWritten, filePath };
291
- }