mandrel 1.94.0 → 2.0.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 +59 -73
- package/.agents/agents/story-worker.md +12 -13
- package/.agents/docs/SDLC.md +489 -1285
- package/.agents/docs/agentrc-reference.json +177 -67
- package/.agents/docs/configuration.md +104 -138
- package/.agents/docs/execution-reference.md +17 -20
- package/.agents/docs/quality-gates.md +13 -19
- package/.agents/docs/workflows.md +3 -3
- package/.agents/instructions.md +70 -81
- package/.agents/rules/ci-remediation.md +8 -12
- package/.agents/rules/git-conventions-reference.md +6 -7
- package/.agents/rules/git-conventions.md +16 -22
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +54 -214
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
- package/.agents/schemas/risk-verdict.schema.json +0 -13
- package/.agents/scripts/analyze-execution.js +1 -1
- package/.agents/scripts/audit-to-stories.js +7 -7
- package/.agents/scripts/boot-sweep.js +1 -1
- package/.agents/scripts/check-lifecycle-lint.js +6 -9
- package/.agents/scripts/check-prepush-recovery.js +1 -1
- package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
- package/.agents/scripts/lib/Logger.js +6 -10
- package/.agents/scripts/lib/audit-suite/runner.js +2 -2
- package/.agents/scripts/lib/audit-suite/selector.js +5 -5
- package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
- package/.agents/scripts/lib/baselines/kernel.js +206 -18
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -4
- package/.agents/scripts/lib/baselines/reader.js +1 -6
- package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
- package/.agents/scripts/lib/checks/core-bare-clean.js +2 -2
- package/.agents/scripts/lib/checks/index.js +2 -1
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +23 -21
- package/.agents/scripts/lib/cli/standard-args.js +13 -22
- package/.agents/scripts/lib/cli-args.js +16 -7
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/config/ci.js +6 -31
- package/.agents/scripts/lib/config/delivery-routing.js +45 -29
- package/.agents/scripts/lib/config/explain.js +55 -36
- package/.agents/scripts/lib/config/limits.js +17 -58
- package/.agents/scripts/lib/config/paths.js +0 -2
- package/.agents/scripts/lib/config/quality.js +1 -1
- package/.agents/scripts/lib/config/runners.js +17 -50
- package/.agents/scripts/lib/config/temp-paths.js +19 -14
- package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
- package/.agents/scripts/lib/config-resolver.js +2 -7
- package/.agents/scripts/lib/config-settings-schema-delivery.js +24 -148
- package/.agents/scripts/lib/config-settings-schema-quality.js +8 -14
- package/.agents/scripts/lib/config-settings-schema.js +52 -38
- package/.agents/scripts/lib/dependency-parser.js +3 -2
- package/.agents/scripts/lib/doc-tiers.js +2 -2
- package/.agents/scripts/lib/duplicate-search.js +211 -41
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +5 -5
- package/.agents/scripts/lib/framework-version.js +2 -3
- package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
- package/.agents/scripts/lib/git-branch-lifecycle.js +17 -22
- package/.agents/scripts/lib/git-utils.js +32 -6
- package/.agents/scripts/lib/github/framework-repo.js +6 -0
- package/.agents/scripts/lib/label-constants.js +10 -23
- package/.agents/scripts/lib/label-taxonomy.js +9 -43
- package/.agents/scripts/lib/observability/active-story-env.js +1 -1
- package/.agents/scripts/lib/observability/hook-heartbeat.js +20 -52
- package/.agents/scripts/lib/observability/source-classifier.js +3 -3
- package/.agents/scripts/lib/onboard/init-tail.js +1 -3
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +8 -5
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +83 -20
- package/.agents/scripts/lib/orchestration/code-review.js +20 -268
- package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +1 -1
- package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
- package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
- package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +8 -8
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +2 -2
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +17 -43
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
- package/.agents/scripts/lib/orchestration/merge-block-class.js +1 -1
- package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
- package/.agents/scripts/lib/orchestration/plan-context.js +248 -266
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +61 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +97 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +223 -854
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +361 -0
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +35 -108
- package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +13 -13
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/risk-verdict.js +3 -4
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +2 -2
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +8 -20
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -2
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
- package/.agents/scripts/lib/orchestration/preflight-cache.js +5 -5
- package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +155 -0
- package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +8 -8
- package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
- package/.agents/scripts/lib/orchestration/review-depth.js +26 -12
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
- package/.agents/scripts/lib/orchestration/run-epilogue.js +426 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +95 -41
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +16 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +40 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +11 -3
- package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
- package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
- package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -12
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +237 -0
- package/.agents/scripts/lib/orchestration/story-init-remote.js +47 -0
- package/.agents/scripts/lib/orchestration/story-plan-state.js +48 -0
- package/.agents/scripts/lib/orchestration/{epic-runner → story-progress}/story-run-progress-writer.js +3 -3
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -18
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +11 -61
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +189 -373
- package/.agents/scripts/lib/orchestration/ticket-validator.js +2 -7
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +0 -25
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +29 -26
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -5
- package/.agents/scripts/lib/planning-corpus.js +16 -11
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +36 -0
- package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
- package/.agents/scripts/lib/story-adjacency.js +11 -14
- package/.agents/scripts/lib/story-body/story-body.js +124 -70
- package/.agents/scripts/lib/story-plan.js +2 -4
- package/.agents/scripts/lib/templates/decomposer-prompts.js +46 -45
- package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
- package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +26 -26
- package/.agents/scripts/lib/validation-evidence.js +1 -1
- package/.agents/scripts/lib/wave-runner/ready-set.js +6 -6
- package/.agents/scripts/lib/workspace-provisioner.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +5 -7
- package/.agents/scripts/lint-issue-body.js +71 -40
- package/.agents/scripts/mandrel-update-preflight.js +1 -1
- package/.agents/scripts/notify.js +4 -3
- package/.agents/scripts/plan-context.js +64 -74
- package/.agents/scripts/plan-persist.js +121 -280
- package/.agents/scripts/plan-run-epilogue.js +97 -0
- package/.agents/scripts/providers/github/issues.js +17 -33
- package/.agents/scripts/providers/github/mappers.js +0 -12
- package/.agents/scripts/providers/github/tickets.js +2 -5
- package/.agents/scripts/resolve-plan-run.js +117 -0
- package/.agents/scripts/signals-view.js +24 -19
- package/.agents/scripts/single-story-close.js +11 -14
- package/.agents/scripts/single-story-confirm-merge.js +39 -23
- package/.agents/scripts/single-story-init.js +29 -20
- package/.agents/scripts/stories-wave-tick.js +6 -6
- package/.agents/scripts/story-plan.js +26 -47
- package/.agents/scripts/update-ticket-state.js +6 -15
- package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
- package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
- package/.agents/skills/core/scope-triage/SKILL.md +28 -172
- package/.agents/skills/skills.index.json +3 -43
- package/.agents/starter-agentrc.json +0 -5
- package/.agents/templates/agent-protocol.md +9 -10
- package/.agents/workflows/audit-architecture.md +3 -3
- package/.agents/workflows/audit-clean-code.md +3 -3
- package/.agents/workflows/audit-dependencies.md +3 -3
- package/.agents/workflows/audit-devops.md +3 -3
- package/.agents/workflows/audit-documentation.md +5 -5
- package/.agents/workflows/audit-lighthouse.md +3 -3
- package/.agents/workflows/audit-navigability.md +3 -2
- package/.agents/workflows/audit-performance.md +3 -3
- package/.agents/workflows/audit-privacy.md +3 -3
- package/.agents/workflows/audit-quality.md +3 -3
- package/.agents/workflows/audit-security.md +3 -3
- package/.agents/workflows/audit-seo.md +3 -3
- package/.agents/workflows/audit-sre.md +3 -3
- package/.agents/workflows/audit-to-stories.md +20 -20
- package/.agents/workflows/audit-ux-ui.md +3 -3
- package/.agents/workflows/deliver.md +122 -174
- package/.agents/workflows/git-cleanup.md +3 -4
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +16 -29
- package/.agents/workflows/helpers/code-review.md +70 -193
- package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +12 -14
- package/.agents/workflows/helpers/{single-story-deliver.md → deliver-story.md} +113 -139
- package/.agents/workflows/helpers/diagnose.md +10 -10
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/helpers/signals.md +16 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +48 -64
- package/.agents/workflows/mandrel-update.md +2 -1
- package/.agents/workflows/plan.md +112 -145
- package/.agents/workflows/qa-assist.md +24 -30
- package/.agents/workflows/qa-explore.md +29 -38
- package/.agents/workflows/qa-run.md +2 -2
- package/README.md +9 -8
- package/docs/CHANGELOG.md +30 -0
- package/lib/migrations/index.js +6 -5
- package/package.json +2 -2
- package/.agents/agents/retro.md +0 -42
- package/.agents/personas/architect.md +0 -113
- package/.agents/personas/devops-engineer.md +0 -38
- package/.agents/personas/engineer.md +0 -33
- package/.agents/personas/project-manager.md +0 -114
- package/.agents/personas/qa-engineer.md +0 -95
- package/.agents/personas/security-engineer.md +0 -111
- package/.agents/personas/technical-writer.md +0 -101
- package/.agents/schemas/dispatch-manifest.json +0 -232
- package/.agents/schemas/epic-spec.schema.json +0 -153
- package/.agents/schemas/lifecycle/slice.end.schema.json +0 -21
- package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +0 -20
- package/.agents/schemas/lifecycle/slice.start.schema.json +0 -17
- package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
- package/.agents/scripts/bookkeeping-reconcile.js +0 -117
- package/.agents/scripts/dispatcher.js +0 -295
- package/.agents/scripts/epic-audit-prepare.js +0 -497
- package/.agents/scripts/epic-audit-recheck.js +0 -274
- package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
- package/.agents/scripts/epic-deliver-preflight.js +0 -462
- package/.agents/scripts/epic-deliver-prepare.js +0 -852
- package/.agents/scripts/epic-execute-record-wave.js +0 -449
- package/.agents/scripts/epic-plan-clarity.js +0 -211
- package/.agents/scripts/epic-plan-healthcheck.js +0 -581
- package/.agents/scripts/epic-reconcile.js +0 -625
- package/.agents/scripts/lib/baseline-snapshot.js +0 -979
- package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
- package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
- package/.agents/scripts/lib/config/lifecycle.js +0 -40
- package/.agents/scripts/lib/config/preflight.js +0 -58
- package/.agents/scripts/lib/config/retro.js +0 -77
- package/.agents/scripts/lib/epic-merge-lock.js +0 -322
- package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
- package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -539
- package/.agents/scripts/lib/orchestration/deliver-route.js +0 -173
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -621
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
- package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
- package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
- package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +0 -270
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -465
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -284
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
- package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
- package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
- package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
- package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
- package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
- package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
- package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
- package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
- package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
- package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
- package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
- package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
- package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
- package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
- package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
- package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
- package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
- package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
- package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
- package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -302
- package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
- package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
- package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
- package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
- package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
- package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
- package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
- package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
- package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
- package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
- package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
- package/.agents/scripts/lib/push-epic-retry.js +0 -209
- package/.agents/scripts/lib/spec/index.js +0 -36
- package/.agents/scripts/lib/spec/loader.js +0 -425
- package/.agents/scripts/lib/spec/state.js +0 -208
- package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
- package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
- package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
- package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
- package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
- package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
- package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
- package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
- package/.agents/scripts/lib/wave-runner/tick.js +0 -754
- package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
- package/.agents/scripts/lifecycle-emit.js +0 -510
- package/.agents/scripts/retro-run.js +0 -218
- package/.agents/scripts/slice-phase.js +0 -361
- package/.agents/scripts/standalone-feedback-rollup.js +0 -188
- package/.agents/scripts/story-close.js +0 -294
- package/.agents/scripts/story-init.js +0 -599
- package/.agents/scripts/story-phase.js +0 -369
- package/.agents/scripts/wave-tick.js +0 -464
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
- package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
- package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
- package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
- package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -383
- package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
- package/.agents/workflows/helpers/deliver-epic-reference.md +0 -547
- package/.agents/workflows/helpers/deliver-epic-single.md +0 -331
- package/.agents/workflows/helpers/deliver-epic.md +0 -998
- package/.agents/workflows/helpers/deliver-stories.md +0 -450
- package/.agents/workflows/helpers/epic-audit.md +0 -189
- package/.agents/workflows/helpers/epic-deliver-story.md +0 -436
- package/.agents/workflows/helpers/epic-testing.md +0 -125
- package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
- package/.agents/workflows/helpers/plan-epic.md +0 -353
- package/.agents/workflows/helpers/plan-story.md +0 -251
- package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
- /package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +0 -0
|
@@ -1,852 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* node:coverage ignore file */
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* epic-deliver-prepare.js — Step 0/1 of the operator-driven `/deliver`.
|
|
6
|
-
*
|
|
7
|
-
* Composes the existing engine phases that the in-process epic-runner used to
|
|
8
|
-
* call sequentially, but does NOT dispatch any Stories. The CLI is the single
|
|
9
|
-
* point at which the slash-command captures:
|
|
10
|
-
*
|
|
11
|
-
* 1. The Epic ticket snapshot (`runSnapshotPhase`).
|
|
12
|
-
* 2. The story DAG (`runBuildWaveDagPhase`) computed from every child Story —
|
|
13
|
-
* used here only to enumerate the open Story set and run the
|
|
14
|
-
* concurrency-hazard gate; the ready-set runtime re-derives readiness
|
|
15
|
-
* from live labels on every `tick`, so the prepare no longer persists a
|
|
16
|
-
* wave grouping.
|
|
17
|
-
* 3. The seeded `epic-run-state` checkpoint (`epic-run-state-store.initialize`)
|
|
18
|
-
* in the per-Story-status shape (Story #4155): a flat
|
|
19
|
-
* `stories: { [storyId]: { status: 'pending' } }` map plus the GLOBAL
|
|
20
|
-
* in-flight `concurrencyCap`. Idempotent — re-running prepare against a
|
|
21
|
-
* partially-driven Epic preserves the original `startedAt` and every
|
|
22
|
-
* already-recorded Story status (it never resets recorded progress).
|
|
23
|
-
* 4. The dispatch hint (`StoryLauncher.planWave`) — a deterministic list of
|
|
24
|
-
* `{ storyId, worktree }` entries the slash command uses to resolve
|
|
25
|
-
* per-Story worktree paths. The ready-set `tick` selects which of these
|
|
26
|
-
* to dispatch on each beat; the prepare only enumerates the set.
|
|
27
|
-
*
|
|
28
|
-
* Stdout is a single JSON envelope so the slash command can parse without
|
|
29
|
-
* re-reading any tickets.
|
|
30
|
-
*
|
|
31
|
-
* Usage:
|
|
32
|
-
* node .agents/scripts/epic-deliver-prepare.js --epic <epicId>
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
import fs from 'node:fs';
|
|
36
|
-
import path from 'node:path';
|
|
37
|
-
import { parseArgs } from 'node:util';
|
|
38
|
-
|
|
39
|
-
import { runBootSweep } from './boot-sweep.js';
|
|
40
|
-
import { buildChecklistPayload } from './lib/audit-suite/index.js';
|
|
41
|
-
import { runAsCli } from './lib/cli-utils.js';
|
|
42
|
-
import {
|
|
43
|
-
getPaths,
|
|
44
|
-
getRunners,
|
|
45
|
-
resolveConfig,
|
|
46
|
-
resolveRuntime,
|
|
47
|
-
} from './lib/config-resolver.js';
|
|
48
|
-
import { cachedGitFetch } from './lib/git/cached-fetch.js';
|
|
49
|
-
import {
|
|
50
|
-
ensureEpicBranchRef,
|
|
51
|
-
currentBranch as gitCurrentBranch,
|
|
52
|
-
} from './lib/git-branch-lifecycle.js';
|
|
53
|
-
import { getEpicBranch, gitSpawn, gitSync } from './lib/git-utils.js';
|
|
54
|
-
import { Logger } from './lib/Logger.js';
|
|
55
|
-
import { ACCEPTANCE_NA, TYPE_LABELS } from './lib/label-constants.js';
|
|
56
|
-
import { parseDeliverySlicingTable } from './lib/orchestration/consolidation-precondition.js';
|
|
57
|
-
import { ensureDocsDigest } from './lib/orchestration/docs-digest.js';
|
|
58
|
-
import {
|
|
59
|
-
resolveOperator,
|
|
60
|
-
runPrepareGuards,
|
|
61
|
-
} from './lib/orchestration/epic-deliver-lease-guard.js';
|
|
62
|
-
import {
|
|
63
|
-
initialize as initializeEpicRunState,
|
|
64
|
-
initializeSingle as initializeEpicRunStateSingle,
|
|
65
|
-
write as writeEpicRunState,
|
|
66
|
-
} from './lib/orchestration/epic-run-state-store.js';
|
|
67
|
-
import {
|
|
68
|
-
collectPendingStoryKeys,
|
|
69
|
-
evaluateConcurrencyGate,
|
|
70
|
-
filterFindingsToPending,
|
|
71
|
-
renderGateErrorMessage,
|
|
72
|
-
} from './lib/orchestration/epic-runner/concurrency-gate.js';
|
|
73
|
-
import { runBuildWaveDagPhase } from './lib/orchestration/epic-runner/phases/build-wave-dag.js';
|
|
74
|
-
import { runSnapshotPhase } from './lib/orchestration/epic-runner/phases/snapshot.js';
|
|
75
|
-
import { StoryLauncher } from './lib/orchestration/epic-runner/story-launcher.js';
|
|
76
|
-
import { collectStoryAssumptionEntries } from './lib/orchestration/file-assumptions.js';
|
|
77
|
-
import {
|
|
78
|
-
computeBaseSha,
|
|
79
|
-
readPreflightCache,
|
|
80
|
-
} from './lib/orchestration/preflight-cache.js';
|
|
81
|
-
import {
|
|
82
|
-
latestHeartbeatForOwner,
|
|
83
|
-
currentOwner as leaseCurrentOwner,
|
|
84
|
-
} from './lib/orchestration/ticket-lease.js';
|
|
85
|
-
import { createProvider } from './lib/provider-factory.js';
|
|
86
|
-
|
|
87
|
-
const HELP = `Usage: node .agents/scripts/epic-deliver-prepare.js --epic <epicId> [--single] [--ignore-concurrency-hazards] [--steal] [--as <handle>]
|
|
88
|
-
|
|
89
|
-
Snapshots Epic #<id>, builds the wave DAG, initializes the epic-run-state
|
|
90
|
-
checkpoint, and prints the per-wave dispatch plan as JSON. Before any of that,
|
|
91
|
-
runs two fail-closed preflight guards (Story #3482): a checkout-safety check
|
|
92
|
-
(refuse on a dirty tree or an unexpected branch) and an Epic-lease acquisition
|
|
93
|
-
(refuse on a live foreign claim).
|
|
94
|
-
|
|
95
|
-
Options:
|
|
96
|
-
--single Single-delivery prepare (Epic #4475). Short-
|
|
97
|
-
circuits Story enumeration: seeds epic/<id>,
|
|
98
|
-
materializes ONE worktree on it, and writes a
|
|
99
|
-
slice-map epic-run-state (deliveryShape:
|
|
100
|
-
"single", storyCount: 0) from the Epic body's
|
|
101
|
-
## Delivery Slicing table. Refuses
|
|
102
|
-
acceptance::n-a (fail-closed). INERT until
|
|
103
|
-
M4-B: nothing in production drives this flag
|
|
104
|
-
yet.
|
|
105
|
-
--ignore-concurrency-hazards Bypass the cross-Story concurrency-hazard
|
|
106
|
-
gate (Story #2297). The flag's use is
|
|
107
|
-
recorded on the Epic checkpoint so retro
|
|
108
|
-
tooling can flag a run that shipped
|
|
109
|
-
despite an outstanding hazard.
|
|
110
|
-
--steal Forcibly transfer a live foreign Epic lease
|
|
111
|
-
to this operator instead of refusing. The
|
|
112
|
-
takeover is logged for auditability.
|
|
113
|
-
--as <handle> Operator identity to claim the Epic lease as.
|
|
114
|
-
Defaults to github.operatorHandle, then the
|
|
115
|
-
local git config user.email.
|
|
116
|
-
`;
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Build the production git shim the checkout-safety guard reads through. Pure
|
|
120
|
-
* `git` subprocess wrappers over `cwd`; injected as a seam so the unit suite
|
|
121
|
-
* can substitute an in-memory shim.
|
|
122
|
-
*
|
|
123
|
-
* @param {string} cwd
|
|
124
|
-
* @returns {{ statusPorcelain: () => { dirty: boolean, entries: string }, currentBranch: () => string|null }}
|
|
125
|
-
*/
|
|
126
|
-
function createGitShim(cwd) {
|
|
127
|
-
return {
|
|
128
|
-
statusPorcelain() {
|
|
129
|
-
const res = gitSpawn(cwd, 'status', '--porcelain');
|
|
130
|
-
if (res.status !== 0) {
|
|
131
|
-
throw new Error(
|
|
132
|
-
`[epic-deliver] Failed to read git status: ${res.stderr || '(no stderr)'}`,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
const entries = res.stdout ?? '';
|
|
136
|
-
return { dirty: entries.length > 0, entries };
|
|
137
|
-
},
|
|
138
|
-
currentBranch() {
|
|
139
|
-
return gitCurrentBranch(cwd);
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Resolve the local `git config user.email` as the last-resort operator
|
|
146
|
-
* identity. Returns null when git is unavailable or the value is unset.
|
|
147
|
-
*
|
|
148
|
-
* @param {string} cwd
|
|
149
|
-
* @returns {string|null}
|
|
150
|
-
*/
|
|
151
|
-
function resolveGitUserEmail(cwd) {
|
|
152
|
-
const res = gitSpawn(cwd, 'config', 'user.email');
|
|
153
|
-
if (res.status !== 0) return null;
|
|
154
|
-
const value = (res.stdout ?? '').trim();
|
|
155
|
-
return value.length > 0 ? value : null;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* End-to-end prepare. DI-friendly: tests pass `injectedProvider` and skip the
|
|
160
|
-
* real GitHub round-trips.
|
|
161
|
-
*
|
|
162
|
-
* @param {{
|
|
163
|
-
* epicId: number,
|
|
164
|
-
* cwd?: string,
|
|
165
|
-
* injectedProvider?: object,
|
|
166
|
-
* injectedConfig?: object,
|
|
167
|
-
* }} args
|
|
168
|
-
* @returns {Promise<{
|
|
169
|
-
* epicId: number,
|
|
170
|
-
* storyCount: number,
|
|
171
|
-
* concurrencyCap: number,
|
|
172
|
-
* stories: Array<{ storyId: number, title: string, worktree?: string }>,
|
|
173
|
-
* checkpointInitializedAt: string,
|
|
174
|
-
* }>}
|
|
175
|
-
*/
|
|
176
|
-
/**
|
|
177
|
-
* Run the fail-closed preflight guards (Story #3482): refuse on a
|
|
178
|
-
* dirty/foreign-branch checkout and on a live foreign Epic lease, BEFORE any
|
|
179
|
-
* snapshot or git mutation. No-op when guards are suppressed. The guards are
|
|
180
|
-
* skipped when `skipPreflightGuards` is set, OR — implicitly — when a caller
|
|
181
|
-
* injects a provider but no git seam (the signature of the prepare-runner
|
|
182
|
-
* unit tests that drive an in-memory provider and never stand up a tree). The
|
|
183
|
-
* real CLI path injects neither, so the guards always run for an
|
|
184
|
-
* operator-driven invocation. Story #4075 — extracted from
|
|
185
|
-
* `runEpicDeliverPrepare`.
|
|
186
|
-
*/
|
|
187
|
-
async function runPreflightGuardsForPrepare({
|
|
188
|
-
epicId,
|
|
189
|
-
cwd,
|
|
190
|
-
config,
|
|
191
|
-
provider,
|
|
192
|
-
injectedProvider,
|
|
193
|
-
injectedGit,
|
|
194
|
-
asOperator,
|
|
195
|
-
steal,
|
|
196
|
-
leaseHeartbeatAt,
|
|
197
|
-
leaseNow,
|
|
198
|
-
skipPreflightGuards,
|
|
199
|
-
}) {
|
|
200
|
-
const guardsSuppressed =
|
|
201
|
-
skipPreflightGuards || (Boolean(injectedProvider) && !injectedGit);
|
|
202
|
-
if (guardsSuppressed) return;
|
|
203
|
-
|
|
204
|
-
const guardCwd = cwd ?? process.cwd();
|
|
205
|
-
const git = injectedGit ?? createGitShim(guardCwd);
|
|
206
|
-
const baseBranch = config.project?.baseBranch ?? 'main';
|
|
207
|
-
const expectedBranch = [getEpicBranch(epicId), baseBranch];
|
|
208
|
-
const operator =
|
|
209
|
-
resolveOperator({
|
|
210
|
-
asFlag: asOperator,
|
|
211
|
-
config,
|
|
212
|
-
gitUserEmail: injectedGit ? undefined : resolveGitUserEmail(guardCwd),
|
|
213
|
-
}) ?? null;
|
|
214
|
-
|
|
215
|
-
// Liveness seam: a foreign claim is only "live" (and so refuses) when the
|
|
216
|
-
// claim *owner* has a recent `story.heartbeat`. Without this the lease
|
|
217
|
-
// guard is inert — every foreign claim looks stale and gets silently
|
|
218
|
-
// reclaimed (audit #3513). Read the Epic's current assignee (the claim
|
|
219
|
-
// owner) and resolve that owner's latest heartbeat from the Epic lifecycle
|
|
220
|
-
// ledger via the shared resolver. Tests may inject `leaseHeartbeatAt`
|
|
221
|
-
// directly (any value, including null) to bypass the ledger read.
|
|
222
|
-
let heartbeatAt = leaseHeartbeatAt;
|
|
223
|
-
if (heartbeatAt === undefined) {
|
|
224
|
-
const epicTicket = await provider.getTicket(epicId);
|
|
225
|
-
const claimOwner = leaseCurrentOwner(epicTicket?.assignees);
|
|
226
|
-
heartbeatAt = claimOwner
|
|
227
|
-
? latestHeartbeatForOwner({ epicId, owner: claimOwner, config })
|
|
228
|
-
: null;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
await runPrepareGuards({
|
|
232
|
-
epicId,
|
|
233
|
-
expectedBranch,
|
|
234
|
-
git,
|
|
235
|
-
provider,
|
|
236
|
-
operator,
|
|
237
|
-
heartbeatAt,
|
|
238
|
-
steal,
|
|
239
|
-
config,
|
|
240
|
-
now: leaseNow,
|
|
241
|
-
logger: Logger,
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Route the Epic boot cleanup through the shared protected boot-sweep
|
|
247
|
-
* engine (Story #4373). Reaps merged, done `story-*` branches left over
|
|
248
|
-
* from prior runs — the protection partition skips any branch with
|
|
249
|
-
* unpushed work, a dirty worktree, or a still-open parent Story, so an
|
|
250
|
-
* in-flight Story is never touched. Fast-forward is off: the prepare may
|
|
251
|
-
* run on the Epic branch, and the fast-forward phase would otherwise
|
|
252
|
-
* check out the base branch.
|
|
253
|
-
*
|
|
254
|
-
* Best-effort — a sweep failure (lock contention, git/gh error) is
|
|
255
|
-
* swallowed and never blocks or fails the prepare. Skipped in the same
|
|
256
|
-
* injected-test shape the preflight guards use (a provider injected with
|
|
257
|
-
* no git seam) so unit tests never spawn real git/gh.
|
|
258
|
-
*/
|
|
259
|
-
async function runBootSweepForPrepare({
|
|
260
|
-
cwd,
|
|
261
|
-
config,
|
|
262
|
-
provider,
|
|
263
|
-
injectedProvider,
|
|
264
|
-
injectedGit,
|
|
265
|
-
injectedSweep,
|
|
266
|
-
skipPreflightGuards,
|
|
267
|
-
}) {
|
|
268
|
-
const suppressed =
|
|
269
|
-
skipPreflightGuards || (Boolean(injectedProvider) && !injectedGit);
|
|
270
|
-
if (suppressed) return;
|
|
271
|
-
try {
|
|
272
|
-
await runBootSweep({
|
|
273
|
-
cwd,
|
|
274
|
-
include: ['story-*'],
|
|
275
|
-
fastForward: false,
|
|
276
|
-
injectedConfig: config,
|
|
277
|
-
injectedProvider: provider,
|
|
278
|
-
injectedSweep,
|
|
279
|
-
logger: Logger,
|
|
280
|
-
});
|
|
281
|
-
} catch (err) {
|
|
282
|
-
Logger.warn(
|
|
283
|
-
`[epic-deliver-prepare] ⚠️ boot sweep threw (prepare continues): ${err?.message ?? err}`,
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Resolve the Epic state, preferring the preflight cache (Story #3027) and
|
|
290
|
-
* falling back to a fresh snapshot + wave-DAG pass on miss or baseSha
|
|
291
|
-
* mismatch. Returns `{ state, cacheStatus }`. Story #4075 — extracted from
|
|
292
|
-
* `runEpicDeliverPrepare`.
|
|
293
|
-
*/
|
|
294
|
-
async function resolvePrepareState({ epicId, cwd, provider }) {
|
|
295
|
-
const cached = await readPreflightCache({ epicId, cwd });
|
|
296
|
-
if (cached) {
|
|
297
|
-
const freshEpic = await provider.getTicket(epicId);
|
|
298
|
-
const cachedStoryIds = cached.stories
|
|
299
|
-
.map((s) => Number(s?.id ?? s?.number))
|
|
300
|
-
.filter((id) => Number.isInteger(id) && id > 0);
|
|
301
|
-
const freshStories = await Promise.all(
|
|
302
|
-
cachedStoryIds.map((id) => provider.getTicket(id)),
|
|
303
|
-
);
|
|
304
|
-
const freshBaseSha = computeBaseSha(freshEpic, freshStories);
|
|
305
|
-
if (freshBaseSha === cached.baseSha) {
|
|
306
|
-
return {
|
|
307
|
-
state: {
|
|
308
|
-
epic: cached.epic,
|
|
309
|
-
stories: cached.stories,
|
|
310
|
-
waves: cached.waves,
|
|
311
|
-
},
|
|
312
|
-
cacheStatus: 'hit',
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
const ctx = { epicId, provider };
|
|
317
|
-
let state = await runSnapshotPhase(ctx, {}, {});
|
|
318
|
-
state = await runBuildWaveDagPhase(ctx, {}, state);
|
|
319
|
-
return { state, cacheStatus: cached ? 'stale' : 'miss' };
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Evaluate the cross-Story concurrency-hazard gate (Story #2297). Throws on a
|
|
324
|
-
* tripped, non-bypassed gate; warns (and returns `gate`) on a bypassed trip.
|
|
325
|
-
* Story #4075 — extracted from `runEpicDeliverPrepare`.
|
|
326
|
-
*/
|
|
327
|
-
function evaluatePrepareConcurrencyGate({
|
|
328
|
-
config,
|
|
329
|
-
waves,
|
|
330
|
-
injectedFindings,
|
|
331
|
-
ignoreConcurrencyHazards,
|
|
332
|
-
}) {
|
|
333
|
-
const findings = Array.isArray(injectedFindings) ? injectedFindings : [];
|
|
334
|
-
const pendingKeys = collectPendingStoryKeys(waves);
|
|
335
|
-
const pendingFindings = filterFindingsToPending(findings, pendingKeys);
|
|
336
|
-
const gate = evaluateConcurrencyGate({
|
|
337
|
-
findings: pendingFindings,
|
|
338
|
-
policy: {
|
|
339
|
-
failOnConcurrencyHazards:
|
|
340
|
-
config?.delivery?.failOnConcurrencyHazards === true,
|
|
341
|
-
},
|
|
342
|
-
ignore: ignoreConcurrencyHazards === true,
|
|
343
|
-
});
|
|
344
|
-
if (gate.tripped && !gate.bypassed) {
|
|
345
|
-
const ownerRepo =
|
|
346
|
-
config?.github?.owner && config?.github?.repo
|
|
347
|
-
? `${config.github.owner}/${config.github.repo}`
|
|
348
|
-
: undefined;
|
|
349
|
-
throw new Error(renderGateErrorMessage(gate.findings, ownerRepo));
|
|
350
|
-
}
|
|
351
|
-
if (gate.tripped && gate.bypassed) {
|
|
352
|
-
Logger.warn(
|
|
353
|
-
`[epic-deliver-prepare] ⚠️ Concurrency-hazard gate bypassed via --ignore-concurrency-hazards (reason=${gate.reason}, count=${gate.findings.length}).`,
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
return gate;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Build the per-Epic docs digest and write it to
|
|
361
|
-
* `<tempRoot>/epic-<id>/docs-digest.md`, returning its repo-relative path.
|
|
362
|
-
* Story #4338 — the parent threads this path into every child prompt so
|
|
363
|
-
* delivery sub-agents read one compact outline instead of re-ingesting the
|
|
364
|
-
* full `project.docsContextFiles` set per Story.
|
|
365
|
-
*
|
|
366
|
-
* Keyed off the **un-defaulted** config (`config.raw`): when the operator has
|
|
367
|
-
* not configured `project.docsContextFiles`, this returns `null` (no file
|
|
368
|
-
* written) rather than digesting the resolver's built-in default set — the
|
|
369
|
-
* digest is an opt-in surface for projects that curate their docs context.
|
|
370
|
-
*
|
|
371
|
-
* @param {{ epicId: number, cwd?: string, config: object }} args
|
|
372
|
-
* @returns {Promise<string|null>} repo-relative digest path, or null when
|
|
373
|
-
* `project.docsContextFiles` is empty/unset (or every file is missing).
|
|
374
|
-
*/
|
|
375
|
-
async function writeDocsDigest({ epicId, cwd, config }) {
|
|
376
|
-
const rawFiles = config?.raw?.project?.docsContextFiles;
|
|
377
|
-
const docsContextFiles = Array.isArray(rawFiles) ? rawFiles : [];
|
|
378
|
-
if (docsContextFiles.length === 0) return null;
|
|
379
|
-
|
|
380
|
-
const paths = getPaths(config);
|
|
381
|
-
const root = path.resolve(cwd ?? process.cwd());
|
|
382
|
-
const docsRoot = path.resolve(root, paths.docsRoot);
|
|
383
|
-
const relPath = path.join(paths.tempRoot, `epic-${epicId}`, 'docs-digest.md');
|
|
384
|
-
const absPath = path.resolve(root, relPath);
|
|
385
|
-
const result = await ensureDocsDigest({
|
|
386
|
-
docsContextFiles,
|
|
387
|
-
docsRoot,
|
|
388
|
-
outputPath: absPath,
|
|
389
|
-
});
|
|
390
|
-
return result ? relPath : null;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Thread footprint-matched **local**-lens authoring checklists into the
|
|
395
|
-
* per-Story dispatch entries (Epic #4405, Story #4410). For each planned Story,
|
|
396
|
-
* derive its predicted footprint from the full ticket body's `changes[]` /
|
|
397
|
-
* `references[]` entries, build the budget-capped checklist payload (matched by
|
|
398
|
-
* `resolveLensTier(lens) === 'local'` + `matchesAnyFilePattern` — NOT
|
|
399
|
-
* `selectAudits`, so no provider or git diff runs here), and write it to
|
|
400
|
-
* `<tempRoot>/epic-<id>/checklists/story-<sid>.md`. The parent threads the
|
|
401
|
-
* returned repo-relative `checklistPath` into that child's maker prompt, next
|
|
402
|
-
* to `docsDigestPath`; a Story that matches no local lens gets a `null` path
|
|
403
|
-
* and no file.
|
|
404
|
-
*
|
|
405
|
-
* @param {{
|
|
406
|
-
* epicId: number,
|
|
407
|
-
* cwd?: string,
|
|
408
|
-
* config: object,
|
|
409
|
-
* stories: Array<{ storyId: number, worktree?: string, title?: string }>,
|
|
410
|
-
* storyById: Map<number, object>,
|
|
411
|
-
* }} args
|
|
412
|
-
* @returns {Promise<Array<{ storyId: number, worktree?: string, title?: string, checklistPath: string|null }>>}
|
|
413
|
-
*/
|
|
414
|
-
export async function writeStoryChecklists({
|
|
415
|
-
epicId,
|
|
416
|
-
cwd,
|
|
417
|
-
config,
|
|
418
|
-
stories,
|
|
419
|
-
storyById,
|
|
420
|
-
buildPayload = buildChecklistPayload,
|
|
421
|
-
}) {
|
|
422
|
-
const paths = getPaths(config);
|
|
423
|
-
const root = path.resolve(cwd ?? process.cwd());
|
|
424
|
-
|
|
425
|
-
return Promise.all(
|
|
426
|
-
stories.map(async (entry) => {
|
|
427
|
-
const ticket = storyById.get(Number(entry.storyId));
|
|
428
|
-
const footprint = ticket
|
|
429
|
-
? collectStoryAssumptionEntries(ticket).map((e) => e.path)
|
|
430
|
-
: [];
|
|
431
|
-
const { payload } = buildPayload({ footprint, logger: Logger });
|
|
432
|
-
if (!payload) return { ...entry, checklistPath: null };
|
|
433
|
-
|
|
434
|
-
const relPath = path.join(
|
|
435
|
-
paths.tempRoot,
|
|
436
|
-
`epic-${epicId}`,
|
|
437
|
-
'checklists',
|
|
438
|
-
`story-${entry.storyId}.md`,
|
|
439
|
-
);
|
|
440
|
-
const absPath = path.resolve(root, relPath);
|
|
441
|
-
await fs.promises.mkdir(path.dirname(absPath), { recursive: true });
|
|
442
|
-
await fs.promises.writeFile(absPath, payload, 'utf-8');
|
|
443
|
-
return { ...entry, checklistPath: relPath };
|
|
444
|
-
}),
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* Seed `epic/<id>` and materialize the ONE worktree the single-delivery
|
|
450
|
-
* executor walks — the single-delivery counterpart to `single-story-init.js`'s
|
|
451
|
-
* single-worktree seed (Epic #4475). Fetches origin so remote-tracking refs
|
|
452
|
-
* are authoritative, publishes the Epic integration branch via the shared
|
|
453
|
-
* `ensureEpicBranchRef` seeder (the same helper `branch-initializer.js` uses),
|
|
454
|
-
* then adds a worktree at `.worktrees/epic-<id>/` on `epic/<id>` — idempotent,
|
|
455
|
-
* reused on a re-prepare. When worktree isolation is off it checks the branch
|
|
456
|
-
* out on the main tree instead (mirroring `provisionWorktree`).
|
|
457
|
-
*
|
|
458
|
-
* Skipped in the same injected-test shape the preflight guards use (a provider
|
|
459
|
-
* injected with no git seam) so unit tests never spawn real git.
|
|
460
|
-
*
|
|
461
|
-
* @param {{
|
|
462
|
-
* epicId: number,
|
|
463
|
-
* cwd: string,
|
|
464
|
-
* baseBranch: string,
|
|
465
|
-
* worktreeEnabled: boolean,
|
|
466
|
-
* progress?: (stage: string, msg: string) => void,
|
|
467
|
-
* }} args
|
|
468
|
-
* @returns {{ epicBranch: string, workCwd: string, worktreeCreated: boolean }}
|
|
469
|
-
*/
|
|
470
|
-
export function provisionEpicWorktree({
|
|
471
|
-
epicId,
|
|
472
|
-
cwd,
|
|
473
|
-
baseBranch,
|
|
474
|
-
worktreeEnabled,
|
|
475
|
-
progress = () => {},
|
|
476
|
-
}) {
|
|
477
|
-
const epicBranch = getEpicBranch(epicId);
|
|
478
|
-
|
|
479
|
-
// Fetch origin so `ensureEpicBranchRef` can read remote-tracking refs
|
|
480
|
-
// instead of a second network round-trip (mirrors materializeBaseBranch).
|
|
481
|
-
cachedGitFetch(cwd, 'origin');
|
|
482
|
-
ensureEpicBranchRef(epicBranch, baseBranch, cwd, { progress });
|
|
483
|
-
|
|
484
|
-
if (!worktreeEnabled) {
|
|
485
|
-
// Single-tree mode: check out the Epic integration branch in place.
|
|
486
|
-
gitSync(cwd, 'checkout', epicBranch);
|
|
487
|
-
progress('WORKTREE', `Checked out ${epicBranch} on the main tree.`);
|
|
488
|
-
return { epicBranch, workCwd: cwd, worktreeCreated: false };
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const worktreeRoot = path.join(cwd, '.worktrees');
|
|
492
|
-
const wtPath = path.join(worktreeRoot, `epic-${epicId}`);
|
|
493
|
-
fs.mkdirSync(worktreeRoot, { recursive: true });
|
|
494
|
-
|
|
495
|
-
const listed = gitSpawn(cwd, 'worktree', 'list', '--porcelain');
|
|
496
|
-
const alreadyPresent =
|
|
497
|
-
listed.status === 0 && (listed.stdout ?? '').includes(wtPath);
|
|
498
|
-
if (alreadyPresent) {
|
|
499
|
-
progress('WORKTREE', `♻️ Reusing worktree: ${wtPath}`);
|
|
500
|
-
return { epicBranch, workCwd: wtPath, worktreeCreated: false };
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
const res = gitSpawn(cwd, 'worktree', 'add', wtPath, epicBranch);
|
|
504
|
-
if (res.status !== 0) {
|
|
505
|
-
const stderr = res.stderr || res.stdout || '';
|
|
506
|
-
if (/already (exists|checked out)/.test(stderr)) {
|
|
507
|
-
progress('WORKTREE', `♻️ Reusing worktree (race): ${wtPath}`);
|
|
508
|
-
return { epicBranch, workCwd: wtPath, worktreeCreated: false };
|
|
509
|
-
}
|
|
510
|
-
throw new Error(
|
|
511
|
-
`epic-deliver-prepare --single: git worktree add failed for epic-${epicId}: ${stderr}`,
|
|
512
|
-
);
|
|
513
|
-
}
|
|
514
|
-
progress('WORKTREE', `✨ Created worktree: ${wtPath}`);
|
|
515
|
-
return { epicBranch, workCwd: wtPath, worktreeCreated: true };
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* Step 0/1 of `/deliver` for a single-delivery Epic (Epic #4475, design §S1).
|
|
520
|
-
*
|
|
521
|
-
* The single-delivery counterpart to `runEpicDeliverPrepare`. It short-circuits
|
|
522
|
-
* Story enumeration entirely — a spec-only plan authored NO Story tickets, so
|
|
523
|
-
* there is nothing to fan out. Instead it:
|
|
524
|
-
*
|
|
525
|
-
* 1. Refuses `acceptance::n-a` (fail-closed front gate). Under single
|
|
526
|
-
* delivery the non-waivable epic-level acceptance reconcile is the ONLY
|
|
527
|
-
* acceptance gate; an Epic that declares "no acceptance criteria" is
|
|
528
|
-
* structurally incoherent with that contract.
|
|
529
|
-
* 2. Runs the same fail-closed preflight guards (checkout-safety + Epic
|
|
530
|
-
* lease) as the fan-out prepare.
|
|
531
|
-
* 3. Seeds `epic/<id>` and materializes ONE worktree on it.
|
|
532
|
-
* 4. Parses the Epic body's `## Delivery Slicing` table and writes an
|
|
533
|
-
* `epic-run-state` **slice map** (`deliveryShape: "single"`,
|
|
534
|
-
* `storyCount: 0`, `concurrencyCap: 1`) — idempotent + resume-preserving
|
|
535
|
-
* (a re-run keeps every already-`done` slice).
|
|
536
|
-
* 5. Writes the per-Epic docs digest.
|
|
537
|
-
*
|
|
538
|
-
* BEHAVIOR-PRESERVING (M4-A): this function is reachable only through the
|
|
539
|
-
* `--single` flag, which nothing in production drives yet (the `deliver.md`
|
|
540
|
-
* router's single verdict falls through to the fan-out helper until M4-B). The
|
|
541
|
-
* slice-map checkpoint round-trips but no executor consumes it here.
|
|
542
|
-
*
|
|
543
|
-
* @param {object} args — same DI surface as `runEpicDeliverPrepare` minus the
|
|
544
|
-
* concurrency-hazard knobs (single delivery fans out nothing to gate).
|
|
545
|
-
* @returns {Promise<{
|
|
546
|
-
* epicId: number,
|
|
547
|
-
* deliveryShape: 'single',
|
|
548
|
-
* storyCount: 0,
|
|
549
|
-
* concurrencyCap: number,
|
|
550
|
-
* sliceCount: number,
|
|
551
|
-
* slices: Record<string, { status: string, title?: string }>,
|
|
552
|
-
* epicBranch: string,
|
|
553
|
-
* workCwd: string,
|
|
554
|
-
* worktreeCreated: boolean,
|
|
555
|
-
* checkpointInitializedAt: string,
|
|
556
|
-
* docsDigestPath: string|null,
|
|
557
|
-
* }>}
|
|
558
|
-
*/
|
|
559
|
-
export async function runEpicDeliverPrepareSingle({
|
|
560
|
-
epicId,
|
|
561
|
-
cwd,
|
|
562
|
-
injectedProvider,
|
|
563
|
-
injectedConfig,
|
|
564
|
-
asOperator,
|
|
565
|
-
steal = false,
|
|
566
|
-
injectedGit,
|
|
567
|
-
leaseHeartbeatAt,
|
|
568
|
-
leaseNow,
|
|
569
|
-
skipPreflightGuards = false,
|
|
570
|
-
} = {}) {
|
|
571
|
-
if (!Number.isInteger(epicId) || epicId <= 0) {
|
|
572
|
-
throw new TypeError(
|
|
573
|
-
'runEpicDeliverPrepareSingle: --epic must be a positive integer',
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
const config = injectedConfig ?? resolveConfig({ cwd });
|
|
578
|
-
if (!config.github) {
|
|
579
|
-
throw new Error(
|
|
580
|
-
'runEpicDeliverPrepareSingle: no github block in .agentrc.json',
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
const provider = injectedProvider ?? createProvider(config);
|
|
584
|
-
// Single delivery collapses the whole Epic into ONE guarded in-session slice
|
|
585
|
-
// walk — the concurrency cap is 1 by definition (nothing fans out).
|
|
586
|
-
const concurrencyCap = 1;
|
|
587
|
-
|
|
588
|
-
const epic = await provider.getTicket(epicId);
|
|
589
|
-
const labels = Array.isArray(epic?.labels) ? epic.labels : [];
|
|
590
|
-
if (!labels.includes(TYPE_LABELS.EPIC)) {
|
|
591
|
-
throw new Error(
|
|
592
|
-
`runEpicDeliverPrepareSingle: #${epicId} is not a ${TYPE_LABELS.EPIC} (labels: ${labels.join(', ') || 'none'}).`,
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// Fail-closed front gate (design §"Non-waivable epic reconcile"): under
|
|
597
|
-
// single delivery the epic-level acceptance reconcile is the ONLY acceptance
|
|
598
|
-
// gate that runs — there is no per-Story self-eval critic tier behind it. An
|
|
599
|
-
// Epic labelled `acceptance::n-a` (no acceptance criteria) is therefore
|
|
600
|
-
// structurally incoherent with single delivery: it would waive the sole
|
|
601
|
-
// gate. Refuse loudly instead of silently shipping ungated.
|
|
602
|
-
if (labels.includes(ACCEPTANCE_NA)) {
|
|
603
|
-
throw new Error(
|
|
604
|
-
`[epic-deliver-prepare] BLOCKER: Epic #${epicId} carries ${ACCEPTANCE_NA}, ` +
|
|
605
|
-
'but single delivery makes the non-waivable epic-level acceptance ' +
|
|
606
|
-
'reconcile the ONLY acceptance gate — an Epic with no acceptance ' +
|
|
607
|
-
'criteria would ship ungated. Remove the label (author an ' +
|
|
608
|
-
'## Acceptance Table), or re-plan the Epic as fan-out.',
|
|
609
|
-
);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
await runPreflightGuardsForPrepare({
|
|
613
|
-
epicId,
|
|
614
|
-
cwd,
|
|
615
|
-
config,
|
|
616
|
-
provider,
|
|
617
|
-
injectedProvider,
|
|
618
|
-
injectedGit,
|
|
619
|
-
asOperator,
|
|
620
|
-
steal,
|
|
621
|
-
leaseHeartbeatAt,
|
|
622
|
-
leaseNow,
|
|
623
|
-
skipPreflightGuards,
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
const baseBranch = config.project?.baseBranch ?? 'main';
|
|
627
|
-
const runtime = resolveRuntime({ config });
|
|
628
|
-
|
|
629
|
-
// Seed epic/<id> + materialize the one worktree. Skipped in the injected-
|
|
630
|
-
// test shape (a provider injected with no git seam) so unit tests never
|
|
631
|
-
// spawn real git — matching the preflight-guard suppression rule.
|
|
632
|
-
const worktreeSuppressed =
|
|
633
|
-
skipPreflightGuards || (Boolean(injectedProvider) && !injectedGit);
|
|
634
|
-
let epicBranch = getEpicBranch(epicId);
|
|
635
|
-
let workCwd = cwd ?? process.cwd();
|
|
636
|
-
let worktreeCreated = false;
|
|
637
|
-
if (!worktreeSuppressed) {
|
|
638
|
-
({ epicBranch, workCwd, worktreeCreated } = provisionEpicWorktree({
|
|
639
|
-
epicId,
|
|
640
|
-
cwd: cwd ?? process.cwd(),
|
|
641
|
-
baseBranch,
|
|
642
|
-
worktreeEnabled: runtime.worktreeEnabled,
|
|
643
|
-
progress: (stage, msg) =>
|
|
644
|
-
Logger.info(`[epic-deliver-prepare:single] ${stage} ${msg}`),
|
|
645
|
-
}));
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// Parse the Epic body's `## Delivery Slicing` table — the single mode's
|
|
649
|
-
// audit trail and the source of the slice map. A missing/unparseable table
|
|
650
|
-
// yields an empty slice set (the executor has nothing to walk); that is a
|
|
651
|
-
// plan-quality problem surfaced downstream, not a prepare-time throw.
|
|
652
|
-
const slices = parseDeliverySlicingTable(epic?.body ?? '') ?? [];
|
|
653
|
-
|
|
654
|
-
const checkpointState = await initializeEpicRunStateSingle({
|
|
655
|
-
provider,
|
|
656
|
-
epicId,
|
|
657
|
-
slices,
|
|
658
|
-
concurrencyCap,
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
const docsDigestPath = await writeDocsDigest({ epicId, cwd, config });
|
|
662
|
-
|
|
663
|
-
return {
|
|
664
|
-
epicId,
|
|
665
|
-
deliveryShape: 'single',
|
|
666
|
-
storyCount: 0,
|
|
667
|
-
concurrencyCap,
|
|
668
|
-
sliceCount: Object.keys(checkpointState.slices ?? {}).length,
|
|
669
|
-
slices: checkpointState.slices ?? {},
|
|
670
|
-
epicBranch,
|
|
671
|
-
workCwd,
|
|
672
|
-
worktreeCreated,
|
|
673
|
-
checkpointInitializedAt:
|
|
674
|
-
checkpointState.startedAt ??
|
|
675
|
-
checkpointState.lastUpdatedAt ??
|
|
676
|
-
new Date().toISOString(),
|
|
677
|
-
docsDigestPath,
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
export async function runEpicDeliverPrepare({
|
|
682
|
-
epicId,
|
|
683
|
-
cwd,
|
|
684
|
-
injectedProvider,
|
|
685
|
-
injectedConfig,
|
|
686
|
-
injectedFindings,
|
|
687
|
-
ignoreConcurrencyHazards = false,
|
|
688
|
-
steal = false,
|
|
689
|
-
asOperator,
|
|
690
|
-
injectedGit,
|
|
691
|
-
injectedSweep,
|
|
692
|
-
leaseHeartbeatAt,
|
|
693
|
-
leaseNow,
|
|
694
|
-
skipPreflightGuards = false,
|
|
695
|
-
} = {}) {
|
|
696
|
-
if (!Number.isInteger(epicId) || epicId <= 0) {
|
|
697
|
-
throw new TypeError(
|
|
698
|
-
'runEpicDeliverPrepare: --epic must be a positive integer',
|
|
699
|
-
);
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
const config = injectedConfig ?? resolveConfig({ cwd });
|
|
703
|
-
if (!config.github) {
|
|
704
|
-
throw new Error('runEpicDeliverPrepare: no github block in .agentrc.json');
|
|
705
|
-
}
|
|
706
|
-
const provider = injectedProvider ?? createProvider(config);
|
|
707
|
-
const { deliverRunner } = getRunners(config);
|
|
708
|
-
const concurrencyCap = deliverRunner.concurrencyCap;
|
|
709
|
-
|
|
710
|
-
await runPreflightGuardsForPrepare({
|
|
711
|
-
epicId,
|
|
712
|
-
cwd,
|
|
713
|
-
config,
|
|
714
|
-
provider,
|
|
715
|
-
injectedProvider,
|
|
716
|
-
injectedGit,
|
|
717
|
-
asOperator,
|
|
718
|
-
steal,
|
|
719
|
-
leaseHeartbeatAt,
|
|
720
|
-
leaseNow,
|
|
721
|
-
skipPreflightGuards,
|
|
722
|
-
});
|
|
723
|
-
|
|
724
|
-
await runBootSweepForPrepare({
|
|
725
|
-
cwd,
|
|
726
|
-
config,
|
|
727
|
-
provider,
|
|
728
|
-
injectedProvider,
|
|
729
|
-
injectedGit,
|
|
730
|
-
injectedSweep,
|
|
731
|
-
skipPreflightGuards,
|
|
732
|
-
});
|
|
733
|
-
|
|
734
|
-
const { state, cacheStatus } = await resolvePrepareState({
|
|
735
|
-
epicId,
|
|
736
|
-
cwd,
|
|
737
|
-
provider,
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
const gate = evaluatePrepareConcurrencyGate({
|
|
741
|
-
config,
|
|
742
|
-
waves: state.waves,
|
|
743
|
-
injectedFindings,
|
|
744
|
-
ignoreConcurrencyHazards,
|
|
745
|
-
});
|
|
746
|
-
|
|
747
|
-
// Flatten the wave-DAG into the open Story set. The ready-set runtime
|
|
748
|
-
// re-derives readiness from live labels on every tick, so the checkpoint
|
|
749
|
-
// stores only the Story set in scope (seeded at `pending`) and the global
|
|
750
|
-
// in-flight cap — no wave grouping, no `currentWave`, no `totalWaves`.
|
|
751
|
-
const openStories = state.waves.flat();
|
|
752
|
-
const checkpointState = await initializeEpicRunState({
|
|
753
|
-
provider,
|
|
754
|
-
epicId,
|
|
755
|
-
storyIds: openStories,
|
|
756
|
-
concurrencyCap,
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
// Resolve per-Story worktree paths via the launcher so the slash command
|
|
760
|
-
// has a deterministic `{ storyId, worktree, title }` list to seed Agent
|
|
761
|
-
// dispatch from. This is a dispatch *hint* — the ready-set tick decides
|
|
762
|
-
// which Stories to dispatch on each beat; the prepare only enumerates them.
|
|
763
|
-
const launcher = new StoryLauncher({ concurrencyCap });
|
|
764
|
-
const plannedStories = launcher.planWave(openStories).map((entry, i) => ({
|
|
765
|
-
...entry,
|
|
766
|
-
title: openStories[i]?.title ?? '',
|
|
767
|
-
}));
|
|
768
|
-
|
|
769
|
-
// Thread footprint-matched local-lens authoring checklists into each Story's
|
|
770
|
-
// dispatch entry (Story #4410). Keyed off the full discovered tickets (which
|
|
771
|
-
// carry the `changes[]`/`references[]` bodies the footprint is derived from);
|
|
772
|
-
// a Story that matches no local lens gets a `null` checklistPath.
|
|
773
|
-
const storyById = new Map(
|
|
774
|
-
(state.stories ?? []).map((t) => [Number(t.id ?? t.number), t]),
|
|
775
|
-
);
|
|
776
|
-
const stories = await writeStoryChecklists({
|
|
777
|
-
epicId,
|
|
778
|
-
cwd,
|
|
779
|
-
config,
|
|
780
|
-
stories: plannedStories,
|
|
781
|
-
storyById,
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
// Persist the `--ignore-concurrency-hazards` flag on the checkpoint so
|
|
785
|
-
// retro tooling can flag a run that shipped despite an outstanding hazard
|
|
786
|
-
// (the warning above is one-shot; the checkpoint is durable).
|
|
787
|
-
if (gate.bypassed) {
|
|
788
|
-
await writeEpicRunState({
|
|
789
|
-
provider,
|
|
790
|
-
epicId,
|
|
791
|
-
state: { ...checkpointState, ignoreConcurrencyHazards: true },
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
const docsDigestPath = await writeDocsDigest({ epicId, cwd, config });
|
|
796
|
-
|
|
797
|
-
return {
|
|
798
|
-
epicId,
|
|
799
|
-
storyCount: openStories.length,
|
|
800
|
-
concurrencyCap,
|
|
801
|
-
stories,
|
|
802
|
-
checkpointInitializedAt:
|
|
803
|
-
checkpointState.startedAt ??
|
|
804
|
-
checkpointState.lastUpdatedAt ??
|
|
805
|
-
new Date().toISOString(),
|
|
806
|
-
concurrencyHazardsBypassed: gate.bypassed,
|
|
807
|
-
preflightCache: cacheStatus,
|
|
808
|
-
docsDigestPath,
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
async function main() {
|
|
813
|
-
const { values } = parseArgs({
|
|
814
|
-
options: {
|
|
815
|
-
epic: { type: 'string' },
|
|
816
|
-
single: { type: 'boolean', default: false },
|
|
817
|
-
help: { type: 'boolean', short: 'h' },
|
|
818
|
-
'ignore-concurrency-hazards': { type: 'boolean', default: false },
|
|
819
|
-
steal: { type: 'boolean', default: false },
|
|
820
|
-
as: { type: 'string' },
|
|
821
|
-
},
|
|
822
|
-
strict: false,
|
|
823
|
-
});
|
|
824
|
-
|
|
825
|
-
if (values.help) {
|
|
826
|
-
Logger.info(HELP);
|
|
827
|
-
return;
|
|
828
|
-
}
|
|
829
|
-
const epicId = Number.parseInt(values.epic ?? '', 10);
|
|
830
|
-
if (Number.isNaN(epicId) || epicId <= 0) {
|
|
831
|
-
Logger.error('[epic-deliver-prepare] ERROR: --epic <epicId> is required.');
|
|
832
|
-
Logger.error(HELP);
|
|
833
|
-
process.exit(2);
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
const asOperator = typeof values.as === 'string' ? values.as : undefined;
|
|
837
|
-
const steal = values.steal === true;
|
|
838
|
-
|
|
839
|
-
const result =
|
|
840
|
-
values.single === true
|
|
841
|
-
? await runEpicDeliverPrepareSingle({ epicId, asOperator, steal })
|
|
842
|
-
: await runEpicDeliverPrepare({
|
|
843
|
-
epicId,
|
|
844
|
-
ignoreConcurrencyHazards:
|
|
845
|
-
values['ignore-concurrency-hazards'] === true,
|
|
846
|
-
steal,
|
|
847
|
-
asOperator,
|
|
848
|
-
});
|
|
849
|
-
Logger.info(JSON.stringify(result, null, 2));
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
runAsCli(import.meta.url, main, { source: 'epic-deliver-prepare' });
|