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
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Thin facade composing:
|
|
5
5
|
* - `dispatch-pipeline.js` — internal resolve/fetch/reconcile/graph helpers
|
|
6
6
|
*
|
|
7
|
-
* Every Epic is
|
|
8
|
-
* Story-level wave plan and emits a
|
|
7
|
+
* Every Epic is 2-tier (Epic → Story); `dispatch()` computes a
|
|
8
|
+
* Story-level wave plan and emits a 2-tier manifest. The legacy Task-tier
|
|
9
9
|
* dispatch runtime (Task fetcher, single-Story executor, the per-Task
|
|
10
10
|
* wave fan-out, and the Epic-completion detector) was removed in Epic
|
|
11
11
|
* #3163.
|
|
@@ -20,8 +20,7 @@ import { createProvider } from '../provider-factory.js';
|
|
|
20
20
|
import {
|
|
21
21
|
buildStoryDispatchGraph,
|
|
22
22
|
fetchEpicContext,
|
|
23
|
-
|
|
24
|
-
reconcileEpicState,
|
|
23
|
+
isTwoTierDispatch,
|
|
25
24
|
resolveDispatchContext,
|
|
26
25
|
} from './dispatch-pipeline.js';
|
|
27
26
|
import { buildManifest } from './manifest-builder.js';
|
|
@@ -60,14 +59,13 @@ export async function resolveAndDispatch(options) {
|
|
|
60
59
|
|
|
61
60
|
const isStory = labels.includes(TYPE_LABELS.STORY);
|
|
62
61
|
const isEpic = labels.includes(TYPE_LABELS.EPIC);
|
|
63
|
-
const isFeature = labels.includes(TYPE_LABELS.FEATURE);
|
|
64
62
|
|
|
65
63
|
if (isStory) {
|
|
66
64
|
throw new Error(
|
|
67
65
|
`[Dispatcher] Ticket #${ticketId} is a **Story**. Stories are dispatched ` +
|
|
68
|
-
'through the
|
|
69
|
-
`Run \`/
|
|
70
|
-
`or dispatch its parent Epic with \`/
|
|
66
|
+
'through the Story delivery path, not directly via the dispatcher. ' +
|
|
67
|
+
`Run \`/deliver ${ticketId}\` to execute this Story, ` +
|
|
68
|
+
`or dispatch its parent Epic with \`/deliver #<epicId>\`.`,
|
|
71
69
|
);
|
|
72
70
|
}
|
|
73
71
|
|
|
@@ -75,14 +73,6 @@ export async function resolveAndDispatch(options) {
|
|
|
75
73
|
return dispatch({ epicId: ticketId, dryRun, provider });
|
|
76
74
|
}
|
|
77
75
|
|
|
78
|
-
if (isFeature) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
`[Dispatcher] Ticket #${ticketId} is a **Feature**. Features are containers and cannot be executed directly. ` +
|
|
81
|
-
`Please execute individual Stories within this Feature using \`/epic-deliver #[Story ID]\`, ` +
|
|
82
|
-
`or dispatch the entire Epic using \`/epic-deliver #${ticket.body?.match(/^parent:\s*#(\d+)/m)?.[1] || 'ID'}\`.`,
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
76
|
const typeLabel = labels.find((l) => l.startsWith('type::')) || 'unknown';
|
|
87
77
|
throw new Error(
|
|
88
78
|
`[Dispatcher] Ticket #${ticketId} has type "${typeLabel.replace('type::', '')}". ` +
|
|
@@ -100,17 +90,16 @@ export async function dispatch(options) {
|
|
|
100
90
|
const { epicId, dryRun } = ctx;
|
|
101
91
|
|
|
102
92
|
const fetched = await fetchEpicContext(ctx);
|
|
103
|
-
await reconcileEpicState(ctx, fetched);
|
|
104
93
|
|
|
105
|
-
// Every Epic is
|
|
106
|
-
// waves directly from the Story tickets and emit a
|
|
94
|
+
// Every Epic is 2-tier (Epic → Story). Compute Story-level
|
|
95
|
+
// waves directly from the Story tickets and emit a 2-tier-shaped
|
|
107
96
|
// manifest with `waves[].stories[]` so downstream consumers (manifest
|
|
108
|
-
// renderer, /
|
|
109
|
-
// Per-Story execution is owned by `/
|
|
97
|
+
// renderer, /deliver wave planner) see the correct execution plan.
|
|
98
|
+
// Per-Story execution is owned by `/deliver` (story-init →
|
|
110
99
|
// story-close), not by this dispatcher.
|
|
111
|
-
if (
|
|
100
|
+
if (isTwoTierDispatch(fetched.allTickets)) {
|
|
112
101
|
Logger.info(
|
|
113
|
-
'Detected
|
|
102
|
+
'Detected 2-tier hierarchy — computing Story-level execution waves.',
|
|
114
103
|
);
|
|
115
104
|
const { allWaves: storyWaves } = buildStoryDispatchGraph(
|
|
116
105
|
fetched.allTickets,
|
|
@@ -123,22 +112,25 @@ export async function dispatch(options) {
|
|
|
123
112
|
waves: storyWaves,
|
|
124
113
|
dispatched: [],
|
|
125
114
|
dryRun,
|
|
126
|
-
hierarchy: '
|
|
115
|
+
hierarchy: '2-tier',
|
|
127
116
|
});
|
|
128
117
|
}
|
|
129
118
|
|
|
130
|
-
// No Story tickets under the Epic —
|
|
131
|
-
// manifest
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
119
|
+
// No Story tickets under the Epic — throw loudly rather than emit an
|
|
120
|
+
// empty manifest, matching the wave-loop's behavior (build-wave-dag.js
|
|
121
|
+
// throws the same message shape on this input). A silently-empty
|
|
122
|
+
// manifest masks a pre-cutover Epic whose children are legacy Features.
|
|
123
|
+
const typedChildren = (fetched.allTickets ?? [])
|
|
124
|
+
.map((t) => (t.labels ?? []).find((l) => l.startsWith('type::')))
|
|
125
|
+
.filter(Boolean);
|
|
126
|
+
const legacyHint =
|
|
127
|
+
typedChildren.length > 0
|
|
128
|
+
? ` Found ${typedChildren.length} non-Story child ticket(s) ` +
|
|
129
|
+
`(${[...new Set(typedChildren)].join(', ')}) — this Epic looks ` +
|
|
130
|
+
`pre-cutover (legacy Feature children) and needs migration to the ` +
|
|
131
|
+
`2-tier (Epic → Story) hierarchy before dispatch.`
|
|
132
|
+
: '';
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Epic #${epicId} has no child stories to dispatch.${legacyHint}`,
|
|
135
|
+
);
|
|
144
136
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Internal pipeline helpers composed by `dispatch-engine.js::dispatch()`.
|
|
5
5
|
* Keeping these out of the coordinator keeps the public entry point compact
|
|
6
|
-
* and focused on the
|
|
6
|
+
* and focused on the 2-tier flow: resolve → fetch → Story-graph.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { PROJECT_ROOT, resolveConfig } from '../config-resolver.js';
|
|
@@ -14,13 +14,12 @@ import { createProvider } from '../provider-factory.js';
|
|
|
14
14
|
import { buildStoryAdjacency } from '../story-adjacency.js';
|
|
15
15
|
import { WorktreeManager } from '../worktree-manager.js';
|
|
16
16
|
import { computeStoryWaves } from './dependency-analyzer.js';
|
|
17
|
-
import { reconcileHierarchy } from './reconciler.js';
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
19
|
* Runtime context for a single dispatch cycle.
|
|
21
20
|
*
|
|
22
21
|
* Produced by {@link resolveDispatchContext} and consumed by every pipeline
|
|
23
|
-
* stage (fetch →
|
|
22
|
+
* stage (fetch → graph → scaffold → GC → dispatch). All fields
|
|
24
23
|
* are resolved once up-front so downstream helpers can stay free of
|
|
25
24
|
* configuration look-ups.
|
|
26
25
|
*
|
|
@@ -40,7 +39,7 @@ import { reconcileHierarchy } from './reconciler.js';
|
|
|
40
39
|
*
|
|
41
40
|
* @typedef {object} FetchedEpic
|
|
42
41
|
* @property {object} epic The Epic ticket record.
|
|
43
|
-
* @property {object[]} allTickets Every ticket under the Epic (stories +
|
|
42
|
+
* @property {object[]} allTickets Every ticket under the Epic (stories + health).
|
|
44
43
|
* @property {Map<number, object>} allTicketsById Index of `allTickets` by ticket id.
|
|
45
44
|
*/
|
|
46
45
|
|
|
@@ -106,30 +105,15 @@ export async function fetchEpicContext(ctx) {
|
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
/**
|
|
109
|
-
*
|
|
110
|
-
* reality before dispatch. Walks Stories → Features bottom-up.
|
|
111
|
-
*
|
|
112
|
-
* @param {DispatchContext} ctx Dispatch context.
|
|
113
|
-
* @param {FetchedEpic} fetched Result of {@link fetchEpicContext}.
|
|
114
|
-
* @returns {Promise<void>}
|
|
115
|
-
*/
|
|
116
|
-
export async function reconcileEpicState(ctx, fetched) {
|
|
117
|
-
const { provider, dryRun, epicId } = ctx;
|
|
118
|
-
const { epic, allTickets } = fetched;
|
|
119
|
-
|
|
120
|
-
await reconcileHierarchy(provider, epicId, epic, allTickets, dryRun);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Detect 3-tier hierarchy from the fetched ticket graph. After Epic #3163's
|
|
108
|
+
* Detect 2-tier hierarchy from the fetched ticket graph. After Epic #3163's
|
|
125
109
|
* hard cutover deleted the `type::task` ticket layer, shape selection is
|
|
126
110
|
* purely structural: any Epic carrying at least one `type::story` ticket
|
|
127
|
-
* resolves to
|
|
111
|
+
* resolves to 2-tier.
|
|
128
112
|
*
|
|
129
113
|
* @param {object[]} allTickets
|
|
130
114
|
* @returns {boolean}
|
|
131
115
|
*/
|
|
132
|
-
export function
|
|
116
|
+
export function isTwoTierDispatch(allTickets) {
|
|
133
117
|
if (!Array.isArray(allTickets) || allTickets.length === 0) return false;
|
|
134
118
|
return allTickets.some((t) =>
|
|
135
119
|
(t.labelSet ?? new Set(t.labels ?? [])).has(TYPE_LABELS.STORY),
|
|
@@ -137,7 +121,7 @@ export function isThreeTierDispatch(allTickets) {
|
|
|
137
121
|
}
|
|
138
122
|
|
|
139
123
|
/**
|
|
140
|
-
* Build the Story-level dispatch graph for a
|
|
124
|
+
* Build the Story-level dispatch graph for a 2-tier Epic. Reads story
|
|
141
125
|
* tickets from `allTickets`, parses cross-Story `blocked by` references
|
|
142
126
|
* from each Story body (also honoring an optional `dependencies[]`
|
|
143
127
|
* field set by fixture providers), and computes wave indices via
|
|
@@ -151,7 +135,7 @@ export function isThreeTierDispatch(allTickets) {
|
|
|
151
135
|
* are placed in their own trailing wave so they remain visible in the
|
|
152
136
|
* manifest output.
|
|
153
137
|
*
|
|
154
|
-
* @param {object[]} allTickets Fetched ticket graph (Epic +
|
|
138
|
+
* @param {object[]} allTickets Fetched ticket graph (Epic + Stories).
|
|
155
139
|
* @returns {{ allWaves: object[][], storyMap: Map<number, object> }}
|
|
156
140
|
* @throws {Error} When the Story dependency graph contains a cycle.
|
|
157
141
|
*/
|
|
@@ -193,7 +177,7 @@ export function buildStoryDispatchGraph(allTickets) {
|
|
|
193
177
|
if (byWave.has(-1)) allWaves.push(byWave.get(-1));
|
|
194
178
|
|
|
195
179
|
Logger.info(
|
|
196
|
-
`Computed ${allWaves.length} Story-level execution wave(s) (
|
|
180
|
+
`Computed ${allWaves.length} Story-level execution wave(s) (2-tier).`,
|
|
197
181
|
);
|
|
198
182
|
return { allWaves, storyMap };
|
|
199
183
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* node:coverage ignore file -- MI 0 orchestration glue; reaps live worktrees + runs `git branch -D` — testing requires mocking real worktree/git state to the point of asserting only the mock structure */
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Epic-cleanup primitives — local branch + worktree reap for `/
|
|
4
|
+
* Epic-cleanup primitives — local branch + worktree reap for `/deliver`
|
|
5
5
|
* Phase 8.
|
|
6
6
|
*
|
|
7
7
|
* Once a PR has merged (auto or operator-button), the Epic branch and every
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* epic-deliver-lease-guard.js — preflight guards for `/
|
|
2
|
+
* epic-deliver-lease-guard.js — preflight guards for `/deliver`
|
|
3
3
|
* (Story #3482, Epic #3457).
|
|
4
4
|
*
|
|
5
|
-
* `/
|
|
5
|
+
* `/deliver`'s prepare phase used to checkout `epic/<id>` over whatever
|
|
6
6
|
* the operator had checked out, yanking HEAD and resetting baselines under a
|
|
7
7
|
* live working session (the documented "epic-deliver shares the main checkout"
|
|
8
8
|
* footgun). This module supplies the two preflight guards the Tech Spec
|
|
@@ -60,7 +60,7 @@ export function resolveOperator({ asFlag, config, gitUserEmail } = {}) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Normalise the expected-branch argument into a non-empty list. `/
|
|
63
|
+
* Normalise the expected-branch argument into a non-empty list. `/deliver`
|
|
64
64
|
* may legitimately start from either the Epic integration branch (`epic/<id>`,
|
|
65
65
|
* on a resume) or the project base branch (`main`, on a fresh run), so the
|
|
66
66
|
* guard accepts a set rather than a single name.
|
|
@@ -153,20 +153,20 @@ export function renderCheckoutRefusal(result) {
|
|
|
153
153
|
if (result.reason === 'dirty') {
|
|
154
154
|
return (
|
|
155
155
|
`[epic-deliver] Refusing to start: the working tree is dirty. ` +
|
|
156
|
-
`/
|
|
156
|
+
`/deliver will not check out ${expected} over uncommitted ` +
|
|
157
157
|
`or untracked changes — commit, stash, or clean them, then re-run.\n` +
|
|
158
158
|
`--- dirty entries ---\n${result.dirtyEntries ?? '(unavailable)'}`
|
|
159
159
|
);
|
|
160
160
|
}
|
|
161
161
|
if (result.reason === 'detached-head') {
|
|
162
162
|
return (
|
|
163
|
-
`[epic-deliver] Refusing to start: HEAD is detached. /
|
|
163
|
+
`[epic-deliver] Refusing to start: HEAD is detached. /deliver ` +
|
|
164
164
|
`expects HEAD on ${expected}. Check one out before re-running.`
|
|
165
165
|
);
|
|
166
166
|
}
|
|
167
167
|
return (
|
|
168
168
|
`[epic-deliver] Refusing to start: HEAD is on '${result.currentBranch}', ` +
|
|
169
|
-
`not the expected ${expected}. /
|
|
169
|
+
`not the expected ${expected}. /deliver will not yank HEAD ` +
|
|
170
170
|
`away from your branch — switch to ${expected} yourself before re-running.`
|
|
171
171
|
);
|
|
172
172
|
}
|
|
@@ -182,7 +182,7 @@ export function renderLeaseRefusal(lease, epicId) {
|
|
|
182
182
|
return (
|
|
183
183
|
`[epic-deliver] Refusing to start: Epic #${epicId} is already claimed by ` +
|
|
184
184
|
`'${lease.owner}' with a live lease (recent heartbeat within the TTL). ` +
|
|
185
|
-
`Another /
|
|
185
|
+
`Another /deliver run is driving this Epic. Wait for it to finish, ` +
|
|
186
186
|
`or pass --steal to forcibly transfer the claim.`
|
|
187
187
|
);
|
|
188
188
|
}
|
|
@@ -281,7 +281,7 @@ export async function runPrepareGuards({
|
|
|
281
281
|
'[epic-deliver] Refusing to start: no operator identity could be ' +
|
|
282
282
|
'resolved. --as, github.operatorHandle (unset or still the shipped ' +
|
|
283
283
|
'`@[USERNAME]` placeholder), and git user.email are all empty, so the ' +
|
|
284
|
-
'Epic-lease has no owner and concurrent /
|
|
284
|
+
'Epic-lease has no owner and concurrent /deliver runs cannot be ' +
|
|
285
285
|
'serialised. Set your own handle in .agentrc.local.json (e.g. ' +
|
|
286
286
|
'{ "github": { "operatorHandle": "@your-login" } }), pass --as <handle>, ' +
|
|
287
287
|
'or configure git user.email, then re-run.',
|
|
@@ -69,6 +69,6 @@ export function warnTicketCapNearLimit(
|
|
|
69
69
|
) {
|
|
70
70
|
if (tickets.length < maxTickets) return;
|
|
71
71
|
logger.warn(
|
|
72
|
-
`[${tag}] ⚠️ Received ${tickets.length} tickets against a reviewability budget of ${maxTickets}. Review the
|
|
72
|
+
`[${tag}] ⚠️ Received ${tickets.length} tickets against a reviewability budget of ${maxTickets}. Review the Story decomposition before persisting; over-budget persistence requires --allow-over-budget.`,
|
|
73
73
|
);
|
|
74
74
|
}
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Owns the deterministic DAG helpers used by the reconciler pipeline:
|
|
5
5
|
* - `resolveDependencies(ticket, slugMap)`
|
|
6
|
-
* - `orderTicketsForCreation(validated)` (topological sort
|
|
7
|
-
*
|
|
8
|
-
* order so parents always exist before their children get created)
|
|
6
|
+
* - `orderTicketsForCreation(validated)` (topological sort of the
|
|
7
|
+
* Story set so dependency producers are created before consumers)
|
|
9
8
|
*
|
|
10
9
|
* Extracted verbatim from `epic-plan-decompose.js` so the named exports
|
|
11
10
|
* (`resolveDependencies`, `orderTicketsForCreation`) that the
|
|
@@ -53,26 +52,13 @@ function topoSortGroup(group) {
|
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
/**
|
|
56
|
-
* Topologically sort
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
55
|
+
* Topologically sort the Story set so intra-set dep chains resolve
|
|
56
|
+
* before their dependents are created. The 2-tier hierarchy has a
|
|
57
|
+
* single ticket type (story) attached directly to the Epic, so there
|
|
58
|
+
* is no parent-type ordering to interleave.
|
|
60
59
|
*/
|
|
61
60
|
export function orderTicketsForCreation(validated) {
|
|
62
|
-
const typeOrder = { feature: 0, story: 1, task: 2 };
|
|
63
|
-
const groups = new Map();
|
|
64
|
-
for (const t of validated) {
|
|
65
|
-
const parentKey = t.parent_slug ?? '__epic__';
|
|
66
|
-
const key = `${t.type}::${parentKey}`;
|
|
67
|
-
if (!groups.has(key)) groups.set(key, { type: t.type, items: [] });
|
|
68
|
-
groups.get(key).items.push(t);
|
|
69
|
-
}
|
|
70
|
-
const ordered = [...groups.values()].sort(
|
|
71
|
-
(a, b) => typeOrder[a.type] - typeOrder[b.type],
|
|
72
|
-
);
|
|
73
61
|
const result = [];
|
|
74
|
-
for (const
|
|
75
|
-
for (const t of topoSortGroup(group.items)) result.push(t);
|
|
76
|
-
}
|
|
62
|
+
for (const t of topoSortGroup([...validated])) result.push(t);
|
|
77
63
|
return result;
|
|
78
64
|
}
|
|
@@ -16,8 +16,8 @@ import { TYPE_LABELS } from '../../../label-constants.js';
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Count open child tickets under the Epic without distinguishing by
|
|
19
|
-
* type. Under the
|
|
20
|
-
* tickets are always
|
|
19
|
+
* type. Under the 2-tier hierarchy (Epic → Story), child
|
|
20
|
+
* tickets are always Stories.
|
|
21
21
|
*/
|
|
22
22
|
async function emitOpenChildrenDiagnostic(provider, epicId) {
|
|
23
23
|
if (typeof provider.getSubTickets !== 'function') return;
|
|
@@ -27,7 +27,7 @@ async function emitOpenChildrenDiagnostic(provider, epicId) {
|
|
|
27
27
|
// closed children) yields the same open-child count without paging
|
|
28
28
|
// every issue in the repo.
|
|
29
29
|
const existing = await provider.getSubTickets(epicId);
|
|
30
|
-
const childTypes = [TYPE_LABELS.
|
|
30
|
+
const childTypes = [TYPE_LABELS.STORY];
|
|
31
31
|
const created = (existing || []).filter(
|
|
32
32
|
(t) =>
|
|
33
33
|
(t.labels || []).some((l) => childTypes.includes(l)) &&
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* epic-plan-lease-guard.js — `/
|
|
2
|
+
* epic-plan-lease-guard.js — `/plan` workflow guards (Story #3481,
|
|
3
3
|
* Epic #3457).
|
|
4
4
|
*
|
|
5
5
|
* Wires the assignee-as-lease primitive (`ticket-lease.js`, Story #3480) and a
|
|
6
6
|
* decompose-idempotency guard into the split planning flow so two concurrent
|
|
7
|
-
* `/
|
|
7
|
+
* `/plan` runs cannot both drive the same Epic, and so a re-run does not
|
|
8
8
|
* silently duplicate the Feature/Story tree:
|
|
9
9
|
*
|
|
10
10
|
* - `acquireEpicPlanLease` — claim the Epic before Phase 7 (spec). Refuses
|
|
11
11
|
* (throws, exit non-zero) when a live foreign
|
|
12
12
|
* claim already holds the Epic, naming the
|
|
13
13
|
* current owner. **Claim-time liveness
|
|
14
|
-
* (Story #4019):** `/
|
|
14
|
+
* (Story #4019):** `/plan` emits no
|
|
15
15
|
* `story.heartbeat`, so the lease records its
|
|
16
16
|
* own claim-time in a `plan-lease` structured
|
|
17
17
|
* comment on the Epic at acquire time. A
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* Best-effort and self-scoped: a no-op once the
|
|
25
25
|
* Epic was reassigned elsewhere.
|
|
26
26
|
* - `assertNoOpenPlanChildren` — refuse Phase 8 persist when the Epic already
|
|
27
|
-
* has open
|
|
27
|
+
* has open Story children, unless the
|
|
28
28
|
* operator passed `--force` (a deliberate
|
|
29
29
|
* re-decompose that closes the old tree).
|
|
30
30
|
*
|
|
@@ -45,7 +45,7 @@ import { currentOwner, releaseLease } from './ticket-lease.js';
|
|
|
45
45
|
import { findStructuredComment, upsertStructuredComment } from './ticketing.js';
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
* Resolve the operator handle that owns this `/
|
|
48
|
+
* Resolve the operator handle that owns this `/plan` run from
|
|
49
49
|
* `github.operatorHandle`. The assignee-as-lease primitive is single-holder
|
|
50
50
|
* keyed on a non-empty string; when no operator is configured (unset, or the
|
|
51
51
|
* shipped `@[USERNAME]` placeholder, both of which `normalizeOperatorHandle`
|
|
@@ -99,7 +99,7 @@ export function buildPlanLeaseCommentBody({ epicId, owner, claimedAt }) {
|
|
|
99
99
|
`### 🔒 Plan Lease — claimed by \`${owner}\``,
|
|
100
100
|
'',
|
|
101
101
|
`This Epic is being planned by \`${owner}\` (claimed ${claimedAt}). A`,
|
|
102
|
-
'concurrent `/
|
|
102
|
+
'concurrent `/plan` run refuses while this claim is fresher than the',
|
|
103
103
|
'lease TTL, and reclaims automatically once it goes stale.',
|
|
104
104
|
'',
|
|
105
105
|
'```json',
|
|
@@ -141,7 +141,7 @@ export function parsePlanLeaseClaim(body) {
|
|
|
141
141
|
* Acquire the Epic-lease before Phase 7.
|
|
142
142
|
*
|
|
143
143
|
* **Claim-time liveness (Story #4019, superseding the audit-#3513
|
|
144
|
-
* fail-closed anchor).** `/
|
|
144
|
+
* fail-closed anchor).** `/plan` emits no `story.heartbeat`, so the
|
|
145
145
|
* old guard treated EVERY foreign assignee as live — which made the
|
|
146
146
|
* documented "`--steal` once you have confirmed the other run is dead"
|
|
147
147
|
* contract undecidable (there was no in-band liveness signal to confirm
|
|
@@ -186,7 +186,7 @@ export async function acquireEpicPlanLease({
|
|
|
186
186
|
`[epic-plan] Refusing to plan Epic #${epicId}: no operator identity is ` +
|
|
187
187
|
'configured. github.operatorHandle is unset or still the shipped ' +
|
|
188
188
|
'`@[USERNAME]` placeholder, so the Epic-lease has no owner and ' +
|
|
189
|
-
'concurrent /
|
|
189
|
+
'concurrent /plan runs cannot be serialised. Set your own handle ' +
|
|
190
190
|
'in .agentrc.local.json (e.g. { "github": { "operatorHandle": ' +
|
|
191
191
|
'"@your-login" } }) and re-run.',
|
|
192
192
|
);
|
|
@@ -242,7 +242,7 @@ export async function acquireEpicPlanLease({
|
|
|
242
242
|
: '';
|
|
243
243
|
return (
|
|
244
244
|
`[epic-plan] Epic #${epicId} is currently claimed by '${refused.owner}'. ` +
|
|
245
|
-
`Refusing to plan concurrently — another /
|
|
245
|
+
`Refusing to plan concurrently — another /plan run owns this Epic. ` +
|
|
246
246
|
`${ageNote}Wait for that run to finish (the claim auto-expires at the ` +
|
|
247
247
|
`lease TTL), or re-run with --steal to forcibly transfer the claim.`
|
|
248
248
|
);
|
|
@@ -341,10 +341,14 @@ export async function assertNoOpenPlanChildren({
|
|
|
341
341
|
const openChildren = (children ?? []).filter((t) => {
|
|
342
342
|
const labels = Array.isArray(t.labels) ? t.labels : [];
|
|
343
343
|
const isOpen = t.state === undefined || t.state === 'open';
|
|
344
|
+
// Any open typed plan ticket counts — `type::story` plus pre-v4
|
|
345
|
+
// `type::feature` leftovers. The prefix check is legacy-data
|
|
346
|
+
// detection, not compat support: the guard only refuses, it never
|
|
347
|
+
// processes the legacy tier. Context tickets (`context::*`) are
|
|
348
|
+
// untouched.
|
|
344
349
|
return (
|
|
345
350
|
isOpen &&
|
|
346
|
-
|
|
347
|
-
labels.includes(TYPE_LABELS.STORY))
|
|
351
|
+
labels.some((l) => typeof l === 'string' && l.startsWith('type::'))
|
|
348
352
|
);
|
|
349
353
|
});
|
|
350
354
|
|
|
@@ -357,12 +361,21 @@ export async function assertNoOpenPlanChildren({
|
|
|
357
361
|
openChildren.length > 10
|
|
358
362
|
? `\n …and ${openChildren.length - 10} more`
|
|
359
363
|
: '';
|
|
364
|
+
const legacyCount = openChildren.filter(
|
|
365
|
+
(t) => !(t.labels ?? []).includes(TYPE_LABELS.STORY),
|
|
366
|
+
).length;
|
|
367
|
+
const legacyHint =
|
|
368
|
+
legacyCount > 0
|
|
369
|
+
? `\n${legacyCount} of these are not type::story — they look like ` +
|
|
370
|
+
`legacy pre-v4 Feature tickets; migrate or close them per the ` +
|
|
371
|
+
`v1.60.0 migration notes before re-planning.`
|
|
372
|
+
: '';
|
|
360
373
|
throw new Error(
|
|
361
374
|
`[epic-plan-decompose] Epic #${epicId} already has ` +
|
|
362
|
-
`${openChildren.length} open
|
|
375
|
+
`${openChildren.length} open plan child ticket(s):\n${summary}${more}\n\n` +
|
|
363
376
|
`Persisting now would duplicate the breakdown. Re-run with --force to ` +
|
|
364
377
|
`close the existing tree and re-decompose, or close the stale children ` +
|
|
365
|
-
`by hand first
|
|
378
|
+
`by hand first.${legacyHint}`,
|
|
366
379
|
);
|
|
367
380
|
}
|
|
368
381
|
|
|
@@ -78,7 +78,7 @@ export async function overwriteContextTicket(
|
|
|
78
78
|
try {
|
|
79
79
|
await provider.postComment(ticketId, {
|
|
80
80
|
type: 'notification',
|
|
81
|
-
body: `♻️ **Regeneration Audit**: This ${artifact} body was regenerated in place by a \`/
|
|
81
|
+
body: `♻️ **Regeneration Audit**: This ${artifact} body was regenerated in place by a \`/plan --force\` re-plan. The issue number and prior discussion history are preserved.`,
|
|
82
82
|
});
|
|
83
83
|
} catch (_err) {
|
|
84
84
|
// Audit comment is best-effort — never fail the overwrite on a comment
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* phases/prompts.js — Canonical PRD / Tech Spec / Acceptance Spec system
|
|
3
|
-
* prompts for the spec phase of `/
|
|
3
|
+
* prompts for the spec phase of `/plan`.
|
|
4
4
|
*
|
|
5
5
|
* These ride along on the `--emit-context` envelope as a backstop. The
|
|
6
6
|
* `epic-plan-spec-author` Skill
|
|
@@ -112,7 +112,7 @@ export async function runSpecPhase(
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
// Workflow-guards (Story #3481): acquire the Epic-lease before any Phase 7
|
|
115
|
-
// mutation so two concurrent /
|
|
115
|
+
// mutation so two concurrent /plan runs cannot both drive this Epic. The
|
|
116
116
|
// guard fails closed (audit #3513) — any foreign assignee refuses here and
|
|
117
117
|
// the CLI exits non-zero naming the owner, unless `--steal` transfers it.
|
|
118
118
|
await acquireEpicPlanLease({ provider, epicId, config, steal });
|
|
@@ -158,7 +158,7 @@ export async function runSpecPhase(
|
|
|
158
158
|
// Story #1585 (Epic #1471): the baseline-snapshot fork was previously
|
|
159
159
|
// performed here at plan-time. It now runs at first-story-init time
|
|
160
160
|
// inside `lib/story-init/branch-initializer.js#bootstrapWorktree` so
|
|
161
|
-
// `/
|
|
161
|
+
// `/plan` remains git-state-free. `forkAndCommitEpicSnapshot` and
|
|
162
162
|
// `forkMainToEpic` remain exported for that caller.
|
|
163
163
|
|
|
164
164
|
const reviewRouting = resolveReviewRouting({ planningRisk, forceReview });
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* the lifecycle phase is already authoritative on the Epic's `agent::*` labels,
|
|
31
31
|
* so the duplicate `phase` telemetry (and its `setPhase` round-trips) was
|
|
32
32
|
* deleted. The fields that survive — `spec`, `decompose`, `planningRisk`,
|
|
33
|
-
* `reviewRouting`, `manifestCommentId` — are the ones `/
|
|
33
|
+
* `reviewRouting`, `manifestCommentId` — are the ones `/plan --resume`
|
|
34
34
|
* reads to skip already-completed work.
|
|
35
35
|
*/
|
|
36
36
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* epic-run-state-store — stateless functions for reading and writing the
|
|
3
|
-
* `epic-run-state` structured comment used by `/
|
|
3
|
+
* `epic-run-state` structured comment used by `/deliver`.
|
|
4
4
|
*
|
|
5
5
|
* This module is the function-based replacement for the legacy
|
|
6
6
|
* `Checkpointer` class that previously lived at
|
|
@@ -133,7 +133,7 @@ export async function initialize({
|
|
|
133
133
|
* Reconcile the resume pointer (`currentWave` + `waves[]` history) against
|
|
134
134
|
* a freshly-recomputed wave plan.
|
|
135
135
|
*
|
|
136
|
-
* Story #3358 — when `/
|
|
136
|
+
* Story #3358 — when `/deliver` is resumed on a partially-complete
|
|
137
137
|
* Epic, `epic-deliver-prepare.js` recomputes the wave DAG over only the
|
|
138
138
|
* **not-done** Stories (`build-wave-dag.js#discoverOpenStories` drops the
|
|
139
139
|
* closed/merged Stories). The recomputed plan is therefore *shorter* and
|
|
@@ -270,7 +270,7 @@ export async function appendIntervention({ provider, epicId, entry } = {}) {
|
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
/**
|
|
273
|
-
* Advance the checkpoint's `phase` field to the next `/
|
|
273
|
+
* Advance the checkpoint's `phase` field to the next `/deliver`
|
|
274
274
|
* phase. Reads the current state first so the caller does not need to
|
|
275
275
|
* keep an in-memory copy. Other state fields are preserved verbatim.
|
|
276
276
|
*
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cross-Story conflict-finding gate for `epic-deliver-prepare.js`.
|
|
3
3
|
*
|
|
4
|
-
* Story #2297 — when the bounded `/
|
|
4
|
+
* Story #2297 — when the bounded `/plan` flow emitted concurrency
|
|
5
5
|
* findings (Story #2296's validator pass), the operator may have shipped
|
|
6
|
-
* them through to `/
|
|
7
|
-
* `depends_on` gaps. This gate runs at Phase 1 of `/
|
|
6
|
+
* them through to `/deliver` without resolving the underlying
|
|
7
|
+
* `depends_on` gaps. This gate runs at Phase 1 of `/deliver` and
|
|
8
8
|
* refuses to flip the Epic to `agent::executing` when the upcoming
|
|
9
9
|
* waves still contain unresolved conflicts — surfacing the exact
|
|
10
10
|
* remediation commands the operator should run before retrying.
|
|
@@ -138,7 +138,7 @@ export function renderGateErrorMessage(findings, ownerRepo) {
|
|
|
138
138
|
lines.push('');
|
|
139
139
|
}
|
|
140
140
|
lines.push(
|
|
141
|
-
'Resolve the listed conflicts and re-run `/
|
|
141
|
+
'Resolve the listed conflicts and re-run `/deliver`, or pass `--ignore-concurrency-hazards` to bypass this gate.',
|
|
142
142
|
);
|
|
143
143
|
return lines.join('\n');
|
|
144
144
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* deliver-phases.js — phase enum and ordering utility for `/
|
|
2
|
+
* deliver-phases.js — phase enum and ordering utility for `/deliver`.
|
|
3
3
|
*
|
|
4
4
|
* Story #1155 (Epic #1142, 5.40.0). Originally extracted from the
|
|
5
5
|
* legacy class-based checkpoint module so the phase enum and close-tail
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Ordered list of `/
|
|
14
|
+
* Ordered list of `/deliver` phases. The checkpoint's `phase` field
|
|
15
15
|
* stores the **next phase to run**, so a mid-flight crash during
|
|
16
16
|
* `code-review` resumes by reading `phase === 'code-review'` and re-
|
|
17
17
|
* entering Phase D from the start.
|
|
@@ -44,7 +44,7 @@ export function assertValidDeliverPhase(nextPhase) {
|
|
|
44
44
|
if (nextPhase === 'done') return;
|
|
45
45
|
if (phaseIndex(nextPhase) >= 0) return;
|
|
46
46
|
throw new Error(
|
|
47
|
-
`Invalid /
|
|
47
|
+
`Invalid /deliver phase ${JSON.stringify(nextPhase)}. ` +
|
|
48
48
|
`Expected one of ${DELIVER_PHASES.join(', ')} or 'done'.`,
|
|
49
49
|
);
|
|
50
50
|
}
|
|
@@ -2,13 +2,9 @@
|
|
|
2
2
|
* Build the wave DAG from the Epic's open child Stories.
|
|
3
3
|
*
|
|
4
4
|
* `getSubTickets` returns the **direct** children of a parent ticket via
|
|
5
|
-
* native sub-issues + checklist links + body reverse-lookup
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* **grandchildren** of the Epic. We therefore walk one level deeper from
|
|
9
|
-
* each `type::feature` direct child to collect the real Story set, and
|
|
10
|
-
* union with any direct-child Stories (some Epics still carry Stories
|
|
11
|
-
* directly while migrating).
|
|
5
|
+
* native sub-issues + checklist links + body reverse-lookup. The
|
|
6
|
+
* canonical 2-tier hierarchy is Epic → Story, so the Epic's direct
|
|
7
|
+
* `type::story` children are the complete Story set.
|
|
12
8
|
*
|
|
13
9
|
* We additionally filter out closed Stories — `getSubTickets`'s reverse-
|
|
14
10
|
* reference search can surface closed-as-obsolete tickets whose body
|
|
@@ -25,8 +21,8 @@ import { buildStoryAdjacency } from '../../../story-adjacency.js';
|
|
|
25
21
|
import { WaveScheduler } from '../wave-scheduler.js';
|
|
26
22
|
|
|
27
23
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
24
|
+
* Collect the Epic's direct `type::story` children and return the open
|
|
25
|
+
* ones, deduped by id.
|
|
30
26
|
*
|
|
31
27
|
* Exported so `snapshot.js#discoverStoryIds` and `epic-deliver-preflight`
|
|
32
28
|
* can share the same enumeration contract — the snapshot.end payload,
|
|
@@ -34,20 +30,9 @@ import { WaveScheduler } from '../wave-scheduler.js';
|
|
|
34
30
|
*/
|
|
35
31
|
export async function discoverOpenStories({ epicId, provider }) {
|
|
36
32
|
const descendants = (await provider.getSubTickets(epicId)) ?? [];
|
|
37
|
-
const features = descendants.filter((t) =>
|
|
38
|
-
(t.labels ?? []).includes(TYPE_LABELS.FEATURE),
|
|
39
|
-
);
|
|
40
|
-
const grandchildren = (
|
|
41
|
-
await Promise.all(
|
|
42
|
-
features.map(async (f) => {
|
|
43
|
-
const id = f.id ?? f.number;
|
|
44
|
-
return id == null ? [] : ((await provider.getSubTickets(id)) ?? []);
|
|
45
|
-
}),
|
|
46
|
-
)
|
|
47
|
-
).flat();
|
|
48
33
|
const seen = new Set();
|
|
49
34
|
const stories = [];
|
|
50
|
-
for (const t of
|
|
35
|
+
for (const t of descendants) {
|
|
51
36
|
const labels = t.labels ?? [];
|
|
52
37
|
if (!labels.includes(TYPE_LABELS.STORY)) continue;
|
|
53
38
|
const rawState = t.state ?? 'open';
|