mandrel 1.93.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/acceptance-critic.md +129 -0
- package/.agents/agents/story-worker.md +161 -0
- package/.agents/docs/SDLC.md +489 -1285
- package/.agents/docs/agentrc-reference.json +177 -67
- package/.agents/docs/configuration.md +108 -136
- package/.agents/docs/execution-reference.md +44 -22
- package/.agents/docs/quality-gates.md +13 -19
- package/.agents/docs/workflows.md +3 -3
- package/.agents/instructions.md +107 -108
- package/.agents/rules/ci-remediation.md +8 -12
- package/.agents/rules/git-conventions-reference.md +224 -0
- package/.agents/rules/git-conventions.md +42 -223
- package/.agents/rules/security-baseline.md +5 -0
- package/.agents/rules/testing-standards.md +106 -13
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +71 -201
- 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/acceptance-eval.js +62 -18
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- 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-context-budget.js +62 -5
- 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/diagnose-friction.js +0 -6
- 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/bootstrap/project-bootstrap.js +56 -18
- 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 +160 -22
- package/.agents/scripts/lib/config/acceptance-eval.js +52 -5
- package/.agents/scripts/lib/config/ci.js +6 -31
- package/.agents/scripts/lib/config/delivery-routing.js +103 -0
- package/.agents/scripts/lib/config/explain.js +57 -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 +3 -8
- package/.agents/scripts/lib/config-settings-schema-delivery.js +46 -136
- package/.agents/scripts/lib/config-settings-schema-quality.js +17 -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 +39 -4
- 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 +112 -3
- package/.agents/scripts/lib/observability/hook-heartbeat.js +187 -0
- package/.agents/scripts/lib/observability/source-classifier.js +3 -3
- package/.agents/scripts/lib/observability/tool-trace-hook.js +15 -4
- package/.agents/scripts/lib/onboard/init-tail.js +1 -3
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +111 -0
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +32 -4
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +273 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +204 -0
- 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-critic-conditions.js +3 -2
- 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 +14 -14
- 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/close-validation.js +1 -0
- 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 +3 -8
- 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/provider-factory.js +1 -1
- 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/post-structured-comment.js +38 -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/sync-claude-agents.js +165 -0
- package/.agents/scripts/update-ticket-state.js +37 -15
- package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
- package/.agents/skills/core/api-and-interface-design/SKILL.md +5 -3
- package/.agents/skills/core/code-review-and-quality/SKILL.md +63 -7
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +1 -1
- package/.agents/skills/core/gates-and-baselines/SKILL.md +149 -0
- package/.agents/skills/core/idea-refinement/SKILL.md +8 -14
- package/.agents/skills/core/qa-coverage-mapping/SKILL.md +7 -7
- package/.agents/skills/core/scope-triage/SKILL.md +28 -172
- package/.agents/skills/skills.index.json +8 -418
- 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 -131
- 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 +52 -40
- 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/mandrel-sync-config.md +1 -1
- 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 +3 -2
- 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 +46 -0
- package/lib/cli/registry.js +95 -0
- package/lib/migrations/index.js +6 -5
- package/package.json +5 -3
- package/.agents/personas/architect.md +0 -113
- package/.agents/personas/devops-engineer.md +0 -38
- package/.agents/personas/engineer-mobile.md +0 -120
- package/.agents/personas/engineer-web.md +0 -111
- package/.agents/personas/engineer.md +0 -119
- package/.agents/personas/product.md +0 -94
- package/.agents/personas/project-manager.md +0 -114
- package/.agents/personas/qa-engineer.md +0 -95
- package/.agents/personas/refactorer.md +0 -113
- package/.agents/personas/security-engineer.md +0 -112
- package/.agents/personas/sre.md +0 -86
- package/.agents/personas/technical-writer.md +0 -101
- package/.agents/personas/ux-designer.md +0 -95
- package/.agents/schemas/dispatch-manifest.json +0 -232
- package/.agents/schemas/epic-spec.schema.json +0 -153
- package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
- 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 -590
- package/.agents/scripts/epic-execute-record-wave.js +0 -449
- package/.agents/scripts/epic-plan-clarity.js +0 -211
- package/.agents/scripts/epic-plan-decompose.js +0 -54
- package/.agents/scripts/epic-plan-healthcheck.js +0 -581
- package/.agents/scripts/epic-plan-spec.js +0 -64
- 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 -660
- 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 -388
- 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-story-dispatch-end.js +0 -147
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -384
- 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 -278
- 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/skill-capsule-loader.js +0 -109
- 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 -301
- 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/plan-critics.js +0 -199
- package/.agents/scripts/retro-run.js +0 -218
- 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 -335
- package/.agents/skills/core/baseline-refresh/SKILL.md +0 -181
- package/.agents/skills/core/ci-cd-and-automation/SKILL.md +0 -274
- package/.agents/skills/core/ci-cd-and-automation/examples.md +0 -211
- package/.agents/skills/core/code-simplification/SKILL.md +0 -389
- package/.agents/skills/core/context-engineering/SKILL.md +0 -309
- package/.agents/skills/core/context-engineering/examples.md +0 -58
- package/.agents/skills/core/deprecation-and-migration/SKILL.md +0 -250
- 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 -413
- package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
- package/.agents/skills/core/frontend-ui-engineering/SKILL.md +0 -357
- package/.agents/skills/core/hydrate-context/SKILL.md +0 -123
- package/.agents/skills/core/idea-refinement/examples.md +0 -437
- package/.agents/skills/core/idea-refinement/frameworks.md +0 -135
- package/.agents/skills/core/incremental-implementation/SKILL.md +0 -271
- package/.agents/skills/core/introducing-a-baseline-gate/SKILL.md +0 -213
- package/.agents/skills/core/knowledge-transfer/SKILL.md +0 -180
- package/.agents/skills/core/mutation-survivor-remediation/SKILL.md +0 -117
- package/.agents/skills/core/performance-optimization/SKILL.md +0 -314
- package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +0 -277
- package/.agents/skills/core/property-based-testing/SKILL.md +0 -148
- package/.agents/skills/core/refactoring-discipline/SKILL.md +0 -111
- package/.agents/skills/core/shipping-and-launch/SKILL.md +0 -328
- package/.agents/skills/core/spec-driven-development/SKILL.md +0 -252
- package/.agents/skills/core/test-driven-development/SKILL.md +0 -475
- package/.agents/skills/core/using-agent-skills/SKILL.md +0 -232
- package/.agents/skills/stack/architecture/monorepo-path-strategist/SKILL.md +0 -31
- package/.agents/skills/stack/architecture/structured-output-zod/SKILL.md +0 -51
- package/.agents/skills/stack/architecture/subagent-orchestration/SKILL.md +0 -76
- package/.agents/skills/stack/backend/cloudflare-hono-architect/SKILL.md +0 -31
- package/.agents/skills/stack/backend/cloudflare-hono-architect/examples/route-template.ts +0 -33
- package/.agents/skills/stack/backend/cloudflare-queue-manager/SKILL.md +0 -31
- package/.agents/skills/stack/backend/cloudflare-workers/SKILL.md +0 -51
- package/.agents/skills/stack/backend/highlevel-crm/SKILL.md +0 -54
- package/.agents/skills/stack/backend/sqlite-drizzle-expert/SKILL.md +0 -29
- package/.agents/skills/stack/backend/sqlite-drizzle-expert/examples/schema-template.ts +0 -30
- package/.agents/skills/stack/backend/stripe-integration/SKILL.md +0 -57
- package/.agents/skills/stack/backend/stripe-integration/scripts/listen-stripe.sh +0 -9
- package/.agents/skills/stack/backend/turso-sqlite/SKILL.md +0 -48
- package/.agents/skills/stack/frontend/astro/SKILL.md +0 -62
- package/.agents/skills/stack/frontend/astro-react-island-strategist/SKILL.md +0 -30
- package/.agents/skills/stack/frontend/expo-react-native-developer/SKILL.md +0 -29
- package/.agents/skills/stack/frontend/google-analytics-v4/SKILL.md +0 -50
- package/.agents/skills/stack/frontend/tailwind-v4/SKILL.md +0 -58
- package/.agents/skills/stack/frontend/ui-accessibility-engineer/SKILL.md +0 -34
- package/.agents/skills/stack/qa/audit-accessibility/SKILL.md +0 -51
- package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +0 -199
- package/.agents/skills/stack/security/backend-security-patterns/SKILL.md +0 -68
- package/.agents/workflows/helpers/deliver-epic-reference.md +0 -534
- package/.agents/workflows/helpers/deliver-epic.md +0 -955
- package/.agents/workflows/helpers/deliver-stories.md +0 -440
- package/.agents/workflows/helpers/epic-audit.md +0 -189
- package/.agents/workflows/helpers/epic-deliver-story.md +0 -427
- 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 -351
- 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,135 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* run-plan-persist.js —
|
|
3
|
-
* (
|
|
2
|
+
* run-plan-persist.js — flat Story persist for the v2 `/plan` collapse
|
|
3
|
+
* (Stage 3 — `docs/roadmap.md`).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* retired 12-phase pipeline's separate persist halves
|
|
7
|
-
* (design §1 Step 3 + §2 mode matrix, issue #4474):
|
|
5
|
+
* Ordered, fail-closed pipeline:
|
|
8
6
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* 4. ticket validator + file-assumption gate + DAG + sizing + budget
|
|
19
|
-
* (fan-out and amend; amend validates the MERGED set — existing
|
|
20
|
-
* keeps + adds + modifies, closes excluded; all git-local, still
|
|
21
|
-
* zero provider calls). Skipped entirely in single mode (no tickets
|
|
22
|
-
* exist to validate).
|
|
23
|
-
* 4.5. deterministic draft reachability (Epic #4474 PR6, design §4 —
|
|
24
|
-
* the 8.4 critic demoted into persist): route-glob scan of the
|
|
25
|
-
* draft set vs `planning.navigation.navRegistry`, mirroring the
|
|
26
|
-
* `--paranoid` F7 healthcheck mechanics. Orphan surfaces are a
|
|
27
|
-
* NAMED SOFT FAILURE (`code: PLAN_REACHABILITY_ORPHANS`, CLI exit
|
|
28
|
-
* 3) raised before any provider call — the author appends the
|
|
29
|
-
* single reachability Story in one targeted amend and re-runs the
|
|
30
|
-
* persist once. Silent no-op when `planning.navigation` is
|
|
31
|
-
* unconfigured; skip decisions are appended to the plan-metrics
|
|
32
|
-
* ledger (`kind: critic-skip`) for audit.
|
|
33
|
-
* 5. ideation fold — `renderEpicBody` / `openEpicFromOnePager` create
|
|
34
|
-
* the Epic when the run starts from a one-pager (the first provider
|
|
35
|
-
* call of the run). Amend additionally resolves every
|
|
36
|
-
* modify/keep/close slug to its live issue and enforces the close-op
|
|
37
|
-
* confirmation gate (exit 2 without `--explicit-delete`) BEFORE the
|
|
38
|
-
* lease and before any mutation.
|
|
39
|
-
* 6. Epic lease (KEEP — documented double-create at ~80 creations).
|
|
40
|
-
* From here the lease is released on EVERY exit path (success, gate
|
|
41
|
-
* failure, throw) via try/finally.
|
|
42
|
-
* 7. managed Tech Spec / Acceptance Table sections + risk-verdict
|
|
43
|
-
* structured comment + spec-freshness advisory.
|
|
44
|
-
* 8. mode-split mutation:
|
|
45
|
-
* - fan-out: story creation via the structural reconciler
|
|
46
|
-
* (idempotent per-slug creation; the reconciler's state file is
|
|
47
|
-
* the per-slug resume ledger), bracketed by checkpoint-v2 writes
|
|
48
|
-
* so a rate-limit crash resumes losslessly with `--resume`;
|
|
49
|
-
* - single: NO story tree — the `delivery::single` routing marker is
|
|
50
|
-
* applied instead (inert until #4475 lands the deliver-side
|
|
51
|
-
* reader), and `decompose = { ticketCount: 0, shape: "single" }`
|
|
52
|
-
* is checkpointed so delivery-time consumers never misread absence
|
|
53
|
-
* as unplanned;
|
|
54
|
-
* - amend: op-mapped delta — close ops close, modify ops
|
|
55
|
-
* close-and-recreate, add ops create, keep ops are untouched by
|
|
56
|
-
* construction; the state ledger and blocked-by edges are rebuilt
|
|
57
|
-
* over the merged set.
|
|
58
|
-
* 9. inline post-plan healthcheck (the `agent::ready` exit condition,
|
|
59
|
-
* Story #2921).
|
|
60
|
-
* 10. single terminal `agent::ready` flip — the intermediate
|
|
61
|
-
* `agent::review-spec` flip is retired on this surface (its readers
|
|
62
|
-
* were visibility-only; the /deliver start gate needs only
|
|
63
|
-
* `agent::ready`).
|
|
64
|
-
* 11. checkpoint v2 + single `plan-summary` comment carrying the dry-run
|
|
65
|
-
* wave table as closing text (replaces the Phase 9 dispatcher
|
|
66
|
-
* round-trip and the Phase 12 notify). The single-mode summary
|
|
67
|
-
* records `{ deliveryShape: "single", sliceCount, routingReasons }`.
|
|
68
|
-
* 12. temp cleanup ONLY at terminal success — a failed run leaves
|
|
69
|
-
* techspec/acceptance/risk-verdict/tickets artifacts on disk so a
|
|
70
|
-
* `--force`/`--resume` re-persist reuses them (fixes the
|
|
71
|
-
* `plan-phase-cleanup.js` mid-pipeline deletion defect).
|
|
7
|
+
* 1. Risk-verdict presence (schema validation is CLI-owned)
|
|
8
|
+
* 2. Ticket validator + file-assumption + DAG + capacity + budget
|
|
9
|
+
* 3. Draft reachability (named soft failure, exit 3)
|
|
10
|
+
* 4. Split-policy partition (`assertAcceptancePartition`) + spec fold/spill
|
|
11
|
+
* 5. Create Story issues (`type::story` + `agent::ready`; `plan-run::`
|
|
12
|
+
* label when N>1)
|
|
13
|
+
* 6. Upsert `risk-verdict` + `story-plan-state` on every created Story;
|
|
14
|
+
* upsert `plan-summary` on the primary Story
|
|
15
|
+
* 7. Temp cleanup at terminal success only
|
|
72
16
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* consumers — `lib/orchestration/code-review.js` (review depth),
|
|
77
|
-
* `epic-audit-prepare.js` (audit-lens routing),
|
|
78
|
-
* `story-close/phases/locked-pipeline.js` (parent-risk inheritance), and
|
|
79
|
-
* the decompose context reader — read it without modification. The only
|
|
80
|
-
* additions are the `version` bump and the additive `persist` progress
|
|
81
|
-
* block; consumers key on field presence, never on `version`.
|
|
17
|
+
* Hard cutover: no Epic parent, no reconciler, no `deliveryShape`, no
|
|
18
|
+
* `--amend` tree cascades. Those surfaces die with Stages 4–5 for any
|
|
19
|
+
* remaining epic-delivery readers.
|
|
82
20
|
*
|
|
83
21
|
* @module lib/orchestration/plan-persist/run-plan-persist
|
|
84
22
|
*/
|
|
85
23
|
|
|
86
|
-
import {
|
|
87
|
-
import path from 'node:path';
|
|
24
|
+
import { rm } from 'node:fs/promises';
|
|
88
25
|
|
|
89
|
-
import { runPlanHealthcheck as defaultRunPlanHealthcheck } from '../../../epic-plan-healthcheck.js';
|
|
90
|
-
import { verifyBddRunnerPendingTag } from '../../bdd-runner-detect.js';
|
|
91
26
|
import { getLimits, PROJECT_ROOT } from '../../config-resolver.js';
|
|
92
|
-
import { openEpicFromOnePager } from '../../epic-plan-ideation.js';
|
|
93
27
|
import { gitSpawn } from '../../git-utils.js';
|
|
94
28
|
import { Logger } from '../../Logger.js';
|
|
95
|
-
import {
|
|
96
|
-
AGENT_LABELS,
|
|
97
|
-
DELIVERY_LABELS,
|
|
98
|
-
TYPE_LABELS,
|
|
99
|
-
} from '../../label-constants.js';
|
|
100
|
-
import { cleanupPhaseTempFiles } from '../../plan-phase-cleanup.js';
|
|
101
|
-
import { loadState, writeSpec, writeState } from '../../spec/index.js';
|
|
102
|
-
import {
|
|
103
|
-
reconcileSubIssueLinks,
|
|
104
|
-
setBlockedByDependencies,
|
|
105
|
-
setEpicLabel,
|
|
106
|
-
warnTicketCapNearLimit,
|
|
107
|
-
} from '../epic-plan-decompose/phases/creation.js';
|
|
108
|
-
import {
|
|
109
|
-
enforceFanOutGate,
|
|
110
|
-
runHealthcheckGate,
|
|
111
|
-
surfaceSoftConflictFindings,
|
|
112
|
-
} from '../epic-plan-decompose/phases/persist.js';
|
|
113
|
-
import {
|
|
114
|
-
buildEpicSpecInput,
|
|
115
|
-
validateTickets,
|
|
116
|
-
} from '../epic-plan-decompose/phases/persist-helpers.js';
|
|
117
|
-
import {
|
|
118
|
-
RECONCILE_CLI,
|
|
119
|
-
spawnReconcilerApply,
|
|
120
|
-
} from '../epic-plan-decompose/phases/reconcile-spawn.js';
|
|
121
|
-
import {
|
|
122
|
-
acquireEpicPlanLease,
|
|
123
|
-
assertNoOpenPlanChildren,
|
|
124
|
-
releaseEpicPlanLease,
|
|
125
|
-
} from '../epic-plan-lease-guard.js';
|
|
126
|
-
import { planEpic } from '../epic-plan-spec/phases/plan-epic.js';
|
|
127
|
-
import { runSpecFreshnessCheck } from '../epic-plan-spec/phases/spec-freshness.js';
|
|
128
|
-
import {
|
|
129
|
-
initialize as initializePlanState,
|
|
130
|
-
read as readPlanState,
|
|
131
|
-
write as writePlanState,
|
|
132
|
-
} from '../epic-plan-state-store.js';
|
|
133
29
|
import { evaluatePlanCritics } from '../plan-critics-evaluate.js';
|
|
134
30
|
import {
|
|
135
31
|
appendCriticSkip,
|
|
@@ -143,182 +39,128 @@ import {
|
|
|
143
39
|
} from '../plan-reachability.js';
|
|
144
40
|
import { resolveReviewRouting } from '../plan-review-routing.js';
|
|
145
41
|
import { deriveRiskEnvelope } from '../planning-risk.js';
|
|
146
|
-
import { renderSpec } from '../spec-renderer.js';
|
|
147
|
-
import {
|
|
148
|
-
formatMissingSectionMessage,
|
|
149
|
-
validateSpecSections,
|
|
150
|
-
} from '../spec-section-validator.js';
|
|
151
42
|
import { upsertStructuredComment } from '../ticketing.js';
|
|
152
43
|
import {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
resolveAmendTargets,
|
|
159
|
-
} from './amend.js';
|
|
160
|
-
import { countDeliverySlices, resolveDeliveryMode } from './delivery-mode.js';
|
|
44
|
+
enforceFanOutGate,
|
|
45
|
+
surfaceSoftConflictFindings,
|
|
46
|
+
} from './fan-out-gate.js';
|
|
47
|
+
import { validateTickets } from './persist-helpers.js';
|
|
48
|
+
import { assemblePlanStories, createStoryIssues } from './story-ops.js';
|
|
161
49
|
import {
|
|
162
50
|
buildPlanSummaryCommentBody,
|
|
163
51
|
buildWaveTable,
|
|
164
52
|
PLAN_SUMMARY_COMMENT_TYPE,
|
|
165
53
|
} from './summary.js';
|
|
166
54
|
|
|
167
|
-
/** Checkpoint schema version written
|
|
168
|
-
|
|
55
|
+
/** Checkpoint schema version written on each Story's story-plan-state. */
|
|
56
|
+
const PLAN_CHECKPOINT_SCHEMA_VERSION_V2 = 2;
|
|
169
57
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// API keeps a single import root for the persist surface.
|
|
173
|
-
export { resolveDeliveryMode };
|
|
58
|
+
/** Structured-comment type for the per-plan Story checkpoint. */
|
|
59
|
+
const STORY_PLAN_STATE_TYPE = 'story-plan-state';
|
|
174
60
|
|
|
175
61
|
/**
|
|
176
|
-
*
|
|
177
|
-
* current checkpoint (or initializes a fresh skeleton), shallow-merges
|
|
178
|
-
* `patch`, and stamps `version: 2`. Field shapes for `planningRisk`,
|
|
179
|
-
* `riskVerdict`, `reviewRouting`, `spec`, and `decompose` are byte-compatible
|
|
180
|
-
* with v1 — v2 is additive only.
|
|
62
|
+
* Write the `story-plan-state` checkpoint on a Story.
|
|
181
63
|
*
|
|
182
64
|
* @param {object} provider
|
|
183
|
-
* @param {number}
|
|
184
|
-
* @param {object}
|
|
185
|
-
* @returns {Promise<object>} the written checkpoint payload
|
|
65
|
+
* @param {number} storyId
|
|
66
|
+
* @param {object} state
|
|
186
67
|
*/
|
|
187
|
-
export async function writeCheckpointV2(provider,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
(await initializePlanState({ provider, epicId }));
|
|
191
|
-
// One-level deep merge for object-valued blocks (`spec`, `decompose`,
|
|
192
|
-
// `persist`, …) so a partial patch (e.g. `persist: { completedAt }`)
|
|
193
|
-
// refines rather than replaces the block — same discipline the v1
|
|
194
|
-
// writers applied by hand with `...currentState.decompose`.
|
|
195
|
-
const merged = { ...current };
|
|
196
|
-
for (const [key, value] of Object.entries(patch ?? {})) {
|
|
197
|
-
const existing = merged[key];
|
|
198
|
-
if (
|
|
199
|
-
value !== null &&
|
|
200
|
-
typeof value === 'object' &&
|
|
201
|
-
!Array.isArray(value) &&
|
|
202
|
-
existing !== null &&
|
|
203
|
-
typeof existing === 'object' &&
|
|
204
|
-
!Array.isArray(existing)
|
|
205
|
-
) {
|
|
206
|
-
merged[key] = { ...existing, ...value };
|
|
207
|
-
} else {
|
|
208
|
-
merged[key] = value;
|
|
209
|
-
}
|
|
68
|
+
export async function writeCheckpointV2(provider, storyId, state) {
|
|
69
|
+
if (!Number.isInteger(storyId)) {
|
|
70
|
+
throw new TypeError('writeCheckpointV2 requires a numeric storyId');
|
|
210
71
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
72
|
+
const body = [
|
|
73
|
+
'### story-plan-state',
|
|
74
|
+
'',
|
|
75
|
+
'```json',
|
|
76
|
+
JSON.stringify(
|
|
77
|
+
{
|
|
78
|
+
version: PLAN_CHECKPOINT_SCHEMA_VERSION_V2,
|
|
79
|
+
storyId,
|
|
80
|
+
...state,
|
|
81
|
+
},
|
|
82
|
+
null,
|
|
83
|
+
2,
|
|
84
|
+
),
|
|
85
|
+
'```',
|
|
86
|
+
].join('\n');
|
|
87
|
+
await upsertStructuredComment(provider, storyId, STORY_PLAN_STATE_TYPE, body);
|
|
88
|
+
return state;
|
|
219
89
|
}
|
|
220
90
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
* @returns {Promise<{ epicId: number, epic: object, created: boolean }>}
|
|
234
|
-
*/
|
|
235
|
-
async function resolveTargetEpic({
|
|
236
|
-
epicId,
|
|
237
|
-
onePagerContent,
|
|
238
|
-
templateContent,
|
|
239
|
-
provider,
|
|
240
|
-
}) {
|
|
241
|
-
if (onePagerContent) {
|
|
242
|
-
if (typeof provider.createIssue !== 'function') {
|
|
243
|
-
throw new Error(
|
|
244
|
-
'[plan-persist] provider does not expose createIssue; cannot open ' +
|
|
245
|
-
'an Epic from a one-pager.',
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
const created = await openEpicFromOnePager({
|
|
249
|
-
onePager: onePagerContent,
|
|
250
|
-
template: templateContent,
|
|
251
|
-
createIssue: (payload) => provider.createIssue(payload),
|
|
252
|
-
});
|
|
253
|
-
Logger.info(
|
|
254
|
-
`[plan-persist] Opened Epic #${created.id} from one-pager ("${created.title}").`,
|
|
91
|
+
function enforceTicketValidation(validated, { config, settings, cwd }) {
|
|
92
|
+
const validationErrors = validated.errors ?? [];
|
|
93
|
+
const assumptionFailures = validationErrors.filter((error) =>
|
|
94
|
+
error.startsWith('File assumption mismatch:'),
|
|
95
|
+
);
|
|
96
|
+
const blockingErrors = validationErrors.filter(
|
|
97
|
+
(error) => !error.startsWith('File assumption mismatch:'),
|
|
98
|
+
);
|
|
99
|
+
if (blockingErrors.length > 0) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`[plan-persist] ticket validation failed with ${blockingErrors.length} ` +
|
|
102
|
+
`hard error(s):\n${blockingErrors.map((error) => ` - ${error}`).join('\n')}`,
|
|
255
103
|
);
|
|
256
|
-
const epic = await provider.getEpic(created.id);
|
|
257
|
-
if (!epic) {
|
|
258
|
-
throw new Error(
|
|
259
|
-
`[plan-persist] Epic #${created.id} was created but could not be re-fetched.`,
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
return { epicId: created.id, epic, created: true };
|
|
263
104
|
}
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
105
|
+
if (assumptionFailures.length === 0) return;
|
|
106
|
+
const gateBaseRef = config?.baseBranch ?? settings?.baseBranch ?? 'main';
|
|
107
|
+
const refResolves =
|
|
108
|
+
gitSpawn(
|
|
109
|
+
cwd ?? process.cwd(),
|
|
110
|
+
'rev-parse',
|
|
111
|
+
'--verify',
|
|
112
|
+
'--quiet',
|
|
113
|
+
`${gateBaseRef}^{commit}`,
|
|
114
|
+
).status === 0;
|
|
115
|
+
if (refResolves) {
|
|
270
116
|
throw new Error(
|
|
271
|
-
`[plan-persist]
|
|
117
|
+
`[plan-persist] file-assumption gate: ${assumptionFailures.length} ` +
|
|
118
|
+
`mismatch(es):\n${assumptionFailures.map((error) => ` - ${error}`).join('\n')}`,
|
|
272
119
|
);
|
|
273
120
|
}
|
|
274
|
-
|
|
121
|
+
Logger.warn(
|
|
122
|
+
`[plan-persist] file-assumption gate skipped: base ref '${gateBaseRef}' ` +
|
|
123
|
+
`does not resolve — ${assumptionFailures.length} finding(s) downgraded.`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function riskVerdictCommentBody(riskVerdict) {
|
|
128
|
+
return [
|
|
129
|
+
'### risk-verdict',
|
|
130
|
+
'',
|
|
131
|
+
'```json',
|
|
132
|
+
JSON.stringify(riskVerdict, null, 2),
|
|
133
|
+
'```',
|
|
134
|
+
].join('\n');
|
|
275
135
|
}
|
|
276
136
|
|
|
277
137
|
/**
|
|
278
|
-
* Execute the
|
|
279
|
-
* order). Modes: fan-out (default), single (`deliveryShape: "single"`),
|
|
280
|
-
* amend (`--amend`).
|
|
138
|
+
* Execute the flat Story persist end to end.
|
|
281
139
|
*
|
|
282
140
|
* @param {{
|
|
283
|
-
*
|
|
284
|
-
* provider: import('../../ITicketingProvider.js').ITicketingProvider,
|
|
141
|
+
* provider: object,
|
|
285
142
|
* artifacts: {
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
* onePagerContent?: string|null,
|
|
291
|
-
* templateContent?: string|null,
|
|
143
|
+
* stories: Array<object>,
|
|
144
|
+
* riskVerdict: object,
|
|
145
|
+
* techSpecContent?: string|null,
|
|
146
|
+
* planAcceptance?: string[]|null,
|
|
292
147
|
* },
|
|
293
148
|
* config?: object,
|
|
294
|
-
* settings?:
|
|
149
|
+
* settings?: object,
|
|
295
150
|
* opts?: {
|
|
296
|
-
* force?: boolean,
|
|
297
|
-
* resume?: boolean,
|
|
298
|
-
* amend?: boolean,
|
|
299
|
-
* explicitDelete?: boolean,
|
|
300
|
-
* steal?: boolean,
|
|
301
151
|
* forceReview?: boolean,
|
|
302
152
|
* allowOverBudget?: boolean,
|
|
303
153
|
* allowLargeFanOut?: boolean,
|
|
304
|
-
* // test seams (production callers must not set these)
|
|
305
|
-
* skipHealthcheck?: boolean,
|
|
306
154
|
* skipCleanup?: boolean,
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
* loadStateFn?: typeof loadState,
|
|
312
|
-
* writeStateFn?: typeof writeState,
|
|
313
|
-
* runHealthcheckFn?: typeof defaultRunPlanHealthcheck,
|
|
314
|
-
* bddProbeFn?: typeof verifyBddRunnerPendingTag,
|
|
315
|
-
* fanOutCounter?: (arg: { path: string }) => number,
|
|
155
|
+
* dryRun?: boolean,
|
|
156
|
+
* planRunId?: string,
|
|
157
|
+
* planDir?: string,
|
|
158
|
+
* fanOutCounter?: Function,
|
|
316
159
|
* cwd?: string,
|
|
317
160
|
* },
|
|
318
161
|
* }} input
|
|
319
162
|
*/
|
|
320
163
|
export async function runPlanPersist({
|
|
321
|
-
epicId: requestedEpicId = null,
|
|
322
164
|
provider,
|
|
323
165
|
artifacts,
|
|
324
166
|
config = {},
|
|
@@ -326,227 +168,94 @@ export async function runPlanPersist({
|
|
|
326
168
|
opts = {},
|
|
327
169
|
}) {
|
|
328
170
|
const {
|
|
329
|
-
|
|
330
|
-
acceptanceSpecContent = null,
|
|
171
|
+
stories: rawStories = null,
|
|
331
172
|
riskVerdict,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
templateContent = null,
|
|
173
|
+
techSpecContent = null,
|
|
174
|
+
planAcceptance = null,
|
|
335
175
|
} = artifacts ?? {};
|
|
336
176
|
const {
|
|
337
|
-
force = false,
|
|
338
|
-
resume = false,
|
|
339
|
-
amend = false,
|
|
340
|
-
explicitDelete = false,
|
|
341
|
-
steal = false,
|
|
342
177
|
forceReview = false,
|
|
343
178
|
allowOverBudget = false,
|
|
344
179
|
allowLargeFanOut = false,
|
|
345
|
-
skipHealthcheck = false,
|
|
346
180
|
skipCleanup = false,
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
renderSpecFn = renderSpec,
|
|
351
|
-
loadStateFn = loadState,
|
|
352
|
-
writeStateFn = writeState,
|
|
353
|
-
runHealthcheckFn = defaultRunPlanHealthcheck,
|
|
354
|
-
bddProbeFn = verifyBddRunnerPendingTag,
|
|
181
|
+
dryRun = false,
|
|
182
|
+
planRunId,
|
|
183
|
+
planDir = null,
|
|
355
184
|
fanOutCounter = undefined,
|
|
356
185
|
cwd = PROJECT_ROOT,
|
|
357
186
|
} = opts;
|
|
358
187
|
|
|
359
|
-
|
|
360
|
-
if (force && resume) {
|
|
361
|
-
throw new Error(
|
|
362
|
-
'[plan-persist] --force and --resume are mutually exclusive.',
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
if (amend && (force || resume)) {
|
|
366
|
-
throw new Error(
|
|
367
|
-
'[plan-persist] --amend is mutually exclusive with --force/--resume ' +
|
|
368
|
-
'— the amend delta is already an incremental re-persist.',
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
if (amend && onePagerContent) {
|
|
372
|
-
throw new Error(
|
|
373
|
-
'[plan-persist] --amend requires --epic <id> — there is no existing ' +
|
|
374
|
-
'plan to amend in ideation mode.',
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
if (onePagerContent && resume) {
|
|
188
|
+
if (!riskVerdict || !Array.isArray(riskVerdict.axes)) {
|
|
378
189
|
throw new Error(
|
|
379
|
-
'[plan-persist]
|
|
380
|
-
|
|
381
|
-
'output); an ideation --resume would open a duplicate.',
|
|
190
|
+
'[plan-persist] risk verdict is required — author risk-verdict.json ' +
|
|
191
|
+
'and pass it with --risk-verdict.',
|
|
382
192
|
);
|
|
383
193
|
}
|
|
384
|
-
if (
|
|
194
|
+
if ('deliveryShape' in (riskVerdict ?? {})) {
|
|
385
195
|
throw new Error(
|
|
386
|
-
'[plan-persist]
|
|
387
|
-
'
|
|
196
|
+
'[plan-persist] risk-verdict.deliveryShape was removed in v2 Stage 3 — ' +
|
|
197
|
+
'delete the field; persist always creates Story issue(s).',
|
|
388
198
|
);
|
|
389
199
|
}
|
|
390
|
-
if (!
|
|
200
|
+
if (!Array.isArray(rawStories) || rawStories.length === 0) {
|
|
391
201
|
throw new Error(
|
|
392
|
-
'[plan-persist]
|
|
202
|
+
'[plan-persist] stories payload must be a non-empty array ' +
|
|
203
|
+
'(--stories <file>). Default is one Story.',
|
|
393
204
|
);
|
|
394
205
|
}
|
|
395
206
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
// fail-closed-ordering test).
|
|
399
|
-
const sectionCheck = validateSpecSections({ body: techSpecContent });
|
|
400
|
-
if (!sectionCheck.ok) {
|
|
207
|
+
const maxTickets = getLimits(config).maxTickets;
|
|
208
|
+
if (rawStories.length > maxTickets && !allowOverBudget) {
|
|
401
209
|
throw new Error(
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
missing: sectionCheck.missing,
|
|
405
|
-
}),
|
|
210
|
+
`[plan-persist] Stories (${rawStories.length}) exceed the reviewability ` +
|
|
211
|
+
`budget (${maxTickets}). Re-scope, or rerun with --allow-over-budget.`,
|
|
406
212
|
);
|
|
407
213
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
throw new Error(
|
|
413
|
-
'[plan-persist] risk verdict is required — author risk-verdict.json ' +
|
|
414
|
-
'and pass it with --risk-verdict.',
|
|
214
|
+
if (rawStories.length > maxTickets && allowOverBudget) {
|
|
215
|
+
Logger.warn(
|
|
216
|
+
`[plan-persist] Persisting an over-budget plan: ${rawStories.length} ` +
|
|
217
|
+
`Stories vs. budget ${maxTickets} (--allow-over-budget).`,
|
|
415
218
|
);
|
|
416
219
|
}
|
|
417
|
-
const mode = resolveDeliveryMode(riskVerdict, tickets, { amend });
|
|
418
|
-
|
|
419
|
-
// ---- Step 4: ticket validator + file-assumption gate + DAG + sizing +
|
|
420
|
-
// budget (fan-out and amend; git-local — still no provider call). In
|
|
421
|
-
// amend mode every gate runs over the MERGED set — keeps + adds +
|
|
422
|
-
// modifies, closes excluded — so the DAG is validated against the tree
|
|
423
|
-
// that will actually exist post-amend. The skip branch below is fenced
|
|
424
|
-
// by construction: `resolveDeliveryMode` hard-refuses `deliveryShape:
|
|
425
|
-
// "single"` with any tickets payload, so single mode can only reach here
|
|
426
|
-
// with no tickets to validate. ----
|
|
427
|
-
let validated = null;
|
|
428
|
-
let amendPartition = null;
|
|
429
|
-
let reachability = null;
|
|
430
|
-
let gateSet = null;
|
|
431
|
-
if (mode !== 'single') {
|
|
432
|
-
amendPartition = mode === 'amend' ? partitionAmendTickets(tickets) : null;
|
|
433
|
-
gateSet = mode === 'amend' ? buildMergedTicketSet(tickets) : tickets;
|
|
434
|
-
const maxTickets = getLimits(config).maxTickets;
|
|
435
|
-
if (gateSet.length > maxTickets && !allowOverBudget) {
|
|
436
|
-
throw new Error(
|
|
437
|
-
`[plan-persist] Tickets (${gateSet.length}) exceed the reviewability ` +
|
|
438
|
-
`budget (${maxTickets}). Re-scope the Epic into a smaller plan, or ` +
|
|
439
|
-
'rerun with --allow-over-budget after confirming the over-budget ' +
|
|
440
|
-
'rationale on the Epic.',
|
|
441
|
-
);
|
|
442
|
-
}
|
|
443
|
-
warnTicketCapNearLimit(gateSet, maxTickets, 'plan-persist');
|
|
444
|
-
if (gateSet.length > maxTickets && allowOverBudget) {
|
|
445
|
-
Logger.warn(
|
|
446
|
-
`[plan-persist] Persisting an over-budget decomposition: ${gateSet.length} ` +
|
|
447
|
-
`tickets vs. budget ${maxTickets} (operator override --allow-over-budget).`,
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
Logger.info(
|
|
451
|
-
`[plan-persist] Running cross-validation on ${gateSet.length} tickets` +
|
|
452
|
-
`${mode === 'amend' ? ' (merged amend set)' : ''}...`,
|
|
453
|
-
);
|
|
454
|
-
validated = validateTickets(gateSet, config, { fanOutCounter, cwd });
|
|
455
|
-
enforceFanOutGate(validated.findings, allowLargeFanOut, 'plan-persist');
|
|
456
|
-
surfaceSoftConflictFindings(validated.findings, 'plan-persist');
|
|
457
220
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const assumptionFailures = (validated.errors ?? []).filter((e) =>
|
|
470
|
-
e.startsWith('File assumption mismatch:'),
|
|
471
|
-
);
|
|
472
|
-
if (assumptionFailures.length > 0) {
|
|
473
|
-
const gateBaseRef = config?.baseBranch ?? 'main';
|
|
474
|
-
const refResolves =
|
|
475
|
-
gitSpawn(
|
|
476
|
-
cwd ?? process.cwd(),
|
|
477
|
-
'rev-parse',
|
|
478
|
-
'--verify',
|
|
479
|
-
'--quiet',
|
|
480
|
-
`${gateBaseRef}^{commit}`,
|
|
481
|
-
).status === 0;
|
|
482
|
-
if (refResolves) {
|
|
483
|
-
throw new Error(
|
|
484
|
-
`[plan-persist] file-assumption gate: ${assumptionFailures.length} ` +
|
|
485
|
-
`mismatch(es) between declared assumptions and the base branch:\n` +
|
|
486
|
-
`${assumptionFailures.map((e) => ` - ${e}`).join('\n')}\n` +
|
|
487
|
-
'Fix the Story change declarations (or the plan) and re-run the persist.',
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
Logger.warn(
|
|
491
|
-
`[plan-persist] file-assumption gate skipped: base ref '${gateBaseRef}' ` +
|
|
492
|
-
`does not resolve in this checkout — ${assumptionFailures.length} ` +
|
|
493
|
-
'finding(s) downgraded to warnings.',
|
|
494
|
-
);
|
|
495
|
-
for (const e of assumptionFailures) {
|
|
496
|
-
Logger.warn(`[plan-persist] ${e}`);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
221
|
+
Logger.info(
|
|
222
|
+
`[plan-persist] Running cross-validation on ${rawStories.length} Story ticket(s)...`,
|
|
223
|
+
);
|
|
224
|
+
const validated = validateTickets(rawStories, config, {
|
|
225
|
+
fanOutCounter,
|
|
226
|
+
cwd,
|
|
227
|
+
modelCapacity: opts.modelCapacity,
|
|
228
|
+
});
|
|
229
|
+
enforceFanOutGate(validated.findings, allowLargeFanOut, 'plan-persist');
|
|
230
|
+
surfaceSoftConflictFindings(validated.findings, 'plan-persist');
|
|
231
|
+
enforceTicketValidation(validated, { config, settings, cwd });
|
|
499
232
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
Logger.info(`[plan-persist] reachability: ${reachability.reasons[0]}`);
|
|
511
|
-
} else {
|
|
512
|
-
reachability = {
|
|
513
|
-
status: 'skipped',
|
|
514
|
-
reasons: [
|
|
515
|
-
'single-delivery shape — no draft story tree to scan for orphan surfaces.',
|
|
516
|
-
],
|
|
517
|
-
orphans: [],
|
|
518
|
-
scanned: 0,
|
|
519
|
-
};
|
|
233
|
+
const reachability = evaluateDraftReachability({
|
|
234
|
+
tickets: rawStories,
|
|
235
|
+
config,
|
|
236
|
+
});
|
|
237
|
+
if (reachability.status === 'orphans') {
|
|
238
|
+
const err = new Error(renderReachabilityOrphans(reachability));
|
|
239
|
+
err.code = 'PLAN_REACHABILITY_ORPHANS';
|
|
240
|
+
err.orphans = reachability.orphans;
|
|
241
|
+
throw err;
|
|
520
242
|
}
|
|
243
|
+
Logger.info(`[plan-persist] reachability: ${reachability.reasons[0]}`);
|
|
521
244
|
if (reachability.status === 'skipped') {
|
|
522
|
-
// Audit trail for the skip decision (#4474 PR6). Best-effort by
|
|
523
|
-
// contract — a failed append never fails the persist. Logged before
|
|
524
|
-
// the first provider call so even an ideation run that later fails
|
|
525
|
-
// still records the decision (ideation has no Epic id yet, so the
|
|
526
|
-
// record lands on the standalone stream).
|
|
527
245
|
await appendCriticSkip(
|
|
528
246
|
{
|
|
529
247
|
critic: 'reachability',
|
|
530
248
|
reasons: reachability.reasons,
|
|
531
249
|
cli: 'plan-persist',
|
|
532
|
-
epicId: requestedEpicId,
|
|
533
250
|
},
|
|
534
251
|
config,
|
|
535
252
|
);
|
|
536
253
|
}
|
|
537
254
|
|
|
538
|
-
// ---- Step 4.7: folded critic dispatch evaluation (#4496 fix 6 — the
|
|
539
|
-
// former standalone `plan-critics.js` turn). Deterministic and git-local,
|
|
540
|
-
// still zero provider calls: the verdicts are printed (and returned on
|
|
541
|
-
// the result) as part of the pre-write phase, and every skip decision is
|
|
542
|
-
// appended to the plan-metrics ledger exactly as the standalone CLI did,
|
|
543
|
-
// so under-firing stays auditable without a separate invocation on the
|
|
544
|
-
// headless path. Advisory by construction — the deterministic validators
|
|
545
|
-
// above remain the unchanged hard gates. ----
|
|
546
255
|
const critics = evaluatePlanCritics({
|
|
547
|
-
techSpecContent,
|
|
256
|
+
techSpecContent: techSpecContent ?? '',
|
|
548
257
|
riskVerdict,
|
|
549
|
-
tickets:
|
|
258
|
+
tickets: rawStories,
|
|
550
259
|
config,
|
|
551
260
|
});
|
|
552
261
|
for (const decision of [critics.consolidation, critics.premortem]) {
|
|
@@ -556,460 +265,120 @@ export async function runPlanPersist({
|
|
|
556
265
|
decision.reasons.join('; '),
|
|
557
266
|
);
|
|
558
267
|
if (!decision.dispatch) {
|
|
559
|
-
// Best-effort by contract — a failed append never fails the persist.
|
|
560
268
|
await appendCriticSkip(
|
|
561
269
|
{
|
|
562
270
|
critic: decision.critic,
|
|
563
271
|
reasons: decision.reasons,
|
|
564
272
|
cli: 'plan-persist',
|
|
565
|
-
epicId: requestedEpicId,
|
|
566
273
|
},
|
|
567
274
|
config,
|
|
568
275
|
);
|
|
569
276
|
}
|
|
570
277
|
}
|
|
571
278
|
|
|
572
|
-
//
|
|
573
|
-
const {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
templateContent,
|
|
577
|
-
provider,
|
|
279
|
+
// Split policy + inline Spec fold (over-budget Specs fail closed — no docs/).
|
|
280
|
+
const { stories } = assemblePlanStories(rawStories, {
|
|
281
|
+
sharedSpec: techSpecContent,
|
|
282
|
+
planAcceptance: planAcceptance ?? undefined,
|
|
578
283
|
});
|
|
579
284
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
if (mode === 'amend') {
|
|
586
|
-
const priorState = loadStateFn(epicId);
|
|
587
|
-
amendTargets = await resolveAmendTargets({
|
|
588
|
-
partition: amendPartition,
|
|
589
|
-
stateMapping: priorState.mapping,
|
|
590
|
-
provider,
|
|
591
|
-
});
|
|
592
|
-
enforceAmendCloseGate({
|
|
593
|
-
epicId,
|
|
594
|
-
targets: amendTargets,
|
|
595
|
-
adds: amendPartition.add,
|
|
596
|
-
explicitDelete,
|
|
597
|
-
});
|
|
598
|
-
Logger.info(
|
|
599
|
-
renderAmendPlanDiff({
|
|
600
|
-
epicId,
|
|
601
|
-
targets: amendTargets,
|
|
602
|
-
adds: amendPartition.add,
|
|
603
|
-
}),
|
|
604
|
-
);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// ---- Step 6: Epic lease. Every path after a successful acquire runs
|
|
608
|
-
// through the finally below, so the lease is released on success, on a
|
|
609
|
-
// gate failure, and on a throw alike. ----
|
|
610
|
-
await acquireEpicPlanLease({ provider, epicId, config, steal });
|
|
611
|
-
|
|
612
|
-
try {
|
|
613
|
-
// Refuse a duplicate story tree unless this is a deliberate re-persist
|
|
614
|
-
// (`--force` closes + recreates via the reconciler's close ops;
|
|
615
|
-
// `--resume` continues a partial persist). Amend is exempt by
|
|
616
|
-
// definition — its whole purpose is mutating the existing open tree.
|
|
617
|
-
if (mode !== 'amend') {
|
|
618
|
-
await assertNoOpenPlanChildren({
|
|
619
|
-
provider,
|
|
620
|
-
epicId,
|
|
621
|
-
force: force || resume,
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
await initializePlanState({ provider, epicId });
|
|
626
|
-
|
|
627
|
-
// ---- Step 7: managed sections + risk comment + freshness advisory. ----
|
|
628
|
-
// BDD-runner probe (Story #4145): best-effort; a probe failure degrades
|
|
629
|
-
// to "runner present" and never blocks the persist.
|
|
630
|
-
let bddRunner = null;
|
|
631
|
-
try {
|
|
632
|
-
bddRunner = await bddProbeFn({ cwd: PROJECT_ROOT });
|
|
633
|
-
} catch (err) {
|
|
634
|
-
Logger.warn(
|
|
635
|
-
`[plan-persist] BDD runner probe skipped (${err.message}); ` +
|
|
636
|
-
'acceptance disposition derived from risk axes only.',
|
|
637
|
-
);
|
|
638
|
-
}
|
|
639
|
-
const planningRisk = deriveRiskEnvelope(riskVerdict, { bddRunner });
|
|
640
|
-
if (planningRisk.acceptanceWaivedReason) {
|
|
641
|
-
Logger.info(
|
|
642
|
-
`[plan-persist] Acceptance disposition forced to not-applicable for ` +
|
|
643
|
-
`Epic #${epicId}: ${planningRisk.acceptanceWaivedReason}`,
|
|
644
|
-
);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// Amend always overwrites the managed sections — the amended Tech Spec
|
|
648
|
-
// IS the delta's spec half (planEpic would otherwise short-circuit
|
|
649
|
-
// `already-planned` on the pre-amend sections).
|
|
650
|
-
const planResult = await planEpic(
|
|
651
|
-
epicId,
|
|
652
|
-
provider,
|
|
653
|
-
{ techSpecContent, acceptanceSpecContent },
|
|
654
|
-
settings,
|
|
655
|
-
{ force: force || mode === 'amend', planningRisk },
|
|
656
|
-
);
|
|
657
|
-
|
|
658
|
-
const reviewRouting = resolveReviewRouting({ planningRisk, forceReview });
|
|
659
|
-
Logger.info(`[plan-persist] Review routing: ${reviewRouting.decision}.`);
|
|
660
|
-
|
|
661
|
-
await upsertStructuredComment(
|
|
662
|
-
provider,
|
|
663
|
-
epicId,
|
|
664
|
-
'risk-verdict',
|
|
665
|
-
buildRiskVerdictCommentBody({ epicId, riskVerdict, planningRisk }),
|
|
666
|
-
);
|
|
667
|
-
|
|
668
|
-
const baseBranchRef = settings?.baseBranch ?? 'main';
|
|
669
|
-
const tempRoot = path.resolve(
|
|
670
|
-
PROJECT_ROOT,
|
|
671
|
-
settings?.paths?.tempRoot ?? 'temp',
|
|
672
|
-
);
|
|
673
|
-
const freshness = await runSpecFreshnessCheck({
|
|
674
|
-
epicId,
|
|
675
|
-
techSpecContent,
|
|
676
|
-
baseBranchRef,
|
|
677
|
-
tempRoot,
|
|
678
|
-
provider,
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
// Spec-half checkpoint (v2). A crash after this point resumes with the
|
|
682
|
-
// sections already folded (planEpic short-circuits `already-planned`).
|
|
683
|
-
await writeCheckpointV2(provider, epicId, {
|
|
684
|
-
planningRisk,
|
|
685
|
-
riskVerdict,
|
|
686
|
-
reviewRouting: {
|
|
687
|
-
decision: reviewRouting.decision,
|
|
688
|
-
requiresStop: reviewRouting.requiresStop,
|
|
689
|
-
forceReviewApplied: reviewRouting.forceReviewApplied,
|
|
690
|
-
},
|
|
691
|
-
spec: {
|
|
692
|
-
techSpecPersisted:
|
|
693
|
-
planResult?.techSpecPersisted === true ||
|
|
694
|
-
planResult?.reason === 'already-planned',
|
|
695
|
-
acceptanceTable: planResult?.acceptanceTable ?? 'none',
|
|
696
|
-
completedAt: new Date().toISOString(),
|
|
697
|
-
},
|
|
698
|
-
persist: {
|
|
699
|
-
mode,
|
|
700
|
-
cli: 'plan-persist',
|
|
701
|
-
startedAt: new Date().toISOString(),
|
|
702
|
-
completedAt: null,
|
|
703
|
-
},
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
// ---- Step 8: mode-split mutation. ----
|
|
707
|
-
let reconcile = null;
|
|
708
|
-
let specFilePath = null;
|
|
709
|
-
let ticketCount = 0;
|
|
710
|
-
let single = null;
|
|
711
|
-
let amendSummary = null;
|
|
285
|
+
const planningRisk = deriveRiskEnvelope(riskVerdict);
|
|
286
|
+
const reviewRouting = resolveReviewRouting({
|
|
287
|
+
planningRisk,
|
|
288
|
+
forceReview,
|
|
289
|
+
});
|
|
712
290
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
const spec = renderSpecFn(validated, {
|
|
719
|
-
epic: buildEpicSpecInput(epic, epicId),
|
|
720
|
-
});
|
|
721
|
-
specFilePath = writeSpecFn(epicId, spec, { epicsDir: undefined });
|
|
722
|
-
Logger.info(`[plan-persist] Wrote spec → ${specFilePath}`);
|
|
291
|
+
const { created, planRunLabel } = await createStoryIssues({
|
|
292
|
+
provider,
|
|
293
|
+
stories,
|
|
294
|
+
opts: { planRunId, dryRun },
|
|
295
|
+
});
|
|
723
296
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
297
|
+
const primary = created[0];
|
|
298
|
+
const waveTable = buildWaveTable(
|
|
299
|
+
stories.map((s) => ({
|
|
300
|
+
slug: s.slug,
|
|
301
|
+
title: s.title,
|
|
302
|
+
depends_on: s.depends_on,
|
|
303
|
+
})),
|
|
304
|
+
);
|
|
731
305
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
cwd,
|
|
740
|
-
explicitDelete: force,
|
|
741
|
-
});
|
|
306
|
+
let planMetricsLine = null;
|
|
307
|
+
try {
|
|
308
|
+
const metrics = summarizePlanMetrics(await readPlanMetrics(config));
|
|
309
|
+
planMetricsLine = renderPlanMetricsSummaryLine(metrics);
|
|
310
|
+
} catch {
|
|
311
|
+
planMetricsLine = null;
|
|
312
|
+
}
|
|
742
313
|
|
|
743
|
-
|
|
314
|
+
const summaryBody = buildPlanSummaryCommentBody({
|
|
315
|
+
epicId: primary.id,
|
|
316
|
+
ticketCount: created.length,
|
|
317
|
+
planningRisk,
|
|
318
|
+
reviewRouting,
|
|
319
|
+
freshness: { stale: 0, ambiguous: 0 },
|
|
320
|
+
healthcheck: { skipped: true },
|
|
321
|
+
waveTable,
|
|
322
|
+
mode: 'stories',
|
|
323
|
+
planMetricsLine,
|
|
324
|
+
stories: created,
|
|
325
|
+
planRunLabel,
|
|
326
|
+
});
|
|
744
327
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
328
|
+
if (!dryRun) {
|
|
329
|
+
for (const story of created) {
|
|
330
|
+
await upsertStructuredComment(
|
|
748
331
|
provider,
|
|
749
|
-
|
|
750
|
-
|
|
332
|
+
story.id,
|
|
333
|
+
'risk-verdict',
|
|
334
|
+
riskVerdictCommentBody(riskVerdict),
|
|
751
335
|
);
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
ticketCount,
|
|
758
|
-
shape: 'fan-out',
|
|
759
|
-
completedAt: new Date().toISOString(),
|
|
760
|
-
},
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
// A force re-persist over a former single-delivery plan flips the
|
|
764
|
-
// routing shape — drop the stale marker so #4475's reader never sees
|
|
765
|
-
// a fan-out tree labelled single.
|
|
766
|
-
await removeSingleDeliveryMarker(provider, epicId, epic);
|
|
767
|
-
} else if (mode === 'single') {
|
|
768
|
-
// Single-delivery: NO story tree. The delivery::single routing
|
|
769
|
-
// marker (inert until #4475's deliver-side reader) plus the Delivery
|
|
770
|
-
// Slicing table of the persisted Tech Spec are the plan.
|
|
771
|
-
single = {
|
|
772
|
-
deliveryShape: 'single',
|
|
773
|
-
sliceCount: countDeliverySlices(techSpecContent),
|
|
774
|
-
routingReasons: riskVerdict.deliveryShapeRationale
|
|
775
|
-
? [riskVerdict.deliveryShapeRationale]
|
|
776
|
-
: [],
|
|
777
|
-
};
|
|
778
|
-
Logger.info(
|
|
779
|
-
`[plan-persist] Single-delivery mode: applying ${DELIVERY_LABELS.SINGLE} ` +
|
|
780
|
-
`to Epic #${epicId} (no story tree).`,
|
|
781
|
-
);
|
|
782
|
-
await provider.updateTicket(epicId, {
|
|
783
|
-
labels: { add: [DELIVERY_LABELS.SINGLE], remove: [] },
|
|
784
|
-
});
|
|
785
|
-
// Explicit zero-ticket checkpoint so delivery-time consumers read a
|
|
786
|
-
// deliberate single-shape plan, never an unplanned absence.
|
|
787
|
-
await writeCheckpointV2(provider, epicId, {
|
|
788
|
-
decompose: {
|
|
789
|
-
ticketCount: 0,
|
|
790
|
-
shape: 'single',
|
|
791
|
-
completedAt: new Date().toISOString(),
|
|
792
|
-
},
|
|
793
|
-
});
|
|
794
|
-
} else {
|
|
795
|
-
// Amend delta: close-and-recreate is scoped to modify/close slugs
|
|
796
|
-
// only; keeps are untouched by construction (no code path receives
|
|
797
|
-
// them); adds are created fresh. The state ledger and blocked-by
|
|
798
|
-
// edges are rebuilt over the merged set.
|
|
799
|
-
const spec = renderSpecFn(validated, {
|
|
800
|
-
epic: buildEpicSpecInput(epic, epicId),
|
|
801
|
-
});
|
|
802
|
-
specFilePath = writeSpecFn(epicId, spec, { epicsDir: undefined });
|
|
803
|
-
Logger.info(`[plan-persist] Wrote amended spec → ${specFilePath}`);
|
|
804
|
-
|
|
805
|
-
await writeCheckpointV2(provider, epicId, {
|
|
806
|
-
decompose: { ticketCount: null, completedAt: null },
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
const validatedBySlug = new Map(validated.map((t) => [t.slug, t]));
|
|
810
|
-
const applied = await applyAmendOps({
|
|
811
|
-
epicId,
|
|
812
|
-
provider,
|
|
813
|
-
targets: amendTargets,
|
|
814
|
-
validatedBySlug,
|
|
815
|
-
});
|
|
816
|
-
|
|
817
|
-
// Rebuild the state ledger over the merged set: prior mapping minus
|
|
818
|
-
// closed/replaced slugs, plus the fresh create/recreate numbers.
|
|
819
|
-
const priorState = loadStateFn(epicId);
|
|
820
|
-
const mergedMapping = { ...(priorState.mapping ?? {}) };
|
|
821
|
-
for (const slug of applied.closedSlugs) delete mergedMapping[slug];
|
|
822
|
-
Object.assign(mergedMapping, applied.mapping);
|
|
823
|
-
writeStateFn(epicId, {
|
|
824
|
-
epicId,
|
|
825
|
-
mapping: mergedMapping,
|
|
826
|
-
lastReconciledAt: new Date().toISOString(),
|
|
827
|
-
});
|
|
828
|
-
|
|
829
|
-
await reconcileSubIssueLinks(epicId, provider);
|
|
830
|
-
await setBlockedByDependencies(epicId, provider, spec, mergedMapping);
|
|
831
|
-
|
|
832
|
-
ticketCount = validated.length;
|
|
833
|
-
amendSummary = {
|
|
834
|
-
closed: applied.closed,
|
|
835
|
-
recreated: applied.recreated,
|
|
836
|
-
created: applied.created,
|
|
837
|
-
keptCount: amendTargets.keep.length,
|
|
838
|
-
};
|
|
839
|
-
await writeCheckpointV2(provider, epicId, {
|
|
840
|
-
decompose: {
|
|
841
|
-
ticketCount,
|
|
842
|
-
shape: 'fan-out',
|
|
336
|
+
await writeCheckpointV2(provider, story.id, {
|
|
337
|
+
planningRisk,
|
|
338
|
+
riskVerdict,
|
|
339
|
+
reviewRouting,
|
|
340
|
+
persist: {
|
|
843
341
|
completedAt: new Date().toISOString(),
|
|
342
|
+
storyCount: created.length,
|
|
343
|
+
planRunLabel,
|
|
344
|
+
primaryStoryId: primary.id,
|
|
345
|
+
stories: created.map((createdStory) => ({
|
|
346
|
+
slug: createdStory.slug,
|
|
347
|
+
id: createdStory.id,
|
|
348
|
+
})),
|
|
844
349
|
},
|
|
845
350
|
});
|
|
846
|
-
|
|
847
|
-
// An amended plan is fan-out-shaped; drop a stale single marker.
|
|
848
|
-
await removeSingleDeliveryMarker(provider, epicId, epic);
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
// ---- Step 9: inline healthcheck — the agent::ready exit condition. ----
|
|
852
|
-
const healthcheck = skipHealthcheck
|
|
853
|
-
? { ok: true, skipped: true }
|
|
854
|
-
: await runHealthcheckGate({
|
|
855
|
-
epicId,
|
|
856
|
-
epic,
|
|
857
|
-
runHealthcheckFn,
|
|
858
|
-
tag: 'plan-persist',
|
|
859
|
-
});
|
|
860
|
-
|
|
861
|
-
// ---- Step 10: single terminal agent::ready flip. This surface never
|
|
862
|
-
// writes agent::review-spec — the HITL review gate sits BEFORE persist
|
|
863
|
-
// in the collapsed flow, so the intermediate label has no reader. ----
|
|
864
|
-
Logger.info(
|
|
865
|
-
`[plan-persist] Flipping Epic #${epicId} to ${AGENT_LABELS.READY}...`,
|
|
866
|
-
);
|
|
867
|
-
await setEpicLabel(provider, epicId, AGENT_LABELS.READY);
|
|
868
|
-
|
|
869
|
-
// ---- Step 11: final checkpoint v2 + single plan-summary comment with
|
|
870
|
-
// the dry-run wave table as closing text (single mode records the
|
|
871
|
-
// { deliveryShape, sliceCount, routingReasons } routing record
|
|
872
|
-
// instead). ----
|
|
873
|
-
const waveTable = mode === 'single' ? [] : buildWaveTable(validated);
|
|
874
|
-
const checkpoint = await writeCheckpointV2(provider, epicId, {
|
|
875
|
-
persist: { completedAt: new Date().toISOString() },
|
|
876
|
-
});
|
|
877
|
-
// G2 measurement receipt (Epic #4474 PR7): roll the plan-metrics ledger
|
|
878
|
-
// into the summary comment so turns-per-plan / per-mode counts / critic
|
|
879
|
-
// skips are readable off the Epic. Best-effort — a missing ledger
|
|
880
|
-
// yields no line. The in-flight persist invocation itself is stamped by
|
|
881
|
-
// the CLI wrapper *after* this function returns, so it appears in the
|
|
882
|
-
// stdout JSON (and any later re-persist), not in this comment.
|
|
883
|
-
let planMetricsLine = null;
|
|
884
|
-
try {
|
|
885
|
-
const metricsSummary = summarizePlanMetrics(
|
|
886
|
-
await readPlanMetrics(epicId, config),
|
|
887
|
-
);
|
|
888
|
-
if (metricsSummary) {
|
|
889
|
-
planMetricsLine = renderPlanMetricsSummaryLine(metricsSummary);
|
|
890
|
-
}
|
|
891
|
-
} catch (err) {
|
|
892
|
-
Logger.warn(
|
|
893
|
-
`[plan-persist] plan-metrics summary line skipped: ${err.message}`,
|
|
894
|
-
);
|
|
895
351
|
}
|
|
896
352
|
await upsertStructuredComment(
|
|
897
353
|
provider,
|
|
898
|
-
|
|
354
|
+
primary.id,
|
|
899
355
|
PLAN_SUMMARY_COMMENT_TYPE,
|
|
900
|
-
|
|
901
|
-
epicId,
|
|
902
|
-
ticketCount,
|
|
903
|
-
planningRisk,
|
|
904
|
-
reviewRouting,
|
|
905
|
-
freshness,
|
|
906
|
-
healthcheck,
|
|
907
|
-
waveTable,
|
|
908
|
-
mode,
|
|
909
|
-
planMetricsLine,
|
|
910
|
-
single,
|
|
911
|
-
amend: amendSummary,
|
|
912
|
-
}),
|
|
356
|
+
summaryBody,
|
|
913
357
|
);
|
|
358
|
+
}
|
|
914
359
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
Logger.info(
|
|
921
|
-
`[plan-persist] ✅ Persist complete for Epic #${epicId} (${mode}). ` +
|
|
922
|
-
`${ticketCount} ticket(s) persisted; Epic is ${AGENT_LABELS.READY}.`,
|
|
923
|
-
);
|
|
924
|
-
if (cleanup.deleted.length > 0) {
|
|
925
|
-
Logger.info(
|
|
926
|
-
`[plan-persist] 🧹 Cleaned up ${cleanup.deleted.length} temp file(s).`,
|
|
927
|
-
);
|
|
360
|
+
if (!skipCleanup && planDir) {
|
|
361
|
+
try {
|
|
362
|
+
await rm(planDir, { recursive: true, force: true });
|
|
363
|
+
} catch (err) {
|
|
364
|
+
Logger.warn(`[plan-persist] temp cleanup skipped: ${err.message}`);
|
|
928
365
|
}
|
|
929
|
-
|
|
930
|
-
return {
|
|
931
|
-
epicId,
|
|
932
|
-
epicCreated: created,
|
|
933
|
-
mode,
|
|
934
|
-
ticketCount,
|
|
935
|
-
checkpoint,
|
|
936
|
-
planningRisk,
|
|
937
|
-
reviewRouting,
|
|
938
|
-
freshness,
|
|
939
|
-
healthcheck,
|
|
940
|
-
reachability,
|
|
941
|
-
critics,
|
|
942
|
-
reconcile,
|
|
943
|
-
specPath: specFilePath,
|
|
944
|
-
waveTable,
|
|
945
|
-
single,
|
|
946
|
-
amend: amendSummary,
|
|
947
|
-
cleanup,
|
|
948
|
-
labelTransition: 'ready',
|
|
949
|
-
};
|
|
950
|
-
} finally {
|
|
951
|
-
// Lease release on EVERY exit path (success, gate failure, throw).
|
|
952
|
-
// Best-effort by contract — releaseEpicPlanLease never throws.
|
|
953
|
-
await releaseEpicPlanLease({ provider, epicId, config });
|
|
954
366
|
}
|
|
955
|
-
}
|
|
956
367
|
|
|
957
|
-
/**
|
|
958
|
-
* Drop a stale `delivery::single` marker when a fan-out-shaped persist
|
|
959
|
-
* (full re-persist or amend) lands over a formerly single-delivery plan.
|
|
960
|
-
* No-op — and no API call — when the fetched Epic never carried it.
|
|
961
|
-
*
|
|
962
|
-
* @param {object} provider
|
|
963
|
-
* @param {number} epicId
|
|
964
|
-
* @param {{ labels?: string[] }} epic the Epic as fetched at step 5
|
|
965
|
-
*/
|
|
966
|
-
async function removeSingleDeliveryMarker(provider, epicId, epic) {
|
|
967
|
-
if (!epic?.labels?.includes(DELIVERY_LABELS.SINGLE)) return;
|
|
968
368
|
Logger.info(
|
|
969
|
-
`[plan-persist]
|
|
970
|
-
`
|
|
369
|
+
`[plan-persist] Persisted ${created.length} Story(ies)` +
|
|
370
|
+
(planRunLabel ? ` under ${planRunLabel}` : '') +
|
|
371
|
+
`; primary #${primary.id} is agent::ready.`,
|
|
971
372
|
);
|
|
972
|
-
await provider.updateTicket(epicId, {
|
|
973
|
-
labels: { add: [], remove: [DELIVERY_LABELS.SINGLE] },
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
373
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
* downstream tooling parses.
|
|
982
|
-
*
|
|
983
|
-
* @param {{ epicId: number, riskVerdict: import('../planning-risk.js').RiskVerdict, planningRisk: import('../planning-risk.js').PlanningRiskEnvelope }} input
|
|
984
|
-
* @returns {string}
|
|
985
|
-
*/
|
|
986
|
-
function buildRiskVerdictCommentBody({ epicId, riskVerdict, planningRisk }) {
|
|
987
|
-
const axisRows = planningRisk.axes.map(
|
|
988
|
-
(entry) => `| ${entry.axis} | ${entry.level} | ${entry.rationale} |`,
|
|
989
|
-
);
|
|
990
|
-
const axisTable =
|
|
991
|
-
axisRows.length > 0
|
|
992
|
-
? ['| Axis | Level | Rationale |', '| --- | --- | --- |', ...axisRows]
|
|
993
|
-
: ['_No risk axes apply (planner-asserted)._'];
|
|
994
|
-
const record = {
|
|
995
|
-
kind: 'risk-verdict',
|
|
996
|
-
epicId,
|
|
997
|
-
verdict: riskVerdict,
|
|
374
|
+
return {
|
|
375
|
+
stories: created,
|
|
376
|
+
primaryStoryId: primary.id,
|
|
377
|
+
planRunLabel,
|
|
998
378
|
planningRisk,
|
|
379
|
+
reviewRouting,
|
|
380
|
+
critics,
|
|
381
|
+
reachability,
|
|
382
|
+
waveTable,
|
|
999
383
|
};
|
|
1000
|
-
const waiverNote = planningRisk.acceptanceWaivedReason
|
|
1001
|
-
? ['', `> ⚠️ **Acceptance waived** — ${planningRisk.acceptanceWaivedReason}`]
|
|
1002
|
-
: [];
|
|
1003
|
-
return [
|
|
1004
|
-
`### 🧭 Planning Risk Verdict — ${planningRisk.overallLevel} · ${planningRisk.gateDecision}`,
|
|
1005
|
-
'',
|
|
1006
|
-
riskVerdict.summary,
|
|
1007
|
-
'',
|
|
1008
|
-
...axisTable,
|
|
1009
|
-
...waiverNote,
|
|
1010
|
-
'',
|
|
1011
|
-
'```json',
|
|
1012
|
-
JSON.stringify(record, null, 2),
|
|
1013
|
-
'```',
|
|
1014
|
-
].join('\n');
|
|
1015
384
|
}
|