mandrel 1.59.0 → 1.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/.agents/README.md +86 -44
  2. package/.agents/docs/SDLC.md +135 -141
  3. package/.agents/docs/configuration.md +77 -20
  4. package/.agents/docs/quality-gates.md +796 -0
  5. package/.agents/docs/workflows.md +6 -9
  6. package/.agents/instructions.md +12 -11
  7. package/.agents/personas/architect.md +1 -1
  8. package/.agents/personas/product.md +1 -1
  9. package/.agents/personas/project-manager.md +14 -14
  10. package/.agents/personas/technical-writer.md +1 -1
  11. package/.agents/rules/changelog-style.md +5 -5
  12. package/.agents/rules/git-conventions.md +3 -3
  13. package/.agents/runtime-deps.json +2 -2
  14. package/.agents/schemas/agentrc.schema.json +3 -3
  15. package/.agents/schemas/dispatch-manifest.json +4 -4
  16. package/.agents/schemas/epic-spec.schema.json +15 -45
  17. package/.agents/schemas/lifecycle/README.md +1 -1
  18. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +1 -1
  19. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +1 -1
  20. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +1 -1
  21. package/.agents/schemas/validation-evidence.schema.json +1 -1
  22. package/.agents/scripts/README.md +2 -2
  23. package/.agents/scripts/acceptance-eval.js +1 -1
  24. package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
  25. package/.agents/scripts/agents-bootstrap-github.js +23 -119
  26. package/.agents/scripts/analyze-execution.js +2 -2
  27. package/.agents/scripts/audit-to-stories.js +1 -1
  28. package/.agents/scripts/check-doc-links.js +2 -3
  29. package/.agents/scripts/diagnose-friction.js +1 -1
  30. package/.agents/scripts/dispatcher.js +2 -2
  31. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  32. package/.agents/scripts/epic-audit-prepare.js +3 -3
  33. package/.agents/scripts/epic-deliver-note-intervention.js +2 -2
  34. package/.agents/scripts/epic-deliver-preflight.js +6 -6
  35. package/.agents/scripts/epic-deliver-prepare.js +1 -1
  36. package/.agents/scripts/epic-execute-record-wave.js +4 -4
  37. package/.agents/scripts/epic-plan-healthcheck.js +6 -10
  38. package/.agents/scripts/epic-plan-spec-validate.js +1 -1
  39. package/.agents/scripts/epic-reconcile.js +11 -29
  40. package/.agents/scripts/evidence-gate.js +1 -1
  41. package/.agents/scripts/generate-workflows-doc.js +1 -1
  42. package/.agents/scripts/hierarchy-gate.js +7 -11
  43. package/.agents/scripts/lib/ITicketingProvider.js +1 -1
  44. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  45. package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +2 -2
  46. package/.agents/scripts/lib/baseline-snapshot.js +7 -7
  47. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  48. package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
  49. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +1 -1
  50. package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
  51. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +47 -1
  52. package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
  53. package/.agents/scripts/lib/bootstrap/gh-preflight.js +7 -9
  54. package/.agents/scripts/lib/bootstrap/manifest.js +21 -1
  55. package/.agents/scripts/lib/bootstrap/merge-methods.js +31 -16
  56. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +32 -11
  57. package/.agents/scripts/lib/codebase-snapshot.js +1 -1
  58. package/.agents/scripts/lib/config/explain.js +1 -1
  59. package/.agents/scripts/lib/config/runners.js +2 -2
  60. package/.agents/scripts/lib/config/runtime.js +1 -1
  61. package/.agents/scripts/lib/config/sync-agentrc.js +1 -1
  62. package/.agents/scripts/lib/config/temp-paths.js +2 -2
  63. package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
  64. package/.agents/scripts/lib/config-settings-schema-quality.js +1 -1
  65. package/.agents/scripts/lib/config-settings-schema.js +3 -3
  66. package/.agents/scripts/lib/detect-package-manager.js +72 -0
  67. package/.agents/scripts/lib/duplicate-search.js +1 -1
  68. package/.agents/scripts/lib/dynamic-workflow/capability.js +1 -1
  69. package/.agents/scripts/lib/epic-plan-clarity.js +1 -1
  70. package/.agents/scripts/lib/epic-plan-ideation.js +1 -1
  71. package/.agents/scripts/lib/errors/index.js +4 -4
  72. package/.agents/scripts/lib/feedback-loop/memory-freshness.js +1 -1
  73. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +1 -1
  74. package/.agents/scripts/lib/findings/classify-finding.js +1 -1
  75. package/.agents/scripts/lib/findings/promote-finding.js +10 -10
  76. package/.agents/scripts/lib/label-constants.js +3 -4
  77. package/.agents/scripts/lib/label-taxonomy.js +5 -10
  78. package/.agents/scripts/lib/onboard/detect-stack.js +10 -10
  79. package/.agents/scripts/lib/onboard/init-tail.js +218 -0
  80. package/.agents/scripts/lib/onboard/scaffold-docs.js +18 -3
  81. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +1 -1
  82. package/.agents/scripts/lib/orchestration/code-review.js +5 -5
  83. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +8 -9
  84. package/.agents/scripts/lib/orchestration/dependency-analyzer.js +3 -3
  85. package/.agents/scripts/lib/orchestration/detectors-phase.js +2 -2
  86. package/.agents/scripts/lib/orchestration/dispatch-engine.js +30 -38
  87. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +9 -25
  88. package/.agents/scripts/lib/orchestration/epic-cleanup.js +1 -1
  89. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +8 -8
  90. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +1 -1
  91. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +7 -21
  92. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +3 -3
  93. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +26 -13
  94. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +1 -1
  95. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +1 -1
  96. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +2 -2
  97. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +1 -1
  98. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +3 -3
  99. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +4 -4
  100. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +3 -3
  101. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +6 -21
  102. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +7 -7
  103. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +1 -1
  104. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +2 -2
  105. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +4 -4
  106. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +4 -4
  107. package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +8 -8
  108. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +4 -4
  109. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +7 -15
  110. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +72 -41
  111. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +2 -4
  112. package/.agents/scripts/lib/orchestration/file-assumptions.js +2 -2
  113. package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +1 -1
  114. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +2 -2
  115. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +1 -1
  116. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -3
  117. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +1 -1
  118. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +1 -1
  119. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +1 -1
  120. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +1 -1
  121. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +1 -1
  122. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +1 -1
  123. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +1 -1
  124. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
  125. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +1 -1
  126. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +1 -1
  127. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +1 -1
  128. package/.agents/scripts/lib/orchestration/manifest-builder.js +5 -5
  129. package/.agents/scripts/lib/orchestration/parked-follow-ons.js +2 -2
  130. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +5 -5
  131. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +3 -3
  132. package/.agents/scripts/lib/orchestration/preflight-cache.js +1 -1
  133. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +1 -1
  134. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +1 -1
  135. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +2 -2
  136. package/.agents/scripts/lib/orchestration/retro-runner.js +3 -3
  137. package/.agents/scripts/lib/orchestration/review-depth.js +1 -1
  138. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +1 -1
  139. package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
  140. package/.agents/scripts/lib/orchestration/spec-renderer.js +36 -73
  141. package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
  142. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +1 -1
  143. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +2 -2
  144. package/.agents/scripts/lib/orchestration/task-body-validator.js +6 -6
  145. package/.agents/scripts/lib/orchestration/ticket-lease.js +1 -1
  146. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +2 -2
  147. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -10
  148. package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -70
  149. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +5 -12
  150. package/.agents/scripts/lib/orchestration/ticketing/reads.js +8 -8
  151. package/.agents/scripts/lib/orchestration/ticketing/state.js +3 -3
  152. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +2 -2
  153. package/.agents/scripts/lib/orchestration/wave-record-projection.js +1 -1
  154. package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
  155. package/.agents/scripts/lib/preflight-runner.js +1 -1
  156. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +4 -5
  157. package/.agents/scripts/lib/presentation/manifest-builder.js +28 -34
  158. package/.agents/scripts/lib/presentation/manifest-formatter.js +3 -4
  159. package/.agents/scripts/lib/presentation/manifest-helpers.js +1 -1
  160. package/.agents/scripts/lib/presentation/manifest-procedures.js +4 -4
  161. package/.agents/scripts/lib/presentation/manifest-render-waves.js +4 -23
  162. package/.agents/scripts/lib/presentation/manifest-renderer.js +1 -1
  163. package/.agents/scripts/lib/presentation/manifest-story-views.js +2 -11
  164. package/.agents/scripts/lib/runtime-deps/preflight.js +6 -6
  165. package/.agents/scripts/lib/signals/schema.js +1 -1
  166. package/.agents/scripts/lib/spec/index.js +1 -1
  167. package/.agents/scripts/lib/spec/loader.js +2 -2
  168. package/.agents/scripts/lib/spec/state.js +7 -16
  169. package/.agents/scripts/lib/story-init/context-resolver.js +3 -3
  170. package/.agents/scripts/lib/story-init/state-transitioner.js +2 -2
  171. package/.agents/scripts/lib/story-init/task-graph-builder.js +7 -7
  172. package/.agents/scripts/lib/story-lifecycle.js +8 -8
  173. package/.agents/scripts/lib/story-plan.js +1 -1
  174. package/.agents/scripts/lib/templates/decomposer-prompts.js +59 -52
  175. package/.agents/scripts/lib/wave-runner/tick.js +1 -1
  176. package/.agents/scripts/lib/worktree/node-modules-strategy.js +5 -2
  177. package/.agents/scripts/lifecycle-emit-story-dispatch.js +1 -1
  178. package/.agents/scripts/lifecycle-emit.js +1 -1
  179. package/.agents/scripts/providers/github/board-add.js +1 -1
  180. package/.agents/scripts/providers/github/errors.js +1 -1
  181. package/.agents/scripts/providers/github/mappers.js +2 -2
  182. package/.agents/scripts/providers/github/tickets.js +4 -4
  183. package/.agents/scripts/resync-status-column.js +1 -1
  184. package/.agents/scripts/retro-run.js +2 -2
  185. package/.agents/scripts/run-lint.js +1 -1
  186. package/.agents/scripts/single-story-init.js +1 -1
  187. package/.agents/scripts/stories-wave-tick.js +5 -5
  188. package/.agents/scripts/story-close.js +1 -1
  189. package/.agents/scripts/story-init.js +13 -16
  190. package/.agents/scripts/story-phase.js +5 -5
  191. package/.agents/scripts/story-plan.js +3 -3
  192. package/.agents/scripts/sync-branch-from-base.js +1 -1
  193. package/.agents/scripts/validate-docs-freshness.js +1 -1
  194. package/.agents/scripts/wave-tick.js +1 -1
  195. package/.agents/skills/core/analyze-execution/SKILL.md +2 -2
  196. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +21 -26
  197. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +23 -56
  198. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +4 -4
  199. package/.agents/skills/core/hydrate-context/SKILL.md +2 -2
  200. package/.agents/skills/core/idea-refinement/SKILL.md +4 -4
  201. package/.agents/skills/core/knowledge-transfer/SKILL.md +2 -2
  202. package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +1 -1
  203. package/.agents/skills/core/scope-triage/SKILL.md +9 -10
  204. package/.agents/skills/core/using-agent-skills/SKILL.md +1 -1
  205. package/.agents/skills/skills.index.json +7 -7
  206. package/.agents/templates/agent-protocol.md +2 -2
  207. package/.agents/workflows/agents-update.md +16 -31
  208. package/.agents/workflows/audit-architecture.md +2 -2
  209. package/.agents/workflows/audit-clean-code.md +2 -2
  210. package/.agents/workflows/audit-dependencies.md +1 -1
  211. package/.agents/workflows/audit-devops.md +1 -1
  212. package/.agents/workflows/audit-documentation.md +2 -2
  213. package/.agents/workflows/audit-lighthouse.md +1 -1
  214. package/.agents/workflows/audit-performance.md +2 -2
  215. package/.agents/workflows/audit-privacy.md +1 -1
  216. package/.agents/workflows/audit-quality.md +2 -2
  217. package/.agents/workflows/audit-security.md +2 -2
  218. package/.agents/workflows/audit-seo.md +1 -1
  219. package/.agents/workflows/audit-sre.md +1 -1
  220. package/.agents/workflows/audit-to-stories.md +10 -10
  221. package/.agents/workflows/audit-ux-ui.md +1 -1
  222. package/.agents/workflows/deliver.md +85 -0
  223. package/.agents/workflows/explain.md +3 -3
  224. package/.agents/workflows/git-merge-pr.md +1 -1
  225. package/.agents/workflows/git-pr-all.md +13 -10
  226. package/.agents/workflows/git-push.md +6 -3
  227. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  228. package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
  229. package/.agents/workflows/helpers/agents-sync-config.md +3 -2
  230. package/.agents/workflows/helpers/code-review.md +5 -5
  231. package/.agents/workflows/{epic-deliver.md → helpers/deliver-epic.md} +43 -43
  232. package/.agents/workflows/{story-deliver.md → helpers/deliver-stories.md} +25 -25
  233. package/.agents/workflows/helpers/diagnose.md +1 -1
  234. package/.agents/workflows/helpers/epic-audit.md +6 -6
  235. package/.agents/workflows/helpers/epic-deliver-story.md +13 -13
  236. package/.agents/workflows/helpers/epic-plan-decompose.md +23 -23
  237. package/.agents/workflows/helpers/epic-plan-spec.md +6 -6
  238. package/.agents/workflows/helpers/epic-testing.md +3 -3
  239. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  240. package/.agents/workflows/{epic-plan.md → helpers/plan-epic.md} +84 -84
  241. package/.agents/workflows/{story-plan.md → helpers/plan-story.md} +43 -43
  242. package/.agents/workflows/helpers/signals.md +1 -1
  243. package/.agents/workflows/helpers/single-story-deliver.md +11 -11
  244. package/.agents/workflows/helpers/worktree-lifecycle.md +18 -18
  245. package/.agents/workflows/plan.md +131 -0
  246. package/.agents/workflows/qa-explore.md +1 -1
  247. package/.agents/workflows/qa-run-harness.md +1 -1
  248. package/README.md +19 -39
  249. package/bin/mandrel.js +235 -16
  250. package/docs/CHANGELOG.md +1173 -0
  251. package/lib/cli/doctor.js +45 -3
  252. package/lib/cli/init.js +97 -36
  253. package/lib/cli/registry.js +41 -145
  254. package/lib/cli/sync.js +122 -23
  255. package/lib/cli/uninstall.js +42 -7
  256. package/lib/cli/update.js +524 -210
  257. package/lib/cli/version-helpers.js +59 -0
  258. package/package.json +7 -6
  259. package/.agents/scripts/lib/orchestration/reconciler.js +0 -137
  260. package/.agents/workflows/onboard.md +0 -208
  261. package/lib/cli/__tests__/migrate.test.js +0 -268
  262. package/lib/cli/__tests__/sync-local-zone.test.js +0 -247
  263. package/lib/cli/__tests__/sync.test.js +0 -372
  264. package/lib/cli/__tests__/update-major.test.js +0 -217
  265. package/lib/cli/__tests__/update.test.js +0 -696
  266. package/lib/cli/__tests__/version-check.test.js +0 -398
  267. package/lib/migrations/__tests__/index.test.js +0 -216
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Story #1155 (Epic #1142, 5.40.0) — extracted the helper-driven
5
5
  * `epic-code-review` invocation into a callable module so the
