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.
- package/.agents/README.md +14 -14
- package/.agents/docs/SDLC.md +129 -134
- package/.agents/docs/configuration.md +16 -16
- package/.agents/docs/workflows.md +6 -8
- package/.agents/instructions.md +12 -11
- package/.agents/personas/architect.md +1 -1
- package/.agents/personas/product.md +1 -1
- package/.agents/personas/project-manager.md +14 -14
- package/.agents/personas/technical-writer.md +1 -1
- package/.agents/rules/changelog-style.md +5 -5
- package/.agents/rules/git-conventions.md +3 -3
- package/.agents/schemas/agentrc.schema.json +3 -3
- package/.agents/schemas/dispatch-manifest.json +4 -4
- package/.agents/schemas/epic-spec.schema.json +15 -45
- package/.agents/schemas/lifecycle/README.md +1 -1
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +1 -1
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/README.md +1 -1
- package/.agents/scripts/acceptance-eval.js +1 -1
- package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
- package/.agents/scripts/analyze-execution.js +2 -2
- package/.agents/scripts/audit-to-stories.js +1 -1
- package/.agents/scripts/check-doc-links.js +2 -3
- package/.agents/scripts/diagnose-friction.js +1 -1
- package/.agents/scripts/dispatcher.js +2 -2
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/epic-audit-prepare.js +3 -3
- package/.agents/scripts/epic-deliver-note-intervention.js +2 -2
- package/.agents/scripts/epic-deliver-preflight.js +6 -6
- package/.agents/scripts/epic-deliver-prepare.js +1 -1
- package/.agents/scripts/epic-execute-record-wave.js +4 -4
- package/.agents/scripts/epic-plan-healthcheck.js +6 -10
- package/.agents/scripts/epic-plan-spec-validate.js +1 -1
- package/.agents/scripts/epic-reconcile.js +11 -29
- package/.agents/scripts/evidence-gate.js +1 -1
- package/.agents/scripts/generate-workflows-doc.js +1 -1
- package/.agents/scripts/hierarchy-gate.js +7 -11
- package/.agents/scripts/lib/ITicketingProvider.js +1 -1
- package/.agents/scripts/lib/audit-suite/selector.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +2 -2
- package/.agents/scripts/lib/baseline-snapshot.js +7 -7
- package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
- package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +1 -1
- package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +1 -1
- package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
- package/.agents/scripts/lib/codebase-snapshot.js +1 -1
- package/.agents/scripts/lib/config/explain.js +1 -1
- package/.agents/scripts/lib/config/runners.js +2 -2
- package/.agents/scripts/lib/config/runtime.js +1 -1
- package/.agents/scripts/lib/config/temp-paths.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-quality.js +1 -1
- package/.agents/scripts/lib/config-settings-schema.js +3 -3
- package/.agents/scripts/lib/duplicate-search.js +1 -1
- package/.agents/scripts/lib/dynamic-workflow/capability.js +1 -1
- package/.agents/scripts/lib/epic-plan-clarity.js +1 -1
- package/.agents/scripts/lib/epic-plan-ideation.js +1 -1
- package/.agents/scripts/lib/feedback-loop/memory-freshness.js +1 -1
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +1 -1
- package/.agents/scripts/lib/findings/classify-finding.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +10 -10
- package/.agents/scripts/lib/label-constants.js +3 -4
- package/.agents/scripts/lib/label-taxonomy.js +3 -8
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +1 -1
- package/.agents/scripts/lib/orchestration/code-review.js +5 -5
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +8 -9
- package/.agents/scripts/lib/orchestration/dependency-analyzer.js +3 -3
- package/.agents/scripts/lib/orchestration/detectors-phase.js +2 -2
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +30 -38
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +9 -25
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +8 -8
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +7 -21
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +26 -13
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +2 -2
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +6 -21
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +7 -7
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +2 -2
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +8 -8
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +7 -15
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +72 -41
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +2 -4
- package/.agents/scripts/lib/orchestration/file-assumptions.js +2 -2
- package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +1 -1
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +2 -2
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +1 -1
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -3
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +1 -1
- package/.agents/scripts/lib/orchestration/manifest-builder.js +5 -5
- package/.agents/scripts/lib/orchestration/parked-follow-ons.js +2 -2
- package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +5 -5
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +3 -3
- package/.agents/scripts/lib/orchestration/preflight-cache.js +1 -1
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +1 -1
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +1 -1
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +2 -2
- package/.agents/scripts/lib/orchestration/retro-runner.js +3 -3
- package/.agents/scripts/lib/orchestration/review-depth.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
- package/.agents/scripts/lib/orchestration/spec-renderer.js +36 -73
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +2 -2
- package/.agents/scripts/lib/orchestration/task-body-validator.js +6 -6
- package/.agents/scripts/lib/orchestration/ticket-lease.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +2 -2
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -10
- package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -70
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +5 -12
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +8 -8
- package/.agents/scripts/lib/orchestration/ticketing/state.js +3 -3
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +2 -2
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +1 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
- package/.agents/scripts/lib/preflight-runner.js +1 -1
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +4 -5
- package/.agents/scripts/lib/presentation/manifest-builder.js +28 -34
- package/.agents/scripts/lib/presentation/manifest-formatter.js +3 -4
- package/.agents/scripts/lib/presentation/manifest-helpers.js +1 -1
- package/.agents/scripts/lib/presentation/manifest-procedures.js +4 -4
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +4 -23
- package/.agents/scripts/lib/presentation/manifest-renderer.js +1 -1
- package/.agents/scripts/lib/presentation/manifest-story-views.js +2 -11
- package/.agents/scripts/lib/signals/schema.js +1 -1
- package/.agents/scripts/lib/spec/index.js +1 -1
- package/.agents/scripts/lib/spec/loader.js +2 -2
- package/.agents/scripts/lib/spec/state.js +7 -16
- package/.agents/scripts/lib/story-init/context-resolver.js +3 -3
- package/.agents/scripts/lib/story-init/state-transitioner.js +2 -2
- package/.agents/scripts/lib/story-init/task-graph-builder.js +7 -7
- package/.agents/scripts/lib/story-lifecycle.js +8 -8
- package/.agents/scripts/lib/story-plan.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +59 -52
- package/.agents/scripts/lib/wave-runner/tick.js +1 -1
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +1 -1
- package/.agents/scripts/lifecycle-emit.js +1 -1
- package/.agents/scripts/providers/github/board-add.js +1 -1
- package/.agents/scripts/providers/github/errors.js +1 -1
- package/.agents/scripts/providers/github/mappers.js +2 -2
- package/.agents/scripts/providers/github/tickets.js +4 -4
- package/.agents/scripts/resync-status-column.js +1 -1
- package/.agents/scripts/retro-run.js +2 -2
- package/.agents/scripts/run-lint.js +1 -1
- package/.agents/scripts/single-story-init.js +1 -1
- package/.agents/scripts/stories-wave-tick.js +5 -5
- package/.agents/scripts/story-close.js +1 -1
- package/.agents/scripts/story-init.js +13 -16
- package/.agents/scripts/story-phase.js +5 -5
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/sync-branch-from-base.js +1 -1
- package/.agents/scripts/validate-docs-freshness.js +1 -1
- package/.agents/scripts/wave-tick.js +1 -1
- package/.agents/skills/core/analyze-execution/SKILL.md +2 -2
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +21 -26
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +23 -56
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +4 -4
- package/.agents/skills/core/hydrate-context/SKILL.md +2 -2
- package/.agents/skills/core/idea-refinement/SKILL.md +4 -4
- package/.agents/skills/core/knowledge-transfer/SKILL.md +2 -2
- package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +1 -1
- package/.agents/skills/core/scope-triage/SKILL.md +9 -10
- package/.agents/skills/core/using-agent-skills/SKILL.md +1 -1
- package/.agents/skills/skills.index.json +7 -7
- package/.agents/templates/agent-protocol.md +2 -2
- package/.agents/workflows/agents-update.md +2 -2
- package/.agents/workflows/audit-architecture.md +2 -2
- package/.agents/workflows/audit-clean-code.md +2 -2
- package/.agents/workflows/audit-dependencies.md +1 -1
- package/.agents/workflows/audit-devops.md +1 -1
- package/.agents/workflows/audit-documentation.md +2 -2
- package/.agents/workflows/audit-lighthouse.md +1 -1
- package/.agents/workflows/audit-performance.md +2 -2
- package/.agents/workflows/audit-privacy.md +1 -1
- package/.agents/workflows/audit-quality.md +2 -2
- package/.agents/workflows/audit-security.md +2 -2
- package/.agents/workflows/audit-seo.md +1 -1
- package/.agents/workflows/audit-sre.md +1 -1
- package/.agents/workflows/audit-to-stories.md +10 -10
- package/.agents/workflows/audit-ux-ui.md +1 -1
- package/.agents/workflows/deliver.md +85 -0
- package/.agents/workflows/explain.md +3 -3
- package/.agents/workflows/git-merge-pr.md +1 -1
- package/.agents/workflows/git-pr-all.md +13 -10
- package/.agents/workflows/git-push.md +6 -3
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
- package/.agents/workflows/helpers/code-review.md +5 -5
- package/.agents/workflows/{epic-deliver.md → helpers/deliver-epic.md} +43 -43
- package/.agents/workflows/{story-deliver.md → helpers/deliver-stories.md} +25 -25
- package/.agents/workflows/helpers/diagnose.md +1 -1
- package/.agents/workflows/helpers/epic-audit.md +6 -6
- package/.agents/workflows/helpers/epic-deliver-story.md +13 -13
- package/.agents/workflows/helpers/epic-plan-decompose.md +23 -23
- package/.agents/workflows/helpers/epic-plan-spec.md +6 -6
- package/.agents/workflows/helpers/epic-testing.md +3 -3
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/{epic-plan.md → helpers/plan-epic.md} +84 -84
- package/.agents/workflows/{story-plan.md → helpers/plan-story.md} +43 -43
- package/.agents/workflows/helpers/signals.md +1 -1
- package/.agents/workflows/helpers/single-story-deliver.md +11 -11
- package/.agents/workflows/helpers/worktree-lifecycle.md +18 -18
- package/.agents/workflows/onboard.md +17 -17
- package/.agents/workflows/plan.md +89 -0
- package/.agents/workflows/qa-explore.md +1 -1
- package/.agents/workflows/qa-run-harness.md +1 -1
- package/README.md +4 -12
- package/docs/CHANGELOG.md +1149 -0
- package/lib/cli/__tests__/update-changelog-surface.test.js +357 -0
- package/lib/cli/__tests__/update-reexec.test.js +513 -0
- package/lib/cli/init.js +31 -29
- package/lib/cli/update.js +413 -52
- package/package.json +2 -1
- 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
|
|
204
|
-
*
|
|
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 === '
|
|
433
|
-
else if (t.type === 'story') stories.push(t);
|
|
430
|
+
if (t.type === 'story') stories.push(t);
|
|
434
431
|
}
|
|
435
|
-
return { ticketBySlug,
|
|
432
|
+
return { ticketBySlug, stories, slugAdjacency };
|
|
436
433
|
}
|
|
437
434
|
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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,
|
|
581
|
-
indexTicketsBySlug(tickets);
|
|
540
|
+
const { ticketBySlug, stories, slugAdjacency } = indexTicketsBySlug(tickets);
|
|
582
541
|
|
|
583
|
-
|
|
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
|
-
|
|
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 /
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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 — /
|
|
80
|
-
// on each Story per Task transition. The /
|
|
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 — `/
|
|
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
|
-
// /
|
|
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 /
|
|
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. `/
|
|
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
|
-
*
|
|
272
|
-
* Epic →
|
|
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
|
|
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
|
|
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
|
|
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 `/
|
|
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 /
|
|
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 `/
|
|
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 `/
|
|
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
|
|
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-
|
|
16
|
-
*
|
|
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.
|
|
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 `/
|
|
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-
|
|
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 (
|
|
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 (`
|
|
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
|
|
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
|
-
* - `'
|
|
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` —
|
|
106
|
-
*
|
|
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
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
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
|
|
138
|
-
* rollup counts Stories directly:
|
|
139
|
-
* Story and `doneStories` is the
|
|
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[]}
|
|
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
|
|
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
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
203
|
-
? spec.
|
|
196
|
+
const stories = validateSpecShape('stories', spec?.stories)
|
|
197
|
+
? spec.stories
|
|
204
198
|
: [];
|
|
205
199
|
|
|
206
|
-
const { storyManifest, totalStories, doneStories, waveSet } =
|
|
207
|
-
|
|
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
|
|
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. `/
|
|
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
|
|
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-
|
|
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 `/
|
|
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 \`/
|
|
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 `/
|
|
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**: \`/
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
142
|
+
.filter((s) => s.storyId !== '__ungrouped__')
|
|
143
143
|
.map((s) => Number(s.storyId))
|
|
144
144
|
.filter((n) => Number.isFinite(n));
|
|
145
145
|
|