mandrel 1.94.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (560) hide show
  1. package/.agents/README.md +116 -99
  2. package/.agents/agents/acceptance-critic.md +9 -7
  3. package/.agents/agents/story-worker.md +45 -51
  4. package/.agents/audit-checklists/performance.md +1 -1
  5. package/.agents/docs/SDLC.md +498 -1287
  6. package/.agents/docs/agentrc-reference.json +185 -80
  7. package/.agents/docs/configuration.md +132 -190
  8. package/.agents/docs/execution-reference.md +51 -25
  9. package/.agents/docs/quality-gates.md +25 -33
  10. package/.agents/docs/workflows.md +8 -8
  11. package/.agents/instructions.md +113 -139
  12. package/.agents/rules/ci-remediation.md +11 -15
  13. package/.agents/rules/git-conventions-reference.md +48 -58
  14. package/.agents/rules/git-conventions.md +16 -22
  15. package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
  16. package/.agents/schemas/agentrc.schema.json +83 -254
  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/retro.end.schema.json +1 -1
  24. package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
  25. package/.agents/schemas/signal-event.schema.json +3 -3
  26. package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
  27. package/.agents/schemas/validation-evidence.schema.json +1 -1
  28. package/.agents/scripts/acceptance-eval.js +22 -66
  29. package/.agents/scripts/agents-bootstrap-github.js +1 -1
  30. package/.agents/scripts/audit-to-stories.js +7 -7
  31. package/.agents/scripts/boot-sweep.js +1 -1
  32. package/.agents/scripts/bootstrap.js +3 -3
  33. package/.agents/scripts/check-dead-exports.js +43 -104
  34. package/.agents/scripts/check-doc-links.js +2 -2
  35. package/.agents/scripts/check-lifecycle-lint.js +7 -10
  36. package/.agents/scripts/check-workflow-cli-lint.js +91 -0
  37. package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
  38. package/.agents/scripts/deliver-recover.js +122 -0
  39. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  40. package/.agents/scripts/evidence-gate.js +20 -50
  41. package/.agents/scripts/generate-skills-index.js +17 -1
  42. package/.agents/scripts/generate-workflows-doc.js +4 -4
  43. package/.agents/scripts/lib/ITicketingProvider.js +1 -19
  44. package/.agents/scripts/lib/Logger.js +6 -10
  45. package/.agents/scripts/lib/audit-suite/runner.js +2 -2
  46. package/.agents/scripts/lib/audit-suite/selector.js +328 -28
  47. package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
  48. package/.agents/scripts/lib/baselines/kernel.js +206 -18
  49. package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -15
  50. package/.agents/scripts/lib/baselines/reader.js +1 -6
  51. package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
  52. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
  53. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
  54. package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
  55. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
  56. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
  57. package/.agents/scripts/lib/checks/core-bare-clean.js +6 -3
  58. package/.agents/scripts/lib/checks/index.js +3 -2
  59. package/.agents/scripts/lib/checks/loop-health.js +12 -11
  60. package/.agents/scripts/lib/checks/state.js +17 -248
  61. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +26 -24
  62. package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
  63. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
  64. package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
  65. package/.agents/scripts/lib/cli/standard-args.js +13 -22
  66. package/.agents/scripts/lib/cli-args.js +39 -9
  67. package/.agents/scripts/lib/close-validation/gates.js +15 -15
  68. package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
  69. package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
  70. package/.agents/scripts/lib/close-validation/runner.js +13 -21
  71. package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
  72. package/.agents/scripts/lib/config/ci.js +6 -31
  73. package/.agents/scripts/lib/config/delivery-routing.js +52 -35
  74. package/.agents/scripts/lib/config/explain.js +61 -48
  75. package/.agents/scripts/lib/config/github.js +7 -5
  76. package/.agents/scripts/lib/config/limits.js +29 -80
  77. package/.agents/scripts/lib/config/paths.js +0 -2
  78. package/.agents/scripts/lib/config/quality.js +12 -15
  79. package/.agents/scripts/lib/config/runners.js +20 -66
  80. package/.agents/scripts/lib/config/temp-paths.js +30 -63
  81. package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
  82. package/.agents/scripts/lib/config-resolver.js +2 -7
  83. package/.agents/scripts/lib/config-settings-schema-delivery.js +55 -161
  84. package/.agents/scripts/lib/config-settings-schema-quality.js +17 -16
  85. package/.agents/scripts/lib/config-settings-schema.js +100 -60
  86. package/.agents/scripts/lib/dead-exports-knip.js +105 -0
  87. package/.agents/scripts/lib/dead-exports-mode.js +51 -0
  88. package/.agents/scripts/lib/dependency-parser.js +3 -2
  89. package/.agents/scripts/lib/doc-tiers.js +2 -2
  90. package/.agents/scripts/lib/duplicate-search.js +242 -41
  91. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
  92. package/.agents/scripts/lib/findings/promote-finding.js +23 -14
  93. package/.agents/scripts/lib/format-generated-json.js +97 -0
  94. package/.agents/scripts/lib/framework-version.js +19 -190
  95. package/.agents/scripts/lib/gh-exec.js +8 -0
  96. package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
  97. package/.agents/scripts/lib/git-branch-lifecycle.js +17 -180
  98. package/.agents/scripts/lib/git-utils.js +32 -20
  99. package/.agents/scripts/lib/github/framework-repo.js +6 -0
  100. package/.agents/scripts/lib/json-utils.js +1 -2
  101. package/.agents/scripts/lib/label-constants.js +10 -38
  102. package/.agents/scripts/lib/label-taxonomy.js +10 -55
  103. package/.agents/scripts/lib/observability/active-story-env.js +44 -165
  104. package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
  105. package/.agents/scripts/lib/observability/signal-validator.js +4 -4
  106. package/.agents/scripts/lib/observability/signals-writer.js +6 -82
  107. package/.agents/scripts/lib/observability/source-classifier.js +5 -5
  108. package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
  109. package/.agents/scripts/lib/onboard/init-tail.js +1 -3
  110. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
  111. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
  112. package/.agents/scripts/lib/orchestration/ceremony-routing.js +105 -44
  113. package/.agents/scripts/lib/orchestration/code-review.js +78 -436
  114. package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
  115. package/.agents/scripts/lib/orchestration/consolidation-precondition.js +4 -4
  116. package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
  117. package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
  118. package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
  119. package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
  120. package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
  121. package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
  122. package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
  123. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
  124. package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
  125. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +17 -19
  126. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
  127. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
  128. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
  129. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +21 -43
  130. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
  131. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
  132. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
  133. package/.agents/scripts/lib/orchestration/merge-block-class.js +77 -21
  134. package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
  135. package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
  136. package/.agents/scripts/lib/orchestration/plan-context.js +354 -282
  137. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
  138. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +4 -8
  139. package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
  140. package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
  141. package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +71 -0
  142. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +245 -0
  143. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
  144. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +432 -858
  145. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +778 -0
  146. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +47 -115
  147. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
  148. package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
  149. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
  150. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +52 -51
  151. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
  152. package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +1 -1
  153. package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
  154. package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
  155. package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
  156. package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
  157. package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
  158. package/.agents/scripts/lib/orchestration/review-depth.js +126 -47
  159. package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
  160. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
  161. package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
  162. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
  163. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
  164. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
  165. package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
  166. package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
  167. package/.agents/scripts/lib/orchestration/run-epilogue.js +784 -0
  168. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +25 -1
  169. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
  170. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
  171. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +86 -41
  172. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
  173. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +77 -32
  174. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
  175. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
  176. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +44 -0
  177. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
  178. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
  179. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +275 -46
  180. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  181. package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
  182. package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
  183. package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
  184. package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
  185. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
  186. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
  187. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +28 -42
  188. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
  189. package/.agents/scripts/lib/orchestration/story-follow-ups.js +298 -0
  190. package/.agents/scripts/lib/orchestration/story-init-remote.js +51 -0
  191. package/.agents/scripts/lib/orchestration/story-plan-state.js +33 -0
  192. package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
  193. package/.agents/scripts/lib/orchestration/task-body-validator.js +60 -25
  194. package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
  195. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +130 -75
  196. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +192 -377
  197. package/.agents/scripts/lib/orchestration/ticket-validator.js +123 -25
  198. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -72
  199. package/.agents/scripts/lib/orchestration/ticketing/reads.js +45 -55
  200. package/.agents/scripts/lib/orchestration/ticketing/transition.js +66 -6
  201. package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
  202. package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
  203. package/.agents/scripts/lib/planning-corpus.js +12 -281
  204. package/.agents/scripts/lib/preflight-runner.js +4 -4
  205. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
  206. package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
  207. package/.agents/scripts/lib/signals/index.js +4 -17
  208. package/.agents/scripts/lib/signals/read.js +35 -35
  209. package/.agents/scripts/lib/signals/schema.js +8 -11
  210. package/.agents/scripts/lib/signals/span-tree.js +7 -7
  211. package/.agents/scripts/lib/signals/write.js +0 -1
  212. package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
  213. package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
  214. package/.agents/scripts/lib/skills/parse-skill.js +16 -3
  215. package/.agents/scripts/lib/story-adjacency.js +17 -19
  216. package/.agents/scripts/lib/story-body/story-body.js +130 -75
  217. package/.agents/scripts/lib/story-plan.js +2 -4
  218. package/.agents/scripts/lib/templates/decomposer-prompts.js +51 -46
  219. package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
  220. package/.agents/scripts/lib/test-env.js +14 -1
  221. package/.agents/scripts/lib/test-tiers.js +0 -3
  222. package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +25 -39
  223. package/.agents/scripts/lib/validation-evidence.js +32 -60
  224. package/.agents/scripts/lib/wave-runner/ready-set.js +38 -12
  225. package/.agents/scripts/lib/workspace-provisioner.js +1 -1
  226. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
  227. package/.agents/scripts/lib/worktree/lifecycle/reap.js +72 -25
  228. package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
  229. package/.agents/scripts/lint-issue-body.js +71 -40
  230. package/.agents/scripts/mandrel-update-preflight.js +1 -1
  231. package/.agents/scripts/notify.js +4 -3
  232. package/.agents/scripts/plan-context.js +102 -80
  233. package/.agents/scripts/plan-persist.js +230 -279
  234. package/.agents/scripts/plan-run-epilogue.js +142 -0
  235. package/.agents/scripts/post-structured-comment.js +0 -38
  236. package/.agents/scripts/pr-watch-with-update.js +43 -22
  237. package/.agents/scripts/providers/github/compose.js +0 -1
  238. package/.agents/scripts/providers/github/errors.js +0 -19
  239. package/.agents/scripts/providers/github/issues.js +13 -39
  240. package/.agents/scripts/providers/github/mappers.js +5 -12
  241. package/.agents/scripts/providers/github/sub-issues.js +0 -47
  242. package/.agents/scripts/providers/github/tickets.js +33 -156
  243. package/.agents/scripts/providers/github.js +17 -6
  244. package/.agents/scripts/resolve-stories.js +236 -0
  245. package/.agents/scripts/run-coverage.js +4 -1
  246. package/.agents/scripts/run-lint.js +2 -2
  247. package/.agents/scripts/run-verify.js +31 -2
  248. package/.agents/scripts/signals-view.js +25 -21
  249. package/.agents/scripts/single-story-close.js +178 -26
  250. package/.agents/scripts/single-story-confirm-merge.js +313 -24
  251. package/.agents/scripts/single-story-init.js +35 -30
  252. package/.agents/scripts/stories-wave-tick.js +85 -10
  253. package/.agents/scripts/story-plan.js +28 -49
  254. package/.agents/scripts/update-ticket-state.js +14 -65
  255. package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
  256. package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
  257. package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
  258. package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
  259. package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
  260. package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
  261. package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
  262. package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
  263. package/.agents/skills/core/idea-refinement/SKILL.md +9 -9
  264. package/.agents/skills/core/scope-triage/SKILL.md +31 -172
  265. package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
  266. package/.agents/skills/core/security-and-hardening/reference.md +375 -0
  267. package/.agents/skills/skills.index.json +3 -53
  268. package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
  269. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
  270. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
  271. package/.agents/starter-agentrc.json +0 -5
  272. package/.agents/templates/agent-protocol.md +9 -10
  273. package/.agents/workflows/audit-architecture.md +6 -7
  274. package/.agents/workflows/audit-clean-code.md +7 -7
  275. package/.agents/workflows/audit-dependencies.md +3 -3
  276. package/.agents/workflows/audit-devops.md +3 -3
  277. package/.agents/workflows/audit-documentation.md +9 -10
  278. package/.agents/workflows/audit-lighthouse.md +11 -3
  279. package/.agents/workflows/audit-navigability.md +13 -2
  280. package/.agents/workflows/audit-performance.md +5 -6
  281. package/.agents/workflows/audit-privacy.md +3 -3
  282. package/.agents/workflows/audit-quality.md +11 -12
  283. package/.agents/workflows/audit-security.md +4 -5
  284. package/.agents/workflows/audit-seo.md +13 -3
  285. package/.agents/workflows/audit-sre.md +3 -3
  286. package/.agents/workflows/audit-to-stories.md +20 -20
  287. package/.agents/workflows/audit-ux-ui.md +10 -3
  288. package/.agents/workflows/deliver.md +177 -176
  289. package/.agents/workflows/git-cleanup.md +5 -6
  290. package/.agents/workflows/git-deliver.md +1 -1
  291. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  292. package/.agents/workflows/helpers/acceptance-self-eval.md +35 -40
  293. package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
  294. package/.agents/workflows/helpers/code-review.md +75 -196
  295. package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +83 -44
  296. package/.agents/workflows/helpers/deliver-story.md +606 -0
  297. package/.agents/workflows/helpers/diagnose.md +10 -10
  298. package/.agents/workflows/helpers/parallel-tooling.md +3 -3
  299. package/.agents/workflows/helpers/signals.md +16 -16
  300. package/.agents/workflows/helpers/worktree-lifecycle.md +66 -86
  301. package/.agents/workflows/mandrel-update.md +2 -1
  302. package/.agents/workflows/plan.md +277 -145
  303. package/.agents/workflows/qa-assist.md +27 -33
  304. package/.agents/workflows/qa-explore.md +29 -38
  305. package/.agents/workflows/qa-run.md +2 -6
  306. package/README.md +9 -8
  307. package/bin/mandrel.js +12 -1
  308. package/docs/CHANGELOG.md +70 -0
  309. package/lib/cli/registry.js +262 -19
  310. package/lib/cli/sync-agents.js +157 -0
  311. package/lib/cli/sync-commands.js +115 -6
  312. package/lib/cli/sync.js +168 -6
  313. package/lib/cli/update.js +105 -8
  314. package/lib/cli/version-helpers.js +131 -0
  315. package/lib/migrations/README.md +7 -5
  316. package/lib/migrations/index.js +12 -8
  317. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
  318. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
  319. package/package.json +2 -2
  320. package/.agents/agents/retro.md +0 -42
  321. package/.agents/personas/architect.md +0 -113
  322. package/.agents/personas/devops-engineer.md +0 -38
  323. package/.agents/personas/engineer.md +0 -33
  324. package/.agents/personas/project-manager.md +0 -114
  325. package/.agents/personas/qa-engineer.md +0 -95
  326. package/.agents/personas/security-engineer.md +0 -111
  327. package/.agents/personas/technical-writer.md +0 -101
  328. package/.agents/schemas/dispatch-manifest.json +0 -232
  329. package/.agents/schemas/epic-perf-report.schema.json +0 -89
  330. package/.agents/schemas/epic-spec.schema.json +0 -153
  331. package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
  332. package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
  333. package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
  334. package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
  335. package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
  336. package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
  337. package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
  338. package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
  339. package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
  340. package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
  341. package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
  342. package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
  343. package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
  344. package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
  345. package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
  346. package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
  347. package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
  348. package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
  349. package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
  350. package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
  351. package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
  352. package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
  353. package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
  354. package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
  355. package/.agents/schemas/lifecycle/slice.end.schema.json +0 -21
  356. package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +0 -20
  357. package/.agents/schemas/lifecycle/slice.start.schema.json +0 -17
  358. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
  359. package/.agents/schemas/risk-verdict.schema.json +0 -66
  360. package/.agents/schemas/story-perf-summary.schema.json +0 -73
  361. package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
  362. package/.agents/scripts/analyze-execution.js +0 -444
  363. package/.agents/scripts/bookkeeping-reconcile.js +0 -117
  364. package/.agents/scripts/check-prepush-recovery.js +0 -90
  365. package/.agents/scripts/dispatcher.js +0 -295
  366. package/.agents/scripts/epic-audit-prepare.js +0 -497
  367. package/.agents/scripts/epic-audit-recheck.js +0 -274
  368. package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
  369. package/.agents/scripts/epic-deliver-preflight.js +0 -462
  370. package/.agents/scripts/epic-deliver-prepare.js +0 -852
  371. package/.agents/scripts/epic-execute-record-wave.js +0 -449
  372. package/.agents/scripts/epic-plan-clarity.js +0 -211
  373. package/.agents/scripts/epic-plan-healthcheck.js +0 -581
  374. package/.agents/scripts/epic-reconcile.js +0 -625
  375. package/.agents/scripts/lib/baseline-snapshot.js +0 -979
  376. package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
  377. package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
  378. package/.agents/scripts/lib/config/lifecycle.js +0 -40
  379. package/.agents/scripts/lib/config/preflight.js +0 -58
  380. package/.agents/scripts/lib/config/retro.js +0 -77
  381. package/.agents/scripts/lib/epic-merge-lock.js +0 -322
  382. package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
  383. package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
  384. package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
  385. package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
  386. package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -219
  387. package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
  388. package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
  389. package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
  390. package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -270
  391. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -539
  392. package/.agents/scripts/lib/orchestration/deliver-route.js +0 -173
  393. package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
  394. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
  395. package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
  396. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
  397. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
  398. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
  399. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
  400. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
  401. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
  402. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
  403. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
  404. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
  405. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
  406. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
  407. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
  408. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/risk-verdict.js +0 -105
  409. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
  410. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
  411. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
  412. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -621
  413. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
  414. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
  415. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
  416. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
  417. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
  418. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
  419. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
  420. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
  421. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
  422. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
  423. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
  424. package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
  425. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
  426. package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +0 -400
  427. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
  428. package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
  429. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
  430. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
  431. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
  432. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
  433. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
  434. package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
  435. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
  436. package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
  437. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
  438. package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +0 -270
  439. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
  440. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
  441. package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
  442. package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -465
  443. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
  444. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
  445. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
  446. package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -284
  447. package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
  448. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
  449. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
  450. package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
  451. package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
  452. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
  453. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
  454. package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
  455. package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
  456. package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
  457. package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
  458. package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
  459. package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
  460. package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
  461. package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -33
  462. package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
  463. package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
  464. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
  465. package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -396
  466. package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
  467. package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
  468. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
  469. package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
  470. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
  471. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
  472. package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
  473. package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
  474. package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
  475. package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
  476. package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
  477. package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
  478. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
  479. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
  480. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
  481. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
  482. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
  483. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
  484. package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
  485. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
  486. package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
  487. package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
  488. package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
  489. package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
  490. package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
  491. package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
  492. package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
  493. package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
  494. package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
  495. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
  496. package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
  497. package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
  498. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
  499. package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
  500. package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
  501. package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -302
  502. package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
  503. package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
  504. package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
  505. package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
  506. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
  507. package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
  508. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
  509. package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
  510. package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
  511. package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
  512. package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
  513. package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
  514. package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
  515. package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
  516. package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
  517. package/.agents/scripts/lib/push-epic-retry.js +0 -209
  518. package/.agents/scripts/lib/spec/index.js +0 -36
  519. package/.agents/scripts/lib/spec/loader.js +0 -425
  520. package/.agents/scripts/lib/spec/state.js +0 -208
  521. package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
  522. package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
  523. package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
  524. package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
  525. package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
  526. package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
  527. package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
  528. package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
  529. package/.agents/scripts/lib/wave-runner/tick.js +0 -754
  530. package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
  531. package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
  532. package/.agents/scripts/lifecycle-emit.js +0 -510
  533. package/.agents/scripts/retro-run.js +0 -218
  534. package/.agents/scripts/slice-phase.js +0 -361
  535. package/.agents/scripts/standalone-feedback-rollup.js +0 -188
  536. package/.agents/scripts/story-close.js +0 -294
  537. package/.agents/scripts/story-init.js +0 -599
  538. package/.agents/scripts/story-phase.js +0 -369
  539. package/.agents/scripts/wave-tick.js +0 -464
  540. package/.agents/skills/core/analyze-execution/SKILL.md +0 -98
  541. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
  542. package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
  543. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
  544. package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
  545. package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
  546. package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
  547. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -383
  548. package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
  549. package/.agents/workflows/helpers/deliver-epic-reference.md +0 -547
  550. package/.agents/workflows/helpers/deliver-epic-single.md +0 -331
  551. package/.agents/workflows/helpers/deliver-epic.md +0 -998
  552. package/.agents/workflows/helpers/deliver-stories.md +0 -450
  553. package/.agents/workflows/helpers/epic-audit.md +0 -189
  554. package/.agents/workflows/helpers/epic-deliver-story.md +0 -436
  555. package/.agents/workflows/helpers/epic-testing.md +0 -125
  556. package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
  557. package/.agents/workflows/helpers/plan-epic.md +0 -353
  558. package/.agents/workflows/helpers/plan-story.md +0 -251
  559. package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
  560. package/.agents/workflows/helpers/single-story-deliver.md +0 -557
@@ -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
- }