mandrel 2.0.0 → 2.2.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 -28
- package/.agents/agents/acceptance-critic.md +20 -9
- package/.agents/agents/story-worker.md +45 -48
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +60 -46
- package/.agents/docs/agentrc-reference.json +8 -13
- package/.agents/docs/configuration.md +33 -57
- package/.agents/docs/execution-reference.md +39 -10
- package/.agents/docs/quality-gates.md +17 -19
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +64 -79
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/gherkin-standards.md +10 -0
- package/.agents/rules/git-conventions-reference.md +42 -51
- package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
- package/.agents/schemas/agentrc.schema.json +35 -46
- package/.agents/schemas/audit-rules.json +59 -1
- package/.agents/schemas/audit-rules.schema.json +33 -1
- package/.agents/schemas/lifecycle/README.md +1 -2
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
- package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
- package/.agents/schemas/signal-event.schema.json +3 -3
- package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +24 -68
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/bootstrap.js +3 -3
- package/.agents/scripts/check-dead-exports.js +43 -104
- package/.agents/scripts/check-doc-links.js +2 -2
- package/.agents/scripts/check-lifecycle-lint.js +1 -1
- package/.agents/scripts/check-workflow-cli-lint.js +91 -0
- package/.agents/scripts/deliver-recover.js +122 -0
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/evidence-gate.js +20 -50
- package/.agents/scripts/generate-skills-index.js +17 -1
- package/.agents/scripts/generate-workflows-doc.js +4 -4
- package/.agents/scripts/lib/ITicketingProvider.js +1 -19
- package/.agents/scripts/lib/audit-suite/selector.js +323 -23
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
- package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
- package/.agents/scripts/lib/checks/index.js +1 -1
- package/.agents/scripts/lib/checks/loop-health.js +12 -11
- package/.agents/scripts/lib/checks/state.js +17 -248
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
- package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
- package/.agents/scripts/lib/cli-args.js +23 -2
- package/.agents/scripts/lib/close-validation/gates.js +13 -13
- package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
- package/.agents/scripts/lib/close-validation/runner.js +13 -21
- package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
- package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
- package/.agents/scripts/lib/config/delivery-routing.js +7 -6
- package/.agents/scripts/lib/config/explain.js +10 -16
- package/.agents/scripts/lib/config/github.js +7 -5
- package/.agents/scripts/lib/config/limits.js +15 -25
- package/.agents/scripts/lib/config/quality.js +11 -14
- package/.agents/scripts/lib/config/runners.js +8 -21
- package/.agents/scripts/lib/config/temp-paths.js +18 -56
- package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
- package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
- package/.agents/scripts/lib/config-settings-schema.js +48 -22
- package/.agents/scripts/lib/dead-exports-knip.js +105 -0
- package/.agents/scripts/lib/dead-exports-mode.js +51 -0
- package/.agents/scripts/lib/duplicate-search.js +38 -7
- package/.agents/scripts/lib/findings/promote-finding.js +23 -14
- package/.agents/scripts/lib/format-generated-json.js +97 -0
- package/.agents/scripts/lib/framework-version.js +19 -189
- package/.agents/scripts/lib/gh-exec.js +8 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
- package/.agents/scripts/lib/git-utils.js +0 -14
- package/.agents/scripts/lib/json-utils.js +1 -2
- package/.agents/scripts/lib/label-constants.js +0 -15
- package/.agents/scripts/lib/label-taxonomy.js +1 -12
- package/.agents/scripts/lib/observability/active-story-env.js +42 -163
- package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
- package/.agents/scripts/lib/observability/signal-validator.js +4 -4
- package/.agents/scripts/lib/observability/signals-writer.js +6 -82
- package/.agents/scripts/lib/observability/source-classifier.js +2 -2
- package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
- package/.agents/scripts/lib/orchestration/change-set.js +103 -0
- package/.agents/scripts/lib/orchestration/code-review.js +70 -191
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
- package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
- package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
- package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
- package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
- package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
- package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
- package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
- package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
- package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
- package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
- package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
- package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
- package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
- package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
- package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
- package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
- package/.agents/scripts/lib/planning-corpus.js +12 -286
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
- package/.agents/scripts/lib/signals/index.js +4 -17
- package/.agents/scripts/lib/signals/read.js +35 -35
- package/.agents/scripts/lib/signals/schema.js +8 -11
- package/.agents/scripts/lib/signals/span-tree.js +7 -7
- package/.agents/scripts/lib/signals/write.js +0 -1
- package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
- package/.agents/scripts/lib/skills/parse-skill.js +16 -3
- package/.agents/scripts/lib/story-adjacency.js +8 -7
- package/.agents/scripts/lib/story-body/story-body.js +81 -13
- package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
- package/.agents/scripts/lib/test-env.js +14 -1
- package/.agents/scripts/lib/test-tiers.js +0 -3
- package/.agents/scripts/lib/ticket-body-sections.js +0 -14
- package/.agents/scripts/lib/validation-evidence.js +31 -59
- package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
- package/.agents/scripts/plan-context.js +38 -7
- package/.agents/scripts/plan-critics.js +203 -0
- package/.agents/scripts/plan-persist.js +145 -35
- package/.agents/scripts/plan-run-epilogue.js +83 -38
- package/.agents/scripts/post-structured-comment.js +0 -38
- package/.agents/scripts/pr-watch-with-update.js +43 -22
- package/.agents/scripts/providers/github/compose.js +0 -1
- package/.agents/scripts/providers/github/errors.js +0 -19
- package/.agents/scripts/providers/github/issues.js +1 -11
- package/.agents/scripts/providers/github/mappers.js +5 -0
- package/.agents/scripts/providers/github/sub-issues.js +0 -47
- package/.agents/scripts/providers/github/tickets.js +33 -153
- package/.agents/scripts/providers/github.js +17 -6
- package/.agents/scripts/quality-preview.js +13 -6
- package/.agents/scripts/resolve-stories.js +236 -0
- package/.agents/scripts/run-coverage.js +4 -1
- package/.agents/scripts/run-lint.js +2 -2
- package/.agents/scripts/run-verify.js +31 -2
- package/.agents/scripts/signals-view.js +9 -10
- package/.agents/scripts/single-story-close.js +173 -18
- package/.agents/scripts/single-story-confirm-merge.js +288 -15
- package/.agents/scripts/single-story-init.js +6 -10
- package/.agents/scripts/stories-wave-tick.js +380 -53
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/update-ticket-state.js +8 -50
- package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
- package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
- package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
- package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
- package/.agents/skills/core/scope-triage/SKILL.md +3 -0
- package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
- package/.agents/skills/core/security-and-hardening/reference.md +375 -0
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
- package/.agents/workflows/audit-architecture.md +3 -4
- package/.agents/workflows/audit-clean-code.md +4 -4
- package/.agents/workflows/audit-documentation.md +4 -5
- package/.agents/workflows/audit-lighthouse.md +8 -0
- package/.agents/workflows/audit-navigability.md +10 -0
- package/.agents/workflows/audit-performance.md +2 -3
- package/.agents/workflows/audit-quality.md +8 -9
- package/.agents/workflows/audit-security.md +1 -2
- package/.agents/workflows/audit-seo.md +10 -0
- package/.agents/workflows/audit-ux-ui.md +7 -0
- package/.agents/workflows/deliver.md +133 -45
- package/.agents/workflows/git-cleanup.md +2 -2
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
- package/.agents/workflows/helpers/code-review.md +14 -12
- package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
- package/.agents/workflows/helpers/deliver-story.md +209 -118
- package/.agents/workflows/helpers/parallel-tooling.md +2 -2
- package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
- package/.agents/workflows/plan.md +239 -19
- package/.agents/workflows/qa-assist.md +6 -6
- package/.agents/workflows/qa-explore.md +3 -3
- package/.agents/workflows/qa-run.md +1 -5
- package/bin/mandrel.js +12 -1
- package/docs/CHANGELOG.md +62 -0
- package/lib/cli/registry.js +262 -19
- package/lib/cli/sync-agents.js +157 -0
- package/lib/cli/sync-commands.js +115 -6
- package/lib/cli/sync.js +168 -6
- package/lib/cli/update.js +105 -8
- package/lib/cli/version-helpers.js +131 -0
- package/lib/migrations/README.md +7 -5
- package/lib/migrations/index.js +17 -9
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
- package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
- package/package.json +2 -2
- package/.agents/schemas/epic-perf-report.schema.json +0 -89
- package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
- package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
- package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
- package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
- package/.agents/schemas/risk-verdict.schema.json +0 -53
- package/.agents/schemas/story-perf-summary.schema.json +0 -73
- package/.agents/scripts/analyze-execution.js +0 -444
- package/.agents/scripts/check-prepush-recovery.js +0 -90
- package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
- package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
- package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
- package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
- package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
- package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
- package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
- package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
- package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
- package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
- package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
- package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
- package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
- package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
- package/.agents/scripts/resolve-plan-run.js +0 -117
- package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
|
@@ -4,15 +4,31 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Ordered, fail-closed pipeline:
|
|
6
6
|
*
|
|
7
|
-
* 1.
|
|
8
|
-
* 2.
|
|
9
|
-
* 3.
|
|
10
|
-
* 4.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
7
|
+
* 1. Ticket validator + file-assumption + DAG + capacity + budget
|
|
8
|
+
* 2. Draft reachability (named soft failure, exit 3)
|
|
9
|
+
* 3. Split-policy partition (`assertAcceptancePartition`) + spec fold/spill
|
|
10
|
+
* 4. Create Story issues (`type::story` + sanitized authored labels —
|
|
11
|
+
* deliberately NOT `agent::ready`), resumably via a plan fingerprint
|
|
12
|
+
* 5. Upsert `story-plan-state` on every created Story; upsert `plan-summary`
|
|
13
|
+
* on the primary Story
|
|
14
|
+
* 6. Flip every Story to `agent::ready` — the terminal step, so `ready`
|
|
15
|
+
* always implies "checkpoints written"
|
|
16
|
+
* 7. Comment on + close the superseded `--tickets` source issues
|
|
17
|
+
* (Story #4535) — bookkeeping only; never fails the run
|
|
18
|
+
* 8. Temp cleanup at terminal success + a stale-plan-dir reap
|
|
19
|
+
*
|
|
20
|
+
* **Why `agent::ready` moved to the end (Story #4541).** Issues used to be
|
|
21
|
+
* born `agent::ready` in the creating POST while the checkpoints were
|
|
22
|
+
* written afterwards. Anything picking a Story up in that window — or after
|
|
23
|
+
* a comment failure aborted the loop — read the checkpoint as `null`
|
|
24
|
+
* (`story-plan-state.js` degrades missing/malformed to `null`). Creating
|
|
25
|
+
* unlabelled, writing checkpoints, then flipping closes that race.
|
|
26
|
+
*
|
|
27
|
+
* **No authored risk artifact (Story #4542).** Persist neither requires nor
|
|
28
|
+
* accepts a risk verdict, derives no envelope from one, and computes no
|
|
29
|
+
* review routing. Review depth and the acceptance-critic mode are derived from
|
|
30
|
+
* the diff at close time (`review-depth.js#deriveChangeLevel`); `--force-review`
|
|
31
|
+
* is an explicit operator flag, recorded here as a receipt and never inferred.
|
|
16
32
|
*
|
|
17
33
|
* Hard cutover: no Epic parent, no reconciler, no `deliveryShape`, no
|
|
18
34
|
* `--amend` tree cascades. Those surfaces die with Stages 4–5 for any
|
|
@@ -21,12 +37,13 @@
|
|
|
21
37
|
* @module lib/orchestration/plan-persist/run-plan-persist
|
|
22
38
|
*/
|
|
23
39
|
|
|
24
|
-
import { rm } from 'node:fs/promises';
|
|
40
|
+
import { readdir, rm, stat } from 'node:fs/promises';
|
|
41
|
+
import path from 'node:path';
|
|
25
42
|
|
|
43
|
+
import { anchorTempRoot, tempRootFrom } from '../../config/temp-paths.js';
|
|
26
44
|
import { getLimits, PROJECT_ROOT } from '../../config-resolver.js';
|
|
27
45
|
import { gitSpawn } from '../../git-utils.js';
|
|
28
46
|
import { Logger } from '../../Logger.js';
|
|
29
|
-
import { evaluatePlanCritics } from '../plan-critics-evaluate.js';
|
|
30
47
|
import {
|
|
31
48
|
appendCriticSkip,
|
|
32
49
|
readPlanMetrics,
|
|
@@ -37,20 +54,23 @@ import {
|
|
|
37
54
|
evaluateDraftReachability,
|
|
38
55
|
renderReachabilityOrphans,
|
|
39
56
|
} from '../plan-reachability.js';
|
|
40
|
-
import { resolveReviewRouting } from '../plan-review-routing.js';
|
|
41
|
-
import { deriveRiskEnvelope } from '../planning-risk.js';
|
|
42
57
|
import { upsertStructuredComment } from '../ticketing.js';
|
|
43
58
|
import {
|
|
44
59
|
enforceFanOutGate,
|
|
45
60
|
surfaceSoftConflictFindings,
|
|
46
61
|
} from './fan-out-gate.js';
|
|
47
|
-
import { validateTickets } from './persist-helpers.js';
|
|
48
|
-
import {
|
|
62
|
+
import { resolveBaseBranchRef, validateTickets } from './persist-helpers.js';
|
|
63
|
+
import {
|
|
64
|
+
assemblePlanStories,
|
|
65
|
+
createStoryIssues,
|
|
66
|
+
markStoriesReady,
|
|
67
|
+
} from './story-ops.js';
|
|
49
68
|
import {
|
|
50
69
|
buildPlanSummaryCommentBody,
|
|
51
70
|
buildWaveTable,
|
|
52
71
|
PLAN_SUMMARY_COMMENT_TYPE,
|
|
53
72
|
} from './summary.js';
|
|
73
|
+
import { closeSupersededTickets } from './supersede-ops.js';
|
|
54
74
|
|
|
55
75
|
/** Checkpoint schema version written on each Story's story-plan-state. */
|
|
56
76
|
const PLAN_CHECKPOINT_SCHEMA_VERSION_V2 = 2;
|
|
@@ -88,6 +108,25 @@ export async function writeCheckpointV2(provider, storyId, state) {
|
|
|
88
108
|
return state;
|
|
89
109
|
}
|
|
90
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Enforce the ticket validator's findings and report what the file-assumption
|
|
113
|
+
* gate actually concluded.
|
|
114
|
+
*
|
|
115
|
+
* The return value feeds the posted `plan-summary`'s freshness line, which
|
|
116
|
+
* used to be hard-coded `{ stale: 0, ambiguous: 0 }` — so the comment read
|
|
117
|
+
* "Spec freshness: clean" even on the one run where the gate had *given up*
|
|
118
|
+
* (Story #4541's unresolvable-base-ref downgrade). The summary asserted a
|
|
119
|
+
* clean result precisely when it had the least evidence for one.
|
|
120
|
+
*
|
|
121
|
+
* The mapping is deliberate. A confirmed mismatch is never reported here at
|
|
122
|
+
* all — it throws, and the run posts no summary. The only findings that
|
|
123
|
+
* survive to the summary are ones the gate could not *verify*, because the
|
|
124
|
+
* base ref they were computed against does not resolve; unverifiable is
|
|
125
|
+
* `ambiguous`, not `stale`.
|
|
126
|
+
*
|
|
127
|
+
* @returns {{ stale: number, ambiguous: number }} Freshness counts for the
|
|
128
|
+
* posted summary.
|
|
129
|
+
*/
|
|
91
130
|
function enforceTicketValidation(validated, { config, settings, cwd }) {
|
|
92
131
|
const validationErrors = validated.errors ?? [];
|
|
93
132
|
const assumptionFailures = validationErrors.filter((error) =>
|
|
@@ -102,8 +141,15 @@ function enforceTicketValidation(validated, { config, settings, cwd }) {
|
|
|
102
141
|
`hard error(s):\n${blockingErrors.map((error) => ` - ${error}`).join('\n')}`,
|
|
103
142
|
);
|
|
104
143
|
}
|
|
105
|
-
if (assumptionFailures.length === 0) return;
|
|
106
|
-
|
|
144
|
+
if (assumptionFailures.length === 0) return { stale: 0, ambiguous: 0 };
|
|
145
|
+
// Story #4541: resolve through the canonical `project.baseBranch` (the
|
|
146
|
+
// shape `config-resolver` actually emits) with the legacy settings bag as
|
|
147
|
+
// a fallback — reading a bare `config.baseBranch` meant this probe always
|
|
148
|
+
// targeted the literal `main`.
|
|
149
|
+
const gateBaseRef =
|
|
150
|
+
config?.project?.baseBranch ??
|
|
151
|
+
settings?.baseBranch ??
|
|
152
|
+
resolveBaseBranchRef(config);
|
|
107
153
|
const refResolves =
|
|
108
154
|
gitSpawn(
|
|
109
155
|
cwd ?? process.cwd(),
|
|
@@ -122,16 +168,155 @@ function enforceTicketValidation(validated, { config, settings, cwd }) {
|
|
|
122
168
|
`[plan-persist] file-assumption gate skipped: base ref '${gateBaseRef}' ` +
|
|
123
169
|
`does not resolve — ${assumptionFailures.length} finding(s) downgraded.`,
|
|
124
170
|
);
|
|
171
|
+
return { stale: 0, ambiguous: assumptionFailures.length };
|
|
125
172
|
}
|
|
126
173
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Run the supersede close phase behind a belt-and-braces guard.
|
|
176
|
+
*
|
|
177
|
+
* `closeSupersededTickets` already swallows per-ticket failures, but this
|
|
178
|
+
* phase runs *after* `createIssue`, so an unexpected throw anywhere in it
|
|
179
|
+
* would leave the run half-done with Stories already live. Degrade to a
|
|
180
|
+
* reported failure instead.
|
|
181
|
+
*
|
|
182
|
+
* @returns {Promise<import('./supersede-ops.js').SupersedeReport>}
|
|
183
|
+
*/
|
|
184
|
+
async function runSupersedePhase(args) {
|
|
185
|
+
try {
|
|
186
|
+
return await closeSupersededTickets(args);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
Logger.warn(
|
|
189
|
+
`[plan-persist] supersede close phase failed: ${err.message} — ` +
|
|
190
|
+
'Stories were created; close the source tickets by hand.',
|
|
191
|
+
);
|
|
192
|
+
return {
|
|
193
|
+
enabled: true,
|
|
194
|
+
dryRun: args.dryRun === true,
|
|
195
|
+
reason: `phase-error: ${err.message}`,
|
|
196
|
+
closed: [],
|
|
197
|
+
planned: [],
|
|
198
|
+
skipped: [],
|
|
199
|
+
failed: (args.sourceTicketIds ?? []).map((ticket) => ({
|
|
200
|
+
ticket,
|
|
201
|
+
reason: err.message,
|
|
202
|
+
})),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Render the plan-metrics line for the **posted** `plan-summary` comment,
|
|
209
|
+
* scoped to this invocation.
|
|
210
|
+
*
|
|
211
|
+
* The ordering hazard this closes: the ledger record for the current run is
|
|
212
|
+
* appended by `recordPlanInvocation`'s `finally`, which by construction only
|
|
213
|
+
* fires once `runPlanPersist` has *resolved* — long after this comment body
|
|
214
|
+
* is composed. A plain `since`-filtered ledger read therefore summarized
|
|
215
|
+
* every run except the one being summarized, and on an otherwise-quiet
|
|
216
|
+
* ledger it rendered "plan-metrics: no invocations recorded" onto the very
|
|
217
|
+
* comment reporting the run. (The stdout envelope was always correct: its
|
|
218
|
+
* `attachPlanMetrics` read runs after the wrapper returns.)
|
|
219
|
+
*
|
|
220
|
+
* **The `finally` stays where it is.** It is what guarantees a persist that
|
|
221
|
+
* throws is still recorded, so moving the append earlier — or moving this
|
|
222
|
+
* read later, past the GitHub writes it feeds — would trade a cosmetic bug
|
|
223
|
+
* for a real one. Instead of racing it, fold in a synthetic record standing
|
|
224
|
+
* for the in-flight invocation. It is the same record the wrapper is about to
|
|
225
|
+
* write, minus a final duration; it cannot double-count, because the real one
|
|
226
|
+
* does not exist yet at this point in the pipeline.
|
|
227
|
+
*
|
|
228
|
+
* `ok: true` is honest here: this line is only ever composed on the success
|
|
229
|
+
* path, after `createStoryIssues` has returned.
|
|
230
|
+
*
|
|
231
|
+
* @param {{ config: object, since: string, startedAt: string, mode: string }} args
|
|
232
|
+
* @returns {Promise<string|null>}
|
|
233
|
+
*/
|
|
234
|
+
async function renderRunScopedPlanMetricsLine({
|
|
235
|
+
config,
|
|
236
|
+
since,
|
|
237
|
+
startedAt,
|
|
238
|
+
mode,
|
|
239
|
+
}) {
|
|
240
|
+
try {
|
|
241
|
+
const ledger = await readPlanMetrics(null, config);
|
|
242
|
+
const endedAt = new Date().toISOString();
|
|
243
|
+
const inFlight = {
|
|
244
|
+
v: 1,
|
|
245
|
+
cli: 'plan-persist',
|
|
246
|
+
mode,
|
|
247
|
+
epicId: null,
|
|
248
|
+
startedAt,
|
|
249
|
+
endedAt,
|
|
250
|
+
durationMs: Math.max(0, Date.parse(endedAt) - Date.parse(startedAt)) || 0,
|
|
251
|
+
ok: true,
|
|
252
|
+
};
|
|
253
|
+
const summary = summarizePlanMetrics(
|
|
254
|
+
{ ...ledger, entries: [...(ledger.entries ?? []), inFlight] },
|
|
255
|
+
{ since },
|
|
256
|
+
);
|
|
257
|
+
return renderPlanMetricsSummaryLine(summary);
|
|
258
|
+
} catch (err) {
|
|
259
|
+
Logger.warn(`[plan-persist] plan-metrics summary skipped: ${err.message}`);
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Age after which an abandoned `temp/plan-*` directory is reaped. A plan run
|
|
266
|
+
* that is still being authored is minutes-to-hours old; a week is far past
|
|
267
|
+
* any live run and comfortably past an operator returning to a paused one.
|
|
268
|
+
*/
|
|
269
|
+
const STALE_PLAN_DIR_MS = 7 * 24 * 60 * 60 * 1000;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Reap abandoned `plan-*` directories under the temp root (Story #4541).
|
|
273
|
+
*
|
|
274
|
+
* Terminal-success cleanup only ever removed the *current* run's `planDir`,
|
|
275
|
+
* so every plan that failed a gate, was abandoned mid-authoring, or ran
|
|
276
|
+
* `--dry-run` left its directory behind forever. This sweeps the stragglers
|
|
277
|
+
* on each persist.
|
|
278
|
+
*
|
|
279
|
+
* Best-effort throughout: this is hygiene, never a reason to fail a run that
|
|
280
|
+
* has already created Stories. The current run's own `planDir` is always
|
|
281
|
+
* excluded — its cleanup is the caller's decision.
|
|
282
|
+
*
|
|
283
|
+
* @param {{ config?: object, keepDir?: string|null, now?: number }} args
|
|
284
|
+
* @returns {Promise<{ reaped: string[] }>}
|
|
285
|
+
*/
|
|
286
|
+
export async function reapStalePlanDirs({
|
|
287
|
+
config = {},
|
|
288
|
+
keepDir = null,
|
|
289
|
+
now = Date.now(),
|
|
290
|
+
} = {}) {
|
|
291
|
+
const reaped = [];
|
|
292
|
+
const tempRoot = anchorTempRoot(tempRootFrom(config));
|
|
293
|
+
let entries;
|
|
294
|
+
try {
|
|
295
|
+
entries = await readdir(tempRoot, { withFileTypes: true });
|
|
296
|
+
} catch {
|
|
297
|
+
return { reaped }; // No temp root yet — nothing to reap.
|
|
298
|
+
}
|
|
299
|
+
const keep = keepDir ? path.resolve(keepDir) : null;
|
|
300
|
+
for (const entry of entries) {
|
|
301
|
+
if (!entry.isDirectory() || !entry.name.startsWith('plan-')) continue;
|
|
302
|
+
const dir = path.resolve(tempRoot, entry.name);
|
|
303
|
+
if (keep !== null && dir === keep) continue;
|
|
304
|
+
try {
|
|
305
|
+
const { mtimeMs } = await stat(dir);
|
|
306
|
+
if (now - mtimeMs < STALE_PLAN_DIR_MS) continue;
|
|
307
|
+
await rm(dir, { recursive: true, force: true });
|
|
308
|
+
reaped.push(dir);
|
|
309
|
+
} catch {
|
|
310
|
+
// A racing writer or a permission error: leave it for the next run.
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (reaped.length > 0) {
|
|
314
|
+
Logger.info(
|
|
315
|
+
`[plan-persist] reaped ${reaped.length} abandoned plan director(ies) ` +
|
|
316
|
+
`older than 7d under ${tempRoot}.`,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
return { reaped };
|
|
135
320
|
}
|
|
136
321
|
|
|
137
322
|
/**
|
|
@@ -141,7 +326,6 @@ function riskVerdictCommentBody(riskVerdict) {
|
|
|
141
326
|
* provider: object,
|
|
142
327
|
* artifacts: {
|
|
143
328
|
* stories: Array<object>,
|
|
144
|
-
* riskVerdict: object,
|
|
145
329
|
* techSpecContent?: string|null,
|
|
146
330
|
* planAcceptance?: string[]|null,
|
|
147
331
|
* },
|
|
@@ -153,10 +337,12 @@ function riskVerdictCommentBody(riskVerdict) {
|
|
|
153
337
|
* allowLargeFanOut?: boolean,
|
|
154
338
|
* skipCleanup?: boolean,
|
|
155
339
|
* dryRun?: boolean,
|
|
156
|
-
* planRunId?: string,
|
|
157
340
|
* planDir?: string,
|
|
158
341
|
* fanOutCounter?: Function,
|
|
159
342
|
* cwd?: string,
|
|
343
|
+
* sourceTicketIds?: number[],
|
|
344
|
+
* sourceTicketOrigin?: 'flag'|'envelope'|'none',
|
|
345
|
+
* closeSuperseded?: boolean,
|
|
160
346
|
* },
|
|
161
347
|
* }} input
|
|
162
348
|
*/
|
|
@@ -169,7 +355,6 @@ export async function runPlanPersist({
|
|
|
169
355
|
}) {
|
|
170
356
|
const {
|
|
171
357
|
stories: rawStories = null,
|
|
172
|
-
riskVerdict,
|
|
173
358
|
techSpecContent = null,
|
|
174
359
|
planAcceptance = null,
|
|
175
360
|
} = artifacts ?? {};
|
|
@@ -179,24 +364,20 @@ export async function runPlanPersist({
|
|
|
179
364
|
allowLargeFanOut = false,
|
|
180
365
|
skipCleanup = false,
|
|
181
366
|
dryRun = false,
|
|
182
|
-
planRunId,
|
|
183
367
|
planDir = null,
|
|
184
368
|
fanOutCounter = undefined,
|
|
185
369
|
cwd = PROJECT_ROOT,
|
|
370
|
+
sourceTicketIds = [],
|
|
371
|
+
sourceTicketOrigin = 'none',
|
|
372
|
+
closeSuperseded = true,
|
|
186
373
|
} = opts;
|
|
187
374
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if ('deliveryShape' in (riskVerdict ?? {})) {
|
|
195
|
-
throw new Error(
|
|
196
|
-
'[plan-persist] risk-verdict.deliveryShape was removed in v2 Stage 3 — ' +
|
|
197
|
-
'delete the field; persist always creates Story issue(s).',
|
|
198
|
-
);
|
|
199
|
-
}
|
|
375
|
+
// Boundary for the plan-metrics summary below: everything this invocation
|
|
376
|
+
// appends to the ledger is stamped at or after this instant, so filtering
|
|
377
|
+
// on it scopes the counts to *this* run rather than every plan ever run
|
|
378
|
+
// through the shared standalone ledger (Story #4541).
|
|
379
|
+
const runStartedAt = opts.metricsSince ?? new Date().toISOString();
|
|
380
|
+
|
|
200
381
|
if (!Array.isArray(rawStories) || rawStories.length === 0) {
|
|
201
382
|
throw new Error(
|
|
202
383
|
'[plan-persist] stories payload must be a non-empty array ' +
|
|
@@ -228,7 +409,11 @@ export async function runPlanPersist({
|
|
|
228
409
|
});
|
|
229
410
|
enforceFanOutGate(validated.findings, allowLargeFanOut, 'plan-persist');
|
|
230
411
|
surfaceSoftConflictFindings(validated.findings, 'plan-persist');
|
|
231
|
-
enforceTicketValidation(validated, {
|
|
412
|
+
const freshness = enforceTicketValidation(validated, {
|
|
413
|
+
config,
|
|
414
|
+
settings,
|
|
415
|
+
cwd,
|
|
416
|
+
});
|
|
232
417
|
|
|
233
418
|
const reachability = evaluateDraftReachability({
|
|
234
419
|
tickets: rawStories,
|
|
@@ -252,46 +437,17 @@ export async function runPlanPersist({
|
|
|
252
437
|
);
|
|
253
438
|
}
|
|
254
439
|
|
|
255
|
-
const critics = evaluatePlanCritics({
|
|
256
|
-
techSpecContent: techSpecContent ?? '',
|
|
257
|
-
riskVerdict,
|
|
258
|
-
tickets: rawStories,
|
|
259
|
-
config,
|
|
260
|
-
});
|
|
261
|
-
for (const decision of [critics.consolidation, critics.premortem]) {
|
|
262
|
-
Logger.info(
|
|
263
|
-
`[plan-persist] critic ${decision.critic}: ` +
|
|
264
|
-
`${decision.dispatch ? 'dispatch' : 'skip'} — ` +
|
|
265
|
-
decision.reasons.join('; '),
|
|
266
|
-
);
|
|
267
|
-
if (!decision.dispatch) {
|
|
268
|
-
await appendCriticSkip(
|
|
269
|
-
{
|
|
270
|
-
critic: decision.critic,
|
|
271
|
-
reasons: decision.reasons,
|
|
272
|
-
cli: 'plan-persist',
|
|
273
|
-
},
|
|
274
|
-
config,
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
440
|
// Split policy + inline Spec fold (over-budget Specs fail closed — no docs/).
|
|
280
441
|
const { stories } = assemblePlanStories(rawStories, {
|
|
281
442
|
sharedSpec: techSpecContent,
|
|
282
443
|
planAcceptance: planAcceptance ?? undefined,
|
|
444
|
+
sourceTicketIds,
|
|
283
445
|
});
|
|
284
446
|
|
|
285
|
-
const
|
|
286
|
-
const reviewRouting = resolveReviewRouting({
|
|
287
|
-
planningRisk,
|
|
288
|
-
forceReview,
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
const { created, planRunLabel } = await createStoryIssues({
|
|
447
|
+
const { created } = await createStoryIssues({
|
|
292
448
|
provider,
|
|
293
449
|
stories,
|
|
294
|
-
opts: {
|
|
450
|
+
opts: { dryRun },
|
|
295
451
|
});
|
|
296
452
|
|
|
297
453
|
const primary = created[0];
|
|
@@ -303,44 +459,39 @@ export async function runPlanPersist({
|
|
|
303
459
|
})),
|
|
304
460
|
);
|
|
305
461
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
462
|
+
// Story #4541: `readPlanMetrics` is declared `(epicId, config)` but was
|
|
463
|
+
// called with `config` first, so the ledger path resolver received the
|
|
464
|
+
// config object as an `epicId` and threw its guard on every single run —
|
|
465
|
+
// a throw this try/catch then swallowed into a silently absent summary.
|
|
466
|
+
// v2 persist is always Epic-less, hence the explicit `null`. The `since`
|
|
467
|
+
// filter keeps the counts about this invocation, and the in-flight record
|
|
468
|
+
// is folded in because the wrapper has not written it yet — see
|
|
469
|
+
// `renderRunScopedPlanMetricsLine`.
|
|
470
|
+
const planMetricsLine = await renderRunScopedPlanMetricsLine({
|
|
471
|
+
config,
|
|
472
|
+
since: runStartedAt,
|
|
473
|
+
startedAt: runStartedAt,
|
|
474
|
+
mode: dryRun ? 'dry-run' : 'persist',
|
|
475
|
+
});
|
|
313
476
|
|
|
314
477
|
const summaryBody = buildPlanSummaryCommentBody({
|
|
315
478
|
epicId: primary.id,
|
|
316
479
|
ticketCount: created.length,
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
freshness: { stale: 0, ambiguous: 0 },
|
|
480
|
+
forceReview,
|
|
481
|
+
freshness,
|
|
320
482
|
healthcheck: { skipped: true },
|
|
321
483
|
waveTable,
|
|
322
484
|
mode: 'stories',
|
|
323
485
|
planMetricsLine,
|
|
324
486
|
stories: created,
|
|
325
|
-
planRunLabel,
|
|
326
487
|
});
|
|
327
488
|
|
|
328
489
|
if (!dryRun) {
|
|
329
490
|
for (const story of created) {
|
|
330
|
-
await upsertStructuredComment(
|
|
331
|
-
provider,
|
|
332
|
-
story.id,
|
|
333
|
-
'risk-verdict',
|
|
334
|
-
riskVerdictCommentBody(riskVerdict),
|
|
335
|
-
);
|
|
336
491
|
await writeCheckpointV2(provider, story.id, {
|
|
337
|
-
planningRisk,
|
|
338
|
-
riskVerdict,
|
|
339
|
-
reviewRouting,
|
|
340
492
|
persist: {
|
|
341
493
|
completedAt: new Date().toISOString(),
|
|
342
494
|
storyCount: created.length,
|
|
343
|
-
planRunLabel,
|
|
344
495
|
primaryStoryId: primary.id,
|
|
345
496
|
stories: created.map((createdStory) => ({
|
|
346
497
|
slug: createdStory.slug,
|
|
@@ -355,8 +506,26 @@ export async function runPlanPersist({
|
|
|
355
506
|
PLAN_SUMMARY_COMMENT_TYPE,
|
|
356
507
|
summaryBody,
|
|
357
508
|
);
|
|
509
|
+
|
|
510
|
+
// Terminal step: every checkpoint above is now on every Story, so
|
|
511
|
+
// `agent::ready` can honestly mean "fully persisted" (Story #4541).
|
|
512
|
+
// Anything that picks a Story up from here reads a real checkpoint.
|
|
513
|
+
await markStoriesReady({ provider, created });
|
|
358
514
|
}
|
|
359
515
|
|
|
516
|
+
const supersede = await runSupersedePhase({
|
|
517
|
+
provider,
|
|
518
|
+
stories,
|
|
519
|
+
created,
|
|
520
|
+
sourceTicketIds,
|
|
521
|
+
dryRun,
|
|
522
|
+
closeSuperseded,
|
|
523
|
+
});
|
|
524
|
+
// Record which channel the ids came from so a run that superseded nothing
|
|
525
|
+
// says *why* (`none` = neither the envelope nor --source-tickets carried
|
|
526
|
+
// any) rather than reading as a clean no-op — Story #4554.
|
|
527
|
+
supersede.sourceTicketOrigin = sourceTicketOrigin;
|
|
528
|
+
|
|
360
529
|
if (!skipCleanup && planDir) {
|
|
361
530
|
try {
|
|
362
531
|
await rm(planDir, { recursive: true, force: true });
|
|
@@ -364,21 +533,32 @@ export async function runPlanPersist({
|
|
|
364
533
|
Logger.warn(`[plan-persist] temp cleanup skipped: ${err.message}`);
|
|
365
534
|
}
|
|
366
535
|
}
|
|
536
|
+
// Terminal-success cleanup only ever removes *this* run's planDir, so
|
|
537
|
+
// abandoned ones accumulated forever. Sweep them (Story #4541).
|
|
538
|
+
await reapStalePlanDirs({ config, keepDir: skipCleanup ? planDir : null });
|
|
367
539
|
|
|
540
|
+
const adopted = created.filter((story) => story.adopted);
|
|
541
|
+
if (adopted.length > 0) {
|
|
542
|
+
Logger.info(
|
|
543
|
+
`[plan-persist] resumed ${adopted.length} of ${created.length} Story(ies) ` +
|
|
544
|
+
`from a previous persist: ${adopted.map((s2) => `#${s2.id}`).join(', ')}.`,
|
|
545
|
+
);
|
|
546
|
+
}
|
|
368
547
|
Logger.info(
|
|
369
548
|
`[plan-persist] Persisted ${created.length} Story(ies)` +
|
|
370
|
-
(planRunLabel ? ` under ${planRunLabel}` : '') +
|
|
371
549
|
`; primary #${primary.id} is agent::ready.`,
|
|
372
550
|
);
|
|
551
|
+
Logger.info(
|
|
552
|
+
`[plan-persist] Deliver with: /deliver ${created.map((s2) => s2.id).join(' ')}`,
|
|
553
|
+
);
|
|
373
554
|
|
|
374
555
|
return {
|
|
375
556
|
stories: created,
|
|
376
557
|
primaryStoryId: primary.id,
|
|
377
|
-
|
|
378
|
-
planningRisk,
|
|
379
|
-
reviewRouting,
|
|
380
|
-
critics,
|
|
558
|
+
forceReview,
|
|
381
559
|
reachability,
|
|
560
|
+
freshness,
|
|
382
561
|
waveTable,
|
|
562
|
+
supersede,
|
|
383
563
|
};
|
|
384
564
|
}
|