mandrel 1.93.0 → 2.0.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 (463) hide show
  1. package/.agents/README.md +59 -73
  2. package/.agents/agents/acceptance-critic.md +129 -0
  3. package/.agents/agents/story-worker.md +161 -0
  4. package/.agents/docs/SDLC.md +489 -1285
  5. package/.agents/docs/agentrc-reference.json +177 -67
  6. package/.agents/docs/configuration.md +108 -136
  7. package/.agents/docs/execution-reference.md +44 -22
  8. package/.agents/docs/quality-gates.md +13 -19
  9. package/.agents/docs/workflows.md +3 -3
  10. package/.agents/instructions.md +107 -108
  11. package/.agents/rules/ci-remediation.md +8 -12
  12. package/.agents/rules/git-conventions-reference.md +224 -0
  13. package/.agents/rules/git-conventions.md +42 -223
  14. package/.agents/rules/security-baseline.md +5 -0
  15. package/.agents/rules/testing-standards.md +106 -13
  16. package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
  17. package/.agents/schemas/agentrc.schema.json +71 -201
  18. package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
  20. package/.agents/schemas/risk-verdict.schema.json +0 -13
  21. package/.agents/scripts/acceptance-eval.js +62 -18
  22. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  23. package/.agents/scripts/analyze-execution.js +1 -1
  24. package/.agents/scripts/audit-to-stories.js +7 -7
  25. package/.agents/scripts/boot-sweep.js +1 -1
  26. package/.agents/scripts/check-context-budget.js +62 -5
  27. package/.agents/scripts/check-lifecycle-lint.js +6 -9
  28. package/.agents/scripts/check-prepush-recovery.js +1 -1
  29. package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
  30. package/.agents/scripts/diagnose-friction.js +0 -6
  31. package/.agents/scripts/lib/Logger.js +6 -10
  32. package/.agents/scripts/lib/audit-suite/runner.js +2 -2
  33. package/.agents/scripts/lib/audit-suite/selector.js +5 -5
  34. package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
  35. package/.agents/scripts/lib/baselines/kernel.js +206 -18
  36. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -4
  37. package/.agents/scripts/lib/baselines/reader.js +1 -6
  38. package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
  39. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
  40. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +56 -18
  41. package/.agents/scripts/lib/checks/core-bare-clean.js +2 -2
  42. package/.agents/scripts/lib/checks/index.js +2 -1
  43. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +23 -21
  44. package/.agents/scripts/lib/cli/standard-args.js +13 -22
  45. package/.agents/scripts/lib/cli-args.js +16 -7
  46. package/.agents/scripts/lib/close-validation/gates.js +160 -22
  47. package/.agents/scripts/lib/config/acceptance-eval.js +52 -5
  48. package/.agents/scripts/lib/config/ci.js +6 -31
  49. package/.agents/scripts/lib/config/delivery-routing.js +103 -0
  50. package/.agents/scripts/lib/config/explain.js +57 -36
  51. package/.agents/scripts/lib/config/limits.js +17 -58
  52. package/.agents/scripts/lib/config/paths.js +0 -2
  53. package/.agents/scripts/lib/config/quality.js +1 -1
  54. package/.agents/scripts/lib/config/runners.js +17 -50
  55. package/.agents/scripts/lib/config/temp-paths.js +19 -14
  56. package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
  57. package/.agents/scripts/lib/config-resolver.js +3 -8
  58. package/.agents/scripts/lib/config-settings-schema-delivery.js +46 -136
  59. package/.agents/scripts/lib/config-settings-schema-quality.js +17 -14
  60. package/.agents/scripts/lib/config-settings-schema.js +52 -38
  61. package/.agents/scripts/lib/dependency-parser.js +3 -2
  62. package/.agents/scripts/lib/doc-tiers.js +39 -4
  63. package/.agents/scripts/lib/duplicate-search.js +211 -41
  64. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
  65. package/.agents/scripts/lib/findings/promote-finding.js +5 -5
  66. package/.agents/scripts/lib/framework-version.js +2 -3
  67. package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
  68. package/.agents/scripts/lib/git-branch-lifecycle.js +17 -22
  69. package/.agents/scripts/lib/git-utils.js +32 -6
  70. package/.agents/scripts/lib/github/framework-repo.js +6 -0
  71. package/.agents/scripts/lib/label-constants.js +10 -23
  72. package/.agents/scripts/lib/label-taxonomy.js +9 -43
  73. package/.agents/scripts/lib/observability/active-story-env.js +112 -3
  74. package/.agents/scripts/lib/observability/hook-heartbeat.js +187 -0
  75. package/.agents/scripts/lib/observability/source-classifier.js +3 -3
  76. package/.agents/scripts/lib/observability/tool-trace-hook.js +15 -4
  77. package/.agents/scripts/lib/onboard/init-tail.js +1 -3
  78. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +111 -0
  79. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +32 -4
  80. package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
  81. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +273 -0
  82. package/.agents/scripts/lib/orchestration/ceremony-routing.js +204 -0
  83. package/.agents/scripts/lib/orchestration/code-review.js +20 -268
  84. package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
  85. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +1 -1
  86. package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
  87. package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
  88. package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
  89. package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
  90. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +8 -8
  91. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +2 -2
  92. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
  93. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +17 -43
  94. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
  95. package/.agents/scripts/lib/orchestration/merge-block-class.js +1 -1
  96. package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
  97. package/.agents/scripts/lib/orchestration/plan-context.js +248 -266
  98. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +3 -2
  99. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +1 -1
  100. package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
  101. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +61 -0
  102. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +97 -0
  103. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +223 -854
  104. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +361 -0
  105. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +35 -108
  106. package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
  107. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
  108. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +14 -14
  109. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
  110. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/risk-verdict.js +3 -4
  111. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +2 -2
  112. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +8 -20
  113. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -2
  114. package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
  115. package/.agents/scripts/lib/orchestration/preflight-cache.js +5 -5
  116. package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
  117. package/.agents/scripts/lib/orchestration/resolve-plan-run.js +155 -0
  118. package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
  119. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +8 -8
  120. package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
  121. package/.agents/scripts/lib/orchestration/review-depth.js +26 -12
  122. package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
  123. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
  124. package/.agents/scripts/lib/orchestration/run-epilogue.js +426 -0
  125. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +1 -1
  126. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +1 -0
  127. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +95 -41
  128. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +16 -13
  129. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +40 -0
  130. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +11 -3
  131. package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
  132. package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
  133. package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
  134. package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
  135. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
  136. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -12
  137. package/.agents/scripts/lib/orchestration/story-follow-ups.js +237 -0
  138. package/.agents/scripts/lib/orchestration/story-init-remote.js +47 -0
  139. package/.agents/scripts/lib/orchestration/story-plan-state.js +48 -0
  140. package/.agents/scripts/lib/orchestration/{epic-runner → story-progress}/story-run-progress-writer.js +3 -3
  141. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
  142. package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -18
  143. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +11 -61
  144. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +189 -373
  145. package/.agents/scripts/lib/orchestration/ticket-validator.js +3 -8
  146. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +0 -25
  147. package/.agents/scripts/lib/orchestration/ticketing/reads.js +29 -26
  148. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -5
  149. package/.agents/scripts/lib/planning-corpus.js +16 -11
  150. package/.agents/scripts/lib/preflight-runner.js +2 -2
  151. package/.agents/scripts/lib/provider-factory.js +1 -1
  152. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
  153. package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +36 -0
  154. package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
  155. package/.agents/scripts/lib/story-adjacency.js +11 -14
  156. package/.agents/scripts/lib/story-body/story-body.js +124 -70
  157. package/.agents/scripts/lib/story-plan.js +2 -4
  158. package/.agents/scripts/lib/templates/decomposer-prompts.js +46 -45
  159. package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
  160. package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +26 -26
  161. package/.agents/scripts/lib/validation-evidence.js +1 -1
  162. package/.agents/scripts/lib/wave-runner/ready-set.js +6 -6
  163. package/.agents/scripts/lib/workspace-provisioner.js +1 -1
  164. package/.agents/scripts/lib/worktree/lifecycle/reap.js +5 -7
  165. package/.agents/scripts/lint-issue-body.js +71 -40
  166. package/.agents/scripts/mandrel-update-preflight.js +1 -1
  167. package/.agents/scripts/notify.js +4 -3
  168. package/.agents/scripts/plan-context.js +64 -74
  169. package/.agents/scripts/plan-persist.js +121 -280
  170. package/.agents/scripts/plan-run-epilogue.js +97 -0
  171. package/.agents/scripts/post-structured-comment.js +38 -0
  172. package/.agents/scripts/providers/github/issues.js +17 -33
  173. package/.agents/scripts/providers/github/mappers.js +0 -12
  174. package/.agents/scripts/providers/github/tickets.js +2 -5
  175. package/.agents/scripts/resolve-plan-run.js +117 -0
  176. package/.agents/scripts/signals-view.js +24 -19
  177. package/.agents/scripts/single-story-close.js +11 -14
  178. package/.agents/scripts/single-story-confirm-merge.js +39 -23
  179. package/.agents/scripts/single-story-init.js +29 -20
  180. package/.agents/scripts/stories-wave-tick.js +6 -6
  181. package/.agents/scripts/story-plan.js +26 -47
  182. package/.agents/scripts/sync-claude-agents.js +165 -0
  183. package/.agents/scripts/update-ticket-state.js +37 -15
  184. package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
  185. package/.agents/skills/core/api-and-interface-design/SKILL.md +5 -3
  186. package/.agents/skills/core/code-review-and-quality/SKILL.md +63 -7
  187. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +1 -1
  188. package/.agents/skills/core/gates-and-baselines/SKILL.md +149 -0
  189. package/.agents/skills/core/idea-refinement/SKILL.md +8 -14
  190. package/.agents/skills/core/qa-coverage-mapping/SKILL.md +7 -7
  191. package/.agents/skills/core/scope-triage/SKILL.md +28 -172
  192. package/.agents/skills/skills.index.json +8 -418
  193. package/.agents/starter-agentrc.json +0 -5
  194. package/.agents/templates/agent-protocol.md +9 -10
  195. package/.agents/workflows/audit-architecture.md +3 -3
  196. package/.agents/workflows/audit-clean-code.md +3 -3
  197. package/.agents/workflows/audit-dependencies.md +3 -3
  198. package/.agents/workflows/audit-devops.md +3 -3
  199. package/.agents/workflows/audit-documentation.md +5 -5
  200. package/.agents/workflows/audit-lighthouse.md +3 -3
  201. package/.agents/workflows/audit-navigability.md +3 -2
  202. package/.agents/workflows/audit-performance.md +3 -3
  203. package/.agents/workflows/audit-privacy.md +3 -3
  204. package/.agents/workflows/audit-quality.md +3 -3
  205. package/.agents/workflows/audit-security.md +3 -3
  206. package/.agents/workflows/audit-seo.md +3 -3
  207. package/.agents/workflows/audit-sre.md +3 -3
  208. package/.agents/workflows/audit-to-stories.md +20 -20
  209. package/.agents/workflows/audit-ux-ui.md +3 -3
  210. package/.agents/workflows/deliver.md +122 -131
  211. package/.agents/workflows/git-cleanup.md +3 -4
  212. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  213. package/.agents/workflows/helpers/acceptance-self-eval.md +52 -40
  214. package/.agents/workflows/helpers/code-review.md +70 -193
  215. package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +12 -14
  216. package/.agents/workflows/helpers/{single-story-deliver.md → deliver-story.md} +113 -139
  217. package/.agents/workflows/helpers/diagnose.md +10 -10
  218. package/.agents/workflows/helpers/mandrel-sync-config.md +1 -1
  219. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  220. package/.agents/workflows/helpers/signals.md +16 -16
  221. package/.agents/workflows/helpers/worktree-lifecycle.md +48 -64
  222. package/.agents/workflows/mandrel-update.md +3 -2
  223. package/.agents/workflows/plan.md +112 -145
  224. package/.agents/workflows/qa-assist.md +24 -30
  225. package/.agents/workflows/qa-explore.md +29 -38
  226. package/.agents/workflows/qa-run.md +2 -2
  227. package/README.md +9 -8
  228. package/docs/CHANGELOG.md +46 -0
  229. package/lib/cli/registry.js +95 -0
  230. package/lib/migrations/index.js +6 -5
  231. package/package.json +5 -3
  232. package/.agents/personas/architect.md +0 -113
  233. package/.agents/personas/devops-engineer.md +0 -38
  234. package/.agents/personas/engineer-mobile.md +0 -120
  235. package/.agents/personas/engineer-web.md +0 -111
  236. package/.agents/personas/engineer.md +0 -119
  237. package/.agents/personas/product.md +0 -94
  238. package/.agents/personas/project-manager.md +0 -114
  239. package/.agents/personas/qa-engineer.md +0 -95
  240. package/.agents/personas/refactorer.md +0 -113
  241. package/.agents/personas/security-engineer.md +0 -112
  242. package/.agents/personas/sre.md +0 -86
  243. package/.agents/personas/technical-writer.md +0 -101
  244. package/.agents/personas/ux-designer.md +0 -95
  245. package/.agents/schemas/dispatch-manifest.json +0 -232
  246. package/.agents/schemas/epic-spec.schema.json +0 -153
  247. package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
  248. package/.agents/scripts/dispatcher.js +0 -295
  249. package/.agents/scripts/epic-audit-prepare.js +0 -497
  250. package/.agents/scripts/epic-audit-recheck.js +0 -274
  251. package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
  252. package/.agents/scripts/epic-deliver-preflight.js +0 -462
  253. package/.agents/scripts/epic-deliver-prepare.js +0 -590
  254. package/.agents/scripts/epic-execute-record-wave.js +0 -449
  255. package/.agents/scripts/epic-plan-clarity.js +0 -211
  256. package/.agents/scripts/epic-plan-decompose.js +0 -54
  257. package/.agents/scripts/epic-plan-healthcheck.js +0 -581
  258. package/.agents/scripts/epic-plan-spec.js +0 -64
  259. package/.agents/scripts/epic-reconcile.js +0 -625
  260. package/.agents/scripts/lib/baseline-snapshot.js +0 -979
  261. package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
  262. package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
  263. package/.agents/scripts/lib/config/lifecycle.js +0 -40
  264. package/.agents/scripts/lib/config/preflight.js +0 -58
  265. package/.agents/scripts/lib/config/retro.js +0 -77
  266. package/.agents/scripts/lib/epic-merge-lock.js +0 -322
  267. package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
  268. package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
  269. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -660
  270. package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
  271. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
  272. package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
  273. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
  274. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
  275. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
  276. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
  277. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
  278. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
  279. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
  280. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
  281. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
  282. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
  283. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
  284. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
  285. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
  286. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
  287. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
  288. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -388
  289. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
  290. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
  291. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
  292. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
  293. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
  294. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
  295. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
  296. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
  297. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
  298. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
  299. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
  300. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
  301. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
  302. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
  303. package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
  304. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
  305. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
  306. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
  307. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
  308. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
  309. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
  310. package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
  311. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
  312. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
  313. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -384
  314. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
  315. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
  316. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
  317. package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -278
  318. package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
  319. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
  320. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
  321. package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
  322. package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
  323. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
  324. package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
  325. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
  326. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
  327. package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
  328. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
  329. package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
  330. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
  331. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
  332. package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
  333. package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
  334. package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
  335. package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +0 -109
  336. package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
  337. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
  338. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
  339. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
  340. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
  341. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
  342. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
  343. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
  344. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
  345. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
  346. package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
  347. package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
  348. package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
  349. package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
  350. package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
  351. package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
  352. package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
  353. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
  354. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
  355. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
  356. package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
  357. package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
  358. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
  359. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
  360. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
  361. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -301
  362. package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
  363. package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
  364. package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
  365. package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
  366. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
  367. package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
  368. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
  369. package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
  370. package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
  371. package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
  372. package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
  373. package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
  374. package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
  375. package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
  376. package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
  377. package/.agents/scripts/lib/push-epic-retry.js +0 -209
  378. package/.agents/scripts/lib/spec/index.js +0 -36
  379. package/.agents/scripts/lib/spec/loader.js +0 -425
  380. package/.agents/scripts/lib/spec/state.js +0 -208
  381. package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
  382. package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
  383. package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
  384. package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
  385. package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
  386. package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
  387. package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
  388. package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
  389. package/.agents/scripts/lib/wave-runner/tick.js +0 -754
  390. package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
  391. package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
  392. package/.agents/scripts/lifecycle-emit.js +0 -510
  393. package/.agents/scripts/plan-critics.js +0 -199
  394. package/.agents/scripts/retro-run.js +0 -218
  395. package/.agents/scripts/standalone-feedback-rollup.js +0 -188
  396. package/.agents/scripts/story-close.js +0 -294
  397. package/.agents/scripts/story-init.js +0 -599
  398. package/.agents/scripts/story-phase.js +0 -369
  399. package/.agents/scripts/wave-tick.js +0 -335
  400. package/.agents/skills/core/baseline-refresh/SKILL.md +0 -181
  401. package/.agents/skills/core/ci-cd-and-automation/SKILL.md +0 -274
  402. package/.agents/skills/core/ci-cd-and-automation/examples.md +0 -211
  403. package/.agents/skills/core/code-simplification/SKILL.md +0 -389
  404. package/.agents/skills/core/context-engineering/SKILL.md +0 -309
  405. package/.agents/skills/core/context-engineering/examples.md +0 -58
  406. package/.agents/skills/core/deprecation-and-migration/SKILL.md +0 -250
  407. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
  408. package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
  409. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
  410. package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
  411. package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
  412. package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
  413. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -413
  414. package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
  415. package/.agents/skills/core/frontend-ui-engineering/SKILL.md +0 -357
  416. package/.agents/skills/core/hydrate-context/SKILL.md +0 -123
  417. package/.agents/skills/core/idea-refinement/examples.md +0 -437
  418. package/.agents/skills/core/idea-refinement/frameworks.md +0 -135
  419. package/.agents/skills/core/incremental-implementation/SKILL.md +0 -271
  420. package/.agents/skills/core/introducing-a-baseline-gate/SKILL.md +0 -213
  421. package/.agents/skills/core/knowledge-transfer/SKILL.md +0 -180
  422. package/.agents/skills/core/mutation-survivor-remediation/SKILL.md +0 -117
  423. package/.agents/skills/core/performance-optimization/SKILL.md +0 -314
  424. package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +0 -277
  425. package/.agents/skills/core/property-based-testing/SKILL.md +0 -148
  426. package/.agents/skills/core/refactoring-discipline/SKILL.md +0 -111
  427. package/.agents/skills/core/shipping-and-launch/SKILL.md +0 -328
  428. package/.agents/skills/core/spec-driven-development/SKILL.md +0 -252
  429. package/.agents/skills/core/test-driven-development/SKILL.md +0 -475
  430. package/.agents/skills/core/using-agent-skills/SKILL.md +0 -232
  431. package/.agents/skills/stack/architecture/monorepo-path-strategist/SKILL.md +0 -31
  432. package/.agents/skills/stack/architecture/structured-output-zod/SKILL.md +0 -51
  433. package/.agents/skills/stack/architecture/subagent-orchestration/SKILL.md +0 -76
  434. package/.agents/skills/stack/backend/cloudflare-hono-architect/SKILL.md +0 -31
  435. package/.agents/skills/stack/backend/cloudflare-hono-architect/examples/route-template.ts +0 -33
  436. package/.agents/skills/stack/backend/cloudflare-queue-manager/SKILL.md +0 -31
  437. package/.agents/skills/stack/backend/cloudflare-workers/SKILL.md +0 -51
  438. package/.agents/skills/stack/backend/highlevel-crm/SKILL.md +0 -54
  439. package/.agents/skills/stack/backend/sqlite-drizzle-expert/SKILL.md +0 -29
  440. package/.agents/skills/stack/backend/sqlite-drizzle-expert/examples/schema-template.ts +0 -30
  441. package/.agents/skills/stack/backend/stripe-integration/SKILL.md +0 -57
  442. package/.agents/skills/stack/backend/stripe-integration/scripts/listen-stripe.sh +0 -9
  443. package/.agents/skills/stack/backend/turso-sqlite/SKILL.md +0 -48
  444. package/.agents/skills/stack/frontend/astro/SKILL.md +0 -62
  445. package/.agents/skills/stack/frontend/astro-react-island-strategist/SKILL.md +0 -30
  446. package/.agents/skills/stack/frontend/expo-react-native-developer/SKILL.md +0 -29
  447. package/.agents/skills/stack/frontend/google-analytics-v4/SKILL.md +0 -50
  448. package/.agents/skills/stack/frontend/tailwind-v4/SKILL.md +0 -58
  449. package/.agents/skills/stack/frontend/ui-accessibility-engineer/SKILL.md +0 -34
  450. package/.agents/skills/stack/qa/audit-accessibility/SKILL.md +0 -51
  451. package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +0 -199
  452. package/.agents/skills/stack/security/backend-security-patterns/SKILL.md +0 -68
  453. package/.agents/workflows/helpers/deliver-epic-reference.md +0 -534
  454. package/.agents/workflows/helpers/deliver-epic.md +0 -955
  455. package/.agents/workflows/helpers/deliver-stories.md +0 -440
  456. package/.agents/workflows/helpers/epic-audit.md +0 -189
  457. package/.agents/workflows/helpers/epic-deliver-story.md +0 -427
  458. package/.agents/workflows/helpers/epic-testing.md +0 -125
  459. package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
  460. package/.agents/workflows/helpers/plan-epic.md +0 -351
  461. package/.agents/workflows/helpers/plan-story.md +0 -251
  462. package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
  463. /package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +0 -0
