mandrel 1.59.0 → 1.60.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 (240) hide show
  1. package/.agents/README.md +14 -14
  2. package/.agents/docs/SDLC.md +129 -134
  3. package/.agents/docs/configuration.md +16 -16
  4. package/.agents/docs/workflows.md +6 -8
  5. package/.agents/instructions.md +12 -11
  6. package/.agents/personas/architect.md +1 -1
  7. package/.agents/personas/product.md +1 -1
  8. package/.agents/personas/project-manager.md +14 -14
  9. package/.agents/personas/technical-writer.md +1 -1
  10. package/.agents/rules/changelog-style.md +5 -5
  11. package/.agents/rules/git-conventions.md +3 -3
  12. package/.agents/schemas/agentrc.schema.json +3 -3
  13. package/.agents/schemas/dispatch-manifest.json +4 -4
  14. package/.agents/schemas/epic-spec.schema.json +15 -45
  15. package/.agents/schemas/lifecycle/README.md +1 -1
  16. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +1 -1
  17. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +1 -1
  18. package/.agents/schemas/lifecycle/story.heartbeat.schema.json +1 -1
  19. package/.agents/schemas/validation-evidence.schema.json +1 -1
  20. package/.agents/scripts/README.md +1 -1
  21. package/.agents/scripts/acceptance-eval.js +1 -1
  22. package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
  23. package/.agents/scripts/analyze-execution.js +2 -2
  24. package/.agents/scripts/audit-to-stories.js +1 -1
  25. package/.agents/scripts/check-doc-links.js +2 -3
  26. package/.agents/scripts/diagnose-friction.js +1 -1
  27. package/.agents/scripts/dispatcher.js +2 -2
  28. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  29. package/.agents/scripts/epic-audit-prepare.js +3 -3
  30. package/.agents/scripts/epic-deliver-note-intervention.js +2 -2
  31. package/.agents/scripts/epic-deliver-preflight.js +6 -6
  32. package/.agents/scripts/epic-deliver-prepare.js +1 -1
  33. package/.agents/scripts/epic-execute-record-wave.js +4 -4
  34. package/.agents/scripts/epic-plan-healthcheck.js +6 -10
  35. package/.agents/scripts/epic-plan-spec-validate.js +1 -1
  36. package/.agents/scripts/epic-reconcile.js +11 -29
  37. package/.agents/scripts/evidence-gate.js +1 -1
  38. package/.agents/scripts/generate-workflows-doc.js +1 -1
  39. package/.agents/scripts/hierarchy-gate.js +7 -11
  40. package/.agents/scripts/lib/ITicketingProvider.js +1 -1
  41. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  42. package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +2 -2
  43. package/.agents/scripts/lib/baseline-snapshot.js +7 -7
  44. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  45. package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
  46. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +1 -1
  47. package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
  48. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +1 -1
  49. package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
  50. package/.agents/scripts/lib/codebase-snapshot.js +1 -1
  51. package/.agents/scripts/lib/config/explain.js +1 -1
  52. package/.agents/scripts/lib/config/runners.js +2 -2
  53. package/.agents/scripts/lib/config/runtime.js +1 -1
  54. package/.agents/scripts/lib/config/temp-paths.js +2 -2
  55. package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
  56. package/.agents/scripts/lib/config-settings-schema-quality.js +1 -1
  57. package/.agents/scripts/lib/config-settings-schema.js +3 -3
  58. package/.agents/scripts/lib/duplicate-search.js +1 -1
  59. package/.agents/scripts/lib/dynamic-workflow/capability.js +1 -1
  60. package/.agents/scripts/lib/epic-plan-clarity.js +1 -1
  61. package/.agents/scripts/lib/epic-plan-ideation.js +1 -1
  62. package/.agents/scripts/lib/feedback-loop/memory-freshness.js +1 -1
  63. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +1 -1
  64. package/.agents/scripts/lib/findings/classify-finding.js +1 -1
  65. package/.agents/scripts/lib/findings/promote-finding.js +10 -10
  66. package/.agents/scripts/lib/label-constants.js +3 -4
  67. package/.agents/scripts/lib/label-taxonomy.js +3 -8
  68. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +1 -1
  69. package/.agents/scripts/lib/orchestration/code-review.js +5 -5
  70. package/.agents/scripts/lib/orchestration/context-hydration-engine.js +8 -9
  71. package/.agents/scripts/lib/orchestration/dependency-analyzer.js +3 -3
  72. package/.agents/scripts/lib/orchestration/detectors-phase.js +2 -2
  73. package/.agents/scripts/lib/orchestration/dispatch-engine.js +30 -38
  74. package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +9 -25
  75. package/.agents/scripts/lib/orchestration/epic-cleanup.js +1 -1
  76. package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +8 -8
  77. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +1 -1
  78. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +7 -21
  79. package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +3 -3
  80. package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +26 -13
  81. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +1 -1
  82. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +1 -1
  83. package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +2 -2
  84. package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +1 -1
  85. package/.agents/scripts/lib/orchestration/epic-run-state-store.js +3 -3
  86. package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +4 -4
  87. package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +3 -3
  88. package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +6 -21
  89. package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +7 -7
  90. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +1 -1
  91. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +2 -2
  92. package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +4 -4
  93. package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +4 -4
  94. package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +8 -8
  95. package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +4 -4
  96. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +7 -15
  97. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +72 -41
  98. package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +2 -4
  99. package/.agents/scripts/lib/orchestration/file-assumptions.js +2 -2
  100. package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +1 -1
  101. package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +2 -2
  102. package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +1 -1
  103. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -3
  104. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +1 -1
  105. package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +1 -1
  106. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +1 -1
  107. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +1 -1
  108. package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +1 -1
  109. package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +1 -1
  110. package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +1 -1
  111. package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
  112. package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +1 -1
  113. package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +1 -1
  114. package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +1 -1
  115. package/.agents/scripts/lib/orchestration/manifest-builder.js +5 -5
  116. package/.agents/scripts/lib/orchestration/parked-follow-ons.js +2 -2
  117. package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +5 -5
  118. package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +3 -3
  119. package/.agents/scripts/lib/orchestration/preflight-cache.js +1 -1
  120. package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +1 -1
  121. package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +1 -1
  122. package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +2 -2
  123. package/.agents/scripts/lib/orchestration/retro-runner.js +3 -3
  124. package/.agents/scripts/lib/orchestration/review-depth.js +1 -1
  125. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +1 -1
  126. package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
  127. package/.agents/scripts/lib/orchestration/spec-renderer.js +36 -73
  128. package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
  129. package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +1 -1
  130. package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +2 -2
  131. package/.agents/scripts/lib/orchestration/task-body-validator.js +6 -6
  132. package/.agents/scripts/lib/orchestration/ticket-lease.js +1 -1
  133. package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +2 -2
  134. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -10
  135. package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -70
  136. package/.agents/scripts/lib/orchestration/ticketing/bulk.js +5 -12
  137. package/.agents/scripts/lib/orchestration/ticketing/reads.js +8 -8
  138. package/.agents/scripts/lib/orchestration/ticketing/state.js +3 -3
  139. package/.agents/scripts/lib/orchestration/wave-record-notifications.js +2 -2
  140. package/.agents/scripts/lib/orchestration/wave-record-projection.js +1 -1
  141. package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
  142. package/.agents/scripts/lib/preflight-runner.js +1 -1
  143. package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +4 -5
  144. package/.agents/scripts/lib/presentation/manifest-builder.js +28 -34
  145. package/.agents/scripts/lib/presentation/manifest-formatter.js +3 -4
  146. package/.agents/scripts/lib/presentation/manifest-helpers.js +1 -1
  147. package/.agents/scripts/lib/presentation/manifest-procedures.js +4 -4
  148. package/.agents/scripts/lib/presentation/manifest-render-waves.js +4 -23
  149. package/.agents/scripts/lib/presentation/manifest-renderer.js +1 -1
  150. package/.agents/scripts/lib/presentation/manifest-story-views.js +2 -11
  151. package/.agents/scripts/lib/signals/schema.js +1 -1
  152. package/.agents/scripts/lib/spec/index.js +1 -1
  153. package/.agents/scripts/lib/spec/loader.js +2 -2
  154. package/.agents/scripts/lib/spec/state.js +7 -16
  155. package/.agents/scripts/lib/story-init/context-resolver.js +3 -3
  156. package/.agents/scripts/lib/story-init/state-transitioner.js +2 -2
  157. package/.agents/scripts/lib/story-init/task-graph-builder.js +7 -7
  158. package/.agents/scripts/lib/story-lifecycle.js +8 -8
  159. package/.agents/scripts/lib/story-plan.js +1 -1
  160. package/.agents/scripts/lib/templates/decomposer-prompts.js +59 -52
  161. package/.agents/scripts/lib/wave-runner/tick.js +1 -1
  162. package/.agents/scripts/lifecycle-emit-story-dispatch.js +1 -1
  163. package/.agents/scripts/lifecycle-emit.js +1 -1
  164. package/.agents/scripts/providers/github/board-add.js +1 -1
  165. package/.agents/scripts/providers/github/errors.js +1 -1
  166. package/.agents/scripts/providers/github/mappers.js +2 -2
  167. package/.agents/scripts/providers/github/tickets.js +4 -4
  168. package/.agents/scripts/resync-status-column.js +1 -1
  169. package/.agents/scripts/retro-run.js +2 -2
  170. package/.agents/scripts/run-lint.js +1 -1
  171. package/.agents/scripts/single-story-init.js +1 -1
  172. package/.agents/scripts/stories-wave-tick.js +5 -5
  173. package/.agents/scripts/story-close.js +1 -1
  174. package/.agents/scripts/story-init.js +13 -16
  175. package/.agents/scripts/story-phase.js +5 -5
  176. package/.agents/scripts/story-plan.js +3 -3
  177. package/.agents/scripts/sync-branch-from-base.js +1 -1
  178. package/.agents/scripts/validate-docs-freshness.js +1 -1
  179. package/.agents/scripts/wave-tick.js +1 -1
  180. package/.agents/skills/core/analyze-execution/SKILL.md +2 -2
  181. package/.agents/skills/core/epic-plan-consolidate/SKILL.md +21 -26
  182. package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +23 -56
  183. package/.agents/skills/core/epic-plan-spec-author/SKILL.md +4 -4
  184. package/.agents/skills/core/hydrate-context/SKILL.md +2 -2
  185. package/.agents/skills/core/idea-refinement/SKILL.md +4 -4
  186. package/.agents/skills/core/knowledge-transfer/SKILL.md +2 -2
  187. package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +1 -1
  188. package/.agents/skills/core/scope-triage/SKILL.md +9 -10
  189. package/.agents/skills/core/using-agent-skills/SKILL.md +1 -1
  190. package/.agents/skills/skills.index.json +7 -7
  191. package/.agents/templates/agent-protocol.md +2 -2
  192. package/.agents/workflows/agents-update.md +2 -2
  193. package/.agents/workflows/audit-architecture.md +2 -2
  194. package/.agents/workflows/audit-clean-code.md +2 -2
  195. package/.agents/workflows/audit-dependencies.md +1 -1
  196. package/.agents/workflows/audit-devops.md +1 -1
  197. package/.agents/workflows/audit-documentation.md +2 -2
  198. package/.agents/workflows/audit-lighthouse.md +1 -1
  199. package/.agents/workflows/audit-performance.md +2 -2
  200. package/.agents/workflows/audit-privacy.md +1 -1
  201. package/.agents/workflows/audit-quality.md +2 -2
  202. package/.agents/workflows/audit-security.md +2 -2
  203. package/.agents/workflows/audit-seo.md +1 -1
  204. package/.agents/workflows/audit-sre.md +1 -1
  205. package/.agents/workflows/audit-to-stories.md +10 -10
  206. package/.agents/workflows/audit-ux-ui.md +1 -1
  207. package/.agents/workflows/deliver.md +85 -0
  208. package/.agents/workflows/explain.md +3 -3
  209. package/.agents/workflows/git-merge-pr.md +1 -1
  210. package/.agents/workflows/git-pr-all.md +13 -10
  211. package/.agents/workflows/git-push.md +6 -3
  212. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  213. package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
  214. package/.agents/workflows/helpers/code-review.md +5 -5
  215. package/.agents/workflows/{epic-deliver.md → helpers/deliver-epic.md} +43 -43
  216. package/.agents/workflows/{story-deliver.md → helpers/deliver-stories.md} +25 -25
  217. package/.agents/workflows/helpers/diagnose.md +1 -1
  218. package/.agents/workflows/helpers/epic-audit.md +6 -6
  219. package/.agents/workflows/helpers/epic-deliver-story.md +13 -13
  220. package/.agents/workflows/helpers/epic-plan-decompose.md +23 -23
  221. package/.agents/workflows/helpers/epic-plan-spec.md +6 -6
  222. package/.agents/workflows/helpers/epic-testing.md +3 -3
  223. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  224. package/.agents/workflows/{epic-plan.md → helpers/plan-epic.md} +84 -84
  225. package/.agents/workflows/{story-plan.md → helpers/plan-story.md} +43 -43
  226. package/.agents/workflows/helpers/signals.md +1 -1
  227. package/.agents/workflows/helpers/single-story-deliver.md +11 -11
  228. package/.agents/workflows/helpers/worktree-lifecycle.md +18 -18
  229. package/.agents/workflows/onboard.md +17 -17
  230. package/.agents/workflows/plan.md +89 -0
  231. package/.agents/workflows/qa-explore.md +1 -1
  232. package/.agents/workflows/qa-run-harness.md +1 -1
  233. package/README.md +4 -12
  234. package/docs/CHANGELOG.md +1149 -0
  235. package/lib/cli/__tests__/update-changelog-surface.test.js +357 -0
  236. package/lib/cli/__tests__/update-reexec.test.js +513 -0
  237. package/lib/cli/init.js +31 -29
  238. package/lib/cli/update.js +413 -52
  239. package/package.json +2 -1
  240. package/.agents/scripts/lib/orchestration/reconciler.js +0 -137