6
- * `/epic-deliver` runner can run Phase D without spawning a child
6
+ * `/deliver` runner can run Phase D without spawning a child
7
7
  * process or routing through an LLM-driven helper.
8
8
  *
9
9
  * Story #2831 (Epic #2815, Pluggable Code Review) — refactored to load
@@ -25,11 +25,11 @@
25
25
  * - Always posts the structured `code-review` comment on the Epic
26
26
  * issue (the adapter never posts; the orchestrator owns persistence).
27
27
  * - Treats severity.critical > 0 as a halting blocker — the merged
28
- * `/epic-deliver` runner consults `halted` and refuses to advance
28
+ * `/deliver` runner consults `halted` and refuses to advance
29
29
  * to Phase E (retro) when set.
30
30
  *
31
31
  * Halting on critical findings is the in-process replacement for the
32
- * helper's "operator must remediate before /epic-deliver" gate.
32
+ * helper's "operator must remediate before /deliver" gate.
33
33
  */
34
34
 
35
35
  import { resolveConfig } from '../config-resolver.js';
@@ -116,7 +116,7 @@ function resolveTaskSizing(config) {
116
116
  *
117
117
  * Best-effort and total: a missing/unparseable checkpoint, an absent
118
118
  * `planningRisk` field, a read failure, or a malformed `overallLevel` all
119
- * degrade to `standard` (never throws). So an Epic that skipped `/epic-plan`
119
+ * degrade to `standard` (never throws). So an Epic that skipped `/plan`
120
120
  * (no checkpoint) still gets a passing `standard` review with no new failure
121
121
  * mode. The producer resolves depth from the judged risk alone (the diff is
122
122
  * not yet enumerated at this point); the full risk + diff-width combination is
@@ -401,7 +401,7 @@ function resolveScopeEnvelope(opts, config) {
401
401
  }
402
402
 
403
403
  /**
404
- * In-process wrapper that the `/epic-deliver` runner and the
404
+ * In-process wrapper that the `/deliver` runner and the
405
405
  * `/single-story-deliver` close path consume.
406
406
  *
407
407
  * Story #2252 — emits `code-review.start` immediately on entry and
@@ -163,7 +163,7 @@ function getVersion() {
163
163
  /**
164
164
  * Parse the work-breakdown hierarchy from a Task ticket body.
165
165
  *
166
- * Looks for patterns like: `Epic: #1`, `Feature: #2`, `Story: #3`,
166
+ * Looks for patterns like: `Epic: #1`, `Story: #3`,
167
167
  * `PRD: #4`, `Tech Spec: #5`.
168
168
  *
169
169
  * @param {string} body
@@ -177,7 +177,7 @@ export function parseHierarchy(body) {
177
177
  for (const match of matches) {
178
178
  const key = match[1].trim().toLowerCase().replace(/\s+/g, '');
179
179
  const val = Number.parseInt(match[2], 10);
180
- result[key] = val; // e.g. { epic: 1, feature: 2, story: 3, prd: 4, techspec: 5 }
180
+ result[key] = val; // e.g. { epic: 1, story: 3, prd: 4, techspec: 5 }
181
181
  }
182
182
  return result;
183
183
  }
@@ -217,9 +217,9 @@ function extractSectionList(body, heading) {
217
217
 
218
218
  /**
219
219
  * Extract the inline `## Acceptance` / `## Acceptance Criteria` and
220
- * `## Verify` checklists from a Story body. Used by 3-tier hydration to
220
+ * `## Verify` checklists from a Story body. Used by 2-tier hydration to
221
221
  * populate the `acceptanceCriteria` and `verificationCommands` envelope
222
- * sections directly from the dispatched Story ticket — under 3-tier the
222
+ * sections directly from the dispatched Story ticket — under 2-tier the
223
223
  * Story IS the unit of execution and carries acceptance/verify inline
224
224
  * (no child tickets to walk).
225
225
  *
@@ -235,18 +235,18 @@ export function extractStorySections(body) {
235
235
  }
236
236
 
237
237
  /**
238
- * Detect whether the dispatched unit is a 3-tier Story (Story is the
238
+ * Detect whether the dispatched unit is a 2-tier Story (Story is the
239
239
  * leaf, carries inline acceptance/verify) vs. a 4-tier Task (Task is
240
240
  * the leaf, Story is one level up). The decision is made off the
241
241
  * `type::*` label the dispatcher already stamps on every ticket; it
242
242
  * does not depend on `planning.hierarchy`, so this engine
243
- * stays correct even when a 4-tier Epic ships in a 3-tier-default repo
243
+ * stays correct even when a 4-tier Epic ships in a 2-tier-default repo
244
244
  * (or vice versa) during the Epic #3078 dual-shape window.
245
245
  *
246
246
  * @param {object} task
247
247
  * @returns {boolean}
248
248
  */
249
- function isThreeTierStoryTask(task) {
249
+ function isTwoTierStoryTask(task) {
250
250
  const labels = task?.labels ?? [];
251
251
  return labels.includes('type::story');
252
252
  }
@@ -355,7 +355,6 @@ async function buildHierarchySections(task, provider, epicId, agentSettings) {
355
355
  idsToFetch.push({ key: 'Epic', id: epicId || hierarchyKeys.epic });
356
356
  idsToFetch.push({ key: 'PRD', id: hierarchyKeys.prd });
357
357
  idsToFetch.push({ key: 'Tech Spec', id: hierarchyKeys.techspec });
358
- idsToFetch.push({ key: 'Feature', id: hierarchyKeys.feature });
359
358
  idsToFetch.push({ key: 'Story', id: hierarchyKeys.story });
360
359
  } else if (depth === 'standard') {
361
360
  idsToFetch.push({ key: 'Epic', id: epicId || hierarchyKeys.epic });
@@ -475,7 +474,7 @@ function buildStaticSections(
475
474
  }
476
475
  }
477
476
 
478
- if (isThreeTierStoryTask(task)) {
477
+ if (isTwoTierStoryTask(task)) {
479
478
  const { acceptance, verify } = extractStorySections(task.body ?? '');
480
479
  if (acceptance.length > 0) {
481
480
  sections.push({
@@ -6,8 +6,8 @@ import { assignLayers, detectCycle } from '../Graph.js';
6
6
  *
7
7
  * Sources of story dependencies:
8
8
  * 1. **Implicit (cross-story tasks)**: Task T in Story A depends on Task T'
9
- * in Story B → Story A depends on Story B. Under the 3-tier hierarchy
10
- * (Epic → Feature → Story) Stories carry no child Tasks, so this source
9
+ * in Story B → Story A depends on Story B. Under the 2-tier hierarchy
10
+ * (Epic → Story) Stories carry no child Tasks, so this source
11
11
  * is empty in practice; it is retained for callers that adapt a
12
12
  * task-bearing shape into `storyGroups`.
13
13
  * 2. **Explicit (story body)**: Story A body contains `blocked by #B` →
@@ -18,7 +18,7 @@ import { assignLayers, detectCycle } from '../Graph.js';
18
18
  * > **Focus-overlap engine removed (Story #3906).** A third source — a
19
19
  * > focus-area overlap engine that rolled task-level `focusAreas` / `scope`
20
20
  * > up to the Story level and serialized "overlapping" Stories — was deleted
21
- * > because Task deletion in the 3-tier migration left every Story's task
21
+ * > because Task deletion in the 2-tier migration left every Story's task
22
22
  * > list empty, so the rollup produced empty focus bags and the engine added
23
23
  * > **zero** edges on every real plan. It advertised file-contention
24
24
  * > serialization it never delivered. Cross-Story prerequisites are carried
@@ -29,8 +29,8 @@
29
29
  * (`detectors: rework=N retry=M`) is the only stdout signal — one
30
30
  * line per Story rather than two per detector + N per event.
31
31
  *
32
- * - **3-tier (Storyless) closure (Story #3127).** When `tasks` is
33
- * empty (the 3-tier hierarchy shape), `resolveLastTaskId` returns
32
+ * - **2-tier (Storyless) closure (Story #3127).** When `tasks` is
33
+ * empty (the 2-tier hierarchy shape), `resolveLastTaskId` returns
34
34
  * `null` and both detectors run with `taskId: null`. The detector
35
35
  * modules already accept a nullable `taskId` (see
36
36
  * `lib/signals/detectors/{rework,retry}.js`), so no branching on
@@ -4,8 +4,8 @@
4
4
  * Thin facade composing:
5
5
  * - `dispatch-pipeline.js` — internal resolve/fetch/reconcile/graph helpers
6
6
  *
7
- * Every Epic is 3-tier (Epic → Feature → Story); `dispatch()` computes a
8
- * Story-level wave plan and emits a 3-tier manifest. The legacy Task-tier
7
+ * Every Epic is 2-tier (Epic → Story); `dispatch()` computes a
8
+ * Story-level wave plan and emits a 2-tier manifest. The legacy Task-tier
9
9
  * dispatch runtime (Task fetcher, single-Story executor, the per-Task
10
10
  * wave fan-out, and the Epic-completion detector) was removed in Epic
11
11
  * #3163.
@@ -20,8 +20,7 @@ import { createProvider } from '../provider-factory.js';
20
20
  import {
21
21
  buildStoryDispatchGraph,
22
22
  fetchEpicContext,
23
- isThreeTierDispatch,
24
- reconcileEpicState,
23
+ isTwoTierDispatch,
25
24
  resolveDispatchContext,
26
25
  } from './dispatch-pipeline.js';
27
26
  import { buildManifest } from './manifest-builder.js';
@@ -60,14 +59,13 @@ export async function resolveAndDispatch(options) {
60
59
 
61
60
  const isStory = labels.includes(TYPE_LABELS.STORY);
62
61
  const isEpic = labels.includes(TYPE_LABELS.EPIC);
63
- const isFeature = labels.includes(TYPE_LABELS.FEATURE);
64
62
 
65
63
  if (isStory) {
66
64
  throw new Error(
67
65
  `[Dispatcher] Ticket #${ticketId} is a **Story**. Stories are dispatched ` +
68
- 'through the 3-tier Story path, not directly via the dispatcher. ' +
69
- `Run \`/story-deliver ${ticketId}\` to execute this Story, ` +
70
- `or dispatch its parent Epic with \`/epic-deliver #<epicId>\`.`,
66
+ 'through the Story delivery path, not directly via the dispatcher. ' +
67
+ `Run \`/deliver ${ticketId}\` to execute this Story, ` +
68
+ `or dispatch its parent Epic with \`/deliver #<epicId>\`.`,
71
69
  );
72
70
  }
73
71
 
@@ -75,14 +73,6 @@ export async function resolveAndDispatch(options) {
75
73
  return dispatch({ epicId: ticketId, dryRun, provider });
76
74
  }
77
75
 
78
- if (isFeature) {
79
- throw new Error(
80
- `[Dispatcher] Ticket #${ticketId} is a **Feature**. Features are containers and cannot be executed directly. ` +
81
- `Please execute individual Stories within this Feature using \`/epic-deliver #[Story ID]\`, ` +
82
- `or dispatch the entire Epic using \`/epic-deliver #${ticket.body?.match(/^parent:\s*#(\d+)/m)?.[1] || 'ID'}\`.`,
83
- );
84
- }
85
-
86
76
  const typeLabel = labels.find((l) => l.startsWith('type::')) || 'unknown';
87
77
  throw new Error(
88
78
  `[Dispatcher] Ticket #${ticketId} has type "${typeLabel.replace('type::', '')}". ` +
@@ -100,17 +90,16 @@ export async function dispatch(options) {
100
90
  const { epicId, dryRun } = ctx;
101
91
 
102
92
  const fetched = await fetchEpicContext(ctx);
103
- await reconcileEpicState(ctx, fetched);
104
93
 
105
- // Every Epic is 3-tier (Epic → Feature → Story). Compute Story-level
106
- // waves directly from the Story tickets and emit a 3-tier-shaped
94
+ // Every Epic is 2-tier (Epic → Story). Compute Story-level
95
+ // waves directly from the Story tickets and emit a 2-tier-shaped
107
96
  // manifest with `waves[].stories[]` so downstream consumers (manifest
108
- // renderer, /epic-deliver wave planner) see the correct execution plan.
109
- // Per-Story execution is owned by `/story-deliver` (story-init →
97
+ // renderer, /deliver wave planner) see the correct execution plan.
98
+ // Per-Story execution is owned by `/deliver` (story-init →
110
99
  // story-close), not by this dispatcher.
111
- if (isThreeTierDispatch(fetched.allTickets)) {
100
+ if (isTwoTierDispatch(fetched.allTickets)) {
112
101
  Logger.info(
113
- 'Detected 3-tier hierarchy — computing Story-level execution waves.',
102
+ 'Detected 2-tier hierarchy — computing Story-level execution waves.',
114
103
  );
115
104
  const { allWaves: storyWaves } = buildStoryDispatchGraph(
116
105
  fetched.allTickets,
@@ -123,22 +112,25 @@ export async function dispatch(options) {
123
112
  waves: storyWaves,
124
113
  dispatched: [],
125
114
  dryRun,
126
- hierarchy: '3-tier',
115
+ hierarchy: '2-tier',
127
116
  });
128
117
  }
129
118
 
130
- // No Story tickets under the Epic — nothing to dispatch. Emit an empty
131
- // manifest so callers (renderer, /epic-deliver) get a well-formed
132
- // artifact instead of a throw.
133
- Logger.info('No Story tickets found under the Epic. Nothing to dispatch.');
134
- return buildManifest({
135
- epicId,
136
- epic: fetched.epic,
137
- tasks: [],
138
- allTickets: fetched.allTickets,
139
- waves: [],
140
- dispatched: [],
141
- dryRun,
142
- hierarchy: '3-tier',
143
- });
119
+ // No Story tickets under the Epic — throw loudly rather than emit an
120
+ // empty manifest, matching the wave-loop's behavior (build-wave-dag.js
121
+ // throws the same message shape on this input). A silently-empty
122
+ // manifest masks a pre-cutover Epic whose children are legacy Features.
123
+ const typedChildren = (fetched.allTickets ?? [])
124
+ .map((t) => (t.labels ?? []).find((l) => l.startsWith('type::')))
125
+ .filter(Boolean);
126
+ const legacyHint =
127
+ typedChildren.length > 0
128
+ ? ` Found ${typedChildren.length} non-Story child ticket(s) ` +
129
+ `(${[...new Set(typedChildren)].join(', ')}) — this Epic looks ` +
130
+ `pre-cutover (legacy Feature children) and needs migration to the ` +
131
+ `2-tier (Epic → Story) hierarchy before dispatch.`
132
+ : '';
133
+ throw new Error(
134
+ `Epic #${epicId} has no child stories to dispatch.${legacyHint}`,
135
+ );
144
136
  }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Internal pipeline helpers composed by `dispatch-engine.js::dispatch()`.
5
5
  * Keeping these out of the coordinator keeps the public entry point compact
6
- * and focused on the 3-tier flow: resolve → fetch → reconcile → Story-graph.
6
+ * and focused on the 2-tier flow: resolve → fetch → Story-graph.
7
7
  */
8
8
 
9
9
  import { PROJECT_ROOT, resolveConfig } from '../config-resolver.js';
@@ -14,13 +14,12 @@ import { createProvider } from '../provider-factory.js';
14
14
  import { buildStoryAdjacency } from '../story-adjacency.js';
15
15
  import { WorktreeManager } from '../worktree-manager.js';
16
16
  import { computeStoryWaves } from './dependency-analyzer.js';
17
- import { reconcileHierarchy } from './reconciler.js';
18
17
 
19
18
  /**
20
19
  * Runtime context for a single dispatch cycle.
21
20
  *
22
21
  * Produced by {@link resolveDispatchContext} and consumed by every pipeline
23
- * stage (fetch → reconcile → graph → scaffold → GC → dispatch). All fields
22
+ * stage (fetch → graph → scaffold → GC → dispatch). All fields
24
23
  * are resolved once up-front so downstream helpers can stay free of
25
24
  * configuration look-ups.
26
25
  *
@@ -40,7 +39,7 @@ import { reconcileHierarchy } from './reconciler.js';
40
39
  *
41
40
  * @typedef {object} FetchedEpic
42
41
  * @property {object} epic The Epic ticket record.
43
- * @property {object[]} allTickets Every ticket under the Epic (stories + features + health).
42
+ * @property {object[]} allTickets Every ticket under the Epic (stories + health).
44
43
  * @property {Map<number, object>} allTicketsById Index of `allTickets` by ticket id.
45
44
  */
46
45
 
@@ -106,30 +105,15 @@ export async function fetchEpicContext(ctx) {
106
105
  }
107
106
 
108
107
  /**
109
- * Propagate already-done work up the hierarchy so the manifest reflects
110
- * reality before dispatch. Walks Stories → Features bottom-up.
111
- *
112
- * @param {DispatchContext} ctx Dispatch context.
113
- * @param {FetchedEpic} fetched Result of {@link fetchEpicContext}.
114
- * @returns {Promise<void>}
115
- */
116
- export async function reconcileEpicState(ctx, fetched) {
117
- const { provider, dryRun, epicId } = ctx;
118
- const { epic, allTickets } = fetched;
119
-
120
- await reconcileHierarchy(provider, epicId, epic, allTickets, dryRun);
121
- }
122
-
123
- /**
124
- * Detect 3-tier hierarchy from the fetched ticket graph. After Epic #3163's
108
+ * Detect 2-tier hierarchy from the fetched ticket graph. After Epic #3163's
125
109
  * hard cutover deleted the `type::task` ticket layer, shape selection is
126
110
  * purely structural: any Epic carrying at least one `type::story` ticket
127
- * resolves to 3-tier.
111
+ * resolves to 2-tier.
128
112
  *
129
113
  * @param {object[]} allTickets
130
114
  * @returns {boolean}
131
115
  */
132
- export function isThreeTierDispatch(allTickets) {
116
+ export function isTwoTierDispatch(allTickets) {
133
117
  if (!Array.isArray(allTickets) || allTickets.length === 0) return false;
134
118
  return allTickets.some((t) =>
135
119
  (t.labelSet ?? new Set(t.labels ?? [])).has(TYPE_LABELS.STORY),
@@ -137,7 +121,7 @@ export function isThreeTierDispatch(allTickets) {
137
121
  }
138
122
 
139
123
  /**
140
- * Build the Story-level dispatch graph for a 3-tier Epic. Reads story
124
+ * Build the Story-level dispatch graph for a 2-tier Epic. Reads story
141
125
  * tickets from `allTickets`, parses cross-Story `blocked by` references
142
126
  * from each Story body (also honoring an optional `dependencies[]`
143
127
  * field set by fixture providers), and computes wave indices via
@@ -151,7 +135,7 @@ export function isThreeTierDispatch(allTickets) {
151
135
  * are placed in their own trailing wave so they remain visible in the
152
136
  * manifest output.
153
137
  *
154
- * @param {object[]} allTickets Fetched ticket graph (Epic + Features + Stories).
138
+ * @param {object[]} allTickets Fetched ticket graph (Epic + Stories).
155
139
  * @returns {{ allWaves: object[][], storyMap: Map<number, object> }}
156
140
  * @throws {Error} When the Story dependency graph contains a cycle.
157
141
  */
@@ -193,7 +177,7 @@ export function buildStoryDispatchGraph(allTickets) {
193
177
  if (byWave.has(-1)) allWaves.push(byWave.get(-1));
194
178
 
195
179
  Logger.info(
196
- `Computed ${allWaves.length} Story-level execution wave(s) (3-tier).`,
180
+ `Computed ${allWaves.length} Story-level execution wave(s) (2-tier).`,
197
181
  );
198
182
  return { allWaves, storyMap };
199
183
  }
@@ -1,7 +1,7 @@
1
1
  /* node:coverage ignore file -- MI 0 orchestration glue; reaps live worktrees + runs `git branch -D` — testing requires mocking real worktree/git state to the point of asserting only the mock structure */
2
2
 
3
3
  /**
4
- * Epic-cleanup primitives — local branch + worktree reap for `/epic-deliver`
4
+ * Epic-cleanup primitives — local branch + worktree reap for `/deliver`
5
5
  * Phase 8.
6
6
  *
7
7
  * Once a PR has merged (auto or operator-button), the Epic branch and every
@@ -1,8 +1,8 @@
1
1
  /**
2
- * epic-deliver-lease-guard.js — preflight guards for `/epic-deliver`
2
+ * epic-deliver-lease-guard.js — preflight guards for `/deliver`
3
3
  * (Story #3482, Epic #3457).
4
4
  *
5
- * `/epic-deliver`'s prepare phase used to checkout `epic/<id>` over whatever
5
+ * `/deliver`'s prepare phase used to checkout `epic/<id>` over whatever
6
6
  * the operator had checked out, yanking HEAD and resetting baselines under a
7
7
  * live working session (the documented "epic-deliver shares the main checkout"
8
8
  * footgun). This module supplies the two preflight guards the Tech Spec
@@ -60,7 +60,7 @@ export function resolveOperator({ asFlag, config, gitUserEmail } = {}) {
60
60
  }
61
61
 
62
62
  /**
63
- * Normalise the expected-branch argument into a non-empty list. `/epic-deliver`
63
+ * Normalise the expected-branch argument into a non-empty list. `/deliver`
64
64
  * may legitimately start from either the Epic integration branch (`epic/<id>`,
65
65
  * on a resume) or the project base branch (`main`, on a fresh run), so the
66
66
  * guard accepts a set rather than a single name.
@@ -153,20 +153,20 @@ export function renderCheckoutRefusal(result) {
153
153
  if (result.reason === 'dirty') {
154
154
  return (
155
155
  `[epic-deliver] Refusing to start: the working tree is dirty. ` +
156
- `/epic-deliver will not check out ${expected} over uncommitted ` +
156
+ `/deliver will not check out ${expected} over uncommitted ` +
157
157
  `or untracked changes — commit, stash, or clean them, then re-run.\n` +
158
158
  `--- dirty entries ---\n${result.dirtyEntries ?? '(unavailable)'}`
159
159
  );
160
160
  }
161
161
  if (result.reason === 'detached-head') {
162
162
  return (
163
- `[epic-deliver] Refusing to start: HEAD is detached. /epic-deliver ` +
163
+ `[epic-deliver] Refusing to start: HEAD is detached. /deliver ` +
164
164
  `expects HEAD on ${expected}. Check one out before re-running.`
165
165
  );
166
166
  }
167
167
  return (
168
168
  `[epic-deliver] Refusing to start: HEAD is on '${result.currentBranch}', ` +
169
- `not the expected ${expected}. /epic-deliver will not yank HEAD ` +
169
+ `not the expected ${expected}. /deliver will not yank HEAD ` +
170
170
  `away from your branch — switch to ${expected} yourself before re-running.`
171
171
  );
172
172
  }
@@ -182,7 +182,7 @@ export function renderLeaseRefusal(lease, epicId) {
182
182
  return (
183
183
  `[epic-deliver] Refusing to start: Epic #${epicId} is already claimed by ` +
184
184
  `'${lease.owner}' with a live lease (recent heartbeat within the TTL). ` +
185
- `Another /epic-deliver run is driving this Epic. Wait for it to finish, ` +
185
+ `Another /deliver run is driving this Epic. Wait for it to finish, ` +
186
186
  `or pass --steal to forcibly transfer the claim.`
187
187
  );
188
188
  }
@@ -281,7 +281,7 @@ export async function runPrepareGuards({
281
281
  '[epic-deliver] Refusing to start: no operator identity could be ' +
282
282
  'resolved. --as, github.operatorHandle (unset or still the shipped ' +
283
283
  '`@[USERNAME]` placeholder), and git user.email are all empty, so the ' +
284
- 'Epic-lease has no owner and concurrent /epic-deliver runs cannot be ' +
284
+ 'Epic-lease has no owner and concurrent /deliver runs cannot be ' +
285
285
  'serialised. Set your own handle in .agentrc.local.json (e.g. ' +
286
286
  '{ "github": { "operatorHandle": "@your-login" } }), pass --as <handle>, ' +
287
287
  'or configure git user.email, then re-run.',
@@ -69,6 +69,6 @@ export function warnTicketCapNearLimit(
69
69
  ) {
70
70
  if (tickets.length < maxTickets) return;
71
71
  logger.warn(
72
- `[${tag}] ⚠️ Received ${tickets.length} tickets against a reviewability budget of ${maxTickets}. Review the Feature/Story split before persisting; over-budget persistence requires --allow-over-budget.`,
72
+ `[${tag}] ⚠️ Received ${tickets.length} tickets against a reviewability budget of ${maxTickets}. Review the Story decomposition before persisting; over-budget persistence requires --allow-over-budget.`,
73
73
  );
74
74
  }
@@ -3,9 +3,8 @@
3
3
  *
4
4
  * Owns the deterministic DAG helpers used by the reconciler pipeline:
5
5
  * - `resolveDependencies(ticket, slugMap)`
6
- * - `orderTicketsForCreation(validated)` (topological sort within each
7
- * (parent_slug, type) group, concatenated in feature story → task
8
- * order so parents always exist before their children get created)
6
+ * - `orderTicketsForCreation(validated)` (topological sort of the
7
+ * Story set so dependency producers are created before consumers)
9
8
  *
10
9
  * Extracted verbatim from `epic-plan-decompose.js` so the named exports
11
10
  * (`resolveDependencies`, `orderTicketsForCreation`) that the
@@ -53,26 +52,13 @@ function topoSortGroup(group) {
53
52
  }
54
53
 
55
54
  /**
56
- * Topologically sort tickets within each (parent_slug, type) group, then
57
- * concatenate groups in typeOrder so parents are always created before
58
- * children (Feature Story Task) and intra-group dep chains resolve
59
- * before their dependents are created.
55
+ * Topologically sort the Story set so intra-set dep chains resolve
56
+ * before their dependents are created. The 2-tier hierarchy has a
57
+ * single ticket type (story) attached directly to the Epic, so there
58
+ * is no parent-type ordering to interleave.
60
59
  */
61
60
  export function orderTicketsForCreation(validated) {
62
- const typeOrder = { feature: 0, story: 1, task: 2 };
63
- const groups = new Map();
64
- for (const t of validated) {
65
- const parentKey = t.parent_slug ?? '__epic__';
66
- const key = `${t.type}::${parentKey}`;
67
- if (!groups.has(key)) groups.set(key, { type: t.type, items: [] });
68
- groups.get(key).items.push(t);
69
- }
70
- const ordered = [...groups.values()].sort(
71
- (a, b) => typeOrder[a.type] - typeOrder[b.type],
72
- );
73
61
  const result = [];
74
- for (const group of ordered) {
75
- for (const t of topoSortGroup(group.items)) result.push(t);
76
- }
62
+ for (const t of topoSortGroup([...validated])) result.push(t);
77
63
  return result;
78
64
  }
@@ -16,8 +16,8 @@ import { TYPE_LABELS } from '../../../label-constants.js';
16
16
 
17
17
  /**
18
18
  * Count open child tickets under the Epic without distinguishing by
19
- * type. Under the 3-tier hierarchy (Epic → Feature → Story), child
20
- * tickets are always Feature or Story.
19
+ * type. Under the 2-tier hierarchy (Epic → Story), child
20
+ * tickets are always Stories.
21
21
  */
22
22
  async function emitOpenChildrenDiagnostic(provider, epicId) {
23
23
  if (typeof provider.getSubTickets !== 'function') return;
@@ -27,7 +27,7 @@ async function emitOpenChildrenDiagnostic(provider, epicId) {
27
27
  // closed children) yields the same open-child count without paging
28
28
  // every issue in the repo.
29
29
  const existing = await provider.getSubTickets(epicId);
30
- const childTypes = [TYPE_LABELS.FEATURE, TYPE_LABELS.STORY];
30
+ const childTypes = [TYPE_LABELS.STORY];
31
31
  const created = (existing || []).filter(
32
32
  (t) =>
33
33
  (t.labels || []).some((l) => childTypes.includes(l)) &&
@@ -1,17 +1,17 @@
1
1
  /**
2
- * epic-plan-lease-guard.js — `/epic-plan` workflow guards (Story #3481,
2
+ * epic-plan-lease-guard.js — `/plan` workflow guards (Story #3481,
3
3
  * Epic #3457).
4
4
  *
5
5
  * Wires the assignee-as-lease primitive (`ticket-lease.js`, Story #3480) and a
6
6
  * decompose-idempotency guard into the split planning flow so two concurrent
7
- * `/epic-plan` runs cannot both drive the same Epic, and so a re-run does not
7
+ * `/plan` runs cannot both drive the same Epic, and so a re-run does not
8
8
  * silently duplicate the Feature/Story tree:
9
9
  *
10
10
  * - `acquireEpicPlanLease` — claim the Epic before Phase 7 (spec). Refuses
11
11
  * (throws, exit non-zero) when a live foreign
12
12
  * claim already holds the Epic, naming the
13
13
  * current owner. **Claim-time liveness
14
- * (Story #4019):** `/epic-plan` emits no
14
+ * (Story #4019):** `/plan` emits no
15
15
  * `story.heartbeat`, so the lease records its
16
16
  * own claim-time in a `plan-lease` structured
17
17
  * comment on the Epic at acquire time. A
@@ -24,7 +24,7 @@
24
24
  * Best-effort and self-scoped: a no-op once the
25
25
  * Epic was reassigned elsewhere.
26
26
  * - `assertNoOpenPlanChildren` — refuse Phase 8 persist when the Epic already
27
- * has open Feature/Story children, unless the
27
+ * has open Story children, unless the
28
28
  * operator passed `--force` (a deliberate
29
29
  * re-decompose that closes the old tree).
30
30
  *
@@ -45,7 +45,7 @@ import { currentOwner, releaseLease } from './ticket-lease.js';
45
45
  import { findStructuredComment, upsertStructuredComment } from './ticketing.js';
46
46
 
47
47
  /**
48
- * Resolve the operator handle that owns this `/epic-plan` run from
48
+ * Resolve the operator handle that owns this `/plan` run from
49
49
  * `github.operatorHandle`. The assignee-as-lease primitive is single-holder
50
50
  * keyed on a non-empty string; when no operator is configured (unset, or the
51
51
  * shipped `@[USERNAME]` placeholder, both of which `normalizeOperatorHandle`
@@ -99,7 +99,7 @@ export function buildPlanLeaseCommentBody({ epicId, owner, claimedAt }) {
99
99
  `### 🔒 Plan Lease — claimed by \`${owner}\``,
100
100
  '',
101
101
  `This Epic is being planned by \`${owner}\` (claimed ${claimedAt}). A`,
102
- 'concurrent `/epic-plan` run refuses while this claim is fresher than the',
102
+ 'concurrent `/plan` run refuses while this claim is fresher than the',
103
103
  'lease TTL, and reclaims automatically once it goes stale.',
104
104
  '',
105
105
  '```json',
@@ -141,7 +141,7 @@ export function parsePlanLeaseClaim(body) {
141
141
  * Acquire the Epic-lease before Phase 7.
142
142
  *
143
143
  * **Claim-time liveness (Story #4019, superseding the audit-#3513
144
- * fail-closed anchor).** `/epic-plan` emits no `story.heartbeat`, so the
144
+ * fail-closed anchor).** `/plan` emits no `story.heartbeat`, so the
145
145
  * old guard treated EVERY foreign assignee as live — which made the
146
146
  * documented "`--steal` once you have confirmed the other run is dead"
147
147
  * contract undecidable (there was no in-band liveness signal to confirm
@@ -186,7 +186,7 @@ export async function acquireEpicPlanLease({
186
186
  `[epic-plan] Refusing to plan Epic #${epicId}: no operator identity is ` +
187
187
  'configured. github.operatorHandle is unset or still the shipped ' +
188
188
  '`@[USERNAME]` placeholder, so the Epic-lease has no owner and ' +
189
- 'concurrent /epic-plan runs cannot be serialised. Set your own handle ' +
189
+ 'concurrent /plan runs cannot be serialised. Set your own handle ' +
190
190
  'in .agentrc.local.json (e.g. { "github": { "operatorHandle": ' +
191
191
  '"@your-login" } }) and re-run.',
192
192
  );
@@ -242,7 +242,7 @@ export async function acquireEpicPlanLease({
242
242
  : '';
243
243
  return (
244
244
  `[epic-plan] Epic #${epicId} is currently claimed by '${refused.owner}'. ` +
245
- `Refusing to plan concurrently — another /epic-plan run owns this Epic. ` +
245
+ `Refusing to plan concurrently — another /plan run owns this Epic. ` +
246
246
  `${ageNote}Wait for that run to finish (the claim auto-expires at the ` +
247
247
  `lease TTL), or re-run with --steal to forcibly transfer the claim.`
248
248
  );
@@ -341,10 +341,14 @@ export async function assertNoOpenPlanChildren({
341
341
  const openChildren = (children ?? []).filter((t) => {
342
342
  const labels = Array.isArray(t.labels) ? t.labels : [];
343
343
  const isOpen = t.state === undefined || t.state === 'open';
344
+ // Any open typed plan ticket counts — `type::story` plus pre-v4
345
+ // `type::feature` leftovers. The prefix check is legacy-data
346
+ // detection, not compat support: the guard only refuses, it never
347
+ // processes the legacy tier. Context tickets (`context::*`) are
348
+ // untouched.
344
349
  return (
345
350
  isOpen &&
346
- (labels.includes(TYPE_LABELS.FEATURE) ||
347
- labels.includes(TYPE_LABELS.STORY))
351
+ labels.some((l) => typeof l === 'string' && l.startsWith('type::'))
348
352
  );
349
353
  });
350
354
 
@@ -357,12 +361,21 @@ export async function assertNoOpenPlanChildren({
357
361
  openChildren.length > 10
358
362
  ? `\n …and ${openChildren.length - 10} more`
359
363
  : '';
364
+ const legacyCount = openChildren.filter(
365
+ (t) => !(t.labels ?? []).includes(TYPE_LABELS.STORY),
366
+ ).length;
367
+ const legacyHint =
368
+ legacyCount > 0
369
+ ? `\n${legacyCount} of these are not type::story — they look like ` +
370
+ `legacy pre-v4 Feature tickets; migrate or close them per the ` +
371
+ `v1.60.0 migration notes before re-planning.`
372
+ : '';
360
373
  throw new Error(
361
374
  `[epic-plan-decompose] Epic #${epicId} already has ` +
362
- `${openChildren.length} open Feature/Story child ticket(s):\n${summary}${more}\n\n` +
375
+ `${openChildren.length} open plan child ticket(s):\n${summary}${more}\n\n` +
363
376
  `Persisting now would duplicate the breakdown. Re-run with --force to ` +
364
377
  `close the existing tree and re-decompose, or close the stale children ` +
365
- `by hand first.`,
378
+ `by hand first.${legacyHint}`,
366
379
  );
367
380
  }
368
381
 
@@ -78,7 +78,7 @@ export async function overwriteContextTicket(
78
78
  try {
79
79
  await provider.postComment(ticketId, {
80
80
  type: 'notification',
81
- body: `♻️ **Regeneration Audit**: This ${artifact} body was regenerated in place by a \`/epic-plan --force\` re-plan. The issue number and prior discussion history are preserved.`,
81
+ body: `♻️ **Regeneration Audit**: This ${artifact} body was regenerated in place by a \`/plan --force\` re-plan. The issue number and prior discussion history are preserved.`,
82
82
  });
83
83
  } catch (_err) {
84
84
  // Audit comment is best-effort — never fail the overwrite on a comment
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * phases/prompts.js — Canonical PRD / Tech Spec / Acceptance Spec system
3
- * prompts for the spec phase of `/epic-plan`.
3
+ * prompts for the spec phase of `/plan`.
4
4
  *
5
5
  * These ride along on the `--emit-context` envelope as a backstop. The
6
6
  * `epic-plan-spec-author` Skill