@@ -1,676 +0,0 @@
1
- /**
2
- * lib/orchestration/epic-spec-reconciler-diff.js — pure-function diff
3
- * engine for the epic-spec reconciler (Epic #1182 / Tech Spec #1483 /
4
- * Story #1492).
5
- *
6
- * `diff({ spec, state, ghState })` walks the three inputs and emits a
7
- * `Plan` (see `epic-spec-reconciler-ops.js`) carrying the structural
8
- * mutations the apply engine needs to perform. The function is **pure**:
9
- *
10
- * • No file I/O, no GitHub provider calls, no clock, no env lookups.
11
- * • Same inputs → byte-identical plan (operations sorted by slug; field
12
- * keys + label arrays canonicalised).
13
- * • Safe to call repeatedly. An empty diff is idempotent — re-running
14
- * against the same `(spec, state, ghState)` triple yields the same
15
- * empty plan.
16
- *
17
- * ## Inputs
18
- *
19
- * @typedef {object} SpecInput
20
- * The parsed YAML returned by `lib/spec/loader.js#loadSpec`. Shape is
21
- * `{ epic: {...}, stories: [...], gates?: {...} }`
22
- * per `.agents/schemas/epic-spec.schema.json`.
23
- *
24
- * @typedef {object} StateMappingEntry
25
- * @property {number} issueNumber GH issue number this slug maps to.
26
- * @property {string} entity 'epic'|'story'.
27
- * @property {string} [contentHash] Content hash captured at last
28
- * reconcile; absence forces an
29
- * update when ghState carries
30
- * structural fields. Present from
31
- * the writer in the apply phase.
32
- * @property {string} [parentSlug] Parent slug at last reconcile.
33
- * Used for relink detection.
34
- * @property {string[]} [dependsOn] Sibling-story slugs at last
35
- * reconcile (stories only).
36
- *
37
- * @typedef {object} StateInput
38
- * @property {number} epicId
39
- * @property {Record<string, StateMappingEntry>} mapping Slug → entry.
40
- * @property {string} [lastReconciledAt]
41
- *
42
- * @typedef {object} GhIssueObservation
43
- * @property {string} title
44
- * @property {string} [body]
45
- * @property {string[]} [labels]
46
- * @property {'open'|'closed'} [state]
47
- *
48
- * @typedef {Record<string|number, GhIssueObservation>} GhStateInput
49
- * Keyed by GH issue number. Stringified numeric keys are coerced so
50
- * callers may pass either `{ 1234: {...} }` or `{ "1234": {...} }`.
51
- *
52
- * ## Algorithm
53
- *
54
- * 1. Walk the spec depth-first, emitting one logical entity per
55
- * `(epic|feature|story|task)`. For each entity:
56
- * - look up the slug in `state.mapping`.
57
- * - if no mapping → Create.
58
- * - if mapped → compare structural fields against ghState[
59
- * mapping.issueNumber] → Update for any diff in title/body/
60
- * labels/wave.
61
- * - if mapping carries `parentSlug` or `dependsOn` and they
62
- * differ from the spec → Relink.
63
- * 2. Walk `state.mapping` for any slug that did NOT appear in the
64
- * spec walk → Close.
65
- * 3. Sort each bucket by slug for deterministic output, then return
66
- * the plan.
67
- *
68
- * Edge cases the engine deliberately handles:
69
- *
70
- * • Epic-level entity has no parent → `parentSlug` is always `null`
71
- * for the epic; relink never fires on the epic.
72
- * • Tasks have no `wave` / `dependsOn` → those fields are skipped on
73
- * tasks even when present in the input (defensive).
74
- * • A slug in the mapping whose `ghState` is missing entirely still
75
- * yields a Close (the mapping itself is the ground truth that the
76
- * spec dropped this entity).
77
- * • Label comparisons ignore order: arrays are sorted before compare.
78
- * • `body` and other optional fields treat `undefined === ''` as
79
- * equality so a spec that omits `body` does not flap against a GH
80
- * issue with an empty body.
81
- */
82
-
83
- import { composeStoryBody } from '../../providers/github/tickets.js';
84
- import {
85
- extractFrameworkStamp,
86
- stampFrameworkVersion,
87
- } from '../framework-version.js';
88
- import { assertPlanLabelAllowList } from './epic-spec-reconciler-discriminator.js';
89
- import {
90
- closeOp,
91
- createOp,
92
- ENTITY_KINDS,
93
- emptyPlan,
94
- relinkOp,
95
- updateOp,
96
- } from './epic-spec-reconciler-ops.js';
97
-
98
- /**
99
- * Compare two label arrays for equality, ignoring order. Returns true
100
- * when both lists carry the same multiset of strings.
101
- *
102
- * @param {string[]|undefined} a
103
- * @param {string[]|undefined} b
104
- * @returns {boolean}
105
- */
106
- function labelsEqual(a, b) {
107
- const left = [...(a ?? [])].sort();
108
- const right = [...(b ?? [])].sort();
109
- if (left.length !== right.length) return false;
110
- for (let i = 0; i < left.length; i += 1) {
111
- if (left[i] !== right[i]) return false;
112
- }
113
- return true;
114
- }
115
-
116
- /**
117
- * Label-namespace prefixes that the reconciler must NOT strip from the
118
- * Epic on persist. The decomposer renders the Epic spec entry from
119
- * `{ id, title }` only — it does not carry `epic.labels` through — so
120
- * a naive replace-style label diff would propose removing operator-
121
- * managed metadata that lives in these namespaces.
122
- *
123
- * Why: Story #2056 / Epic #1994 — `/plan` was silently stripping
124
- * `type::epic` and `risk::*` from the parent Epic on every decompose,
125
- * which then broke `dispatcher.js` (`type "unknown"`). Defence-in-depth
126
- * lives here in the diff engine: even if a future spec author drops
127
- * these labels, the reconciler will not propose their removal.
128
- *
129
- * Symmetry with the `agent::*` allow-list (owned by the wave-runner,
130
- * defended in `epic-spec-reconciler-discriminator.js`): the diff engine
131
- * treats both namespaces as out-of-scope for structural reconciliation,
132
- * but via different mechanisms — `agent::*` is rejected at construction
133
- * time, while these structural namespaces are merged into the Epic's
134
- * after-set so the comparison stays a no-op.
135
- */
136
- const PROTECTED_EPIC_LABEL_NAMESPACES = Object.freeze([
137
- 'type::',
138
- 'risk::',
139
- // Story #3050 — `acceptance::*` is set by Phase 7 spec-persist when
140
- // `planningRisk.acceptanceDisposition='not-applicable'` (or another
141
- // disposition) and gates downstream `/deliver` start/finalize
142
- // behavior. Before this namespace was protected, Phase 8 decompose
143
- // diffed the Epic's labels against a spec entry that doesn't carry
144
- // `acceptance::*`, silently emitting an Update that stripped the
145
- // waiver and broke the contract documented in `.agents/docs/SDLC.md`.
146
- 'acceptance::',
147
- // `planning::*` carries operator-applied planning-gate waivers
148
- // (e.g. `planning::healthcheck-waived`, see persist.js gate). Same
149
- // failure mode as `acceptance::*`: applied between spec and
150
- // decompose, stripped by the naive replace-style diff.
151
- 'planning::',
152
- ]);
153
-
154
- /**
155
- * @param {unknown} label
156
- * @returns {boolean}
157
- */
158
- function isProtectedEpicLabel(label) {
159
- if (typeof label !== 'string') return false;
160
- return PROTECTED_EPIC_LABEL_NAMESPACES.some((ns) => label.startsWith(ns));
161
- }
162
-
163
- /**
164
- * Return the spec's label list for the Epic entity, augmented with any
165
- * protected-namespace labels observed on the live GH issue. Stable
166
- * across calls (uses a Set to deduplicate). When neither input carries
167
- * anything to merge, the original `specLabels` reference is returned
168
- * unchanged so callers that compare references stay correct.
169
- *
170
- * @param {string[]|undefined} specLabels
171
- * @param {string[]|undefined} obsLabels
172
- * @returns {string[]|undefined}
173
- */
174
- function mergeProtectedEpicLabels(specLabels, obsLabels) {
175
- if (!Array.isArray(obsLabels) || obsLabels.length === 0) return specLabels;
176
- const preserved = obsLabels.filter(isProtectedEpicLabel);
177
- if (preserved.length === 0) return specLabels;
178
- const merged = new Set([...(specLabels ?? []), ...preserved]);
179
- return [...merged];
180
- }
181
-
182
- /**
183
- * Treat undefined/null body as the empty string for comparison.
184
- *
185
- * @param {string|undefined|null} value
186
- * @returns {string}
187
- */
188
- function normaliseBody(value) {
189
- return typeof value === 'string' ? value : '';
190
- }
191
-
192
- /**
193
- * Match the canonical orchestrator footer anchored on a `---` separator
194
- * line followed by `parent: #<digits>`. Greedy through end of string so
195
- * duplicate footer blocks and trailing `blocked by` lines are removed in
196
- * a single pass. Kept local to this module so the diff engine does not
197
- * depend on the Task-body renderer (Story #3185 / Epic #3163: the renderer
198
- * is going away with the 2-tier producer cutover).
199
- */
200
- const ORCHESTRATOR_FOOTER_RE = /\n?---[ \t]*\r?\n+parent:\s*#\d+[\s\S]*$/;
201
-
202
- /**
203
- * Strip the canonical orchestrator footer from a body and return just the
204
- * description portion. Safe to call on bodies without a footer — returns
205
- * the input unchanged modulo a trailing-whitespace trim. The trim keeps
206
- * the result byte-stable when fed back through `composeBodyWithFooter`.
207
- *
208
- * @param {string|null|undefined} body
209
- * @returns {string}
210
- */
211
- function stripFooter(body) {
212
- const value = typeof body === 'string' ? body : '';
213
- return value.replace(ORCHESTRATOR_FOOTER_RE, '').replace(/\s+$/, '');
214
- }
215
-
216
- /**
217
- * Compose the canonical orchestrator footer onto a spec body for non-epic
218
- * entities. Resolves `parentSlug`/`dependsOn` slugs against the running
219
- * `state.mapping` so the rendered footer carries the live issue numbers.
220
- * Pure: identical inputs produce a byte-identical body.
221
- *
222
- * Story #2982 — without this re-composition, a body Update sourced from
223
- * the YAML spec writes just the description, silently stripping
224
- * `parent: #N` / `Epic: #M` / `blocked by #X` and breaking the cascade.
225
- *
226
- * Story #4300 — the footer rendering is single-sourced from
227
- * `composeStoryBody` (`providers/github/tickets.js`), the same helper the
228
- * CREATE path (`epic-spec-reconciler-apply.js` → `provider.createTicket`)
229
- * uses. Story #3185 previously inlined a parallel `renderFooter` here to
230
- * avoid depending on the (now-removed) legacy Task-body renderer; that
231
- * inlined copy silently diverged from `composeStoryBody` by gating the
232
- * `Epic: #<id>` line on `epicId !== parentId` — a 3-tier-era condition
233
- * that is always false under the 2-tier hierarchy (a Story's parent IS
234
- * the Epic), so force re-decompose (`/plan --force`, which routes through
235
- * this UPDATE path) silently dropped `Epic: #<id>` from every refreshed
236
- * Story body and broke `story-init.js`'s hierarchy resolution. Importing
237
- * `composeStoryBody` directly makes that divergence structurally
238
- * impossible going forward.
239
- *
240
- * Story #4382 — the create-time authoring stamp (`mandrel_version` /
241
- * `authored_at` meta field + visible marker) is provenance, not spec-derived
242
- * content, so this UPDATE path never mints or bumps it (which would also break
243
- * this function's purity — the stamp uses a clock). Instead the stamp already
244
- * present on the **live** GH body (`obsBody`) is preserved verbatim, and a
245
- * legacy stamp-less body is left stamp-less (no backfill — see the Story's
246
- * Out-of-Scope). Footer recomposition therefore runs in `stamp: false` mode.
247
- *
248
- * @param {{entity: string, parentSlug?: string|null, dependsOn?: string[]}} specEntity
249
- * @param {string} specBody
250
- * @param {{state?: StateInput}} ctx
251
- * @param {string} [obsBody] - The live GH body, source of the preserved stamp.
252
- * @returns {string}
253
- */
254
- function composeBodyWithFooter(specEntity, specBody, ctx, obsBody = '') {
255
- const state = ctx?.state ?? {};
256
- const mapping = state.mapping ?? {};
257
- const parentSlug = specEntity.parentSlug ?? null;
258
- const parentId =
259
- parentSlug && mapping[parentSlug]
260
- ? mapping[parentSlug].issueNumber
261
- : undefined;
262
- // Without a resolved parent we cannot render a meaningful footer.
263
- // Fall back to the raw spec body — apply still writes something the
264
- // operator can inspect; the missing-parent case is rare (the only
265
- // current trigger is a relink-in-flight where the new parent has not
266
- // landed yet, which surfaces elsewhere via the relink op anyway).
267
- if (typeof parentId !== 'number') return specBody;
268
- const epicId = typeof state.epicId === 'number' ? state.epicId : undefined;
269
- const dependencies = Array.isArray(specEntity.dependsOn)
270
- ? specEntity.dependsOn
271
- .map((slug) => mapping[slug]?.issueNumber)
272
- .filter((id) => typeof id === 'number')
273
- : [];
274
- // Strip any orchestrator footer the spec already carries before
275
- // recomposing. Without the strip we double-wrap when the spec body
276
- // came from a producer that stored the raw GH body verbatim (footer
277
- // included) or emits a canonical-form body. With the strip, the
278
- // function is idempotent against its own output.
279
- const head = stripFooter(specBody);
280
- // Preserve the live body's create-time authoring stamp (Story #4382). When
281
- // the spec head already carries it, `stampFrameworkVersion` is a no-op
282
- // (immutable); when it doesn't, the stamp is restored from the live body so
283
- // the recomposed form matches GH and no spurious body Update fires. A
284
- // legacy body with no stamp stays stamp-less (no backfill).
285
- const priorStamp = extractFrameworkStamp(obsBody);
286
- const stampedHead = priorStamp
287
- ? stampFrameworkVersion(head, {
288
- version: priorStamp.version,
289
- authoredAt: priorStamp.authoredAt,
290
- })
291
- : head;
292
- return composeStoryBody({
293
- body: stampedHead,
294
- parentId,
295
- epicId,
296
- dependencies,
297
- stamp: false,
298
- });
299
- }
300
-
301
- /**
302
- * Pick the ghState observation for an issue number, coercing the key
303
- * type so numeric and string keys interop.
304
- *
305
- * @param {GhStateInput|undefined|null} ghState
306
- * @param {number} issueNumber
307
- * @returns {GhIssueObservation|undefined}
308
- */
309
- function ghObservation(ghState, issueNumber) {
310
- if (!ghState) return undefined;
311
- return ghState[issueNumber] ?? ghState[String(issueNumber)] ?? undefined;
312
- }
313
-
314
- /**
315
- * Compute the structural-field changes between a spec entity and the GH
316
- * observation. Returns an empty object when nothing changed.
317
- *
318
- * @param {{title: string, body?: string, labels?: string[], wave?: number, entity: string}} specEntity
319
- * @param {GhIssueObservation|undefined} obs
320
- * @param {StateMappingEntry} mapping
321
- * @returns {Record<string, {before: unknown, after: unknown}>}
322
- */
323
- function fieldChanges(specEntity, obs, mapping, ctx = {}) {
324
- const changes = {};
325
- if (!obs) {
326
- // Mapped but GH side missing → treat as full update (apply will
327
- // recreate body/labels/title). Callers can choose to escalate via
328
- // the close-discriminator.
329
- return changes;
330
- }
331
- if (specEntity.title !== obs.title) {
332
- changes.title = { before: obs.title, after: specEntity.title };
333
- }
334
- // Schema contract (epic-spec.schema.json §epic.body and the parallel
335
- // feature/story/task body fields): "When omitted, the GH issue body
336
- // is left untouched". Pre-Story-#2283 the engine treated `undefined`
337
- // as `""`, which emitted a destructive `body: <existing> → ""` Update
338
- // on every `/plan` Phase 8 because the decomposer's renderer
339
- // projects the Epic spec entry from `{ id, title }` only. Skip the
340
- // body diff entirely when the spec did not carry a body string. An
341
- // explicit `body: ""` in the spec still produces a clear-op when the
342
- // GH side is non-empty (operator-authored intent to blank the body).
343
- if (typeof specEntity.body === 'string') {
344
- const specBody = specEntity.body;
345
- const obsBody = normaliseBody(obs.body);
346
- const isEpic = specEntity.entity === ENTITY_KINDS.EPIC;
347
- if (isEpic) {
348
- if (specBody !== obsBody) {
349
- changes.body = { before: obsBody, after: specBody };
350
- }
351
- } else {
352
- // Story #2982 — for non-epic entities, compare the spec body
353
- // (re-composed with the canonical orchestrator footer) against
354
- // the raw GH body. Single comparison catches:
355
- // • description-only changes,
356
- // • parent/Epic id changes (footer differs),
357
- // • dependsOn changes (`blocked by` block differs),
358
- // • duplicated footer blocks (obs has more than one),
359
- // • missing footer (obs has none).
360
- // Emit a body change only when the canonical form differs from
361
- // what is on GH today — and write the canonical form back, so the
362
- // footer cascade-readers depend on stays intact across resumes.
363
- const after = composeBodyWithFooter(specEntity, specBody, ctx, obsBody);
364
- if (after !== obsBody) {
365
- changes.body = { before: obsBody, after };
366
- }
367
- }
368
- }
369
- const effectiveAfterLabels =
370
- specEntity.entity === ENTITY_KINDS.EPIC
371
- ? mergeProtectedEpicLabels(specEntity.labels, obs.labels)
372
- : specEntity.labels;
373
- if (!labelsEqual(effectiveAfterLabels, obs.labels)) {
374
- changes.labels = {
375
- before: [...(obs.labels ?? [])].sort(),
376
- after: [...(effectiveAfterLabels ?? [])].sort(),
377
- };
378
- }
379
- // wave is story-only; only fire when both sides carry an integer and
380
- // they differ. Mapping carries the last-known wave under
381
- // `mapping.wave` (apply-engine populates it); absent → skip.
382
- if (specEntity.entity === ENTITY_KINDS.STORY) {
383
- const beforeWave =
384
- typeof mapping.wave === 'number' ? mapping.wave : undefined;
385
- const afterWave =
386
- typeof specEntity.wave === 'number' ? specEntity.wave : undefined;
387
- if (
388
- beforeWave !== undefined &&
389
- afterWave !== undefined &&
390
- beforeWave !== afterWave
391
- ) {
392
- changes.wave = { before: beforeWave, after: afterWave };
393
- }
394
- }
395
- return changes;
396
- }
397
-
398
- /**
399
- * Compare two parent-edge values. `null` represents "no parent" (the
400
- * epic root). Strings compare by value.
401
- *
402
- * @param {string|null|undefined} a
403
- * @param {string|null|undefined} b
404
- * @returns {boolean}
405
- */
406
- function parentEqual(a, b) {
407
- const left = a == null ? null : a;
408
- const right = b == null ? null : b;
409
- return left === right;
410
- }
411
-
412
- /**
413
- * Sort an array of operations by slug, returning a new array.
414
- *
415
- * @template {{slug: string}} T
416
- * @param {T[]} ops
417
- * @returns {T[]}
418
- */
419
- function sortBySlug(ops) {
420
- return [...ops].sort((a, b) => a.slug.localeCompare(b.slug));
421
- }
422
-
423
- /**
424
- * Walk the spec and yield one structural-entity record per visited
425
- * node. The spec is 2-tier (epic → stories), so the walk is a single
426
- * flat pass over `spec.stories` after the Epic record.
427
- *
428
- * @param {SpecInput} spec
429
- * @returns {Array<{
430
- * slug: string,
431
- * entity: string,
432
- * title: string,
433
- * body?: string,
434
- * labels?: string[],
435
- * wave?: number,
436
- * parentSlug: string|null,
437
- * dependsOn?: string[],
438
- * }>}
439
- */
440
- function flattenSpec(spec) {
441
- const out = [];
442
- if (!spec || typeof spec !== 'object') return out;
443
-
444
- // Epic — no parent.
445
- if (spec.epic && typeof spec.epic === 'object') {
446
- out.push({
447
- slug: epicSlug(spec.epic),
448
- entity: ENTITY_KINDS.EPIC,
449
- title: String(spec.epic.title ?? ''),
450
- body: spec.epic.body,
451
- labels: spec.epic.labels,
452
- parentSlug: null,
453
- });
454
- }
455
-
456
- const epicAnchor = spec.epic ? epicSlug(spec.epic) : null;
457
- // Duplicate-slug guard. The schema cannot express slug uniqueness across
458
- // a hand-edited spec, and two same-slug Creates would orphan one GH
459
- // issue (the second create overwrites the first's mapping entry).
460
- // flattenSpec is the chokepoint both the loader path and the diff path
461
- // flow through, so the check lives here.
462
- const seenSlugs = new Set();
463
- const duplicateSlugs = new Set();
464
- for (const story of spec.stories ?? []) {
465
- if (seenSlugs.has(story.slug)) duplicateSlugs.add(story.slug);
466
- seenSlugs.add(story.slug);
467
- }
468
- if (duplicateSlugs.size > 0) {
469
- throw new Error(
470
- `spec contains duplicate story slug(s): ${[...duplicateSlugs].join(', ')}. ` +
471
- `Each story slug must be unique — rename the duplicated entries in ` +
472
- `the spec and re-run.`,
473
- );
474
- }
475
- for (const story of spec.stories ?? []) {
476
- out.push({
477
- slug: story.slug,
478
- entity: ENTITY_KINDS.STORY,
479
- title: String(story.title ?? ''),
480
- body: story.body,
481
- labels: story.labels,
482
- wave: story.wave,
483
- parentSlug: epicAnchor,
484
- dependsOn: story.dependsOn ?? [],
485
- });
486
- }
487
- return out;
488
- }
489
-
490
- /**
491
- * The epic-level slug is synthetic — the spec keys the epic by GH issue
492
- * number, not by a slug — but the reconciler needs a stable identifier
493
- * to thread the epic entity through the operation surface (state
494
- * mapping, plan formatter, etc). We use the canonical literal `epic`
495
- * so the formatter can render it without special-casing.
496
- *
497
- * @param {{id: number}} epic
498
- * @returns {string}
499
- */
500
- function epicSlug(_epic) {
501
- // Single epic per spec — schema requires it — so a constant slug is
502
- // unambiguous and matches the way mapping is keyed in writeState
503
- // (where the epic entry is stored under `epic`).
504
- return `epic`;
505
- }
506
-
507
- /**
508
- * Compute equality between two `dependsOn` lists, ignoring order.
509
- *
510
- * @param {string[]|undefined} a
511
- * @param {string[]|undefined} b
512
- * @returns {boolean}
513
- */
514
- function dependsOnEqual(a, b) {
515
- return labelsEqual(a, b);
516
- }
517
-
518
- /**
519
- * Entity kinds that only exist in pre-v4 (3-tier) state files. The v4
520
- * hard cutover (v1.60.0) removed the Feature/Task tiers; encountering one
521
- * of these in `state.mapping` means the state file predates the cutover
522
- * and must be migrated by the operator — there is deliberately no legacy
523
- * close-op support (hard-cutover policy, git-conventions.md).
524
- */
525
- const LEGACY_ENTITY_KINDS = Object.freeze(new Set(['feature', 'task']));
526
-
527
- /**
528
- * Throw a loud, actionable error when the state mapping carries legacy
529
- * Feature/Task entries. Raised early in `diff()` so the operator sees a
530
- * migration instruction instead of `unknown entity kind: feature` from
531
- * deep inside `closeOp`.
532
- *
533
- * @param {Record<string, StateMappingEntry>} mapping
534
- */
535
- function assertNoLegacyEntities(mapping) {
536
- const legacy = Object.entries(mapping).filter(([, entry]) =>
537
- LEGACY_ENTITY_KINDS.has(entry?.entity),
538
- );
539
- if (legacy.length === 0) return;
540
- const summary = legacy
541
- .map(([slug, entry]) => `${slug} (#${entry.issueNumber}, ${entry.entity})`)
542
- .join(', ');
543
- throw new Error(
544
- `diff: the epic state file is pre-v4 — it carries legacy Feature/Task ` +
545
- `mapping entries: ${summary}. The 2-tier reconciler cannot process ` +
546
- `these. Close the legacy Feature issues manually, then delete (or ` +
547
- `reseed) the .agents/epics/<epicId>.state.json file per the v1.60.0 ` +
548
- `migration notes, and re-run.`,
549
- );
550
- }
551
-
552
- /**
553
- * Diff `(spec, state, ghState)` into a `Plan`. See the module header for
554
- * the full contract.
555
- *
556
- * @param {{spec: SpecInput, state: StateInput, ghState?: GhStateInput}} input
557
- * @returns {import('./epic-spec-reconciler-ops.js').Plan}
558
- */
559
- export function diff({ spec, state, ghState } = {}) {
560
- const plan = emptyPlan();
561
- if (!spec || typeof spec !== 'object') return plan;
562
- if (!state || typeof state !== 'object') {
563
- throw new TypeError('diff: state argument is required');
564
- }
565
- const mapping = state.mapping ?? {};
566
- assertNoLegacyEntities(mapping);
567
- const seenSpecSlugs = new Set();
568
-
569
- for (const entity of flattenSpec(spec)) {
570
- seenSpecSlugs.add(entity.slug);
571
- // Epic #4474 (PR3) — a mapping entry without a numeric `issueNumber`
572
- // is a partial-failure tombstone: the apply's state writer projects an
573
- // entry per spec slug, but only completed creations carry a number
574
- // (`buildState` seeds new slugs with `issueNumber: null`). Treating
575
- // such an entry as "mapped" made a `--resume` after a rate-limit crash
576
- // silently skip every failed creation (no Create emitted; the content
577
- // hash matches, so no Update either — a lossy resume). A numberless
578
- // entry is therefore unmapped here → Create. No duplicate risk: any
579
- // live same-title issue was already reseeded onto the slug (with its
580
- // number) by `reseedMappingFromGh` before the diff runs.
581
- const mappedEntry = mapping[entity.slug];
582
- const mapped = Number.isInteger(mappedEntry?.issueNumber)
583
- ? mappedEntry
584
- : undefined;
585
-
586
- if (!mapped) {
587
- plan.creates.push(
588
- createOp({
589
- slug: entity.slug,
590
- entity: entity.entity,
591
- title: entity.title,
592
- body: entity.body,
593
- labels: entity.labels,
594
- parentSlug:
595
- entity.parentSlug === null ? undefined : entity.parentSlug,
596
- dependsOn: entity.dependsOn,
597
- wave: entity.wave,
598
- }),
599
- );
600
- continue;
601
- }
602
-
603
- // Mapped: check for content updates.
604
- const obs = ghObservation(ghState, mapped.issueNumber);
605
- const changes = fieldChanges(entity, obs, mapped, { state });
606
- if (Object.keys(changes).length > 0) {
607
- plan.updates.push(
608
- updateOp({
609
- slug: entity.slug,
610
- entity: entity.entity,
611
- issueNumber: mapped.issueNumber,
612
- changes,
613
- }),
614
- );
615
- }
616
-
617
- // Mapped: check for relink (parent / dependsOn edge changes).
618
- const relinkPayload = {};
619
- const beforeParent = mapped.parentSlug ?? null;
620
- const afterParent = entity.parentSlug ?? null;
621
- if (
622
- !parentEqual(beforeParent, afterParent) &&
623
- entity.entity !== ENTITY_KINDS.EPIC
624
- ) {
625
- relinkPayload.parent = { before: beforeParent, after: afterParent };
626
- }
627
- if (entity.entity === ENTITY_KINDS.STORY) {
628
- const beforeDeps = mapped.dependsOn ?? [];
629
- const afterDeps = entity.dependsOn ?? [];
630
- if (!dependsOnEqual(beforeDeps, afterDeps)) {
631
- relinkPayload.dependsOn = { before: beforeDeps, after: afterDeps };
632
- }
633
- }
634
- if (Object.keys(relinkPayload).length > 0) {
635
- plan.relinks.push(
636
- relinkOp({
637
- slug: entity.slug,
638
- entity: entity.entity,
639
- issueNumber: mapped.issueNumber,
640
- ...relinkPayload,
641
- }),
642
- );
643
- }
644
- }
645
-
646
- // Closes — anything in mapping not seen in spec. A numberless
647
- // partial-failure tombstone (see above) has no live issue to close;
648
- // dropping it from the spec simply drops the entry.
649
- for (const [slug, mapped] of Object.entries(mapping)) {
650
- if (seenSpecSlugs.has(slug)) continue;
651
- if (!Number.isInteger(mapped?.issueNumber)) continue;
652
- plan.closes.push(
653
- closeOp({
654
- slug,
655
- entity: mapped.entity ?? ENTITY_KINDS.STORY,
656
- issueNumber: mapped.issueNumber,
657
- title: mapped.title,
658
- }),
659
- );
660
- }
661
-
662
- plan.creates = sortBySlug(plan.creates);
663
- plan.updates = sortBySlug(plan.updates);
664
- plan.closes = sortBySlug(plan.closes);
665
- plan.relinks = sortBySlug(plan.relinks);
666
-
667
- // Diff-time safety net (Story #1493 / Task #1515). The diff engine
668
- // never *intends* to emit an agent::* payload, but defence-in-depth
669
- // catches both a future spec-loader bug that would smuggle an agent::*
670
- // through a structural field and an apply-pipeline regression that
671
- // would otherwise silently corrupt wave-runner state. Throws
672
- // `LabelAllowListViolation` synchronously.
673
- assertPlanLabelAllowList(plan);
674
-
675
- return plan;
676
- }