@@ -200,9 +200,8 @@ function makeMemoizedGitRunner(runner) {
200
200
  * path was deleted between planning and decomposition) — refuse to decompose
201
201
  * because the resulting Task would be unimplementable as written.
202
202
  *
203
- * Only Tasks are scanned; Features and Stories carry narrative copy, not
204
- * implementation paths, and their bodies routinely reference docs/templates
205
- * the freshness regex would (correctly) ignore.
203
+ * Only Stories are scanned they are the implementation unit; the Epic
204
+ * carries narrative copy, not implementation paths.
206
205
  *
207
206
  * @param {object} opts
208
207
  * @param {object[]} opts.tickets - Validated ticket hierarchy.
@@ -416,7 +415,6 @@ function renderMissLine({ slug, path }) {
416
415
 
417
416
  function indexTicketsBySlug(tickets) {
418
417
  const ticketBySlug = new Map();
419
- const features = [];
420
418
  const stories = [];
421
419
  const slugAdjacency = new Map();
422
420
  for (const t of tickets) {
@@ -429,75 +427,37 @@ function indexTicketsBySlug(tickets) {
429
427
  ticketBySlug.set(t.slug, t);
430
428
  }
431
429
  slugAdjacency.set(t.slug, t.depends_on ?? []);
432
- if (t.type === 'feature') features.push(t);
433
- else if (t.type === 'story') stories.push(t);
430
+ if (t.type === 'story') stories.push(t);
434
431
  }
435
- return { ticketBySlug, features, stories, slugAdjacency };
432
+ return { ticketBySlug, stories, slugAdjacency };
436
433
  }
437
434
 
438
- function assertEachTypePresent({ features, stories }) {
439
- if (features.length === 0)
435
+ /**
436
+ * 2-tier invariant (Story #4041): the decomposer emits Stories only — every
437
+ * ticket in the backlog must be `type: "story"` and at least one must be
438
+ * present. Any other type (the retired `feature`/`task` tiers, or planner
439
+ * hallucinations) HARD-rejects the decomposition.
440
+ */
441
+ function assertAllTicketsAreStories({ tickets, stories }) {
442
+ const nonStories = (tickets ?? []).filter((t) => t.type !== 'story');
443
+ if (nonStories.length > 0) {
444
+ const list = nonStories
445
+ .map((t) => `"${t.title}" (${t.slug ?? '<no slug>'}, type: ${t.type})`)
446
+ .join(', ');
440
447
  throw new Error(
441
- 'Cross-Validation Failed: Backlog must contain at least one Feature.',
448
+ `Cross-Validation Failed: ${nonStories.length} ticket(s) are not Stories: ${list}. ` +
449
+ 'The 2-tier hierarchy (Epic → Story) admits type "story" only — there is no Feature or Task tier.',
442
450
  );
451
+ }
443
452
  if (stories.length === 0)
444
453
  throw new Error(
445
454
  'Cross-Validation Failed: Backlog must contain at least one Story.',
446
455
  );
447
456
  }
448
457
 
449
- function assertHierarchy({ stories, ticketBySlug }) {
450
- for (const story of stories) {
451
- if (!story.parent_slug)
452
- throw new Error(
453
- `Cross-Validation Failed: Story "${story.title}" must have a parent_slug.`,
454
- );
455
- const parent = ticketBySlug.get(story.parent_slug);
456
- if (!parent || parent.type !== 'feature')
457
- throw new Error(
458
- `Cross-Validation Failed: Story "${story.title}" parent must be a Feature.`,
459
- );
460
- }
461
- }
462
-
463
- /**
464
- * Deterministic invariant (Story #3777): a Feature MUST decompose into at
465
- * least two Stories. A single-Story Feature is the work of a Story, not a
466
- * Feature — the Feature wrapper is dead weight and signals decomposition at
467
- * module/task granularity rather than deliverable granularity. HARD-reject
468
- * the decomposition, naming the offending Feature(s) and telling the planner
469
- * to collapse them, in the same throw-on-violation style as the surrounding
470
- * hierarchy invariants.
471
- */
472
- function assertNoSingleStoryFeature({ features, stories }) {
473
- const storyCountByParent = new Map();
474
- for (const story of stories) {
475
- if (!story.parent_slug) continue;
476
- storyCountByParent.set(
477
- story.parent_slug,
478
- (storyCountByParent.get(story.parent_slug) ?? 0) + 1,
479
- );
480
- }
481
- const undersized = features.filter(
482
- (feature) => (storyCountByParent.get(feature.slug) ?? 0) < 2,
483
- );
484
- if (undersized.length === 0) return;
485
- const list = undersized
486
- .map((feature) => {
487
- const count = storyCountByParent.get(feature.slug) ?? 0;
488
- return `"${feature.title}" (${feature.slug}, ${count} ${count === 1 ? 'Story' : 'Stories'})`;
489
- })
490
- .join(', ');
491
- throw new Error(
492
- `Cross-Validation Failed: ${undersized.length} Feature(s) decompose into fewer than two Stories: ${list}. ` +
493
- 'Every Feature MUST contain at least two Stories — a single-Story Feature is the work of a Story, not a Feature. ' +
494
- 'Collapse each offending Feature: drop the Feature wrapper and attach its lone Story to a sibling Feature, or merge the Feature into another.',
495
- );
496
- }
497
-
498
458
  /**
499
459
  * Return true when a Story object carries inline acceptance + verify
500
- * arrays — the 3-tier shape (Epic #3078) where the Story is itself the
460
+ * arrays — the inline-contract shape (Epic #3078) where the Story is itself the
501
461
  * implementation unit and acceptance / verify live on the Story body
502
462
  * rather than in child Task tickets.
503
463
  *
@@ -506,7 +466,7 @@ function assertNoSingleStoryFeature({ features, stories }) {
506
466
  * `acceptance[]` (no `verify[]`) cannot be implemented without a
507
467
  * verification handle, and a Story with only `verify[]` (no
508
468
  * `acceptance[]`) carries no observable criterion. Requiring both is the
509
- * inline-contract invariant every Story must satisfy in the 3-tier model.
469
+ * inline-contract invariant every Story must satisfy.
510
470
  */
511
471
  function hasInlineAcceptanceAndVerify(story) {
512
472
  if (story === null || typeof story !== 'object') return false;
@@ -520,7 +480,7 @@ function hasInlineAcceptanceAndVerify(story) {
520
480
  }
521
481
 
522
482
  function assertEveryStoryHasInlineContract({ stories }) {
523
- // 3-tier (Epic #3078 / #3238): every Story is its own implementation
483
+ // Every Story is its own implementation
524
484
  // unit and MUST carry a non-empty inline contract — top-level
525
485
  // `acceptance[]` AND `verify[]`. A Story missing either is the legacy
526
486
  // 4-tier shape that expected child Tasks; there is no Task tier any
@@ -577,12 +537,9 @@ function attachFindingsAndErrors(tickets, findings, errors) {
577
537
  }
578
538
 
579
539
  export function validateAndNormalizeTickets(tickets, opts = {}) {
580
- const { ticketBySlug, features, stories, slugAdjacency } =
581
- indexTicketsBySlug(tickets);
540
+ const { ticketBySlug, stories, slugAdjacency } = indexTicketsBySlug(tickets);
582
541
 
583
- assertEachTypePresent({ features, stories });
584
- assertHierarchy({ stories, ticketBySlug });
585
- assertNoSingleStoryFeature({ features, stories });
542
+ assertAllTicketsAreStories({ tickets, stories });
586
543
  assertEveryStoryHasInlineContract({ stories });
587
544
  assertNoUnknownDeps({ tickets, ticketBySlug });
588
545
 
@@ -680,9 +637,7 @@ export function validateAndNormalizeTickets(tickets, opts = {}) {
680
637
  // Internal helpers exposed for unit tests; not part of the public surface.
681
638
  export const _internal = {
682
639
  indexTicketsBySlug,
683
- assertEachTypePresent,
684
- assertHierarchy,
685
- assertNoSingleStoryFeature,
640
+ assertAllTicketsAreStories,
686
641
  assertEveryStoryHasInlineContract,
687
642
  assertNoUnknownDeps,
688
643
  assertAcyclic,
@@ -291,7 +291,7 @@ async function processCascadeParentLocked(
291
291
  if (!allDone) return { cascadedTo, failed };
292
292
 
293
293
  // EXCLUSION: Epics do not auto-close via cascade. Epics close via
294
- // formal /epic-deliver (its own machinery handles branch merges,
294
+ // formal /deliver (its own machinery handles branch merges,
295
295
  // PR-driven `Closes #N` auto-close, and a recovery transition in
296
296
  // `epic-deliver-finalize.js`).
297
297
  //
@@ -305,13 +305,6 @@ async function processCascadeParentLocked(
305
305
  // defense-in-depth path when a Story's tasklist references a
306
306
  // planning ticket directly.
307
307
  //
308
- // Features auto-close via cascade. A Feature is a purely
309
- // hierarchical grouping — no standalone branch, no merge step.
310
- // When its last child Story closes, the Feature is complete by
311
- // definition. Operators who need Feature-level AC verification
312
- // should encode it in the final child Story, not rely on a manual
313
- // close step.
314
- //
315
308
  // Reuse the parentSnapshot from the idempotency check above — it is
316
309
  // a fresh read (cache was invalidated before the getTicket call) and
317
310
  // the parent's type label is invariant within a single cascade lock
@@ -371,7 +364,7 @@ async function processCascadeParentLocked(
371
364
  * If yes, transitions parent to DONE and cascades up.
372
365
  *
373
366
  * Parents run strictly sequentially in input order (Story #4017 —
374
- * fan-out is <= 1 under the 3-tier hierarchy, so the former
367
+ * fan-out is <= 1 under the 2-tier hierarchy, so the former
375
368
  * shared-ancestor grouping / parallel dispatch was deleted); concurrent
376
369
  * transitions against a shared ancestor would race the "all children
377
370
  * done?" check. Within each parent, sibling reads fan out via
@@ -415,7 +408,7 @@ export async function cascadeCompletion(provider, ticketId, opts = {}) {
415
408
  // resume reconciler can strip the `parent: #N` orchestrator footer
416
409
  // from a Story body (see Issue 2 in #2982); without the body marker
417
410
  // the cascade silently returned `{ cascadedTo: [], failed: [] }` and
418
- // left intermediate Feature tickets stranded OPEN. The native link is
411
+ // left intermediate parent tickets stranded OPEN. The native link is
419
412
  // independent of body text, so consult it when the first two
420
413
  // strategies came back empty.
421
414
  if (
@@ -442,7 +435,7 @@ export async function cascadeCompletion(provider, ticketId, opts = {}) {
442
435
  return { cascadedTo: [], failed: [] };
443
436
  }
444
437
 
445
- // Story #4017 — under the 3-tier hierarchy a ticket has at most one
438
+ // Story #4017 — under the 2-tier hierarchy a ticket has at most one
446
439
  // parent, so the shared-ancestor grouping / parallel-group dispatch
447
440
  // machinery was deleted. Parents (fan-out <= 1
448
441
  // in practice; the loop stays general for the body-reference fallback)
@@ -557,7 +550,7 @@ export async function cascadeParentState(provider, ticketId, opts = {}) {
557
550
  if (parsedParents.length === 0) return { cascadedTo: [], failed: [] };
558
551
 
559
552
  // Story #4017 — sequential per-parent walk (fan-out <= 1 under the
560
- // 3-tier hierarchy); see cascadeCompletion for the rationale.
553
+ // 2-tier hierarchy); see cascadeCompletion for the rationale.
561
554
  const cascadedTo = [];
562
555
  const failed = [];
563
556
  for (const parentId of parsedParents) {
@@ -76,8 +76,8 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
76
76
  // downstream workflow steps don't have to infer install state from
77
77
  // node_modules presence.
78
78
  'story-init',
79
- // Story #908 — /story-deliver upserts a `story-run-progress` snapshot
80
- // on each Story per Task transition. The /epic-deliver aggregator and
79
+ // Story #908 — /deliver upserts a `story-run-progress` snapshot
80
+ // on each Story per Task transition. The /deliver aggregator and
81
81
  // the epic-runner progress reporter both read this comment to derive
82
82
  // Story-level state without re-fetching ticket labels.
83
83
  'story-run-progress',
@@ -98,7 +98,7 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
98
98
  // operator can correct drift before Phase 8 decomposes from a stale
99
99
  // spec. Advisory: the run continues regardless of the report contents.
100
100
  'spec-freshness',
101
- // Story #2681 — `/epic-deliver` Phase 4 epic-audit helper upserts an
101
+ // Story #2681 — `/deliver` Phase 4 epic-audit helper upserts an
102
102
  // `audit-results` comment on the Epic listing the per-lens findings
103
103
  // returned by the change-set audit pass. The marker was prescribed by
104
104
  // `helpers/epic-audit.md` Step 4 long before it was added to this
@@ -126,7 +126,7 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
126
126
  'epic-handoff',
127
127
  // Story #2899 (Epic #2880, F13) — `epic-deliver-preflight.js` upserts a
128
128
  // `delivery-preflight` comment on the Epic at the start of
129
- // /epic-deliver Phase 1, surfacing estimated story count, install cost,
129
+ // /deliver Phase 1, surfacing estimated story count, install cost,
130
130
  // wave count, GitHub API request volume, Claude quota burn, and any
131
131
  // threshold breaches against `delivery.preflight.max*`. One entry per
132
132
  // Epic; re-runs replace prior content.
@@ -137,7 +137,7 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
137
137
  // `close-validate.end`. One entry per Epic; re-ticks with the same
138
138
  // findings upsert in place (`upsertStructuredComment` diffs by body).
139
139
  'recurring-failure-class',
140
- // Story #3061 (Epic #3051) — the /epic-deliver §2e Idle Watchdog
140
+ // Story #3061 (Epic #3051) — the /deliver §2e Idle Watchdog
141
141
  // subsection instructs the parent host LLM to upsert a `wave-stall`
142
142
  // comment on the Epic whenever an in-flight Story has been silent for
143
143
  // longer than the configured cadence. `wave-tick.js --check-idle`
@@ -153,7 +153,7 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
153
153
  'risk-verdict',
154
154
  // Story #4019 — `epic-plan-lease-guard.js` upserts a `plan-lease`
155
155
  // comment on the Epic at lease-acquire time, recording the claiming
156
- // operator and the claim timestamp. `/epic-plan` emits no
156
+ // operator and the claim timestamp. `/plan` emits no
157
157
  // `story.heartbeat`, so this claim-time is the liveness signal that
158
158
  // makes the documented `--steal` contract decidable: a foreign claim
159
159
  // older than the lease TTL is reclaimed automatically; a fresh one
@@ -268,8 +268,8 @@ export const _structuredCommentCache = new WeakMap();
268
268
  /**
269
269
  * Build a well-formed ticket snapshot for a Story that has zero child
270
270
  * Tasks. Story #3097 (Wave-0 additive, Epic #3078 Strategy B) — the
271
- * 3-tier hierarchy collapses Epic → Feature → Story → Task into
272
- * Epic → Feature → Story, so a Story may legitimately have no Task
271
+ * 2-tier hierarchy collapses Epic → Story → Task into
272
+ * Epic → Story, so a Story may legitimately have no Task
273
273
  * children. Read-side callers that expect a `subTickets` array on the
274
274
  * Story can route through this helper to materialise an empty-children
275
275
  * snapshot without paying a provider round-trip and without risk of
@@ -66,13 +66,13 @@ registerCascadeRunner(async (provider, ticketId, opts) => {
66
66
  /**
67
67
  * Transition a Story ticket directly to a new `agent::*` state without
68
68
  * walking a Task cascade. Story #3097 (Wave-0 additive, Epic #3078
69
- * Strategy B) — in the 3-tier hierarchy a Story has no Task children, so
69
+ * Strategy B) — in the 2-tier hierarchy a Story has no Task children, so
70
70
  * the canonical `transitionTicketState` upward-cascade path
71
71
  * (`cascadeParentState`) is the only meaningful walk. This helper is a
72
- * thin wrapper that pins `cascade: true` (so the parent Feature/Epic
72
+ * thin wrapper that pins `cascade: true` (so the parent Epic
73
73
  * still receives derived-state updates) and is intentionally a no-op
74
74
  * difference from `transitionTicketState` in 4-tier mode — the helper
75
- * exists so 3-tier callers can opt into a name that documents intent
75
+ * exists so 2-tier callers can opt into a name that documents intent
76
76
  * (and so F8 can pivot the implementation to skip the now-impossible
77
77
  * Task-fan-in without rewriting call sites). The wrapper preserves every
78
78
  * `opts` field the caller supplies; only `cascade` defaults to `true`
@@ -3,7 +3,7 @@
3
3
  * `epic-execute-record-wave.js`.
4
4
  *
5
5
  * The CLI fires curated webhook events at every wave boundary (started,
6
- * progress, blocked, unblocked) so the host-LLM-driven `/epic-deliver` path
6
+ * progress, blocked, unblocked) so the host-LLM-driven `/deliver` path
7
7
  * mirrors the wave-loop emits in
8
8
  * `lib/orchestration/epic-runner/phases/iterate-waves.js`. Each helper here
9
9
  * is fire-and-forget — webhook misconfig or a transient Slack outage must
@@ -45,7 +45,7 @@ export function buildNotifyFn(injectedNotify, config, provider, defaultNotify) {
45
45
  * fire-and-forget (the emit helpers swallow webhook misconfiguration), but
46
46
  * we still serialise them so the order matches the wave-loop emits in
47
47
  * `lib/orchestration/epic-runner/phases/iterate-waves.js` for the host-LLM
48
- * driven /epic-deliver path.
48
+ * driven /deliver path.
49
49
  */
50
50
  export async function emitWaveBoundaryNotifications({
51
51
  injectedNotify,
@@ -32,7 +32,7 @@ import { parseStoryAgentReturn } from './epic-runner/sub-agent-return.js';
32
32
  /** Valid wave-level rollup statuses. */
33
33
  export const VALID_RESULT_STATUSES = new Set(['complete', 'blocked', 'failed']);
34
34
 
35
- /** Per-Story return statuses we accept off `/story-deliver` sub-agents. */
35
+ /** Per-Story return statuses we accept off `/deliver` sub-agents. */
36
36
  export const VALID_STORY_STATUSES = new Set(['done', 'blocked', 'failed']);
37
37
 
38
38
  /**
@@ -1,5 +1,5 @@
1
1
  /**
2
- * plan-phase-cleanup.js — Post-phase temp-file cleanup for `/epic-plan`.
2
+ * plan-phase-cleanup.js — Post-phase temp-file cleanup for `/plan`.
3
3
  *
4
4
  * The spec and decompose phases write several Epic-scoped temp files under
5
5
  * the per-Epic tree (`temp/epic-<id>/planner-context.json`,
@@ -13,7 +13,7 @@
13
13
  * auto-fixes. The wrapper prints a human-readable blocker table
14
14
  * (`id · summary · fixCommand`) before returning. Code 2 is the
15
15
  * project-wide "preflight refused" reservation — see
16
- * .agents/workflows/epic-deliver.md for the rationale.
16
+ * .agents/workflows/helpers/deliver-epic.md for the rationale.
17
17
  *
18
18
  * Auto-fixes are logged via `logFixes` before the blocker check so the
19
19
  * operator sees the "we corrected X" line even when a separate blocker
@@ -12,9 +12,8 @@
12
12
 
13
13
  /**
14
14
  * Pure: project a full dispatch manifest into the `{ stories }` shape
15
- * `renderManifest` accepts. Returns the canonical, non-feature,
16
- * non-ungrouped story rows used by the Epic-level dispatch-manifest
17
- * comment.
15
+ * `renderManifest` accepts. Returns the canonical, non-ungrouped story
16
+ * rows used by the Epic-level dispatch-manifest comment.
18
17
  *
19
18
  * @param {object} manifest
20
19
  * @returns {{ storyId: number|string, wave: number, title: string }[]}
@@ -22,7 +21,7 @@
22
21
  export function projectStoriesFromManifest(manifest) {
23
22
  const storyManifest = manifest?.storyManifest ?? [];
24
23
  return storyManifest
25
- .filter((s) => s && s.type !== 'feature' && s.storyId !== '__ungrouped__')
24
+ .filter((s) => s && s.storyId !== '__ungrouped__')
26
25
  .map((s) => ({
27
26
  storyId: s.storyId,
28
27
  wave: s.earliestWave ?? -1,
@@ -71,7 +70,7 @@ export function renderManifest({ epicId, stories, generatedAt }) {
71
70
  `- **Stories:** ${list.length}`,
72
71
  `- **Generated:** ${generatedAt}`,
73
72
  '',
74
- 'Source of truth for the wave-completeness gate run at `/epic-deliver`.',
73
+ 'Source of truth for the wave-completeness gate run at `/deliver`.',
75
74
  '',
76
75
  '```json',
77
76
  JSON.stringify({ stories: list }, null, 2),
@@ -9,19 +9,19 @@
9
9
  * Extracted from `manifest-formatter.js` (Story #1849 Task #1869). The
10
10
  * shape projection used to be inlined in the formatter; pulling it out
11
11
  * isolates the spec → manifest projection from the Markdown renderer and
12
- * lets the per-feature / per-story guard cascade live behind a single
12
+ * lets the per-story guard cascade live behind a single
13
13
  * private predicate (`validateSpecShape`) so the orchestrator function's
14
14
  * CRAP score drops below 12.
15
15
  *
16
- * Story #3413 (3-tier cutover, final): the residual per-Task projection
16
+ * Story #3413 (2-tier cutover, final): the residual per-Task projection
17
17
  * (the Task projector, the per-Story Task array, and the Task-count
18
- * rollup) has been deleted. The walker (`projectFeatures`) now counts
18
+ * rollup) has been deleted. The walker (`projectStories`) counts
19
19
  * Stories directly, and `summary` carries Story-tier counts only
20
20
  * (`totalStories` / `doneStories` / `progressPercent`), matching the
21
21
  * canonical producer in `lib/orchestration/manifest-builder.js`.
22
22
  *
23
23
  * Story-level status surfaces on each `storyEntry.status` so downstream
24
- * renderers read the Story's own `agent::*` label directly — the 3-tier
24
+ * renderers read the Story's own `agent::*` label directly — the
25
25
  * "Stories are first-class lifecycle units" invariant.
26
26
  *
27
27
  * No fs / network access; pure transform. Caller supplies `state` from
@@ -38,8 +38,7 @@ import { AGENT_LABELS } from '../label-constants.js';
38
38
  * "is this thing iterable / object-shaped?" decisions.
39
39
  *
40
40
  * `level` describes which spec node we are validating:
41
- * - `'features'` → the spec-level `features` array
42
- * - `'stories'` → a feature's `stories` array
41
+ * - `'stories'` → the spec-level `stories` array
43
42
  * - `'story'` → a single Story object (must be a non-null object)
44
43
  *
45
44
  * Returns `true` when the node satisfies the shape contract for that
@@ -52,7 +51,6 @@ import { AGENT_LABELS } from '../label-constants.js';
52
51
  */
53
52
  function validateSpecShape(level, value) {
54
53
  switch (level) {
55
- case 'features':
56
54
  case 'stories':
57
55
  return Array.isArray(value);
58
56
  case 'story':
@@ -102,8 +100,8 @@ function buildResolvers(state) {
102
100
  * Private: project a single spec Story into a manifest Story entry. The
103
101
  * Story's status is resolved directly from the Story-level label
104
102
  * (`state.mapping[slug].lastObservedAgentState`) and surfaces on
105
- * `storyEntry.status` — under the 3-tier hierarchy Stories carry their
106
- * own lifecycle state and are leaves with no child Task tickets. Caller
103
+ * `storyEntry.status` — Stories carry their own lifecycle state and are
104
+ * leaves with no child tickets. Caller
107
105
  * filters non-object stories with `validateSpecShape('story', ...)`
108
106
  * before invoking.
109
107
  *
@@ -129,16 +127,17 @@ function projectStory(story, resolvers) {
129
127
  }
130
128
 
131
129
  /**
132
- * Private: walk every feature → story pair in a spec and collect the
133
- * per-story projections + Story-tier roll-up counters. Keeps the loop
134
- * machinery out of `buildManifestFromSpec` so the entry point reads as a
135
- * straight assembly of the result envelope.
130
+ * Private: walk every Story in a spec and collect the per-story
131
+ * projections + Story-tier roll-up counters. Keeps the loop machinery
132
+ * out of `buildManifestFromSpec` so the entry point reads as a straight
133
+ * assembly of the result envelope.
136
134
  *
137
- * Under the 3-tier hierarchy (Epic #3163) Stories are leaves, so the
138
- * rollup counts Stories directly: `totalStories` is every projected
139
- * Story and `doneStories` is the subset carrying `agent::done`.
135
+ * Under the 2-tier hierarchy (Story #4041) Stories are direct Epic
136
+ * children and leaves, so the rollup counts Stories directly:
137
+ * `totalStories` is every projected Story and `doneStories` is the
138
+ * subset carrying `agent::done`.
140
139
  *
141
- * @param {object[]} features
140
+ * @param {object[]} stories
142
141
  * @param {{ resolveId: Function, resolveStatus: Function }} resolvers
143
142
  * @returns {{
144
143
  * storyManifest: object[],
@@ -147,23 +146,18 @@ function projectStory(story, resolvers) {
147
146
  * waveSet: Set<number>,
148
147
  * }}
149
148
  */
150
- function projectFeatures(features, resolvers) {
149
+ function projectStories(stories, resolvers) {
151
150
  const storyManifest = [];
152
151
  let totalStories = 0;
153
152
  let doneStories = 0;
154
153
  const waveSet = new Set();
155
- for (const feature of features) {
156
- const stories = validateSpecShape('stories', feature?.stories)
157
- ? feature.stories
158
- : [];
159
- for (const story of stories) {
160
- if (!validateSpecShape('story', story)) continue;
161
- const { storyEntry, wave } = projectStory(story, resolvers);
162
- storyManifest.push(storyEntry);
163
- totalStories++;
164
- if (storyEntry.status === AGENT_LABELS.DONE) doneStories++;
165
- if (wave >= 0) waveSet.add(wave);
166
- }
154
+ for (const story of stories) {
155
+ if (!validateSpecShape('story', story)) continue;
156
+ const { storyEntry, wave } = projectStory(story, resolvers);
157
+ storyManifest.push(storyEntry);
158
+ totalStories++;
159
+ if (storyEntry.status === AGENT_LABELS.DONE) doneStories++;
160
+ if (wave >= 0) waveSet.add(wave);
167
161
  }
168
162
  return { storyManifest, totalStories, doneStories, waveSet };
169
163
  }
@@ -199,12 +193,12 @@ export function buildManifestFromSpec(spec, opts = {}) {
199
193
  spec?.epic && typeof spec.epic.id === 'number' ? spec.epic.id : null;
200
194
  const epicTitle =
201
195
  spec?.epic && typeof spec.epic.title === 'string' ? spec.epic.title : '';
202
- const features = validateSpecShape('features', spec?.features)
203
- ? spec.features
196
+ const stories = validateSpecShape('stories', spec?.stories)
197
+ ? spec.stories
204
198
  : [];
205
199
 
206
- const { storyManifest, totalStories, doneStories, waveSet } = projectFeatures(
207
- features,
200
+ const { storyManifest, totalStories, doneStories, waveSet } = projectStories(
201
+ stories,
208
202
  resolvers,
209
203
  );
210
204
 
@@ -144,7 +144,6 @@ function renderManifestHeader(manifest) {
144
144
 
145
145
  /**
146
146
  * Private: emit the Wave Summary table plus the per-wave H2 sections.
147
- * Filters Feature containers out of the wave-eligible set.
148
147
  *
149
148
  * @param {object} manifest
150
149
  * @returns {string[]}
@@ -155,7 +154,7 @@ function renderManifestBody(manifest) {
155
154
  manifest.stories ||
156
155
  manifest.summary?.stories ||
157
156
  [];
158
- const waveEligible = allItems.filter((s) => s.type !== 'feature');
157
+ const waveEligible = allItems;
159
158
  const lines = [];
160
159
  const waveBlock = renderWaveSections(waveEligible);
161
160
  if (waveBlock) lines.push(waveBlock);
@@ -165,7 +164,7 @@ function renderManifestBody(manifest) {
165
164
  if (nestedBlock) lines.push(nestedBlock);
166
165
  }
167
166
  // Cross-Story concurrency hazards block — only emitted when the caller
168
- // attaches `concurrencyFindings` to the manifest (i.e. `/epic-plan`
167
+ // attaches `concurrencyFindings` to the manifest (i.e. `/plan`
169
168
  // Phase 9 dispatcher dry-run forwards the validator's findings array).
170
169
  // Absent for live progress-reporter manifests where the block would
171
170
  // duplicate Story-level state already shown above.
@@ -178,7 +177,7 @@ function renderManifestBody(manifest) {
178
177
 
179
178
  /**
180
179
  * Private: emit the agent-telemetry trailer (friction count + recent
181
- * friction list) when the manifest carries one. Under the 3-tier
180
+ * friction list) when the manifest carries one. Under the 2-tier
182
181
  * hierarchy (Epic #3163) friction records are Story-scoped, so each
183
182
  * recent-friction item is keyed by its `storyId`.
184
183
  *
@@ -8,7 +8,7 @@
8
8
  * re-exports every name here so existing call-sites' import paths stay
9
9
  * unchanged.
10
10
  *
11
- * Post-3-tier (Story #3194 / #3413, Epic #3163): the helpers consume the
11
+ * Post-2-tier (Story #3194 / #3413, Epic #3163): the helpers consume the
12
12
  * Story-only manifest shape. Stories carry their lifecycle state on a
13
13
  * top-level `status` field (the parent Story's `agent::*` label) — the
14
14
  * old per-Story Task array, the per-Task id indirection, and the
@@ -16,7 +16,7 @@
16
16
  * the only HTML the manifest emits by AC — every other section is plain
17
17
  * Markdown.
18
18
  *
19
- * @param {number|string} epicId the Epic id used to substitute `/epic-deliver` examples.
19
+ * @param {number|string} epicId the Epic id used to substitute `/deliver` examples.
20
20
  * @returns {string}
21
21
  */
22
22
  export function renderProceduresAndLegendDetails(epicId) {
@@ -28,13 +28,13 @@ export function renderProceduresAndLegendDetails(epicId) {
28
28
  lines.push('### Operating Procedures');
29
29
  lines.push('');
30
30
  lines.push(
31
- `1. **Deliver**: Run \`/epic-deliver ${epicId}\`. The runner iterates waves in order, fans Stories out in parallel via \`/story-deliver\`, and only pauses when the Epic flips to \`agent::blocked\`.`,
31
+ `1. **Deliver**: Run \`/deliver ${epicId}\`. The runner iterates waves in order, fans Stories out in parallel via \`/deliver\`, and only pauses when the Epic flips to \`agent::blocked\`.`,
32
32
  );
33
33
  lines.push(
34
- '2. **Resume (granular, optional)**: Re-running `/epic-deliver` resumes from the checkpointed wave. To re-drive a single Story, run `/story-deliver <storyId>`. Re-runs are checkpoint-idempotent.',
34
+ '2. **Resume (granular, optional)**: Re-running `/deliver` resumes from the checkpointed wave. To re-drive a single Story, run `/deliver <storyId>`. Re-runs are checkpoint-idempotent.',
35
35
  );
36
36
  lines.push(
37
- `3. **Close**: \`/epic-deliver ${epicId}\` runs close-validation, code-review, retro, and PR-create in its tail. Operators merge the PR via the GitHub UI.`,
37
+ `3. **Close**: \`/deliver ${epicId}\` runs close-validation, code-review, retro, and PR-create in its tail. Operators merge the PR via the GitHub UI.`,
38
38
  );
39
39
  lines.push('');
40
40
  lines.push('### Symbol legend');
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Presentation-only: emits the per-wave `## <emoji> Wave N` H2 sections
5
5
  * (with nested per-Story H3 headings) that sit beneath the Wave Summary
6
- * table in the dispatch manifest. Under the 3-tier hierarchy (Epic
6
+ * table in the dispatch manifest. Under the 2-tier hierarchy (Epic
7
7
  * #3163) Stories are leaves with no child Task tickets, so each wave
8
8
  * counts Stories (done/total) and renders one H3 per Story. The TOC
9
9
  * links emitted by `renderWaveSections` jump directly into the H2
@@ -85,7 +85,7 @@ function pickWaveTail(status, waveIdx, sortedWaves, storyCount) {
85
85
  /**
86
86
  * Group Stories into per-wave buckets and accumulate per-wave Story
87
87
  * totals. Pure helper — keeps the bookkeeping outside the main render
88
- * loop. Under the 3-tier hierarchy (Epic #3163) Stories are leaves with
88
+ * loop. Under the 2-tier hierarchy (Epic #3163) Stories are leaves with
89
89
  * no child Task tickets, so each wave's `total` / `done` counts Stories
90
90
  * (a Story is "done" when it carries `agent::done`) — the unit
91
91
  * `deriveWaveStatus` consumes.
@@ -122,11 +122,8 @@ export function renderNestedWaveSections(storyManifest) {
122
122
  if (!validateWaveSection('storyManifest', storyManifest)) return '';
123
123
  if (storyManifest.length === 0) return '';
124
124
 
125
- const waveStories = storyManifest.filter(
126
- (s) => validateWaveSection('story', s) && s.type !== 'feature',
127
- );
128
- const featureItems = storyManifest.filter(
129
- (s) => validateWaveSection('story', s) && s.type === 'feature',
125
+ const waveStories = storyManifest.filter((s) =>
126
+ validateWaveSection('story', s),
130
127
  );
131
128
 
132
129
  const { waveGroups, waveStats } = groupStoriesByWave(waveStories);
@@ -158,22 +155,6 @@ export function renderNestedWaveSections(storyManifest) {
158
155
  }
159
156
  }
160
157
 
161
- if (featureItems.length > 0) {
162
- lines.push('## Feature Containers');
163
- lines.push('');
164
- lines.push(
165
- '> Features are organizational groupings and are **not directly executable**.',
166
- );
167
- lines.push('> Execute the Stories within each Feature instead.');
168
- lines.push('');
169
- lines.push('| Feature | Title |');
170
- lines.push('| :--- | :--- |');
171
- for (const f of featureItems) {
172
- lines.push(`| #${f.storyId} | ${f.storySlug} |`);
173
- }
174
- lines.push('');
175
- }
176
-
177
158
  return lines.join('\n');
178
159
  }
179
160
 
@@ -139,7 +139,7 @@ export async function postParkedFollowOnsComment(manifest, provider) {
139
139
 
140
140
  const storyManifest = manifest.storyManifest ?? [];
141
141
  const manifestStoryIds = storyManifest
142
- .filter((s) => s.type !== 'feature' && s.storyId !== '__ungrouped__')
142
+ .filter((s) => s.storyId !== '__ungrouped__')
143
143
  .map((s) => Number(s.storyId))
144
144
  .filter((n) => Number.isFinite(n));
145
145