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,621 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* epic-run-state-store — stateless functions for reading and writing the
|
|
3
|
-
* `epic-run-state` structured comment used by `/deliver`.
|
|
4
|
-
*
|
|
5
|
-
* Story #4155 (Epic #4151) — the Epic `/deliver` runtime cut over from
|
|
6
|
-
* the wave-batch scheduler to the continuous ready-set core
|
|
7
|
-
* (`lib/wave-runner/ready-set.js`). The checkpoint shrank with it: it no
|
|
8
|
-
* longer carries `currentWave`, `plan[][]`, `totalWaves`, or the
|
|
9
|
-
* per-wave `waves[]` aggregation. The durable run state is now a flat
|
|
10
|
-
* **per-Story status map** (`stories: { [storyId]: { status, title?,
|
|
11
|
-
* blockerCommentId? } }`) plus the run-level `concurrencyCap` (the
|
|
12
|
-
* GLOBAL in-flight cap the ready-set selector honours), `phase`,
|
|
13
|
-
* `startedAt`, and `manualInterventions[]`. There is no resume-pointer
|
|
14
|
-
* to reconcile — the ready-set core re-derives adjacency and readiness
|
|
15
|
-
* from live Story bodies/labels on every tick, so the checkpoint only
|
|
16
|
-
* records terminal Story outcomes (for the auto-merge predicate, branch
|
|
17
|
-
* cleanup, and the operator rollup) and the run-level knobs.
|
|
18
|
-
*
|
|
19
|
-
* This module is the function-based replacement for the legacy
|
|
20
|
-
* `Checkpointer` class that previously lived at
|
|
21
|
-
* `./epic-runner/checkpointer.js`. Story #2423 (Epic #2307) deleted the
|
|
22
|
-
* class file; the class API survives as a tests-only fixture at
|
|
23
|
-
* `tests/fixtures/epic-run-state-store.js`.
|
|
24
|
-
*
|
|
25
|
-
* The comment is identified by a stable HTML marker so it can be overwritten
|
|
26
|
-
* idempotently across orchestrator restarts. The body is a fenced JSON block.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import { assertValidDeliverPhase } from './epic-runner/deliver-phases.js';
|
|
30
|
-
import { parseFencedJsonComment } from './structured-comment-parser.js';
|
|
31
|
-
import { findStructuredComment, upsertStructuredComment } from './ticketing.js';
|
|
32
|
-
|
|
33
|
-
export const EPIC_RUN_STATE_TYPE = 'epic-run-state';
|
|
34
|
-
export const CHECKPOINT_SCHEMA_VERSION = 2;
|
|
35
|
-
|
|
36
|
-
/** Terminal / in-progress per-Story statuses persisted on the checkpoint. */
|
|
37
|
-
export const STORY_STATUSES = Object.freeze([
|
|
38
|
-
'pending',
|
|
39
|
-
'done',
|
|
40
|
-
'blocked',
|
|
41
|
-
'failed',
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
// Re-export the phase enum + index helper so downstream importers continue
|
|
45
|
-
// to use this module as a single import target.
|
|
46
|
-
export {
|
|
47
|
-
DELIVER_PHASES,
|
|
48
|
-
phaseIndex,
|
|
49
|
-
} from './epic-runner/deliver-phases.js';
|
|
50
|
-
|
|
51
|
-
function assertProvider(provider) {
|
|
52
|
-
if (!provider)
|
|
53
|
-
throw new TypeError('epic-run-state-store requires a provider');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function assertEpicId(epicId) {
|
|
57
|
-
if (!Number.isInteger(epicId)) {
|
|
58
|
-
throw new TypeError('epic-run-state-store requires a numeric epicId');
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Normalize an inbound Story id (accepts the ticket `id` shape, the raw
|
|
64
|
-
* GitHub `number` shape, and a bare integer) to a positive integer, or
|
|
65
|
-
* `null` when it is absent / non-positive / non-integer.
|
|
66
|
-
*
|
|
67
|
-
* @param {object|number|string} entry
|
|
68
|
-
* @returns {number|null}
|
|
69
|
-
*/
|
|
70
|
-
function storyIdOf(entry) {
|
|
71
|
-
if (typeof entry === 'number') {
|
|
72
|
-
return Number.isInteger(entry) && entry > 0 ? entry : null;
|
|
73
|
-
}
|
|
74
|
-
if (!entry || typeof entry !== 'object') {
|
|
75
|
-
const n = Number(entry);
|
|
76
|
-
return Number.isInteger(n) && n > 0 ? n : null;
|
|
77
|
-
}
|
|
78
|
-
const raw = entry.id ?? entry.storyId ?? entry.number;
|
|
79
|
-
const id = Number(raw);
|
|
80
|
-
return Number.isInteger(id) && id > 0 ? id : null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Build the initial per-Story status map from a list of Story records (or
|
|
85
|
-
* ids). Every Story seeds at `status: 'pending'`; an optional `title` is
|
|
86
|
-
* carried through when the record supplies one so the operator rollup and
|
|
87
|
-
* branch-cleanup surfaces have a label without a second fetch. Keys are the
|
|
88
|
-
* positive-integer Story ids as strings (JSON object keys are strings);
|
|
89
|
-
* shapeless / non-positive entries are dropped.
|
|
90
|
-
*
|
|
91
|
-
* Pure helper — exported for unit tests.
|
|
92
|
-
*
|
|
93
|
-
* @param {Array<object|number>} stories
|
|
94
|
-
* @returns {Record<string, { status: string, title?: string }>}
|
|
95
|
-
*/
|
|
96
|
-
export function buildStoryStatusMap(stories) {
|
|
97
|
-
const out = {};
|
|
98
|
-
for (const entry of Array.isArray(stories) ? stories : []) {
|
|
99
|
-
const id = storyIdOf(entry);
|
|
100
|
-
if (id === null) continue;
|
|
101
|
-
const record = { status: 'pending' };
|
|
102
|
-
const title =
|
|
103
|
-
entry && typeof entry === 'object' && typeof entry.title === 'string'
|
|
104
|
-
? entry.title
|
|
105
|
-
: undefined;
|
|
106
|
-
if (title) record.title = title;
|
|
107
|
-
out[String(id)] = record;
|
|
108
|
-
}
|
|
109
|
-
return out;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Read and parse the checkpoint. Returns null if the comment is missing or
|
|
114
|
-
* unparseable (callers treat null as "start fresh").
|
|
115
|
-
*
|
|
116
|
-
* @param {{ provider: import('../ITicketingProvider.js').ITicketingProvider, epicId: number }} opts
|
|
117
|
-
* @returns {Promise<object | null>}
|
|
118
|
-
*/
|
|
119
|
-
export async function read({ provider, epicId } = {}) {
|
|
120
|
-
assertProvider(provider);
|
|
121
|
-
assertEpicId(epicId);
|
|
122
|
-
const comment = await findStructuredComment(
|
|
123
|
-
provider,
|
|
124
|
-
epicId,
|
|
125
|
-
EPIC_RUN_STATE_TYPE,
|
|
126
|
-
);
|
|
127
|
-
return parseFencedJsonComment(comment);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Overwrite the checkpoint with `state`. Idempotent — callers may invoke
|
|
132
|
-
* freely per tick; the marker-scoped upsert deletes the prior comment.
|
|
133
|
-
*
|
|
134
|
-
* @param {{ provider: import('../ITicketingProvider.js').ITicketingProvider, epicId: number, state: object }} opts
|
|
135
|
-
*/
|
|
136
|
-
export async function write({ provider, epicId, state } = {}) {
|
|
137
|
-
assertProvider(provider);
|
|
138
|
-
assertEpicId(epicId);
|
|
139
|
-
const payload = {
|
|
140
|
-
version: CHECKPOINT_SCHEMA_VERSION,
|
|
141
|
-
...state,
|
|
142
|
-
lastUpdatedAt: new Date().toISOString(),
|
|
143
|
-
};
|
|
144
|
-
const body = `\`\`\`json\n${JSON.stringify(payload, null, 2)}\n\`\`\``;
|
|
145
|
-
await upsertStructuredComment(provider, epicId, EPIC_RUN_STATE_TYPE, body);
|
|
146
|
-
return payload;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Initial checkpoint for a brand-new run. Idempotent against re-dispatch:
|
|
151
|
-
* when an existing checkpoint is found and the persisted `concurrencyCap`
|
|
152
|
-
* matches the incoming value, the existing state is returned verbatim (no
|
|
153
|
-
* rewrite) so a re-prepare preserves `startedAt`, prior Story statuses, and
|
|
154
|
-
* `manualInterventions`. When the cap differs (an operator re-tuned the
|
|
155
|
-
* global in-flight cap) it is refreshed in place; the Story status map is
|
|
156
|
-
* **merged** so any Story that already reached a terminal status keeps it
|
|
157
|
-
* while newly-discovered Stories are seeded at `pending`. Prepare owns the
|
|
158
|
-
* Story set (it overwrites it on every run) but never clobbers recorded
|
|
159
|
-
* progress.
|
|
160
|
-
*
|
|
161
|
-
* @param {{
|
|
162
|
-
* provider: import('../ITicketingProvider.js').ITicketingProvider,
|
|
163
|
-
* epicId: number,
|
|
164
|
-
* storyIds: Array<object|number>,
|
|
165
|
-
* concurrencyCap: number,
|
|
166
|
-
* }} opts
|
|
167
|
-
* `concurrencyCap` is the GLOBAL in-flight cap the ready-set selector
|
|
168
|
-
* honours (`selectReadySet({ globalCap })`).
|
|
169
|
-
*/
|
|
170
|
-
export async function initialize({
|
|
171
|
-
provider,
|
|
172
|
-
epicId,
|
|
173
|
-
storyIds,
|
|
174
|
-
concurrencyCap,
|
|
175
|
-
} = {}) {
|
|
176
|
-
assertProvider(provider);
|
|
177
|
-
assertEpicId(epicId);
|
|
178
|
-
const seededStories = buildStoryStatusMap(storyIds);
|
|
179
|
-
const existing = await read({ provider, epicId });
|
|
180
|
-
if (existing) {
|
|
181
|
-
const mergedStories = mergeStoryStatuses(existing.stories, seededStories);
|
|
182
|
-
if (
|
|
183
|
-
existing.concurrencyCap === concurrencyCap &&
|
|
184
|
-
storyMapsEqual(existing.stories, mergedStories)
|
|
185
|
-
) {
|
|
186
|
-
return existing;
|
|
187
|
-
}
|
|
188
|
-
return write({
|
|
189
|
-
provider,
|
|
190
|
-
epicId,
|
|
191
|
-
state: { ...existing, concurrencyCap, stories: mergedStories },
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
return write({
|
|
195
|
-
provider,
|
|
196
|
-
epicId,
|
|
197
|
-
state: {
|
|
198
|
-
epicId,
|
|
199
|
-
startedAt: new Date().toISOString(),
|
|
200
|
-
concurrencyCap,
|
|
201
|
-
phase: 'prepare',
|
|
202
|
-
stories: seededStories,
|
|
203
|
-
manualInterventions: [],
|
|
204
|
-
},
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Merge a freshly-seeded Story status map onto a persisted one. Every Story
|
|
210
|
-
* present in either map appears in the result; when a Story exists in the
|
|
211
|
-
* prior map its recorded status / blockerCommentId win (recorded progress is
|
|
212
|
-
* never lost), while its `title` is refreshed from the incoming seed when the
|
|
213
|
-
* seed supplies one. Stories present only in the incoming seed are added at
|
|
214
|
-
* their seeded (`pending`) status. Pure — exported for unit tests.
|
|
215
|
-
*
|
|
216
|
-
* @param {Record<string, object>|undefined} prior
|
|
217
|
-
* @param {Record<string, object>} incoming
|
|
218
|
-
* @returns {Record<string, object>}
|
|
219
|
-
*/
|
|
220
|
-
export function mergeStoryStatuses(prior, incoming) {
|
|
221
|
-
const priorMap = prior && typeof prior === 'object' ? prior : {};
|
|
222
|
-
const seedMap = incoming && typeof incoming === 'object' ? incoming : {};
|
|
223
|
-
const out = {};
|
|
224
|
-
for (const key of new Set([
|
|
225
|
-
...Object.keys(priorMap),
|
|
226
|
-
...Object.keys(seedMap),
|
|
227
|
-
])) {
|
|
228
|
-
const priorEntry = priorMap[key];
|
|
229
|
-
const seedEntry = seedMap[key];
|
|
230
|
-
if (priorEntry && typeof priorEntry === 'object') {
|
|
231
|
-
const merged = { ...priorEntry };
|
|
232
|
-
if (seedEntry && typeof seedEntry.title === 'string') {
|
|
233
|
-
merged.title = seedEntry.title;
|
|
234
|
-
}
|
|
235
|
-
out[key] = merged;
|
|
236
|
-
} else {
|
|
237
|
-
out[key] = seedEntry;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return out;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Structural equality on two Story status maps — same key set and, per key,
|
|
245
|
-
* the same `status`, `title`, and `blockerCommentId`. Used by `initialize`
|
|
246
|
-
* to decide whether an idempotent re-prepare needs a rewrite. Pure.
|
|
247
|
-
*
|
|
248
|
-
* @param {Record<string, object>|undefined} a
|
|
249
|
-
* @param {Record<string, object>|undefined} b
|
|
250
|
-
* @returns {boolean}
|
|
251
|
-
*/
|
|
252
|
-
function storyMapsEqual(a, b) {
|
|
253
|
-
const left = a && typeof a === 'object' ? a : {};
|
|
254
|
-
const right = b && typeof b === 'object' ? b : {};
|
|
255
|
-
const keys = Object.keys(left);
|
|
256
|
-
if (keys.length !== Object.keys(right).length) return false;
|
|
257
|
-
for (const key of keys) {
|
|
258
|
-
const l = left[key] ?? {};
|
|
259
|
-
const r = right[key];
|
|
260
|
-
if (!r) return false;
|
|
261
|
-
if (
|
|
262
|
-
l.status !== r.status ||
|
|
263
|
-
l.title !== r.title ||
|
|
264
|
-
l.blockerCommentId !== r.blockerCommentId
|
|
265
|
-
) {
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
return true;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Record a per-Story terminal (or in-progress) status on the checkpoint.
|
|
274
|
-
* Reads the current state first, splices the single Story's record into the
|
|
275
|
-
* `stories` map, and re-writes. Other Stories and all run-level fields are
|
|
276
|
-
* preserved verbatim. Tolerant of a legacy/absent `stories` map (treated as
|
|
277
|
-
* empty) so a checkpoint that predates a field is upgraded in place.
|
|
278
|
-
*
|
|
279
|
-
* @param {{
|
|
280
|
-
* provider: import('../ITicketingProvider.js').ITicketingProvider,
|
|
281
|
-
* epicId: number,
|
|
282
|
-
* storyId: number,
|
|
283
|
-
* status: string,
|
|
284
|
-
* title?: string,
|
|
285
|
-
* blockerCommentId?: string|number|null,
|
|
286
|
-
* }} opts
|
|
287
|
-
* @returns {Promise<object>} the persisted state
|
|
288
|
-
*/
|
|
289
|
-
export async function recordStoryStatus({
|
|
290
|
-
provider,
|
|
291
|
-
epicId,
|
|
292
|
-
storyId,
|
|
293
|
-
status,
|
|
294
|
-
title,
|
|
295
|
-
blockerCommentId,
|
|
296
|
-
} = {}) {
|
|
297
|
-
assertProvider(provider);
|
|
298
|
-
assertEpicId(epicId);
|
|
299
|
-
const id = storyIdOf(storyId);
|
|
300
|
-
if (id === null) {
|
|
301
|
-
throw new TypeError(
|
|
302
|
-
'recordStoryStatus: storyId must be a positive integer',
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
if (!STORY_STATUSES.includes(status)) {
|
|
306
|
-
throw new RangeError(
|
|
307
|
-
`recordStoryStatus: status "${status}" must be one of: ${STORY_STATUSES.join(', ')}`,
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
const existing = (await read({ provider, epicId })) ?? {};
|
|
311
|
-
const stories =
|
|
312
|
-
existing.stories && typeof existing.stories === 'object'
|
|
313
|
-
? { ...existing.stories }
|
|
314
|
-
: {};
|
|
315
|
-
const prior = stories[String(id)] ?? {};
|
|
316
|
-
const record = { ...prior, status };
|
|
317
|
-
if (typeof title === 'string' && title) record.title = title;
|
|
318
|
-
if (status === 'blocked' && blockerCommentId != null) {
|
|
319
|
-
record.blockerCommentId = String(blockerCommentId);
|
|
320
|
-
}
|
|
321
|
-
stories[String(id)] = record;
|
|
322
|
-
return write({
|
|
323
|
-
provider,
|
|
324
|
-
epicId,
|
|
325
|
-
state: { ...existing, stories },
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Terminal / in-progress per-slice statuses persisted on a single-delivery
|
|
331
|
-
* checkpoint. The single-delivery analogue of `STORY_STATUSES` — a spec-only
|
|
332
|
-
* plan authors no Story tickets, so the leaf work unit is a `## Delivery
|
|
333
|
-
* Slicing` slice walked in-session on `epic/<id>` (Epic #4475). The design's
|
|
334
|
-
* durable contract is `{pending, done}`; `blocked`/`failed` mirror the story
|
|
335
|
-
* map so the M4-B executor can record a stalled slice without a schema bump.
|
|
336
|
-
*/
|
|
337
|
-
export const SLICE_STATUSES = Object.freeze([
|
|
338
|
-
'pending',
|
|
339
|
-
'done',
|
|
340
|
-
'blocked',
|
|
341
|
-
'failed',
|
|
342
|
-
]);
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Build the initial per-slice status map from parsed `## Delivery Slicing`
|
|
346
|
-
* rows (`parseDeliverySlicingTable` output — `[{ slice, independent }]`).
|
|
347
|
-
* Every slice seeds at `status: 'pending'`, keyed by a **stable, position-
|
|
348
|
-
* derived** id `slice-<n>` (1-based) so a re-run over the same table
|
|
349
|
-
* round-trips its keys. The slice's human label is carried through as `title`
|
|
350
|
-
* for the operator rollup. Pure — exported for unit tests.
|
|
351
|
-
*
|
|
352
|
-
* @param {Array<{ slice?: string, independent?: boolean } | string>} slices
|
|
353
|
-
* @returns {Record<string, { status: string, title?: string }>}
|
|
354
|
-
*/
|
|
355
|
-
export function buildSliceStatusMap(slices) {
|
|
356
|
-
const out = {};
|
|
357
|
-
const rows = Array.isArray(slices) ? slices : [];
|
|
358
|
-
rows.forEach((entry, i) => {
|
|
359
|
-
const sliceId = `slice-${i + 1}`;
|
|
360
|
-
const record = { status: 'pending' };
|
|
361
|
-
const title =
|
|
362
|
-
typeof entry === 'string'
|
|
363
|
-
? entry
|
|
364
|
-
: entry && typeof entry === 'object' && typeof entry.slice === 'string'
|
|
365
|
-
? entry.slice
|
|
366
|
-
: undefined;
|
|
367
|
-
if (title) record.title = title;
|
|
368
|
-
out[sliceId] = record;
|
|
369
|
-
});
|
|
370
|
-
return out;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Merge a freshly-seeded slice status map onto a persisted one. Every slice
|
|
375
|
-
* present in either map appears in the result; when a slice exists in the
|
|
376
|
-
* prior map its recorded `status` wins (recorded progress — a `done` slice
|
|
377
|
-
* whose work already sits on `epic/<id>` — is never lost), while its `title`
|
|
378
|
-
* is refreshed from the incoming seed. Slices present only in the incoming
|
|
379
|
-
* seed are added at `pending`. Pure — exported for unit tests.
|
|
380
|
-
*
|
|
381
|
-
* @param {Record<string, object>|undefined} prior
|
|
382
|
-
* @param {Record<string, object>} incoming
|
|
383
|
-
* @returns {Record<string, object>}
|
|
384
|
-
*/
|
|
385
|
-
export function mergeSliceStatuses(prior, incoming) {
|
|
386
|
-
const priorMap = prior && typeof prior === 'object' ? prior : {};
|
|
387
|
-
const seedMap = incoming && typeof incoming === 'object' ? incoming : {};
|
|
388
|
-
const out = {};
|
|
389
|
-
for (const key of new Set([
|
|
390
|
-
...Object.keys(priorMap),
|
|
391
|
-
...Object.keys(seedMap),
|
|
392
|
-
])) {
|
|
393
|
-
const priorEntry = priorMap[key];
|
|
394
|
-
const seedEntry = seedMap[key];
|
|
395
|
-
if (priorEntry && typeof priorEntry === 'object') {
|
|
396
|
-
const merged = { ...priorEntry };
|
|
397
|
-
if (seedEntry && typeof seedEntry.title === 'string') {
|
|
398
|
-
merged.title = seedEntry.title;
|
|
399
|
-
}
|
|
400
|
-
out[key] = merged;
|
|
401
|
-
} else {
|
|
402
|
-
out[key] = seedEntry;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return out;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Initialize (or resume) the single-delivery checkpoint for an Epic. The
|
|
410
|
-
* single-delivery analogue of `initialize` (Story #4155's per-Story map),
|
|
411
|
-
* writing the slice-map shape the M4-B executor walks:
|
|
412
|
-
*
|
|
413
|
-
* ```json
|
|
414
|
-
* {
|
|
415
|
-
* "epicId": 4475,
|
|
416
|
-
* "deliveryShape": "single",
|
|
417
|
-
* "slices": { "slice-1": { "status": "pending", "title": "…" } },
|
|
418
|
-
* "concurrencyCap": 1,
|
|
419
|
-
* "storyCount": 0
|
|
420
|
-
* }
|
|
421
|
-
* ```
|
|
422
|
-
*
|
|
423
|
-
* Idempotent + resume-preserving: on a re-run an existing single checkpoint
|
|
424
|
-
* has its slice map **merged** (every already-`done` slice keeps its status
|
|
425
|
-
* so the executor skips it — the work already sits on `epic/<id>`), while
|
|
426
|
-
* newly-authored slices seed at `pending`. `startedAt` is preserved. A
|
|
427
|
-
* checkpoint written for the fan-out shape is NOT reinterpreted here — the
|
|
428
|
-
* caller (`epic-deliver-prepare.js --single`) owns route selection.
|
|
429
|
-
*
|
|
430
|
-
* @param {{
|
|
431
|
-
* provider: import('../ITicketingProvider.js').ITicketingProvider,
|
|
432
|
-
* epicId: number,
|
|
433
|
-
* slices: Array<{ slice?: string, independent?: boolean } | string>,
|
|
434
|
-
* concurrencyCap?: number,
|
|
435
|
-
* }} opts
|
|
436
|
-
* @returns {Promise<object>} the persisted (or preserved) state
|
|
437
|
-
*/
|
|
438
|
-
export async function initializeSingle({
|
|
439
|
-
provider,
|
|
440
|
-
epicId,
|
|
441
|
-
slices,
|
|
442
|
-
concurrencyCap = 1,
|
|
443
|
-
} = {}) {
|
|
444
|
-
assertProvider(provider);
|
|
445
|
-
assertEpicId(epicId);
|
|
446
|
-
const seededSlices = buildSliceStatusMap(slices);
|
|
447
|
-
const storyCount = 0;
|
|
448
|
-
const existing = await read({ provider, epicId });
|
|
449
|
-
if (existing && existing.deliveryShape === 'single') {
|
|
450
|
-
const mergedSlices = mergeSliceStatuses(existing.slices, seededSlices);
|
|
451
|
-
if (
|
|
452
|
-
existing.concurrencyCap === concurrencyCap &&
|
|
453
|
-
sliceMapsEqual(existing.slices, mergedSlices)
|
|
454
|
-
) {
|
|
455
|
-
return existing;
|
|
456
|
-
}
|
|
457
|
-
return write({
|
|
458
|
-
provider,
|
|
459
|
-
epicId,
|
|
460
|
-
state: {
|
|
461
|
-
...existing,
|
|
462
|
-
deliveryShape: 'single',
|
|
463
|
-
concurrencyCap,
|
|
464
|
-
storyCount,
|
|
465
|
-
slices: mergedSlices,
|
|
466
|
-
},
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
return write({
|
|
470
|
-
provider,
|
|
471
|
-
epicId,
|
|
472
|
-
state: {
|
|
473
|
-
epicId,
|
|
474
|
-
startedAt: new Date().toISOString(),
|
|
475
|
-
deliveryShape: 'single',
|
|
476
|
-
concurrencyCap,
|
|
477
|
-
storyCount,
|
|
478
|
-
phase: 'prepare',
|
|
479
|
-
slices: seededSlices,
|
|
480
|
-
manualInterventions: [],
|
|
481
|
-
},
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Record a per-slice terminal (or in-progress) status on a single-delivery
|
|
487
|
-
* checkpoint. The single-delivery analogue of `recordStoryStatus`: the M4-B
|
|
488
|
-
* executor calls this after each Delivery-Slicing slice commits to
|
|
489
|
-
* `epic/<id>`, flipping `slices[sliceId].status` `pending → done` so a resumed
|
|
490
|
-
* run skips the already-landed slice. Reads the current state first, splices
|
|
491
|
-
* the single slice's record into the `slices` map, and re-writes; other slices
|
|
492
|
-
* and all run-level fields are preserved verbatim. Tolerant of a
|
|
493
|
-
* legacy/absent `slices` map (treated as empty).
|
|
494
|
-
*
|
|
495
|
-
* @param {{
|
|
496
|
-
* provider: import('../ITicketingProvider.js').ITicketingProvider,
|
|
497
|
-
* epicId: number,
|
|
498
|
-
* sliceId: string,
|
|
499
|
-
* status: string,
|
|
500
|
-
* title?: string,
|
|
501
|
-
* }} opts
|
|
502
|
-
* @returns {Promise<object>} the persisted state
|
|
503
|
-
*/
|
|
504
|
-
export async function recordSliceStatus({
|
|
505
|
-
provider,
|
|
506
|
-
epicId,
|
|
507
|
-
sliceId,
|
|
508
|
-
status,
|
|
509
|
-
title,
|
|
510
|
-
} = {}) {
|
|
511
|
-
assertProvider(provider);
|
|
512
|
-
assertEpicId(epicId);
|
|
513
|
-
if (typeof sliceId !== 'string' || sliceId.length === 0) {
|
|
514
|
-
throw new TypeError(
|
|
515
|
-
'recordSliceStatus: sliceId must be a non-empty string',
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
if (!SLICE_STATUSES.includes(status)) {
|
|
519
|
-
throw new RangeError(
|
|
520
|
-
`recordSliceStatus: status "${status}" must be one of: ${SLICE_STATUSES.join(', ')}`,
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
const existing = (await read({ provider, epicId })) ?? {};
|
|
524
|
-
const slices =
|
|
525
|
-
existing.slices && typeof existing.slices === 'object'
|
|
526
|
-
? { ...existing.slices }
|
|
527
|
-
: {};
|
|
528
|
-
const prior = slices[sliceId] ?? {};
|
|
529
|
-
const record = { ...prior, status };
|
|
530
|
-
if (typeof title === 'string' && title) record.title = title;
|
|
531
|
-
slices[sliceId] = record;
|
|
532
|
-
return write({
|
|
533
|
-
provider,
|
|
534
|
-
epicId,
|
|
535
|
-
state: { ...existing, slices },
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* Structural equality on two slice status maps — same key set and, per key,
|
|
541
|
-
* the same `status` and `title`. Used by `initializeSingle` to decide whether
|
|
542
|
-
* an idempotent re-prepare needs a rewrite. Pure.
|
|
543
|
-
*
|
|
544
|
-
* @param {Record<string, object>|undefined} a
|
|
545
|
-
* @param {Record<string, object>|undefined} b
|
|
546
|
-
* @returns {boolean}
|
|
547
|
-
*/
|
|
548
|
-
function sliceMapsEqual(a, b) {
|
|
549
|
-
const left = a && typeof a === 'object' ? a : {};
|
|
550
|
-
const right = b && typeof b === 'object' ? b : {};
|
|
551
|
-
const keys = Object.keys(left);
|
|
552
|
-
if (keys.length !== Object.keys(right).length) return false;
|
|
553
|
-
for (const key of keys) {
|
|
554
|
-
const l = left[key] ?? {};
|
|
555
|
-
const r = right[key];
|
|
556
|
-
if (!r) return false;
|
|
557
|
-
if (l.status !== r.status || l.title !== r.title) return false;
|
|
558
|
-
}
|
|
559
|
-
return true;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* Append a manual-intervention record to the checkpoint. Out-of-band
|
|
564
|
-
* recovery steps the host LLM performs during a delivery — `AskUserQuestion`
|
|
565
|
-
* calls, `git restore`/`git reset` against the working tree, manual `--no-ff`
|
|
566
|
-
* recovery merges, story-close `--skipValidation` overrides — disqualify the
|
|
567
|
-
* Epic from auto-merge. The auto-merge predicate reads this array and only
|
|
568
|
-
* fires when it is empty.
|
|
569
|
-
*
|
|
570
|
-
* @param {{ provider: import('../ITicketingProvider.js').ITicketingProvider, epicId: number, entry: { reason: string, source?: string, ts?: string } }} opts
|
|
571
|
-
* @returns {Promise<object>} the persisted state
|
|
572
|
-
*/
|
|
573
|
-
export async function appendIntervention({ provider, epicId, entry } = {}) {
|
|
574
|
-
assertProvider(provider);
|
|
575
|
-
assertEpicId(epicId);
|
|
576
|
-
if (!entry || typeof entry.reason !== 'string' || entry.reason.length === 0) {
|
|
577
|
-
throw new TypeError('appendIntervention: { reason: string } is required.');
|
|
578
|
-
}
|
|
579
|
-
const existing = (await read({ provider, epicId })) ?? {};
|
|
580
|
-
const list = Array.isArray(existing.manualInterventions)
|
|
581
|
-
? existing.manualInterventions
|
|
582
|
-
: [];
|
|
583
|
-
const record = {
|
|
584
|
-
reason: entry.reason,
|
|
585
|
-
source: typeof entry.source === 'string' ? entry.source : 'host-llm',
|
|
586
|
-
ts: typeof entry.ts === 'string' ? entry.ts : new Date().toISOString(),
|
|
587
|
-
};
|
|
588
|
-
return write({
|
|
589
|
-
provider,
|
|
590
|
-
epicId,
|
|
591
|
-
state: {
|
|
592
|
-
...existing,
|
|
593
|
-
manualInterventions: [...list, record],
|
|
594
|
-
},
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* Advance the checkpoint's `phase` field to the next `/deliver`
|
|
600
|
-
* phase. Reads the current state first so the caller does not need to
|
|
601
|
-
* keep an in-memory copy. Other state fields are preserved verbatim.
|
|
602
|
-
*
|
|
603
|
-
* Story #1155 / Epic #1142 — phase-granular resume. The runner writes
|
|
604
|
-
* the **next phase to run** here, not the phase that just finished, so
|
|
605
|
-
* a resume can match `phase === 'code-review'` to mean "Phase D is the
|
|
606
|
-
* next thing to do."
|
|
607
|
-
*
|
|
608
|
-
* @param {{ provider: import('../ITicketingProvider.js').ITicketingProvider, epicId: number, nextPhase: string }} opts
|
|
609
|
-
* nextPhase - One of `DELIVER_PHASES` or `'done'`.
|
|
610
|
-
*/
|
|
611
|
-
export async function setPhase({ provider, epicId, nextPhase } = {}) {
|
|
612
|
-
assertProvider(provider);
|
|
613
|
-
assertEpicId(epicId);
|
|
614
|
-
assertValidDeliverPhase(nextPhase);
|
|
615
|
-
const existing = (await read({ provider, epicId })) ?? {};
|
|
616
|
-
return write({
|
|
617
|
-
provider,
|
|
618
|
-
epicId,
|
|
619
|
-
state: { ...existing, phase: nextPhase },
|
|
620
|
-
});
|
|
621
|
-
}
|