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
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* plan-critics.js — the /plan critic-dispatch verdict CLI (Story #4592).
|
|
5
|
+
*
|
|
6
|
+
* `/plan` step 2.5 (between Author and Persist) runs this against the draft
|
|
7
|
+
* `stories.json`. It evaluates the consolidation + pre-mortem dispatch
|
|
8
|
+
* conditions and prints the verdict as JSON on stdout so the workflow can
|
|
9
|
+
* act on it — dispatching a fresh-context critic sub-agent and folding its
|
|
10
|
+
* findings into a re-author round **before** the plan is persisted.
|
|
11
|
+
*
|
|
12
|
+
* Why here and nowhere else. The evaluation used to run inside
|
|
13
|
+
* `run-plan-persist.js`, after authoring was finished and immediately before
|
|
14
|
+
* `createStoryIssues` — the one point in the flow where nothing can act on a
|
|
15
|
+
* `dispatch: true` verdict, because the artifacts are about to become live
|
|
16
|
+
* issues. It logged the verdict and moved on. This CLI is now the **single**
|
|
17
|
+
* evaluation point, sited where a re-author loop actually exists.
|
|
18
|
+
*
|
|
19
|
+
* Advisory by contract: a `dispatch: true` verdict routes work to the
|
|
20
|
+
* workflow, it does not gate the run. This CLI exits 0 on any verdict; only a
|
|
21
|
+
* usage/IO error is a failure. Every `dispatch: false` decision is recorded to
|
|
22
|
+
* the plan-metrics ledger (`appendCriticSkip`) so under-firing stays auditable.
|
|
23
|
+
*
|
|
24
|
+
* CLI:
|
|
25
|
+
* --stories <file> Required. The draft Story ticket array (JSON).
|
|
26
|
+
* --tech-spec <file> Optional. Shared Tech Spec carrying the
|
|
27
|
+
* `## Delivery Slicing` table the consolidation
|
|
28
|
+
* precondition reads.
|
|
29
|
+
*
|
|
30
|
+
* stdout is reserved for the verdict JSON (Story #2278 discipline):
|
|
31
|
+
*
|
|
32
|
+
* {
|
|
33
|
+
* "consolidation": { "critic": "consolidation", "dispatch": false, "reasons": [...] },
|
|
34
|
+
* "premortem": { "critic": "pre-mortem", "dispatch": true, "reasons": [...] },
|
|
35
|
+
* "textHygiene": { "critic": "text-hygiene", "findings": [...] }
|
|
36
|
+
* }
|
|
37
|
+
*
|
|
38
|
+
* `textHygiene` (Story #4599) is advisory-only: deterministic body lints with
|
|
39
|
+
* no dispatch semantics — its findings fold into the re-author round.
|
|
40
|
+
*
|
|
41
|
+
* Human-readable log lines go to stderr, matching the sibling `plan-persist`.
|
|
42
|
+
*
|
|
43
|
+
* Exit codes: 0 success (any verdict); 1 usage/IO error.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import { readFile } from 'node:fs/promises';
|
|
47
|
+
import path from 'node:path';
|
|
48
|
+
import { parseArgs } from 'node:util';
|
|
49
|
+
|
|
50
|
+
import { runAsCli } from './lib/cli-utils.js';
|
|
51
|
+
import { resolveConfig } from './lib/config-resolver.js';
|
|
52
|
+
import { Logger, routeAllOutputToStderr } from './lib/Logger.js';
|
|
53
|
+
import { evaluatePlanCritics } from './lib/orchestration/plan-critics-evaluate.js';
|
|
54
|
+
import { appendCriticSkip } from './lib/orchestration/plan-metrics.js';
|
|
55
|
+
|
|
56
|
+
const CLI_OPTIONS = {
|
|
57
|
+
stories: { type: 'string' },
|
|
58
|
+
'tech-spec': { type: 'string' },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const USAGE = 'Usage: plan-critics.js --stories <file> [--tech-spec <file>]';
|
|
62
|
+
|
|
63
|
+
/** The `cli` discriminator every ledger record from this surface carries. */
|
|
64
|
+
export const PLAN_CRITICS_CLI = 'plan-critics';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Read the draft artifacts the critics evaluate.
|
|
68
|
+
*
|
|
69
|
+
* @param {{ storiesPath: string, techSpecPath?: string|null }} paths
|
|
70
|
+
* @returns {Promise<{ tickets: object[], techSpecContent: string }>}
|
|
71
|
+
*/
|
|
72
|
+
export async function loadCriticArtifacts({
|
|
73
|
+
storiesPath,
|
|
74
|
+
techSpecPath = null,
|
|
75
|
+
}) {
|
|
76
|
+
const raw = await readFile(storiesPath, 'utf8');
|
|
77
|
+
let tickets;
|
|
78
|
+
try {
|
|
79
|
+
tickets = JSON.parse(raw);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Failed to parse stories file "${storiesPath}" as JSON: ${err.message}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(tickets)) {
|
|
86
|
+
throw new Error(`Stories file "${storiesPath}" must contain a JSON array.`);
|
|
87
|
+
}
|
|
88
|
+
const techSpecContent = techSpecPath
|
|
89
|
+
? await readFile(techSpecPath, 'utf8')
|
|
90
|
+
: '';
|
|
91
|
+
return { tickets, techSpecContent };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Log each decision and record every skip on the plan-metrics ledger. The
|
|
96
|
+
* ledger write is best-effort by `appendCriticSkip`'s own contract — it can
|
|
97
|
+
* never fail the plan step.
|
|
98
|
+
*
|
|
99
|
+
* @param {{ consolidation: object, premortem: object, textHygiene?: object }} verdict
|
|
100
|
+
* @param {object} config
|
|
101
|
+
* @param {{ append?: typeof appendCriticSkip }} [deps]
|
|
102
|
+
* @returns {Promise<void>}
|
|
103
|
+
*/
|
|
104
|
+
export async function recordCriticSkips(
|
|
105
|
+
verdict,
|
|
106
|
+
config,
|
|
107
|
+
{ append = appendCriticSkip } = {},
|
|
108
|
+
) {
|
|
109
|
+
for (const decision of [verdict.consolidation, verdict.premortem]) {
|
|
110
|
+
Logger.info(
|
|
111
|
+
`[plan-critics] critic ${decision.critic}: ` +
|
|
112
|
+
`${decision.dispatch ? 'dispatch' : 'skip'} — ` +
|
|
113
|
+
decision.reasons.join('; '),
|
|
114
|
+
);
|
|
115
|
+
if (!decision.dispatch) {
|
|
116
|
+
await append(
|
|
117
|
+
{
|
|
118
|
+
critic: decision.critic,
|
|
119
|
+
reasons: decision.reasons,
|
|
120
|
+
cli: PLAN_CRITICS_CLI,
|
|
121
|
+
},
|
|
122
|
+
config,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Text hygiene (Story #4599) is advisory-only — no dispatch semantics, so
|
|
128
|
+
// "skip" here means "zero findings". Recording that keeps the lint's
|
|
129
|
+
// fire/skip accounting on the same ledger as the dispatching critics.
|
|
130
|
+
const hygiene = verdict.textHygiene;
|
|
131
|
+
if (hygiene) {
|
|
132
|
+
const count = hygiene.findings.length;
|
|
133
|
+
Logger.info(
|
|
134
|
+
`[plan-critics] critic ${hygiene.critic}: ${count} finding(s) (advisory).`,
|
|
135
|
+
);
|
|
136
|
+
if (count === 0) {
|
|
137
|
+
await append(
|
|
138
|
+
{
|
|
139
|
+
critic: hygiene.critic,
|
|
140
|
+
reasons: ['No text-hygiene findings over the draft stories.'],
|
|
141
|
+
cli: PLAN_CRITICS_CLI,
|
|
142
|
+
},
|
|
143
|
+
config,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Load the artifacts, evaluate both critics, record the skips, and return the
|
|
151
|
+
* verdict. Exported as the CLI's whole body so tests drive it in-process with
|
|
152
|
+
* an explicit config and ledger seam.
|
|
153
|
+
*
|
|
154
|
+
* @param {{
|
|
155
|
+
* storiesPath: string,
|
|
156
|
+
* techSpecPath?: string|null,
|
|
157
|
+
* config?: object,
|
|
158
|
+
* append?: typeof appendCriticSkip,
|
|
159
|
+
* }} args
|
|
160
|
+
* @returns {Promise<{ consolidation: object, premortem: object, textHygiene: object }>}
|
|
161
|
+
*/
|
|
162
|
+
export async function evaluateCriticArtifacts({
|
|
163
|
+
storiesPath,
|
|
164
|
+
techSpecPath = null,
|
|
165
|
+
config = {},
|
|
166
|
+
append = appendCriticSkip,
|
|
167
|
+
}) {
|
|
168
|
+
const { tickets, techSpecContent } = await loadCriticArtifacts({
|
|
169
|
+
storiesPath,
|
|
170
|
+
techSpecPath,
|
|
171
|
+
});
|
|
172
|
+
const verdict = evaluatePlanCritics({ techSpecContent, tickets, config });
|
|
173
|
+
await recordCriticSkips(verdict, config, { append });
|
|
174
|
+
return verdict;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function main() {
|
|
178
|
+
const { values } = parseArgs({ options: CLI_OPTIONS });
|
|
179
|
+
|
|
180
|
+
if (!values.stories) {
|
|
181
|
+
throw new Error(USAGE);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// stdout is reserved for the verdict JSON — flip every Logger sink that
|
|
185
|
+
// could land on stdout to stderr before any evaluation runs.
|
|
186
|
+
routeAllOutputToStderr();
|
|
187
|
+
|
|
188
|
+
const verdict = await evaluateCriticArtifacts({
|
|
189
|
+
storiesPath: path.resolve(values.stories),
|
|
190
|
+
techSpecPath: values['tech-spec']
|
|
191
|
+
? path.resolve(values['tech-spec'])
|
|
192
|
+
: null,
|
|
193
|
+
config: resolveConfig(),
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
process.stdout.write(`${JSON.stringify(verdict, null, 2)}\n`);
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
runAsCli(import.meta.url, main, {
|
|
201
|
+
source: 'plan-critics',
|
|
202
|
+
propagateExitCode: true,
|
|
203
|
+
});
|
|
@@ -4,27 +4,51 @@
|
|
|
4
4
|
* plan-persist.js — flat Story GitHub-write surface for v2 `/plan`
|
|
5
5
|
* (Stage 3 — `docs/roadmap.md`).
|
|
6
6
|
*
|
|
7
|
-
* Given the author-written planning artifacts (`stories.json
|
|
8
|
-
*
|
|
9
|
-
* creates Story issue(s) directly:
|
|
7
|
+
* Given the author-written planning artifacts (`stories.json`, optional shared
|
|
8
|
+
* Tech Spec), this CLI validates and creates Story issue(s) directly:
|
|
10
9
|
*
|
|
11
|
-
*
|
|
10
|
+
* ticket validator / DAG / capacity → reachability →
|
|
12
11
|
* split-policy partition → fold/spill Spec into each Story body →
|
|
13
|
-
* createIssue(s) with type::story
|
|
14
|
-
* story-plan-state on every Story;
|
|
15
|
-
*
|
|
12
|
+
* createIssue(s) with type::story, resumably by plan fingerprint (NOT
|
|
13
|
+
* agent::ready) → story-plan-state on every Story;
|
|
14
|
+
* plan-summary on the primary → flip every Story to agent::ready →
|
|
15
|
+
* comment + close superseded source tickets → temp cleanup + stale reap.
|
|
16
|
+
*
|
|
17
|
+
* Story #4542 retired the authored risk verdict: persist neither requires nor
|
|
18
|
+
* accepts one, and no plan-time step produces one. Review depth and the
|
|
19
|
+
* acceptance-critic mode are derived from the diff at close time
|
|
20
|
+
* (`review-depth.js#deriveChangeLevel`). `--force-review` is the only review
|
|
21
|
+
* gate the planner still carries, and it is an explicit operator flag.
|
|
16
22
|
*
|
|
17
23
|
* CLI:
|
|
18
|
-
* --stories <file>
|
|
19
|
-
* --
|
|
20
|
-
* --
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* --plan-
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* --stories <file> Required Story ticket array (default length 1)
|
|
25
|
+
* --tech-spec <file> Optional shared Tech Spec folded into each Story
|
|
26
|
+
* --plan-dir <dir> Optional temp dir deleted at terminal success.
|
|
27
|
+
* Also where the `plan-context.json` envelope is
|
|
28
|
+
* auto-discovered from (see --plan-context)
|
|
29
|
+
* --plan-context <file> Optional explicit path to the `plan-context.js`
|
|
30
|
+
* envelope. Its `sourceTickets[]` is what makes
|
|
31
|
+
* `--tickets` superseding work without a flag
|
|
32
|
+
* --plan-acceptance <file> Optional JSON string[] for partition coverage
|
|
33
|
+
* --source-tickets <ids> Explicit OVERRIDE of the envelope-derived source
|
|
34
|
+
* ids, for hand-driven runs. Each id must be
|
|
35
|
+
* claimed by exactly one Story's `supersedes[]`;
|
|
36
|
+
* they are commented on and closed as superseded
|
|
37
|
+
* --no-close-superseded Keep the source tickets open (no comment, no
|
|
38
|
+
* close) — for a genuinely partial supersede
|
|
39
|
+
* --dry-run Assemble + validate without GitHub writes
|
|
40
|
+
* --force-review Operator-forced review stop before persist lands
|
|
26
41
|
* --allow-over-budget / --allow-large-fan-out
|
|
27
42
|
*
|
|
43
|
+
* Run `--dry-run` first. It exercises every gate — validator, DAG, capacity,
|
|
44
|
+
* budget, reachability, split/supersede partition, Spec fold — write-free, so
|
|
45
|
+
* an authoring mistake surfaces before a single issue exists.
|
|
46
|
+
*
|
|
47
|
+
* stdout is reserved for the JSON result (Story #2278 discipline, extended to
|
|
48
|
+
* this CLI by Story #4541): `routeAllOutputToStderr()` runs before any
|
|
49
|
+
* pipeline code so a headless driver can `JSON.parse` stdout unconditionally.
|
|
50
|
+
* Human-readable log lines go to stderr, matching the sibling `plan-context`.
|
|
51
|
+
*
|
|
28
52
|
* Exit codes: 0 success; 1 fatal; 3 reachability orphans (nothing mutated).
|
|
29
53
|
*/
|
|
30
54
|
|
|
@@ -38,13 +62,17 @@ import {
|
|
|
38
62
|
resolveConfig,
|
|
39
63
|
validateOrchestrationConfig,
|
|
40
64
|
} from './lib/config-resolver.js';
|
|
41
|
-
import { Logger } from './lib/Logger.js';
|
|
65
|
+
import { Logger, routeAllOutputToStderr } from './lib/Logger.js';
|
|
42
66
|
import {
|
|
43
67
|
readPlanMetrics,
|
|
44
68
|
recordPlanInvocation,
|
|
45
69
|
renderPlanMetricsSummaryLine,
|
|
46
70
|
summarizePlanMetrics,
|
|
47
71
|
} from './lib/orchestration/plan-metrics.js';
|
|
72
|
+
import {
|
|
73
|
+
loadPlanContextEnvelope,
|
|
74
|
+
resolvePlanContextPath,
|
|
75
|
+
} from './lib/orchestration/plan-persist/plan-context-source.js';
|
|
48
76
|
import {
|
|
49
77
|
runPlanPersist,
|
|
50
78
|
writeCheckpointV2,
|
|
@@ -54,7 +82,7 @@ import {
|
|
|
54
82
|
buildWaveTable,
|
|
55
83
|
PLAN_SUMMARY_COMMENT_TYPE,
|
|
56
84
|
} from './lib/orchestration/plan-persist/summary.js';
|
|
57
|
-
import {
|
|
85
|
+
import { resolveSourceTicketIds } from './lib/orchestration/plan-persist/supersede-ops.js';
|
|
58
86
|
import { createProvider } from './lib/provider-factory.js';
|
|
59
87
|
|
|
60
88
|
export {
|
|
@@ -67,11 +95,13 @@ export {
|
|
|
67
95
|
|
|
68
96
|
const CLI_OPTIONS = {
|
|
69
97
|
stories: { type: 'string' },
|
|
70
|
-
'risk-verdict': { type: 'string' },
|
|
71
98
|
'tech-spec': { type: 'string' },
|
|
72
99
|
'plan-dir': { type: 'string' },
|
|
100
|
+
'plan-context': { type: 'string' },
|
|
73
101
|
'plan-acceptance': { type: 'string' },
|
|
74
|
-
'
|
|
102
|
+
'source-tickets': { type: 'string' },
|
|
103
|
+
'close-superseded': { type: 'boolean', default: true },
|
|
104
|
+
'no-close-superseded': { type: 'boolean', default: false },
|
|
75
105
|
'dry-run': { type: 'boolean', default: false },
|
|
76
106
|
'force-review': { type: 'boolean', default: false },
|
|
77
107
|
'allow-over-budget': { type: 'boolean', default: false },
|
|
@@ -79,9 +109,11 @@ const CLI_OPTIONS = {
|
|
|
79
109
|
};
|
|
80
110
|
|
|
81
111
|
const USAGE =
|
|
82
|
-
'Usage: plan-persist.js --stories <file>
|
|
83
|
-
'[--tech-spec <file>] [--plan-dir <dir>] [--plan-
|
|
84
|
-
'[--plan-
|
|
112
|
+
'Usage: plan-persist.js --stories <file> ' +
|
|
113
|
+
'[--tech-spec <file>] [--plan-dir <dir>] [--plan-context <file>] ' +
|
|
114
|
+
'[--plan-acceptance <file>] ' +
|
|
115
|
+
'[--source-tickets <ids>] [--no-close-superseded] ' +
|
|
116
|
+
'[--dry-run] [--force-review] ' +
|
|
85
117
|
'[--allow-over-budget] [--allow-large-fan-out]';
|
|
86
118
|
|
|
87
119
|
async function readOptional(filePath, { required }) {
|
|
@@ -104,22 +136,28 @@ async function readJsonFile(filePath, label) {
|
|
|
104
136
|
}
|
|
105
137
|
}
|
|
106
138
|
|
|
107
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Resolve every input path the CLI accepts, including where the
|
|
141
|
+
* `plan-context.js` envelope is discovered from. Exported for tests.
|
|
142
|
+
*
|
|
143
|
+
* @param {object} values Parsed `parseArgs` values.
|
|
144
|
+
*/
|
|
145
|
+
export function resolveInputPaths(values) {
|
|
146
|
+
const planDir = values['plan-dir'] ? path.resolve(values['plan-dir']) : null;
|
|
108
147
|
return {
|
|
109
148
|
storiesPath: path.resolve(values.stories),
|
|
110
|
-
riskVerdictPath: path.resolve(values['risk-verdict']),
|
|
111
149
|
techSpecPath: values['tech-spec']
|
|
112
150
|
? path.resolve(values['tech-spec'])
|
|
113
151
|
: null,
|
|
114
152
|
planAcceptancePath: values['plan-acceptance']
|
|
115
153
|
? path.resolve(values['plan-acceptance'])
|
|
116
154
|
: null,
|
|
117
|
-
planDir
|
|
155
|
+
planDir,
|
|
156
|
+
planContextPath: resolvePlanContextPath(values['plan-context'], planDir),
|
|
118
157
|
};
|
|
119
158
|
}
|
|
120
159
|
|
|
121
160
|
async function loadArtifacts(paths) {
|
|
122
|
-
const riskVerdict = loadRiskVerdict(paths.riskVerdictPath);
|
|
123
161
|
const stories = await readJsonFile(paths.storiesPath, 'stories');
|
|
124
162
|
const techSpecContent = paths.techSpecPath
|
|
125
163
|
? await readOptional(paths.techSpecPath, { required: true })
|
|
@@ -127,23 +165,61 @@ async function loadArtifacts(paths) {
|
|
|
127
165
|
const planAcceptance = paths.planAcceptancePath
|
|
128
166
|
? await readJsonFile(paths.planAcceptancePath, 'plan-acceptance')
|
|
129
167
|
: null;
|
|
168
|
+
const planContextEnvelope = await loadPlanContextEnvelope(
|
|
169
|
+
paths.planContextPath,
|
|
170
|
+
);
|
|
130
171
|
|
|
131
|
-
return {
|
|
172
|
+
return {
|
|
173
|
+
stories,
|
|
174
|
+
techSpecContent,
|
|
175
|
+
planAcceptance,
|
|
176
|
+
planContextEnvelope,
|
|
177
|
+
};
|
|
132
178
|
}
|
|
133
179
|
|
|
134
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Assemble the `runPlanPersist` opts bag from parsed CLI values.
|
|
182
|
+
*
|
|
183
|
+
* Exported for tests: this is the join where the envelope-derived source ids
|
|
184
|
+
* meet the persist engine, so a regression here silently un-wires
|
|
185
|
+
* `/plan --tickets` superseding (Story #4554).
|
|
186
|
+
*
|
|
187
|
+
* @param {object} values Parsed `parseArgs` values.
|
|
188
|
+
* @param {ReturnType<typeof resolveInputPaths>} paths
|
|
189
|
+
* @param {object|null} planContextEnvelope
|
|
190
|
+
* @returns {object} opts for `runPlanPersist`.
|
|
191
|
+
*/
|
|
192
|
+
export function buildPersistOptions(values, paths, planContextEnvelope) {
|
|
193
|
+
const source = resolveSourceTicketIds({
|
|
194
|
+
explicitIds: values['source-tickets'],
|
|
195
|
+
envelope: planContextEnvelope,
|
|
196
|
+
});
|
|
197
|
+
|
|
135
198
|
return {
|
|
136
199
|
forceReview: values['force-review'],
|
|
137
200
|
allowOverBudget: values['allow-over-budget'],
|
|
138
201
|
allowLargeFanOut: values['allow-large-fan-out'],
|
|
139
202
|
dryRun: values['dry-run'],
|
|
140
|
-
planRunId: values['plan-run-id'],
|
|
141
203
|
planDir: paths.planDir,
|
|
142
204
|
skipCleanup: values['dry-run'],
|
|
205
|
+
sourceTicketIds: source.ids,
|
|
206
|
+
sourceTicketOrigin: source.origin,
|
|
207
|
+
// Default-on: `--no-close-superseded` is the explicit escape and always
|
|
208
|
+
// wins over the (default `true`) `--close-superseded`.
|
|
209
|
+
closeSuperseded:
|
|
210
|
+
values['no-close-superseded'] === true
|
|
211
|
+
? false
|
|
212
|
+
: values['close-superseded'] !== false,
|
|
143
213
|
};
|
|
144
214
|
}
|
|
145
215
|
|
|
146
|
-
async function runPersistInvocation({
|
|
216
|
+
async function runPersistInvocation({
|
|
217
|
+
values,
|
|
218
|
+
config,
|
|
219
|
+
provider,
|
|
220
|
+
artifacts,
|
|
221
|
+
metricsSince,
|
|
222
|
+
}) {
|
|
147
223
|
const paths = resolveInputPaths(values);
|
|
148
224
|
const settings = {
|
|
149
225
|
baseBranch: config.project?.baseBranch,
|
|
@@ -164,14 +240,37 @@ async function runPersistInvocation({ values, config, provider, artifacts }) {
|
|
|
164
240
|
artifacts,
|
|
165
241
|
config,
|
|
166
242
|
settings,
|
|
167
|
-
opts:
|
|
243
|
+
opts: {
|
|
244
|
+
...buildPersistOptions(values, paths, artifacts.planContextEnvelope),
|
|
245
|
+
metricsSince,
|
|
246
|
+
},
|
|
168
247
|
}),
|
|
169
248
|
);
|
|
170
249
|
}
|
|
171
250
|
|
|
172
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Attach the plan-metrics roll-up for **this** invocation.
|
|
253
|
+
*
|
|
254
|
+
* Two Story #4541 fixes meet here. `readPlanMetrics` is declared
|
|
255
|
+
* `(epicId, config)` but was called with `config` first, so it threw its
|
|
256
|
+
* `epicId` guard on every run and the catch below turned that into a
|
|
257
|
+
* silently missing summary — v2 persist is always Epic-less, hence the
|
|
258
|
+
* explicit `null`. And the Epic-less ledger is shared across every plan the
|
|
259
|
+
* repo has ever run, so `since` scopes the counts to the current invocation
|
|
260
|
+
* instead of reporting lifetime totals under an invocation-shaped line.
|
|
261
|
+
*
|
|
262
|
+
* This runs *after* `recordPlanInvocation` has appended this run's own
|
|
263
|
+
* record, so the summary always has at least that one entry to report.
|
|
264
|
+
*
|
|
265
|
+
* @param {object} result Mutated in place with `planMetrics`.
|
|
266
|
+
* @param {object} config
|
|
267
|
+
* @param {string} since ISO-8601 instant this invocation started.
|
|
268
|
+
*/
|
|
269
|
+
async function attachPlanMetrics(result, config, since) {
|
|
173
270
|
try {
|
|
174
|
-
const summary = summarizePlanMetrics(await readPlanMetrics(config)
|
|
271
|
+
const summary = summarizePlanMetrics(await readPlanMetrics(null, config), {
|
|
272
|
+
since,
|
|
273
|
+
});
|
|
175
274
|
if (summary) {
|
|
176
275
|
result.planMetrics = summary;
|
|
177
276
|
Logger.info(`[plan-persist] ${renderPlanMetricsSummaryLine(summary)}`);
|
|
@@ -184,10 +283,20 @@ async function attachPlanMetrics(result, config) {
|
|
|
184
283
|
async function main() {
|
|
185
284
|
const { values } = parseArgs({ options: CLI_OPTIONS });
|
|
186
285
|
|
|
187
|
-
if (!values.stories
|
|
286
|
+
if (!values.stories) {
|
|
188
287
|
throw new Error(USAGE);
|
|
189
288
|
}
|
|
190
289
|
|
|
290
|
+
// stdout is reserved for the JSON result: flip every Logger sink that could
|
|
291
|
+
// land on stdout to stderr BEFORE any pipeline code runs (Story #2278
|
|
292
|
+
// discipline, extended here by Story #4541 — this CLI interleaved Logger
|
|
293
|
+
// lines with its own JSON, so a headless driver could not parse stdout).
|
|
294
|
+
routeAllOutputToStderr();
|
|
295
|
+
|
|
296
|
+
// Boundary for this invocation's plan-metrics roll-up — stamped before any
|
|
297
|
+
// ledger-writing work so every record this run appends falls inside it.
|
|
298
|
+
const metricsSince = new Date().toISOString();
|
|
299
|
+
|
|
191
300
|
let config;
|
|
192
301
|
try {
|
|
193
302
|
config = resolveConfig();
|
|
@@ -206,6 +315,7 @@ async function main() {
|
|
|
206
315
|
config,
|
|
207
316
|
provider,
|
|
208
317
|
artifacts,
|
|
318
|
+
metricsSince,
|
|
209
319
|
});
|
|
210
320
|
} catch (err) {
|
|
211
321
|
if (err?.code === 'PLAN_REACHABILITY_ORPHANS') {
|
|
@@ -216,7 +326,7 @@ async function main() {
|
|
|
216
326
|
throw err;
|
|
217
327
|
}
|
|
218
328
|
|
|
219
|
-
await attachPlanMetrics(result, config);
|
|
329
|
+
await attachPlanMetrics(result, config, metricsSince);
|
|
220
330
|
|
|
221
331
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
222
332
|
}
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* plan-run-epilogue.js — execute the real per-run closeout for
|
|
4
|
-
*
|
|
3
|
+
* plan-run-epilogue.js — execute the real per-run closeout for a
|
|
4
|
+
* multi-Story `/deliver`.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* node .agents/scripts/plan-run-epilogue.js --run <planRunId>
|
|
8
|
-
* node .agents/scripts/plan-run-epilogue.js --run <planRunId> --stories 1,2,3
|
|
9
7
|
* node .agents/scripts/plan-run-epilogue.js --stories 1,2,3
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* synthesized from the sorted Story ids.
|
|
9
|
+
* Keyed on the delivered id set: an `adhoc-<sorted-ids>` run id is
|
|
10
|
+
* synthesized from `--stories`. Story #4540 retired the `--run <planRunId>`
|
|
11
|
+
* label-resolution branch along with the `plan-run::<id>` label itself.
|
|
15
12
|
*/
|
|
16
13
|
|
|
17
14
|
import './lib/runtime-deps/ensure-installed.js';
|
|
@@ -20,16 +17,10 @@ import { parseArgs } from 'node:util';
|
|
|
20
17
|
import { runAsCli } from './lib/cli-utils.js';
|
|
21
18
|
import { resolveConfig } from './lib/config-resolver.js';
|
|
22
19
|
import { Logger } from './lib/Logger.js';
|
|
23
|
-
import {
|
|
24
|
-
fetchPlanRunIssues,
|
|
25
|
-
normalizePlanRunLabel,
|
|
26
|
-
resolvePlanRunFromIssues,
|
|
27
|
-
} from './lib/orchestration/resolve-plan-run.js';
|
|
28
20
|
import { runPlanRunEpilogue } from './lib/orchestration/run-epilogue.js';
|
|
29
21
|
import { createProvider } from './lib/provider-factory.js';
|
|
30
22
|
|
|
31
23
|
const CLI_OPTIONS = {
|
|
32
|
-
run: { type: 'string' },
|
|
33
24
|
stories: { type: 'string' },
|
|
34
25
|
cwd: { type: 'string' },
|
|
35
26
|
};
|
|
@@ -44,13 +35,10 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
44
35
|
options: CLI_OPTIONS,
|
|
45
36
|
strict: false,
|
|
46
37
|
});
|
|
47
|
-
const runFlag = typeof values.run === 'string' ? values.run.trim() : '';
|
|
48
38
|
const hasStoriesFlag =
|
|
49
39
|
typeof values.stories === 'string' && values.stories.trim().length > 0;
|
|
50
|
-
if (!
|
|
51
|
-
throw new Error(
|
|
52
|
-
'Usage: node plan-run-epilogue.js (--run <planRunId> | --stories 1,2,3) [--stories 1,2,3]',
|
|
53
|
-
);
|
|
40
|
+
if (!hasStoriesFlag) {
|
|
41
|
+
throw new Error('Usage: node plan-run-epilogue.js --stories 1,2,3');
|
|
54
42
|
}
|
|
55
43
|
const cwd =
|
|
56
44
|
typeof values.cwd === 'string' && values.cwd.trim()
|
|
@@ -59,26 +47,16 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
59
47
|
const config = resolveConfig({ cwd });
|
|
60
48
|
const provider = createProvider(config);
|
|
61
49
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const issues = await fetchPlanRunIssues(provider, {
|
|
71
|
-
planRunLabel,
|
|
72
|
-
state: 'all',
|
|
73
|
-
});
|
|
74
|
-
const envelope = resolvePlanRunFromIssues({ run: runFlag, issues });
|
|
75
|
-
stories = (envelope.stories ?? [])
|
|
76
|
-
.map((s) => Number(s?.id ?? s))
|
|
77
|
-
.filter((n) => Number.isInteger(n) && n > 0);
|
|
78
|
-
}
|
|
50
|
+
// Story #4540 retired the `--run <planRunId>` label-resolution branch
|
|
51
|
+
// along with the label itself. The epilogue is keyed on the delivered id
|
|
52
|
+
// set, and the synthesized `adhoc-<ids>` id it already used for positional
|
|
53
|
+
// runs is now the only id it needs.
|
|
54
|
+
const stories = values.stories
|
|
55
|
+
.split(',')
|
|
56
|
+
.map((s) => Number(s.trim()))
|
|
57
|
+
.filter((n) => Number.isInteger(n) && n > 0);
|
|
79
58
|
|
|
80
|
-
const planRunId =
|
|
81
|
-
runFlag || `adhoc-${[...stories].sort((a, b) => a - b).join('-')}`;
|
|
59
|
+
const planRunId = `adhoc-${[...stories].sort((a, b) => a - b).join('-')}`;
|
|
82
60
|
|
|
83
61
|
const result = await runPlanRunEpilogue({
|
|
84
62
|
planRunId,
|
|
@@ -87,6 +65,8 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
87
65
|
config,
|
|
88
66
|
cwd,
|
|
89
67
|
});
|
|
68
|
+
warnOnUnresolvedBase(result);
|
|
69
|
+
warnOnEmptyRollup(result);
|
|
90
70
|
Logger.info(JSON.stringify(result, null, 2));
|
|
91
71
|
if (result.errors?.length) {
|
|
92
72
|
process.exitCode = 1;
|
|
@@ -94,4 +74,69 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
94
74
|
return result;
|
|
95
75
|
}
|
|
96
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Surface an unresolvable combined landed diff as a loud operator warning.
|
|
79
|
+
*
|
|
80
|
+
* The roster's changed-file set is the input the host walks its audit lenses
|
|
81
|
+
* against; a silent absence would read as "nothing changed" and the lens walk
|
|
82
|
+
* would look complete while covering nothing. Not fatal — with no diff the
|
|
83
|
+
* selector degrades to keyword-only lens selection, so the rest of the
|
|
84
|
+
* roster is still useful.
|
|
85
|
+
*
|
|
86
|
+
* @param {object} result - `runPlanRunEpilogue` envelope.
|
|
87
|
+
* @returns {void}
|
|
88
|
+
*/
|
|
89
|
+
function warnOnUnresolvedBase(result) {
|
|
90
|
+
const roster = (result?.results ?? []).find(
|
|
91
|
+
(r) => r?.kind === 'audit-roster',
|
|
92
|
+
);
|
|
93
|
+
const base = roster?.baseResolution;
|
|
94
|
+
if (base?.resolved !== false) return;
|
|
95
|
+
Logger.warn(
|
|
96
|
+
`⚠️ Combined landed diff unavailable — the pre-run base sha could not be ` +
|
|
97
|
+
`resolved against \`${base.baseRef}\`: ${base.reason}\n` +
|
|
98
|
+
` changedFiles is null (NOT an empty set). Determine the run diff by ` +
|
|
99
|
+
`hand before walking the selected lenses.`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Surface a zero-signal roll-up over a multi-Story run as a loud operator
|
|
105
|
+
* warning (Story #4578).
|
|
106
|
+
*
|
|
107
|
+
* The failure this exists to prevent is a *reassuring* one. The roll-up read
|
|
108
|
+
* "No friction signals — nothing to follow up" for a 7-Story run that
|
|
109
|
+
* contained a mid-run git outage, a parked worker needing an operator
|
|
110
|
+
* resume, and an acceptance critic that needed four rounds. Nothing was
|
|
111
|
+
* broken in the roll-up — the stream really was empty — but the report was
|
|
112
|
+
* indistinguishable from a clean run, so the retro loop that exists to learn
|
|
113
|
+
* from a run was silently blind to that run's pain.
|
|
114
|
+
*
|
|
115
|
+
* Not fatal: a genuinely friction-free multi-Story run is possible, and this
|
|
116
|
+
* cannot tell the two apart — which is precisely why it asks the operator
|
|
117
|
+
* rather than asserting either reading.
|
|
118
|
+
*
|
|
119
|
+
* @param {object} result - `runPlanRunEpilogue` envelope.
|
|
120
|
+
* @returns {void}
|
|
121
|
+
*/
|
|
122
|
+
function warnOnEmptyRollup(result) {
|
|
123
|
+
const rollup = (result?.results ?? []).find(
|
|
124
|
+
(r) => r?.kind === 'follow-up-rollup',
|
|
125
|
+
);
|
|
126
|
+
if (!rollup?.emptyRollupSuspect) return;
|
|
127
|
+
Logger.warn(
|
|
128
|
+
`⚠️ 0 friction signals across ${rollup.storyCount} Stories — telemetry may not ` +
|
|
129
|
+
`have fired.\n` +
|
|
130
|
+
` An empty roll-up is NOT evidence of a clean run: it is the same output a ` +
|
|
131
|
+
`run with\n` +
|
|
132
|
+
` heavy friction produces when nothing recorded it. The runtime emits ` +
|
|
133
|
+
`friction from its\n` +
|
|
134
|
+
` own observables (agent::blocked transitions, failed closes, exhausted ` +
|
|
135
|
+
`merge waits), so\n` +
|
|
136
|
+
` zero here also means none of those fired. If this run had friction you ` +
|
|
137
|
+
`can name, that\n` +
|
|
138
|
+
` telemetry gap is itself worth filing.`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
97
142
|
await runAsCli(import.meta.url, main);
|