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,625 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* node:coverage ignore file */
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* epic-reconcile.js — CLI surface for the structural epic-spec reconciler.
|
|
6
|
-
*
|
|
7
|
-
* Story #1496 (Epic #1182 / Tech Spec #1483). Thin wrapper that
|
|
8
|
-
* composes the existing reconciler modules into the operator-facing
|
|
9
|
-
* surface specified in the Tech Spec:
|
|
10
|
-
*
|
|
11
|
-
* node .agents/scripts/epic-reconcile.js <epicId>
|
|
12
|
-
* [--dry-run] (default)
|
|
13
|
-
* [--apply]
|
|
14
|
-
* [--explicit-delete]
|
|
15
|
-
* [--yes]
|
|
16
|
-
*
|
|
17
|
-
* Control flow (Tech Spec §"Reconciler control flow"):
|
|
18
|
-
*
|
|
19
|
-
* 1. Load `.agents/epics/<id>.yaml` via `lib/spec/loader.js`. Validation
|
|
20
|
-
* failure exits 1 with the failing JSON Pointer.
|
|
21
|
-
* 2. Load `.agents/epics/<id>.state.json` (absent → empty mapping).
|
|
22
|
-
* 3. Fetch the live GH state via the configured `ITicketingProvider`.
|
|
23
|
-
* 4. Compute the plan via `epic-spec-reconciler-diff.js#diff`.
|
|
24
|
-
* 5. Render the plan via `epic-spec-reconciler-format.js#formatPlan`.
|
|
25
|
-
* 6. On `--dry-run` (default) — print and exit 0.
|
|
26
|
-
* On `--apply`:
|
|
27
|
-
* - When the plan carries close ops without `--explicit-delete`,
|
|
28
|
-
* exit 2.
|
|
29
|
-
* - When the plan is non-empty, in a TTY, prompt unless `--yes`.
|
|
30
|
-
* Non-TTY without `--yes` aborts (exit 1).
|
|
31
|
-
* - Invoke `apply()` with the appropriate gate flags.
|
|
32
|
-
*
|
|
33
|
-
* Exit codes (Story #1496 / Task #1521):
|
|
34
|
-
* - 0 No diff or successful apply.
|
|
35
|
-
* - 1 Validation error (`SpecValidationError`, `SpecParseError`,
|
|
36
|
-
* `SpecNotFoundError`), or a non-TTY `--apply` without `--yes`,
|
|
37
|
-
* or an apply-phase provider failure.
|
|
38
|
-
* - 2 Close operation requires `--explicit-delete` but the flag was
|
|
39
|
-
* absent.
|
|
40
|
-
*
|
|
41
|
-
* The CLI is intentionally thin: every behaviour that matters for
|
|
42
|
-
* downstream automation is rooted in the underlying modules. This file
|
|
43
|
-
* adds three things on top: argument parsing, the confirmation gate,
|
|
44
|
-
* and the exit-code contract.
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
import readline from 'node:readline';
|
|
48
|
-
import { parseArgs } from 'node:util';
|
|
49
|
-
|
|
50
|
-
import { runAsCli } from './lib/cli-utils.js';
|
|
51
|
-
import { resolveConfig } from './lib/config-resolver.js';
|
|
52
|
-
import { Logger } from './lib/Logger.js';
|
|
53
|
-
import {
|
|
54
|
-
ApplyGateViolation,
|
|
55
|
-
apply,
|
|
56
|
-
} from './lib/orchestration/epic-spec-reconciler-apply.js';
|
|
57
|
-
import { diff } from './lib/orchestration/epic-spec-reconciler-diff.js';
|
|
58
|
-
import { formatPlan } from './lib/orchestration/epic-spec-reconciler-format.js';
|
|
59
|
-
import { isEmptyPlan } from './lib/orchestration/epic-spec-reconciler-ops.js';
|
|
60
|
-
import { createProvider } from './lib/provider-factory.js';
|
|
61
|
-
import {
|
|
62
|
-
loadSpec,
|
|
63
|
-
loadState,
|
|
64
|
-
SpecNotFoundError,
|
|
65
|
-
SpecParseError,
|
|
66
|
-
SpecValidationError,
|
|
67
|
-
} from './lib/spec/index.js';
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Exit-code contract (see header). Exported for tests so they can assert
|
|
71
|
-
* the constants without re-deriving the integers from prose.
|
|
72
|
-
*/
|
|
73
|
-
export const EXIT_CODES = Object.freeze({
|
|
74
|
-
OK: 0,
|
|
75
|
-
VALIDATION_ERROR: 1,
|
|
76
|
-
EXPLICIT_DELETE_REQUIRED: 2,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Default writer for log/output streams. Tests inject sinks so they can
|
|
81
|
-
* inspect what the CLI printed without intercepting process.stdout.
|
|
82
|
-
*/
|
|
83
|
-
function defaultStdout(line) {
|
|
84
|
-
process.stdout.write(`${line}\n`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function defaultStderr(line) {
|
|
88
|
-
process.stderr.write(`${line}\n`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Parse the CLI argv into a typed bag. Exported for tests.
|
|
93
|
-
*
|
|
94
|
-
* @param {string[]} argv argv slice (excluding `node` and the script path).
|
|
95
|
-
* @returns {{
|
|
96
|
-
* epicId: number|null,
|
|
97
|
-
* dryRun: boolean,
|
|
98
|
-
* apply: boolean,
|
|
99
|
-
* explicitDelete: boolean,
|
|
100
|
-
* yes: boolean,
|
|
101
|
-
* raw: object
|
|
102
|
-
* }}
|
|
103
|
-
*/
|
|
104
|
-
export function parseCli(argv) {
|
|
105
|
-
const { values, positionals } = parseArgs({
|
|
106
|
-
args: argv,
|
|
107
|
-
allowPositionals: true,
|
|
108
|
-
options: {
|
|
109
|
-
'dry-run': { type: 'boolean', default: false },
|
|
110
|
-
apply: { type: 'boolean', default: false },
|
|
111
|
-
'explicit-delete': { type: 'boolean', default: false },
|
|
112
|
-
yes: { type: 'boolean', default: false },
|
|
113
|
-
},
|
|
114
|
-
strict: false,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// Default behaviour: when neither --apply nor --dry-run is set, the
|
|
118
|
-
// CLI runs in dry-run mode (Tech Spec: "dry-run default"). When both
|
|
119
|
-
// are passed, --dry-run wins — safer to no-op than to apply by accident.
|
|
120
|
-
const explicitApply = values.apply === true;
|
|
121
|
-
const explicitDryRun = values['dry-run'] === true;
|
|
122
|
-
const dryRun = explicitDryRun || !explicitApply;
|
|
123
|
-
|
|
124
|
-
const epicArg = positionals[0];
|
|
125
|
-
const epicId =
|
|
126
|
-
epicArg != null && /^\d+$/.test(String(epicArg))
|
|
127
|
-
? Number.parseInt(String(epicArg), 10)
|
|
128
|
-
: null;
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
epicId,
|
|
132
|
-
dryRun,
|
|
133
|
-
apply: explicitApply && !explicitDryRun,
|
|
134
|
-
explicitDelete: values['explicit-delete'] === true,
|
|
135
|
-
yes: values.yes === true,
|
|
136
|
-
raw: values,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Project the GH state observation into the shape the diff engine
|
|
142
|
-
* expects: `{ [issueNumber]: { title, body?, labels?, state } }`.
|
|
143
|
-
*
|
|
144
|
-
* Enumerates the Epic's children via the **server-side-scoped**
|
|
145
|
-
* `getSubTickets(epicId)` (native sub-issue GraphQL graph → checklist
|
|
146
|
-
* links → reverse-search) rather than `getTickets(epicId)`'s repo-wide
|
|
147
|
-
* `state=all` scan-and-filter (Story #3455). The structural diff engine
|
|
148
|
-
* never branches on `obs.state` — Close detection is driven by
|
|
149
|
-
* spec-vs-mapping, `fieldChanges` compares only `title/body/labels/wave`,
|
|
150
|
-
* and `ghState` is consulted only to look up an issue by the number
|
|
151
|
-
* already in the state-file mapping — so dropping the repo-wide
|
|
152
|
-
* closed-issue over-fetch changes nothing but cost (and removes the
|
|
153
|
-
* latent spurious-Update risk on a re-plan of a partially-delivered
|
|
154
|
-
* Epic). The provider's returned children come back as full ticket
|
|
155
|
-
* objects (`id, title, body, labels, state`); the diff engine ignores
|
|
156
|
-
* fields it doesn't use.
|
|
157
|
-
*
|
|
158
|
-
* @param {object} provider
|
|
159
|
-
* @param {number} epicId
|
|
160
|
-
* @returns {Promise<Record<number, {title:string,body?:string,labels?:string[],state?:string}>>}
|
|
161
|
-
*/
|
|
162
|
-
export async function fetchGhState(provider, epicId) {
|
|
163
|
-
const ghState = {};
|
|
164
|
-
// Epic itself (best-effort — older providers may stub getEpic; treat
|
|
165
|
-
// a thrown call as "epic not observable" and continue with children).
|
|
166
|
-
try {
|
|
167
|
-
if (typeof provider.getEpic === 'function') {
|
|
168
|
-
const epic = await provider.getEpic(epicId);
|
|
169
|
-
if (epic && typeof epic.id === 'number') {
|
|
170
|
-
ghState[epic.id] = {
|
|
171
|
-
title: epic.title,
|
|
172
|
-
body: epic.body ?? '',
|
|
173
|
-
labels: epic.labels ?? [],
|
|
174
|
-
state: epic.state ?? 'open',
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
} catch (_err) {
|
|
179
|
-
/* swallow — epic body observation is non-fatal for diff */
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (typeof provider.getSubTickets === 'function') {
|
|
183
|
-
const tickets = await provider.getSubTickets(epicId);
|
|
184
|
-
for (const t of tickets ?? []) {
|
|
185
|
-
if (typeof t.id !== 'number') continue;
|
|
186
|
-
ghState[t.id] = {
|
|
187
|
-
title: t.title,
|
|
188
|
-
body: t.body ?? '',
|
|
189
|
-
labels: t.labels ?? [],
|
|
190
|
-
state: t.state ?? 'open',
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return ghState;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Walk the spec and yield one `{ slug, entity, title, parentSlug,
|
|
199
|
-
* dependsOn }` record per logical entity (epic → story), mirroring the
|
|
200
|
-
* diff engine's `flattenSpec`. Local to the CLI so the reseed pass
|
|
201
|
-
* (below) can map spec slugs onto live GH issues without importing the
|
|
202
|
-
* diff engine's private walker. Pure.
|
|
203
|
-
*
|
|
204
|
-
* @param {object} spec
|
|
205
|
-
* @returns {Array<{slug: string, entity: string, title: string, parentSlug: string|null, dependsOn: string[]}>}
|
|
206
|
-
*/
|
|
207
|
-
export function flattenSpecForReseed(spec) {
|
|
208
|
-
const out = [];
|
|
209
|
-
if (!spec || typeof spec !== 'object') return out;
|
|
210
|
-
if (spec.epic && typeof spec.epic === 'object') {
|
|
211
|
-
out.push({
|
|
212
|
-
slug: 'epic',
|
|
213
|
-
entity: 'epic',
|
|
214
|
-
title: String(spec.epic.title ?? ''),
|
|
215
|
-
parentSlug: null,
|
|
216
|
-
dependsOn: [],
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
for (const story of spec.stories ?? []) {
|
|
220
|
-
out.push({
|
|
221
|
-
slug: story.slug,
|
|
222
|
-
entity: 'story',
|
|
223
|
-
title: String(story.title ?? ''),
|
|
224
|
-
parentSlug: 'epic',
|
|
225
|
-
dependsOn: story.dependsOn ?? [],
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
return out;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Story #3905 — recover the slug→issue mapping from live GitHub state
|
|
233
|
-
* when `state.json` is missing or incomplete.
|
|
234
|
-
*
|
|
235
|
-
* The reconciler's idempotency on `--resume` rests entirely on the
|
|
236
|
-
* gitignored `temp/epic-<id>/<id>.state.json` slug→issue map. When that
|
|
237
|
-
* file is absent (a fresh checkout, a reaped temp dir, the exact
|
|
238
|
-
* situation `--resume` exists to recover from), `loadState` returns an
|
|
239
|
-
* empty mapping, the diff engine sees every spec slug as unmapped, and
|
|
240
|
-
* `apply` recreates the entire Story set on top of the existing
|
|
241
|
-
* one — duplicating every child. This is precisely the failure `--resume`
|
|
242
|
-
* is meant to prevent.
|
|
243
|
-
*
|
|
244
|
-
* This pass closes the gap: for every spec slug that is **not** already
|
|
245
|
-
* in the mapping, it looks for an **open** GH issue whose title matches
|
|
246
|
-
* the spec entity's title (and whose issue number is not already claimed
|
|
247
|
-
* by another mapping entry). A match seeds a mapping entry carrying the
|
|
248
|
-
* structural edges (`entity`, `parentSlug`, `dependsOn`) the diff engine
|
|
249
|
-
* reads, so the slug diffs as an Update/no-op rather than a Create.
|
|
250
|
-
*
|
|
251
|
-
* Pure: it mutates and returns a shallow clone of `state.mapping`; it
|
|
252
|
-
* performs no I/O (the caller supplies the already-fetched `ghState`).
|
|
253
|
-
* Title matching is intentionally conservative — an unmatched slug is
|
|
254
|
-
* left unmapped so a genuinely-missing child is still created (the
|
|
255
|
-
* partial-persist case `--resume` also serves). Returns the reseeded
|
|
256
|
-
* state plus the list of slugs it recovered so the caller can log them.
|
|
257
|
-
*
|
|
258
|
-
* @param {{epicId: number, mapping: Record<string, object>}} state
|
|
259
|
-
* @param {object} spec
|
|
260
|
-
* @param {Record<string|number, {title?: string, state?: string}>} ghState
|
|
261
|
-
* @returns {{ state: object, reseeded: Array<{slug: string, issueNumber: number}> }}
|
|
262
|
-
*/
|
|
263
|
-
export function reseedMappingFromGh(state, spec, ghState) {
|
|
264
|
-
const reseeded = [];
|
|
265
|
-
const mapping = { ...(state?.mapping ?? {}) };
|
|
266
|
-
if (!spec || typeof spec !== 'object' || !ghState) {
|
|
267
|
-
return { state: { ...state, mapping }, reseeded };
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Issue numbers already claimed by the current mapping must not be
|
|
271
|
-
// re-bound to a second slug.
|
|
272
|
-
const claimed = new Set();
|
|
273
|
-
for (const entry of Object.values(mapping)) {
|
|
274
|
-
if (entry && typeof entry.issueNumber === 'number') {
|
|
275
|
-
claimed.add(entry.issueNumber);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Index OPEN gh issues by title → [issueNumber]. Closed issues are
|
|
280
|
-
// skipped: a `--force`/`--resume` should never re-bind a slug to a
|
|
281
|
-
// tombstoned issue.
|
|
282
|
-
const openByTitle = new Map();
|
|
283
|
-
for (const [num, obs] of Object.entries(ghState)) {
|
|
284
|
-
const issueNumber = Number(num);
|
|
285
|
-
if (!Number.isInteger(issueNumber)) continue;
|
|
286
|
-
if (obs?.state && obs.state !== 'open') continue;
|
|
287
|
-
const title = typeof obs?.title === 'string' ? obs.title : null;
|
|
288
|
-
if (title == null) continue;
|
|
289
|
-
if (!openByTitle.has(title)) openByTitle.set(title, []);
|
|
290
|
-
openByTitle.get(title).push(issueNumber);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
for (const entity of flattenSpecForReseed(spec)) {
|
|
294
|
-
// Epic #4474 (PR3) — a mapping entry WITHOUT an issue number is a
|
|
295
|
-
// partial-failure tombstone (the apply's state writer persists every
|
|
296
|
-
// spec slug; only completed creations carry `issueNumber`). Treat it
|
|
297
|
-
// as unmapped here so a title-matched live issue reseeds it instead
|
|
298
|
-
// of leaving a numberless entry the diff engine can neither update
|
|
299
|
-
// nor (post-fix) skip-create.
|
|
300
|
-
if (Number.isInteger(mapping[entity.slug]?.issueNumber)) continue; // already mapped
|
|
301
|
-
const candidates = openByTitle.get(entity.title) ?? [];
|
|
302
|
-
// Pick the lowest unclaimed candidate for determinism.
|
|
303
|
-
const match = candidates
|
|
304
|
-
.filter((n) => !claimed.has(n))
|
|
305
|
-
.sort((a, b) => a - b)[0];
|
|
306
|
-
if (typeof match !== 'number') continue;
|
|
307
|
-
claimed.add(match);
|
|
308
|
-
mapping[entity.slug] = {
|
|
309
|
-
issueNumber: match,
|
|
310
|
-
contentHash: '',
|
|
311
|
-
lastObservedAgentState: null,
|
|
312
|
-
entity: entity.entity,
|
|
313
|
-
parentSlug: entity.parentSlug,
|
|
314
|
-
...(entity.dependsOn.length > 0 ? { dependsOn: entity.dependsOn } : {}),
|
|
315
|
-
};
|
|
316
|
-
reseeded.push({ slug: entity.slug, issueNumber: match });
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return { state: { ...state, mapping }, reseeded };
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Prompt the operator for confirmation on the supplied plan. Resolves
|
|
324
|
-
* `true` when the user answers `y`/`yes` (case-insensitive), `false`
|
|
325
|
-
* otherwise. Uses `readline` against `process.stdin`/`process.stdout` so
|
|
326
|
-
* stubs (test) and the real TTY both work.
|
|
327
|
-
*
|
|
328
|
-
* @param {object} [opts]
|
|
329
|
-
* @param {NodeJS.ReadableStream} [opts.input]
|
|
330
|
-
* @param {NodeJS.WritableStream} [opts.output]
|
|
331
|
-
* @returns {Promise<boolean>}
|
|
332
|
-
*/
|
|
333
|
-
export function confirmInteractive(opts = {}) {
|
|
334
|
-
const input = opts.input ?? process.stdin;
|
|
335
|
-
const output = opts.output ?? process.stdout;
|
|
336
|
-
return new Promise((resolve) => {
|
|
337
|
-
const rl = readline.createInterface({ input, output });
|
|
338
|
-
rl.question('Apply this plan? [y/N] ', (answer) => {
|
|
339
|
-
rl.close();
|
|
340
|
-
const trimmed = String(answer ?? '')
|
|
341
|
-
.trim()
|
|
342
|
-
.toLowerCase();
|
|
343
|
-
resolve(trimmed === 'y' || trimmed === 'yes');
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Identify whether the plan carries any close ops. The exit-code
|
|
350
|
-
* contract gates close ops behind `--explicit-delete`; this predicate
|
|
351
|
-
* is the canonical "needs --explicit-delete" check. Exported so the
|
|
352
|
-
* contract tests can pin the predicate without re-deriving it.
|
|
353
|
-
*
|
|
354
|
-
* @param {object} plan
|
|
355
|
-
* @returns {boolean}
|
|
356
|
-
*/
|
|
357
|
-
export function planHasCloses(plan) {
|
|
358
|
-
return Array.isArray(plan?.closes) && plan.closes.length > 0;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Render the operator-facing message printed on the exit-2 path. Names
|
|
363
|
-
* every close-op slug so the operator can audit precisely what would be
|
|
364
|
-
* removed before re-running with `--explicit-delete`. Exported so the
|
|
365
|
-
* contract tests assert the rendering without re-implementing it.
|
|
366
|
-
*
|
|
367
|
-
* @param {object} plan
|
|
368
|
-
* @returns {string}
|
|
369
|
-
*/
|
|
370
|
-
export function renderExplicitDeleteMessage(plan) {
|
|
371
|
-
const closes = Array.isArray(plan?.closes) ? plan.closes : [];
|
|
372
|
-
const slugList = closes
|
|
373
|
-
.map((op) => `#${op.issueNumber ?? '?'} (${op.slug})`)
|
|
374
|
-
.join(', ');
|
|
375
|
-
return (
|
|
376
|
-
`Plan carries ${closes.length} close operation(s): ${slugList}. ` +
|
|
377
|
-
`Re-run with --explicit-delete to apply.`
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Format the spec-validation failure for stderr — names the failing
|
|
383
|
-
* JSON Pointer and the underlying schema message so operators can fix
|
|
384
|
-
* the spec without re-running with --verbose.
|
|
385
|
-
*
|
|
386
|
-
* @param {SpecValidationError|SpecParseError|SpecNotFoundError} err
|
|
387
|
-
* @returns {string}
|
|
388
|
-
*/
|
|
389
|
-
function formatSpecError(err) {
|
|
390
|
-
if (err instanceof SpecValidationError) {
|
|
391
|
-
// Render every issue on its own line so scripts that grep stderr for
|
|
392
|
-
// a JSON Pointer find each failure individually. The first issue is
|
|
393
|
-
// duplicated into the headline so existing log scrapers reading line 1
|
|
394
|
-
// keep working.
|
|
395
|
-
const issues = Array.isArray(err.issues) ? err.issues : [];
|
|
396
|
-
if (issues.length === 0) {
|
|
397
|
-
return `Spec validation failed: ${err.message}`;
|
|
398
|
-
}
|
|
399
|
-
const head = issues[0];
|
|
400
|
-
const lines = [
|
|
401
|
-
`Spec validation failed at ${head.path}: ${head.message}`,
|
|
402
|
-
...issues.slice(1).map((iss) => ` · ${iss.path}: ${iss.message}`),
|
|
403
|
-
];
|
|
404
|
-
return lines.join('\n');
|
|
405
|
-
}
|
|
406
|
-
if (err instanceof SpecParseError) {
|
|
407
|
-
return `Spec YAML parse error in ${err.filePath}: ${err.cause?.message ?? err.message}`;
|
|
408
|
-
}
|
|
409
|
-
if (err instanceof SpecNotFoundError) {
|
|
410
|
-
return `Spec file missing for epic ${err.epicId}: ${err.filePath}`;
|
|
411
|
-
}
|
|
412
|
-
return `Spec error: ${err.message ?? err}`;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Run one reconcile cycle for a given Epic. Pure dependency-injection
|
|
417
|
-
* surface — every external collaborator (provider, loaders, formatter,
|
|
418
|
-
* apply, confirm prompt, log sinks) is overridable so the contract tests
|
|
419
|
-
* can drive the CLI without hitting the network, the file system, or a
|
|
420
|
-
* real TTY.
|
|
421
|
-
*
|
|
422
|
-
* @param {{
|
|
423
|
-
* epicId: number,
|
|
424
|
-
* dryRun: boolean,
|
|
425
|
-
* apply: boolean,
|
|
426
|
-
* explicitDelete: boolean,
|
|
427
|
-
* yes: boolean,
|
|
428
|
-
* }} args
|
|
429
|
-
* @param {{
|
|
430
|
-
* provider?: object,
|
|
431
|
-
* loadSpec?: typeof loadSpec,
|
|
432
|
-
* loadState?: typeof loadState,
|
|
433
|
-
* diff?: typeof diff,
|
|
434
|
-
* apply?: typeof apply,
|
|
435
|
-
* formatPlan?: typeof formatPlan,
|
|
436
|
-
* fetchGhState?: typeof fetchGhState,
|
|
437
|
-
* confirm?: typeof confirmInteractive,
|
|
438
|
-
* isTty?: () => boolean,
|
|
439
|
-
* stdout?: (line: string) => void,
|
|
440
|
-
* stderr?: (line: string) => void,
|
|
441
|
-
* loaderOpts?: object,
|
|
442
|
-
* }} [deps]
|
|
443
|
-
* @returns {Promise<{exitCode: number, plan?: object, applyResult?: object}>}
|
|
444
|
-
*/
|
|
445
|
-
export async function runReconcile(args, deps = {}) {
|
|
446
|
-
const stdout = deps.stdout ?? defaultStdout;
|
|
447
|
-
const stderr = deps.stderr ?? defaultStderr;
|
|
448
|
-
const loadSpecFn = deps.loadSpec ?? loadSpec;
|
|
449
|
-
const loadStateFn = deps.loadState ?? loadState;
|
|
450
|
-
const diffFn = deps.diff ?? diff;
|
|
451
|
-
const applyFn = deps.apply ?? apply;
|
|
452
|
-
const formatFn = deps.formatPlan ?? formatPlan;
|
|
453
|
-
const fetchGhStateFn = deps.fetchGhState ?? fetchGhState;
|
|
454
|
-
const confirmFn = deps.confirm ?? confirmInteractive;
|
|
455
|
-
const isTtyFn = deps.isTty ?? (() => Boolean(process.stdin.isTTY));
|
|
456
|
-
const loaderOpts = deps.loaderOpts ?? {};
|
|
457
|
-
|
|
458
|
-
if (!Number.isInteger(args.epicId) || args.epicId <= 0) {
|
|
459
|
-
stderr('[epic-reconcile] Error: epic id is required and must be positive');
|
|
460
|
-
stderr(
|
|
461
|
-
'Usage: epic-reconcile.js <epicId> [--dry-run] [--apply] [--explicit-delete] [--yes]',
|
|
462
|
-
);
|
|
463
|
-
return { exitCode: EXIT_CODES.VALIDATION_ERROR };
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// 1. Load + validate the spec.
|
|
467
|
-
let spec;
|
|
468
|
-
try {
|
|
469
|
-
spec = loadSpecFn(args.epicId, loaderOpts);
|
|
470
|
-
} catch (err) {
|
|
471
|
-
stderr(`[epic-reconcile] ${formatSpecError(err)}`);
|
|
472
|
-
return { exitCode: EXIT_CODES.VALIDATION_ERROR };
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// 2. Load the state (absent file → empty mapping).
|
|
476
|
-
let state;
|
|
477
|
-
try {
|
|
478
|
-
state = loadStateFn(args.epicId, loaderOpts);
|
|
479
|
-
} catch (_err) {
|
|
480
|
-
state = { epicId: args.epicId, mapping: {} };
|
|
481
|
-
}
|
|
482
|
-
if (!state || typeof state !== 'object') {
|
|
483
|
-
state = { epicId: args.epicId, mapping: {} };
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// 2a. Seed the synthetic `epic` slug mapping. The diff engine's
|
|
487
|
-
// `flattenSpec` unconditionally yields an entity for the epic, and
|
|
488
|
-
// `applyCreate` carries a long-standing comment asserting the epic
|
|
489
|
-
// "is bootstrapped before reconciliation". On a fresh apply with no
|
|
490
|
-
// state.json, nothing previously did that — so diff emitted a Create
|
|
491
|
-
// op for the epic and `provider.createTicket` materialised a duplicate
|
|
492
|
-
// GH issue (Story #1820). The CLI is the right seam to seed it: keeps
|
|
493
|
-
// diff/apply pure and one-shot per reconcile run.
|
|
494
|
-
if (!state.mapping || typeof state.mapping !== 'object') {
|
|
495
|
-
state.mapping = {};
|
|
496
|
-
}
|
|
497
|
-
if (!state.mapping.epic) {
|
|
498
|
-
state.mapping.epic = {
|
|
499
|
-
issueNumber: args.epicId,
|
|
500
|
-
contentHash: '',
|
|
501
|
-
lastObservedAgentState: null,
|
|
502
|
-
entity: 'epic',
|
|
503
|
-
parentSlug: null,
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// 3. Resolve provider, fetch GH state.
|
|
508
|
-
let provider = deps.provider;
|
|
509
|
-
if (!provider) {
|
|
510
|
-
const config = deps.config ?? resolveConfig({});
|
|
511
|
-
provider = createProvider(config);
|
|
512
|
-
}
|
|
513
|
-
const ghState = await fetchGhStateFn(provider, args.epicId);
|
|
514
|
-
|
|
515
|
-
// 3a. Reseed the slug→issue mapping from live GH state for any spec
|
|
516
|
-
// slug the state file did not cover (Story #3905). Without this, a
|
|
517
|
-
// `--resume` (or any apply) run with a missing/empty `state.json` but
|
|
518
|
-
// open children present would diff every spec slug as a Create and
|
|
519
|
-
// duplicate the entire Feature/Story tree. Title-matched recovery turns
|
|
520
|
-
// those spurious Creates into Updates/no-ops. Pure — no I/O.
|
|
521
|
-
const { state: reseededState, reseeded } = reseedMappingFromGh(
|
|
522
|
-
state,
|
|
523
|
-
spec,
|
|
524
|
-
ghState,
|
|
525
|
-
);
|
|
526
|
-
state = reseededState;
|
|
527
|
-
if (reseeded.length > 0) {
|
|
528
|
-
stderr(
|
|
529
|
-
`[epic-reconcile] Reseeded ${reseeded.length} slug→issue mapping(s) from live GitHub state ` +
|
|
530
|
-
`(state.json was missing/incomplete): ${reseeded
|
|
531
|
-
.map((r) => `${r.slug}→#${r.issueNumber}`)
|
|
532
|
-
.join(', ')}.`,
|
|
533
|
-
);
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// 4. Compute the plan.
|
|
537
|
-
const plan = diffFn({ spec, state, ghState });
|
|
538
|
-
|
|
539
|
-
// 5. Render.
|
|
540
|
-
stdout(formatFn(plan));
|
|
541
|
-
|
|
542
|
-
// 6a. Dry-run path — print and exit 0 regardless of plan size.
|
|
543
|
-
if (args.dryRun) {
|
|
544
|
-
return { exitCode: EXIT_CODES.OK, plan };
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// 6b. Empty plan: nothing to apply. Exit 0 without prompting.
|
|
548
|
-
if (isEmptyPlan(plan)) {
|
|
549
|
-
return { exitCode: EXIT_CODES.OK, plan };
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// 6c. Explicit-delete pre-flight: any close op requires the flag. We
|
|
553
|
-
// gate at the CLI surface (before apply) so the exit code is stable
|
|
554
|
-
// regardless of whether apply's discriminator would also reject — the
|
|
555
|
-
// task contract pins exit 2 to "would close without --explicit-delete",
|
|
556
|
-
// not "apply engine rejected".
|
|
557
|
-
if (planHasCloses(plan) && !args.explicitDelete) {
|
|
558
|
-
stderr(`[epic-reconcile] ${renderExplicitDeleteMessage(plan)}`);
|
|
559
|
-
return { exitCode: EXIT_CODES.EXPLICIT_DELETE_REQUIRED, plan };
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// 6d. Confirmation gate.
|
|
563
|
-
if (!args.yes) {
|
|
564
|
-
if (!isTtyFn()) {
|
|
565
|
-
stderr(
|
|
566
|
-
'[epic-reconcile] --apply requires either --yes or an interactive TTY.',
|
|
567
|
-
);
|
|
568
|
-
return { exitCode: EXIT_CODES.VALIDATION_ERROR, plan };
|
|
569
|
-
}
|
|
570
|
-
const confirmed = await confirmFn();
|
|
571
|
-
if (!confirmed) {
|
|
572
|
-
stdout('[epic-reconcile] Aborted by operator.');
|
|
573
|
-
return { exitCode: EXIT_CODES.OK, plan };
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// 6e. Apply.
|
|
578
|
-
let applyResult;
|
|
579
|
-
try {
|
|
580
|
-
applyResult = await applyFn(plan, provider, {
|
|
581
|
-
epicId: args.epicId,
|
|
582
|
-
spec,
|
|
583
|
-
priorState: state,
|
|
584
|
-
explicitDelete: args.explicitDelete,
|
|
585
|
-
});
|
|
586
|
-
} catch (err) {
|
|
587
|
-
if (
|
|
588
|
-
err instanceof ApplyGateViolation &&
|
|
589
|
-
err.reason === 'explicit-delete-required'
|
|
590
|
-
) {
|
|
591
|
-
stderr(`[epic-reconcile] ${err.message}`);
|
|
592
|
-
return { exitCode: EXIT_CODES.EXPLICIT_DELETE_REQUIRED, plan };
|
|
593
|
-
}
|
|
594
|
-
stderr(`[epic-reconcile] Apply failed: ${err.message ?? err}`);
|
|
595
|
-
return { exitCode: EXIT_CODES.VALIDATION_ERROR, plan };
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
if (applyResult?.failure) {
|
|
599
|
-
stderr(
|
|
600
|
-
`[epic-reconcile] Apply completed with partial failure: ${applyResult.failure.message ?? applyResult.failure}`,
|
|
601
|
-
);
|
|
602
|
-
return { exitCode: EXIT_CODES.VALIDATION_ERROR, plan, applyResult };
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
stdout('[epic-reconcile] Apply complete.');
|
|
606
|
-
return { exitCode: EXIT_CODES.OK, plan, applyResult };
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/**
|
|
610
|
-
* CLI entry point. Parses argv, delegates to `runReconcile`, and exits
|
|
611
|
-
* with the contract-defined integer.
|
|
612
|
-
*/
|
|
613
|
-
async function main(argv = process.argv.slice(2)) {
|
|
614
|
-
const args = parseCli(argv);
|
|
615
|
-
const { exitCode } = await runReconcile(args);
|
|
616
|
-
process.exit(exitCode);
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
runAsCli(import.meta.url, main, {
|
|
620
|
-
source: 'epic-reconcile',
|
|
621
|
-
onError: (err) => {
|
|
622
|
-
Logger.error('[epic-reconcile] Fatal error:', err?.message ?? err);
|
|
623
|
-
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
624
|
-
},
|
|
625
|
-
});
|