mandrel 1.59.0 → 1.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +86 -44
- package/.agents/docs/SDLC.md +135 -141
- package/.agents/docs/configuration.md +77 -20
- package/.agents/docs/quality-gates.md +796 -0
- package/.agents/docs/workflows.md +6 -9
- 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/runtime-deps.json +2 -2
- 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 +2 -2
- package/.agents/scripts/acceptance-eval.js +1 -1
- package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
- package/.agents/scripts/agents-bootstrap-github.js +23 -119
- 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 +47 -1
- package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
- package/.agents/scripts/lib/bootstrap/gh-preflight.js +7 -9
- package/.agents/scripts/lib/bootstrap/manifest.js +21 -1
- package/.agents/scripts/lib/bootstrap/merge-methods.js +31 -16
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +32 -11
- 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/sync-agentrc.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/detect-package-manager.js +72 -0
- 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/errors/index.js +4 -4
- 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 +5 -10
- package/.agents/scripts/lib/onboard/detect-stack.js +10 -10
- package/.agents/scripts/lib/onboard/init-tail.js +218 -0
- package/.agents/scripts/lib/onboard/scaffold-docs.js +18 -3
- 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/runtime-deps/preflight.js +6 -6
- 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/lib/worktree/node-modules-strategy.js +5 -2
- 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 +16 -31
- 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/agents-sync-config.md +3 -2
- 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/plan.md +131 -0
- package/.agents/workflows/qa-explore.md +1 -1
- package/.agents/workflows/qa-run-harness.md +1 -1
- package/README.md +19 -39
- package/bin/mandrel.js +235 -16
- package/docs/CHANGELOG.md +1173 -0
- package/lib/cli/doctor.js +45 -3
- package/lib/cli/init.js +97 -36
- package/lib/cli/registry.js +41 -145
- package/lib/cli/sync.js +122 -23
- package/lib/cli/uninstall.js +42 -7
- package/lib/cli/update.js +524 -210
- package/lib/cli/version-helpers.js +59 -0
- package/package.json +7 -6
- package/.agents/scripts/lib/orchestration/reconciler.js +0 -137
- package/.agents/workflows/onboard.md +0 -208
- package/lib/cli/__tests__/migrate.test.js +0 -268
- package/lib/cli/__tests__/sync-local-zone.test.js +0 -247
- package/lib/cli/__tests__/sync.test.js +0 -372
- package/lib/cli/__tests__/update-major.test.js +0 -217
- package/lib/cli/__tests__/update.test.js +0 -696
- package/lib/cli/__tests__/version-check.test.js +0 -398
- package/lib/migrations/__tests__/index.test.js +0 -216
|
@@ -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';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Epic snapshot phase — fetch Epic ticket and enforce the acceptance-spec
|
|
3
3
|
* start gate.
|
|
4
4
|
*
|
|
5
|
-
* Auto-close is now the default for `/
|
|
5
|
+
* Auto-close is now the default for `/deliver` (the human PR-merge is
|
|
6
6
|
* the gate); no per-Epic label snapshot is required.
|
|
7
7
|
*
|
|
8
8
|
* Acceptance-spec start gate (relaxed): an Epic may be delivered when
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* `context::acceptance-spec` ticket is linked to the Epic. The ticket's
|
|
12
12
|
* GitHub state (open / closed) is **not** checked — presence is
|
|
13
13
|
* sufficient, matching the PRD and Tech Spec contract. The reviewer's
|
|
14
|
-
* OK during /
|
|
14
|
+
* OK during /plan Phase 7 is the approval signal, not a manual
|
|
15
15
|
* ticket-close action. This still refuses to launch Epics that skipped
|
|
16
|
-
* the /
|
|
16
|
+
* the /plan Phase 7 acceptance-spec authoring step (or didn't
|
|
17
17
|
* waive), surfacing the gap at delivery time rather than letting Story
|
|
18
18
|
* dispatch race ahead without a spec at all.
|
|
19
19
|
*/
|
|
@@ -66,8 +66,8 @@ export async function runSnapshotPhase(ctx, collaborators, state) {
|
|
|
66
66
|
/**
|
|
67
67
|
* Enumerate the Story IDs owned by an Epic. Delegates to
|
|
68
68
|
* `discoverOpenStories` so the snapshot.end payload and the wave DAG
|
|
69
|
-
* input set never disagree — both
|
|
70
|
-
* exclude closed reverse-referenced tickets.
|
|
69
|
+
* input set never disagree — both enumerate the Epic's direct Story
|
|
70
|
+
* children and exclude closed reverse-referenced tickets.
|
|
71
71
|
*
|
|
72
72
|
* Returns a sorted array of positive integers (sort order makes the
|
|
73
73
|
* ledger record deterministic across runs and platform iteration
|
|
@@ -82,7 +82,7 @@ async function discoverStoryIds({ epicId, provider }) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* Refuse to launch /
|
|
85
|
+
* Refuse to launch /deliver when the acceptance-spec precondition has
|
|
86
86
|
* not been satisfied. Throws a clear `Error` (per
|
|
87
87
|
* orchestration-error-handling rule) so the `runAsCli` boundary maps it to
|
|
88
88
|
* `process.exit(1)` with the operator-visible message intact.
|
|
@@ -104,7 +104,7 @@ function assertAcceptanceSpecGate({ epic, epicId }) {
|
|
|
104
104
|
if (!acceptanceSpecId) {
|
|
105
105
|
throw new Error(
|
|
106
106
|
`[epic-deliver] Epic #${epicId} cannot launch: no context::acceptance-spec is linked and the acceptance::n-a waiver label is absent. ` +
|
|
107
|
-
'Run /
|
|
107
|
+
'Run /plan Phase 7 to author an acceptance-spec, or apply the acceptance::n-a label to the Epic to opt out.',
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
@@ -240,7 +240,7 @@ export async function renderProgressBody({
|
|
|
240
240
|
/**
|
|
241
241
|
* Render and upsert the rolled-up `epic-run-progress` comment on the Epic.
|
|
242
242
|
*
|
|
243
|
-
* Called by `/
|
|
243
|
+
* Called by `/deliver` Step 2b (`epic-execute-record-wave.js`) after
|
|
244
244
|
* each wave completes. The caller folds `state.waves[]` from the
|
|
245
245
|
* `epic-run-state` checkpoint into the per-wave rows and persists the
|
|
246
246
|
* unified rollup as a fenced-JSON payload on the Epic ticket via
|
|
@@ -29,7 +29,7 @@ export const PHASE_TIMINGS_TYPE = 'phase-timings';
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Structured-comment kind for the per-Story run-progress snapshot that
|
|
32
|
-
* `/
|
|
32
|
+
* `/deliver` upserts on every Task transition. Read by
|
|
33
33
|
* ProgressReporter so the Epic-level table reflects sub-agent state in
|
|
34
34
|
* near-real time instead of label-derived classifications.
|
|
35
35
|
*/
|
|
@@ -91,7 +91,7 @@ export function phaseToState(phase) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
* Parse a `story-run-progress` structured comment posted by `/
|
|
94
|
+
* Parse a `story-run-progress` structured comment posted by `/deliver`.
|
|
95
95
|
* Returns `null` for any malformed body — the caller falls back to the
|
|
96
96
|
* ticket-label state derivation in that case.
|
|
97
97
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* progress-reporter/transport.js — outbound I/O for the
|
|
3
|
-
* `/
|
|
3
|
+
* `/deliver` progress narrative.
|
|
4
4
|
*
|
|
5
5
|
* Extracted from the parent `progress-reporter.js` so the
|
|
6
6
|
* GitHub-comment posting surface (in `composition.js`) and the
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* The webhook events emitted from this module are:
|
|
18
18
|
*
|
|
19
|
-
* - `epic-started` — fired once at /
|
|
19
|
+
* - `epic-started` — fired once at /deliver kickoff
|
|
20
20
|
* - `epic-progress` — fired at wave boundaries / blocker transitions
|
|
21
21
|
* - `epic-blocked` — wave aggregated to blocked/failed outside halt path
|
|
22
22
|
* - `epic-unblocked` — operator flipped back to executing
|
|
@@ -118,7 +118,7 @@ export async function emitEpicProgress({
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
|
-
* Fire a curated `epic-started` webhook event at /
|
|
121
|
+
* Fire a curated `epic-started` webhook event at /deliver kickoff.
|
|
122
122
|
* The Slack consumer anchors the rest of the epic narrative to this fire.
|
|
123
123
|
* Failures are swallowed.
|
|
124
124
|
*/
|
|
@@ -157,7 +157,7 @@ export async function emitEpicStarted({
|
|
|
157
157
|
/**
|
|
158
158
|
* Fire a curated `epic-blocked` webhook event when a wave aggregates to
|
|
159
159
|
* `blocked` or `failed` outside the `BlockerHandler.halt` code path (the
|
|
160
|
-
* /
|
|
160
|
+
* /deliver host-LLM loop has no handler instance — it calls this
|
|
161
161
|
* helper directly from `epic-execute-record-wave.js`). The payload shape
|
|
162
162
|
* matches the inline emit in `BlockerHandler.halt` so downstream consumers
|
|
163
163
|
* see one canonical envelope regardless of which entry point fired.
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* After Story #908, in-session Agent-tool fan-out replaces the subprocess
|
|
6
6
|
* spawn pipeline. The launcher's primary responsibility is `planWave(stories)`:
|
|
7
7
|
* given a wave's Story tickets it returns a stable list of
|
|
8
|
-
* `{ storyId, worktree }` entries. The `/
|
|
8
|
+
* `{ storyId, worktree }` entries. The `/deliver` skill consumes that
|
|
9
9
|
* list (one wave at a time) to format one assistant turn containing N
|
|
10
10
|
* parallel `Agent` tool calls (subagent_type `general-purpose`), each of
|
|
11
|
-
* which drives `/
|
|
11
|
+
* which drives `/deliver <storyId>` for one Story.
|
|
12
12
|
*
|
|
13
13
|
* `launchWave(stories)` is a convenience for callers that already hold a
|
|
14
14
|
* concrete dispatch adapter (tests, future programmatic harnesses). It calls
|
|
@@ -46,7 +46,7 @@ export class StoryLauncher {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Produce the dispatch plan for a wave. Pure: no side effects, no IO. The
|
|
49
|
-
* caller (the `/
|
|
49
|
+
* caller (the `/deliver` skill's wave loop, or `launchWave` below)
|
|
50
50
|
* decides what to do with the plan.
|
|
51
51
|
*
|
|
52
52
|
* @param {Array<number|{id?:number,storyId?:number,number?:number}>} stories
|
|
@@ -78,7 +78,7 @@ export class StoryLauncher {
|
|
|
78
78
|
async launchWave(stories, signal) {
|
|
79
79
|
if (typeof this.dispatch !== 'function') {
|
|
80
80
|
throw new TypeError(
|
|
81
|
-
'StoryLauncher.launchWave requires a dispatch adapter (in-session Agent-tool fan-out is the responsibility of the /
|
|
81
|
+
'StoryLauncher.launchWave requires a dispatch adapter (in-session Agent-tool fan-out is the responsibility of the /deliver skill).',
|
|
82
82
|
);
|
|
83
83
|
}
|
|
84
84
|
const plan = this.planWave(stories);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* removed.
|
|
13
13
|
*
|
|
14
14
|
* The renderer survives because its `{ body, payload }` output still feeds
|
|
15
|
-
* two non-comment contracts: the `renderedBody` markdown the `/
|
|
15
|
+
* two non-comment contracts: the `renderedBody` markdown the `/deliver`
|
|
16
16
|
* and `single-story-deliver` CLIs (`story-phase.js`, the inline `story-init.js` prepare step)
|
|
17
17
|
* relay to chat so the operator sees the phase table inline, and the snapshot
|
|
18
18
|
* payload returned in those CLIs' JSON envelopes. `upsertStoryRunProgress`
|
|
@@ -71,10 +71,10 @@ const PHASE_EMOJI = {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Canonical
|
|
74
|
+
* Canonical 2-tier Story-phase order. The Story-phase snapshot replaces the
|
|
75
75
|
* 4-tier per-Task list when the Story carries inline acceptance (no child
|
|
76
76
|
* Tasks). Each entry tracks `status` + `startedAt` / `endedAt` so the parent
|
|
77
|
-
* `/
|
|
77
|
+
* `/deliver` aggregator can render a coarse progress bar without
|
|
78
78
|
* walking Task tickets.
|
|
79
79
|
*/
|
|
80
80
|
export const STORY_PHASE_ORDER = ['init', 'implement', 'validate', 'close'];
|
|
@@ -89,7 +89,7 @@ const STORY_PHASE_STATUS_EMOJI = {
|
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Build the canonical default `phases[]` array for a freshly-initialized
|
|
92
|
-
*
|
|
92
|
+
* 2-tier Story snapshot. All entries are `pending`; timestamps are null.
|
|
93
93
|
* Exported so call sites (the story-init prepare step, story-phase) and
|
|
94
94
|
* tests can build the same shape without re-implementing it.
|
|
95
95
|
*
|
|
@@ -175,7 +175,7 @@ function normalizeTask(task) {
|
|
|
175
175
|
* Exported so tests can pin the rendered shape without going through the
|
|
176
176
|
* upsert path.
|
|
177
177
|
*
|
|
178
|
-
* Two shapes are supported, selected by whether `input.phases` (
|
|
178
|
+
* Two shapes are supported, selected by whether `input.phases` (2-tier
|
|
179
179
|
* Story-phase snapshot) or `input.tasks` (legacy 4-tier per-Task list) is
|
|
180
180
|
* provided. Callers MUST pass exactly one of the two — passing both is
|
|
181
181
|
* rejected as a contract violation so a mistake at the call site fails
|
|
@@ -216,7 +216,7 @@ export function renderStoryRunProgressBody(input) {
|
|
|
216
216
|
const hasTasks = Array.isArray(input.tasks);
|
|
217
217
|
if (hasPhases && hasTasks) {
|
|
218
218
|
throw new TypeError(
|
|
219
|
-
'renderStoryRunProgressBody: pass either `phases` (
|
|
219
|
+
'renderStoryRunProgressBody: pass either `phases` (2-tier) or `tasks` ' +
|
|
220
220
|
'(4-tier), not both — the snapshot shape is mutually exclusive.',
|
|
221
221
|
);
|
|
222
222
|
}
|
|
@@ -296,7 +296,7 @@ function renderTasksBody({
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
/**
|
|
299
|
-
* Render the
|
|
299
|
+
* Render the 2-tier Story-phase body. Pure helper for
|
|
300
300
|
* `renderStoryRunProgressBody`. Emits a `phases[]` payload whose entries
|
|
301
301
|
* carry `{ name, status, startedAt, endedAt }` for init/implement/validate/close.
|
|
302
302
|
*/
|
|
@@ -351,7 +351,7 @@ function renderPhasesBody({ storyId, branch, phase, phases: raw, updatedAt }) {
|
|
|
351
351
|
* renders only and, when `notify` is supplied, mirrors a low-severity webhook
|
|
352
352
|
* event for operators who wire one up.
|
|
353
353
|
*
|
|
354
|
-
* Two shapes are supported, selected by whether `args.phases` (
|
|
354
|
+
* Two shapes are supported, selected by whether `args.phases` (2-tier
|
|
355
355
|
* Story-phase snapshot) or `args.tasks` (legacy 4-tier per-Task list) is
|
|
356
356
|
* provided. The webhook mirror's `done/total` count is computed from whichever
|
|
357
357
|
* shape is active.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* sub-agent-return.js — parse and reconcile per-Story sub-agent return text.
|
|
3
3
|
*
|
|
4
|
-
* `/
|
|
4
|
+
* `/deliver` Step 2 dispatches one `Agent` tool call per Story per
|
|
5
5
|
* wave. Each sub-agent owes its parent the JSON return contract documented
|
|
6
|
-
* in `.agents/workflows/epic
|
|
6
|
+
* in `.agents/workflows/helpers/deliver-epic.md`:
|
|
7
7
|
*
|
|
8
8
|
* {
|
|
9
9
|
* "storyId": <number>,
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* directly to GitHub-state reconciliation plus a friction record — the
|
|
28
28
|
* stronger backstop that already caught everything the heuristics caught.
|
|
29
29
|
*
|
|
30
|
-
* This module provides the two helpers `/
|
|
30
|
+
* This module provides the two helpers `/deliver`'s wave dispatcher
|
|
31
31
|
* now uses:
|
|
32
32
|
*
|
|
33
33
|
* - `parseStoryAgentReturn(raw)` — accept an already-parsed object or a
|
|
@@ -257,7 +257,7 @@ export function renderMalformedReturnsFriction({ epicId, wave, failures }) {
|
|
|
257
257
|
'',
|
|
258
258
|
`**Reason:** \`malformed-subagent-return\``,
|
|
259
259
|
'',
|
|
260
|
-
`${failures.length} sub-agent return(s) did not match the /
|
|
260
|
+
`${failures.length} sub-agent return(s) did not match the /deliver return contract.`,
|
|
261
261
|
'Each Story below was reconciled from GitHub (labels + `story-run-progress`)',
|
|
262
262
|
'and its wave-row downgraded to `failed` unless the live ticket already carried',
|
|
263
263
|
'`agent::done`.',
|
|
@@ -730,21 +730,13 @@ function collectStructuralEdges(spec) {
|
|
|
730
730
|
const out = {};
|
|
731
731
|
if (!spec || typeof spec !== 'object') return out;
|
|
732
732
|
const epicSlug = 'epic';
|
|
733
|
-
for (const
|
|
734
|
-
if (!
|
|
735
|
-
out[
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
parentSlug: feature.slug,
|
|
741
|
-
dependsOn: [...(story.dependsOn ?? [])].sort(),
|
|
742
|
-
};
|
|
743
|
-
for (const task of story.tasks ?? []) {
|
|
744
|
-
if (!task?.slug) continue;
|
|
745
|
-
out[task.slug] = { entity: 'task', parentSlug: story.slug };
|
|
746
|
-
}
|
|
747
|
-
}
|
|
733
|
+
for (const story of spec.stories ?? []) {
|
|
734
|
+
if (!story?.slug) continue;
|
|
735
|
+
out[story.slug] = {
|
|
736
|
+
entity: 'story',
|
|
737
|
+
parentSlug: epicSlug,
|
|
738
|
+
dependsOn: [...(story.dependsOn ?? [])].sort(),
|
|
739
|
+
};
|
|
748
740
|
}
|
|
749
741
|
return out;
|
|
750
742
|
}
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
*
|
|
19
19
|
* @typedef {object} SpecInput
|
|
20
20
|
* The parsed YAML returned by `lib/spec/loader.js#loadSpec`. Shape is
|
|
21
|
-
* `{ epic: {...},
|
|
21
|
+
* `{ epic: {...}, stories: [...], gates?: {...} }`
|
|
22
22
|
* per `.agents/schemas/epic-spec.schema.json`.
|
|
23
23
|
*
|
|
24
24
|
* @typedef {object} StateMappingEntry
|
|
25
25
|
* @property {number} issueNumber GH issue number this slug maps to.
|
|
26
|
-
* @property {string} entity 'epic'|'
|
|
26
|
+
* @property {string} entity 'epic'|'story'.
|
|
27
27
|
* @property {string} [contentHash] Content hash captured at last
|
|
28
28
|
* reconcile; absence forces an
|
|
29
29
|
* update when ghState carries
|
|
@@ -115,7 +115,7 @@ function labelsEqual(a, b) {
|
|
|
115
115
|
* a naive replace-style label diff would propose removing operator-
|
|
116
116
|
* managed metadata that lives in these namespaces.
|
|
117
117
|
*
|
|
118
|
-
* Why: Story #2056 / Epic #1994 — `/
|
|
118
|
+
* Why: Story #2056 / Epic #1994 — `/plan` was silently stripping
|
|
119
119
|
* `type::epic` and `risk::*` from the parent Epic on every decompose,
|
|
120
120
|
* which then broke `dispatcher.js` (`type "unknown"`). Defence-in-depth
|
|
121
121
|
* lives here in the diff engine: even if a future spec author drops
|
|
@@ -133,7 +133,7 @@ const PROTECTED_EPIC_LABEL_NAMESPACES = Object.freeze([
|
|
|
133
133
|
'risk::',
|
|
134
134
|
// Story #3050 — `acceptance::*` is set by Phase 7 spec-persist when
|
|
135
135
|
// `planningRisk.acceptanceDisposition='not-applicable'` (or another
|
|
136
|
-
// disposition) and gates downstream `/
|
|
136
|
+
// disposition) and gates downstream `/deliver` start/finalize
|
|
137
137
|
// behavior. Before this namespace was protected, Phase 8 decompose
|
|
138
138
|
// diffed the Epic's labels against a spec entry that doesn't carry
|
|
139
139
|
// `acceptance::*`, silently emitting an Update that stripped the
|
|
@@ -190,7 +190,7 @@ function normaliseBody(value) {
|
|
|
190
190
|
* duplicate footer blocks and trailing `blocked by` lines are removed in
|
|
191
191
|
* a single pass. Kept local to this module so the diff engine does not
|
|
192
192
|
* depend on the Task-body renderer (Story #3185 / Epic #3163: the renderer
|
|
193
|
-
* is going away with the
|
|
193
|
+
* is going away with the 2-tier producer cutover).
|
|
194
194
|
*/
|
|
195
195
|
const ORCHESTRATOR_FOOTER_RE = /\n?---[ \t]*\r?\n+parent:\s*#\d+[\s\S]*$/;
|
|
196
196
|
|
|
@@ -247,9 +247,8 @@ function renderFooter({ parentId, epicId, dependencies = [] }) {
|
|
|
247
247
|
* `parent: #N` / `Epic: #M` / `blocked by #X` and breaking the cascade.
|
|
248
248
|
*
|
|
249
249
|
* Story #3185 — the footer compose/strip logic is inlined here rather
|
|
250
|
-
* than reused from the legacy Task-body renderer module.
|
|
251
|
-
*
|
|
252
|
-
* removed, so the diff engine carries its own footer shape. The shape
|
|
250
|
+
* than reused from the legacy Task-body renderer module. That renderer
|
|
251
|
+
* was removed, so the diff engine carries its own footer shape. The shape
|
|
253
252
|
* is byte-identical to the legacy renderer's `parent: #<n>` /
|
|
254
253
|
* `Epic: #<m>` / `blocked by #<x>` output so cascade-readers continue
|
|
255
254
|
* to parse it unchanged.
|
|
@@ -326,7 +325,7 @@ function fieldChanges(specEntity, obs, mapping, ctx = {}) {
|
|
|
326
325
|
// feature/story/task body fields): "When omitted, the GH issue body
|
|
327
326
|
// is left untouched". Pre-Story-#2283 the engine treated `undefined`
|
|
328
327
|
// as `""`, which emitted a destructive `body: <existing> → ""` Update
|
|
329
|
-
// on every `/
|
|
328
|
+
// on every `/plan` Phase 8 because the decomposer's renderer
|
|
330
329
|
// projects the Epic spec entry from `{ id, title }` only. Skip the
|
|
331
330
|
// body diff entirely when the spec did not carry a body string. An
|
|
332
331
|
// explicit `body: ""` in the spec still produces a clear-op when the
|
|
@@ -413,9 +412,8 @@ function sortBySlug(ops) {
|
|
|
413
412
|
|
|
414
413
|
/**
|
|
415
414
|
* Walk the spec and yield one structural-entity record per visited
|
|
416
|
-
* node. The
|
|
417
|
-
*
|
|
418
|
-
* 4 so an explicit work-queue would just obscure the shape.
|
|
415
|
+
* node. The spec is 2-tier (epic → stories), so the walk is a single
|
|
416
|
+
* flat pass over `spec.stories` after the Epic record.
|
|
419
417
|
*
|
|
420
418
|
* @param {SpecInput} spec
|
|
421
419
|
* @returns {Array<{
|
|
@@ -446,37 +444,35 @@ function flattenSpec(spec) {
|
|
|
446
444
|
}
|
|
447
445
|
|
|
448
446
|
const epicAnchor = spec.epic ? epicSlug(spec.epic) : null;
|
|
449
|
-
|
|
447
|
+
// Duplicate-slug guard. The schema cannot express slug uniqueness across
|
|
448
|
+
// a hand-edited spec, and two same-slug Creates would orphan one GH
|
|
449
|
+
// issue (the second create overwrites the first's mapping entry).
|
|
450
|
+
// flattenSpec is the chokepoint both the loader path and the diff path
|
|
451
|
+
// flow through, so the check lives here.
|
|
452
|
+
const seenSlugs = new Set();
|
|
453
|
+
const duplicateSlugs = new Set();
|
|
454
|
+
for (const story of spec.stories ?? []) {
|
|
455
|
+
if (seenSlugs.has(story.slug)) duplicateSlugs.add(story.slug);
|
|
456
|
+
seenSlugs.add(story.slug);
|
|
457
|
+
}
|
|
458
|
+
if (duplicateSlugs.size > 0) {
|
|
459
|
+
throw new Error(
|
|
460
|
+
`spec contains duplicate story slug(s): ${[...duplicateSlugs].join(', ')}. ` +
|
|
461
|
+
`Each story slug must be unique — rename the duplicated entries in ` +
|
|
462
|
+
`the spec and re-run.`,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
for (const story of spec.stories ?? []) {
|
|
450
466
|
out.push({
|
|
451
|
-
slug:
|
|
452
|
-
entity: ENTITY_KINDS.
|
|
453
|
-
title: String(
|
|
454
|
-
body:
|
|
455
|
-
labels:
|
|
467
|
+
slug: story.slug,
|
|
468
|
+
entity: ENTITY_KINDS.STORY,
|
|
469
|
+
title: String(story.title ?? ''),
|
|
470
|
+
body: story.body,
|
|
471
|
+
labels: story.labels,
|
|
472
|
+
wave: story.wave,
|
|
456
473
|
parentSlug: epicAnchor,
|
|
474
|
+
dependsOn: story.dependsOn ?? [],
|
|
457
475
|
});
|
|
458
|
-
for (const story of feature.stories ?? []) {
|
|
459
|
-
out.push({
|
|
460
|
-
slug: story.slug,
|
|
461
|
-
entity: ENTITY_KINDS.STORY,
|
|
462
|
-
title: String(story.title ?? ''),
|
|
463
|
-
body: story.body,
|
|
464
|
-
labels: story.labels,
|
|
465
|
-
wave: story.wave,
|
|
466
|
-
parentSlug: feature.slug,
|
|
467
|
-
dependsOn: story.dependsOn ?? [],
|
|
468
|
-
});
|
|
469
|
-
for (const task of story.tasks ?? []) {
|
|
470
|
-
out.push({
|
|
471
|
-
slug: task.slug,
|
|
472
|
-
entity: ENTITY_KINDS.TASK,
|
|
473
|
-
title: String(task.title ?? ''),
|
|
474
|
-
body: task.body,
|
|
475
|
-
labels: task.labels,
|
|
476
|
-
parentSlug: story.slug,
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
476
|
}
|
|
481
477
|
return out;
|
|
482
478
|
}
|
|
@@ -509,6 +505,40 @@ function dependsOnEqual(a, b) {
|
|
|
509
505
|
return labelsEqual(a, b);
|
|
510
506
|
}
|
|
511
507
|
|
|
508
|
+
/**
|
|
509
|
+
* Entity kinds that only exist in pre-v4 (3-tier) state files. The v4
|
|
510
|
+
* hard cutover (v1.60.0) removed the Feature/Task tiers; encountering one
|
|
511
|
+
* of these in `state.mapping` means the state file predates the cutover
|
|
512
|
+
* and must be migrated by the operator — there is deliberately no legacy
|
|
513
|
+
* close-op support (hard-cutover policy, git-conventions.md).
|
|
514
|
+
*/
|
|
515
|
+
const LEGACY_ENTITY_KINDS = Object.freeze(new Set(['feature', 'task']));
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Throw a loud, actionable error when the state mapping carries legacy
|
|
519
|
+
* Feature/Task entries. Raised early in `diff()` so the operator sees a
|
|
520
|
+
* migration instruction instead of `unknown entity kind: feature` from
|
|
521
|
+
* deep inside `closeOp`.
|
|
522
|
+
*
|
|
523
|
+
* @param {Record<string, StateMappingEntry>} mapping
|
|
524
|
+
*/
|
|
525
|
+
function assertNoLegacyEntities(mapping) {
|
|
526
|
+
const legacy = Object.entries(mapping).filter(([, entry]) =>
|
|
527
|
+
LEGACY_ENTITY_KINDS.has(entry?.entity),
|
|
528
|
+
);
|
|
529
|
+
if (legacy.length === 0) return;
|
|
530
|
+
const summary = legacy
|
|
531
|
+
.map(([slug, entry]) => `${slug} (#${entry.issueNumber}, ${entry.entity})`)
|
|
532
|
+
.join(', ');
|
|
533
|
+
throw new Error(
|
|
534
|
+
`diff: the epic state file is pre-v4 — it carries legacy Feature/Task ` +
|
|
535
|
+
`mapping entries: ${summary}. The 2-tier reconciler cannot process ` +
|
|
536
|
+
`these. Close the legacy Feature issues manually, then delete (or ` +
|
|
537
|
+
`reseed) the .agents/epics/<epicId>.state.json file per the v1.60.0 ` +
|
|
538
|
+
`migration notes, and re-run.`,
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
512
542
|
/**
|
|
513
543
|
* Diff `(spec, state, ghState)` into a `Plan`. See the module header for
|
|
514
544
|
* the full contract.
|
|
@@ -523,6 +553,7 @@ export function diff({ spec, state, ghState } = {}) {
|
|
|
523
553
|
throw new TypeError('diff: state argument is required');
|
|
524
554
|
}
|
|
525
555
|
const mapping = state.mapping ?? {};
|
|
556
|
+
assertNoLegacyEntities(mapping);
|
|
526
557
|
const seenSpecSlugs = new Set();
|
|
527
558
|
|
|
528
559
|
for (const entity of flattenSpec(spec)) {
|
|
@@ -595,7 +626,7 @@ export function diff({ spec, state, ghState } = {}) {
|
|
|
595
626
|
plan.closes.push(
|
|
596
627
|
closeOp({
|
|
597
628
|
slug,
|
|
598
|
-
entity: mapped.entity ?? ENTITY_KINDS.
|
|
629
|
+
entity: mapped.entity ?? ENTITY_KINDS.STORY,
|
|
599
630
|
issueNumber: mapped.issueNumber,
|
|
600
631
|
title: mapped.title,
|
|
601
632
|
}),
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
* apply pipeline. Arrays are always present (never undefined); an empty
|
|
45
45
|
* plan has all four arrays at length 0.
|
|
46
46
|
*
|
|
47
|
-
* @typedef {'epic'|'
|
|
47
|
+
* @typedef {'epic'|'story'} EntityKind
|
|
48
48
|
* The structural entity kind. Matches schema $defs — agent-execution
|
|
49
49
|
* labels (agent::*) are owned by the wave-runner and never appear in
|
|
50
50
|
* the structural surface.
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
* @typedef {object} RelinkOp
|
|
88
88
|
* @property {'relink'} kind
|
|
89
89
|
* @property {string} slug
|
|
90
|
-
* @property {EntityKind} entity 'story' (dependsOn
|
|
90
|
+
* @property {EntityKind} entity 'story' (dependsOn / parent).
|
|
91
91
|
* @property {number} issueNumber
|
|
92
92
|
* @property {{ before: string|null, after: string|null }} [parent]
|
|
93
93
|
* Parent slug change. `null` on either side means "no parent" (epic
|
|
@@ -117,9 +117,7 @@ export const OP_KINDS = Object.freeze({
|
|
|
117
117
|
/** Entity-kind discriminator values, matching the schema $defs. */
|
|
118
118
|
export const ENTITY_KINDS = Object.freeze({
|
|
119
119
|
EPIC: 'epic',
|
|
120
|
-
FEATURE: 'feature',
|
|
121
120
|
STORY: 'story',
|
|
122
|
-
TASK: 'task',
|
|
123
121
|
});
|
|
124
122
|
|
|
125
123
|
const VALID_OP_KINDS = new Set(Object.values(OP_KINDS));
|