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,236 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* resolve-stories.js — resolve a list of Story ids into the
|
|
5
|
+
* `{ stories, dag, done }` envelope `/deliver` sequences from.
|
|
6
|
+
*
|
|
7
|
+
* This is the ONE resolution step for multi-Story delivery. `/deliver` takes
|
|
8
|
+
* only Story ids; the graph is discovered here, from live state, rather than
|
|
9
|
+
* hand-transcribed by the host or implied by a batch label.
|
|
10
|
+
*
|
|
11
|
+
* What it resolves, per Story:
|
|
12
|
+
* - the issue itself, fetched with **state=all** so an already-landed
|
|
13
|
+
* sibling is present rather than silently dropped;
|
|
14
|
+
* - its dependency edges: the union of body-parsed `blocked by #N` /
|
|
15
|
+
* `depends on #N` and native GitHub `blocked_by` edges;
|
|
16
|
+
* - its declared file footprint, as plain path strings, so the scheduler's
|
|
17
|
+
* co-dispatch overlap guard has something to work with.
|
|
18
|
+
*
|
|
19
|
+
* And across the set: every dependency id — inside the requested set or
|
|
20
|
+
* foreign to it — is checked against live issue state, and a blocker that is
|
|
21
|
+
* closed or `agent::done` lands in `done[]`. That is what makes "deliver
|
|
22
|
+
* Stories across plan runs and over time" work: a Story whose blocker merged
|
|
23
|
+
* weeks ago in a different run is simply ready.
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* node .agents/scripts/resolve-stories.js --ids 101,102
|
|
27
|
+
* node .agents/scripts/resolve-stories.js --ids 101,102 --pretty
|
|
28
|
+
* node .agents/scripts/resolve-stories.js --ids 101 --no-native # skip the dependencies API
|
|
29
|
+
*
|
|
30
|
+
* Exit codes: 0 ok, 1 usage/resolution error.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { parseArgs } from 'node:util';
|
|
34
|
+
|
|
35
|
+
import { runAsCli } from './lib/cli-utils.js';
|
|
36
|
+
import { resolveConfig } from './lib/config-resolver.js';
|
|
37
|
+
import { Logger, routeAllOutputToStderr } from './lib/Logger.js';
|
|
38
|
+
import {
|
|
39
|
+
buildStoriesEnvelope,
|
|
40
|
+
isSatisfiedBlocker,
|
|
41
|
+
parseIds,
|
|
42
|
+
readNativeBlockedBy,
|
|
43
|
+
toStoryRecord,
|
|
44
|
+
} from './lib/orchestration/resolve-stories.js';
|
|
45
|
+
import { createProvider } from './lib/provider-factory.js';
|
|
46
|
+
import { concurrentMap } from './lib/util/concurrent-map.js';
|
|
47
|
+
import { parseApiJson } from './providers/github/request-helpers.js';
|
|
48
|
+
|
|
49
|
+
export { buildStoriesEnvelope, parseIds, readNativeBlockedBy, toStoryRecord };
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Bounded concurrency for the per-issue round-trips. Matches the edge-writer's
|
|
53
|
+
* cap: modest enough for GitHub's secondary rate limits, while collapsing
|
|
54
|
+
* wall-clock from sum(round-trips) toward sum/concurrency.
|
|
55
|
+
*/
|
|
56
|
+
const FETCH_CONCURRENCY = 5;
|
|
57
|
+
|
|
58
|
+
const HELP = `\
|
|
59
|
+
Usage:
|
|
60
|
+
resolve-stories.js --ids <n,n,...> [--pretty] [--no-native]
|
|
61
|
+
|
|
62
|
+
Resolve Story ids into the { stories, dag, done } envelope /deliver sequences
|
|
63
|
+
from. Dependencies are discovered from live state: body edges union native
|
|
64
|
+
blocked_by edges, with every blocker (in-set or foreign) resolved against its
|
|
65
|
+
real issue state.
|
|
66
|
+
|
|
67
|
+
Options:
|
|
68
|
+
--ids <csv> Comma-separated Story issue numbers. Required.
|
|
69
|
+
--pretty Pretty-print the JSON envelope.
|
|
70
|
+
--no-native Skip the native blocked_by read (body edges only).
|
|
71
|
+
--help Show this help.
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {object} [deps]
|
|
76
|
+
* @returns {{ provider: object, config: object }}
|
|
77
|
+
*/
|
|
78
|
+
export function resolveStoriesProvider({
|
|
79
|
+
resolveConfigFn = resolveConfig,
|
|
80
|
+
createProviderFn = createProvider,
|
|
81
|
+
} = {}) {
|
|
82
|
+
const config = resolveConfigFn();
|
|
83
|
+
return { provider: createProviderFn(config), config };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Fetch every requested id and map it to a Story record, failing on the first
|
|
88
|
+
* id that is not a deliverable Story.
|
|
89
|
+
*
|
|
90
|
+
* @param {object} provider
|
|
91
|
+
* @param {number[]} ids
|
|
92
|
+
* @returns {Promise<object[]>}
|
|
93
|
+
*/
|
|
94
|
+
export async function fetchStories(provider, ids) {
|
|
95
|
+
return concurrentMap(
|
|
96
|
+
ids,
|
|
97
|
+
async (id) => {
|
|
98
|
+
const issue = await provider.getTicket(id);
|
|
99
|
+
if (!issue) {
|
|
100
|
+
throw new Error(`[resolve-stories] Issue #${id} was not found.`);
|
|
101
|
+
}
|
|
102
|
+
return toStoryRecord(issue, id);
|
|
103
|
+
},
|
|
104
|
+
{ concurrency: FETCH_CONCURRENCY },
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Read native blocked_by edges for every Story in the set.
|
|
110
|
+
*
|
|
111
|
+
* @returns {Promise<Map<number, number[]>>}
|
|
112
|
+
*/
|
|
113
|
+
export async function readNativeEdges({ provider, stories, owner, repo }) {
|
|
114
|
+
const entries = await concurrentMap(
|
|
115
|
+
stories,
|
|
116
|
+
async (story) => [
|
|
117
|
+
story.id,
|
|
118
|
+
await readNativeBlockedBy({
|
|
119
|
+
gh: provider._gh,
|
|
120
|
+
owner,
|
|
121
|
+
repo,
|
|
122
|
+
issueNumber: story.id,
|
|
123
|
+
parseJson: parseApiJson,
|
|
124
|
+
}),
|
|
125
|
+
],
|
|
126
|
+
{ concurrency: FETCH_CONCURRENCY },
|
|
127
|
+
);
|
|
128
|
+
return new Map(entries);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resolve dependency ids that are NOT in the requested set against live issue
|
|
133
|
+
* state. A foreign blocker that already landed must enter `done[]`, or the
|
|
134
|
+
* scheduler withholds its dependent forever — the exact wedge that made
|
|
135
|
+
* cross-run delivery impossible.
|
|
136
|
+
*
|
|
137
|
+
* A foreign id that cannot be read is left OUT of `done[]`: unknown means
|
|
138
|
+
* "still gating", which withholds the dependent rather than dispatching it
|
|
139
|
+
* against a possibly-unlanded blocker.
|
|
140
|
+
*
|
|
141
|
+
* @returns {Promise<number[]>}
|
|
142
|
+
*/
|
|
143
|
+
export async function resolveForeignDone({ provider, dag, inSetIds }) {
|
|
144
|
+
const foreign = [
|
|
145
|
+
...new Set(
|
|
146
|
+
dag.flatMap((node) => node.dependsOn).filter((dep) => !inSetIds.has(dep)),
|
|
147
|
+
),
|
|
148
|
+
];
|
|
149
|
+
if (foreign.length === 0) return [];
|
|
150
|
+
const resolved = await concurrentMap(
|
|
151
|
+
foreign,
|
|
152
|
+
async (id) => {
|
|
153
|
+
try {
|
|
154
|
+
const issue = await provider.getTicket(id);
|
|
155
|
+
return isSatisfiedBlocker(issue) ? id : null;
|
|
156
|
+
} catch (err) {
|
|
157
|
+
Logger.warn(
|
|
158
|
+
`[resolve-stories] Could not read foreign blocker #${id} (${err?.message ?? err}) — ` +
|
|
159
|
+
`treating it as still gating.`,
|
|
160
|
+
);
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{ concurrency: FETCH_CONCURRENCY },
|
|
165
|
+
);
|
|
166
|
+
return resolved.filter((id) => id !== null);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function main() {
|
|
170
|
+
const { values } = parseArgs({
|
|
171
|
+
options: {
|
|
172
|
+
ids: { type: 'string' },
|
|
173
|
+
pretty: { type: 'boolean', default: false },
|
|
174
|
+
native: { type: 'boolean', default: true },
|
|
175
|
+
help: { type: 'boolean', default: false },
|
|
176
|
+
},
|
|
177
|
+
// The documented opt-out is `--no-native`; without allowNegative,
|
|
178
|
+
// parseArgs rejects it as an unknown option and the CLI has no working
|
|
179
|
+
// way to skip the dependencies API.
|
|
180
|
+
allowNegative: true,
|
|
181
|
+
allowPositionals: false,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
if (values.help) {
|
|
185
|
+
process.stdout.write(HELP);
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
if (!values.ids) {
|
|
189
|
+
process.stderr.write(HELP);
|
|
190
|
+
throw new Error('[resolve-stories] --ids <n,n,...> is required');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// stdout is a JSON stream — keep human-readable output on stderr so a
|
|
194
|
+
// headless caller can pipe this straight into stories-wave-tick.js.
|
|
195
|
+
routeAllOutputToStderr();
|
|
196
|
+
|
|
197
|
+
const ids = parseIds(values.ids);
|
|
198
|
+
const { provider, config } = resolveStoriesProvider();
|
|
199
|
+
const owner = config.github?.owner;
|
|
200
|
+
const repo = config.github?.repo;
|
|
201
|
+
|
|
202
|
+
const stories = await fetchStories(provider, ids);
|
|
203
|
+
const nativeEdges = values.native
|
|
204
|
+
? await readNativeEdges({ provider, stories, owner, repo })
|
|
205
|
+
: new Map();
|
|
206
|
+
|
|
207
|
+
const inSetIds = new Set(stories.map((s) => s.id));
|
|
208
|
+
const provisional = buildStoriesEnvelope({
|
|
209
|
+
stories,
|
|
210
|
+
nativeEdges,
|
|
211
|
+
warn: (m) => Logger.warn(m),
|
|
212
|
+
});
|
|
213
|
+
const foreignDone = await resolveForeignDone({
|
|
214
|
+
provider,
|
|
215
|
+
dag: provisional.dag,
|
|
216
|
+
inSetIds,
|
|
217
|
+
});
|
|
218
|
+
const envelope = buildStoriesEnvelope({
|
|
219
|
+
stories,
|
|
220
|
+
nativeEdges,
|
|
221
|
+
foreignDone,
|
|
222
|
+
warn: () => {},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
process.stdout.write(
|
|
226
|
+
values.pretty
|
|
227
|
+
? `${JSON.stringify(envelope, null, 2)}\n`
|
|
228
|
+
: `${JSON.stringify(envelope)}\n`,
|
|
229
|
+
);
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
runAsCli(import.meta.url, main, {
|
|
234
|
+
source: 'resolve-stories',
|
|
235
|
+
propagateExitCode: true,
|
|
236
|
+
});
|
|
@@ -45,6 +45,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
45
45
|
|
|
46
46
|
import { cleanupRepoTestTempArtifacts } from './cleanup-repo-test-temp.js';
|
|
47
47
|
import { C8_CLI } from './lib/c8-cli-path.js';
|
|
48
|
+
import { buildWebhookSafeTestEnv } from './lib/test-env.js';
|
|
48
49
|
import { TEST_RUNNER_FLAGS } from './run-tests.js';
|
|
49
50
|
|
|
50
51
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -89,7 +90,9 @@ function runCoveragePipeline() {
|
|
|
89
90
|
const testRun = spawnSync(process.execPath, buildCoverageTestArgs(), {
|
|
90
91
|
cwd: ROOT,
|
|
91
92
|
stdio: 'inherit',
|
|
92
|
-
|
|
93
|
+
// GIT_*-scrubbed: under a husky pre-push from a linked worktree the
|
|
94
|
+
// inherited GIT_DIR poisons fixture `git init` runs (#4580).
|
|
95
|
+
env: { ...buildWebhookSafeTestEnv(process.env), NODE_V8_COVERAGE: V8_TMP },
|
|
93
96
|
});
|
|
94
97
|
|
|
95
98
|
cleanupRepoTestTempArtifacts({ repoRoot: ROOT });
|
|
@@ -73,8 +73,8 @@ const tasks = [
|
|
|
73
73
|
// spans for axis-shaped tokens (`type/epic`, etc.) and asserts
|
|
74
74
|
// only the canonical `<axis>::<value>` separator from
|
|
75
75
|
// `lib/label-constants.js` appears. Closes the drift gap that
|
|
76
|
-
// let the original `type/epic` typo land
|
|
77
|
-
//
|
|
76
|
+
// let the original `type/epic` typo land in a since-retired
|
|
77
|
+
// planning workflow helper.
|
|
78
78
|
name: 'label-vocabulary',
|
|
79
79
|
cmd: 'node',
|
|
80
80
|
args: ['.agents/scripts/lint-label-vocabulary.js'],
|
|
@@ -5,14 +5,28 @@
|
|
|
5
5
|
* Local full verification — a true CI mirror for the gates that CAN be proven
|
|
6
6
|
* locally, without epic-scoped MI projection or push semantics.
|
|
7
7
|
*
|
|
8
|
-
* Order: audit (SCA) → lint (includes docs:check
|
|
9
|
-
* unified baselines
|
|
8
|
+
* Order: audit (SCA) → lint (includes docs:check + the arch-cycles ratchet) →
|
|
9
|
+
* full test suite → unified baselines → the dead-exports and context-budget
|
|
10
|
+
* ratchets.
|
|
10
11
|
*
|
|
11
12
|
* The `audit` step runs `npm audit --audit-level=high`, matching CI's
|
|
12
13
|
* "Dependency Vulnerability Audit (SCA)" gate so a local green no longer hides
|
|
13
14
|
* a high-severity advisory that CI would fail on. It is independent of the
|
|
14
15
|
* pre-push `PREPUSH_AUDIT` opt-in, which stays unchanged.
|
|
15
16
|
*
|
|
17
|
+
* The trailing ratchets complete the mirror of CI's "Architecture Cycle Check"
|
|
18
|
+
* step in the `baselines` job (Story #4549). That step runs three checks;
|
|
19
|
+
* `check-arch-cycles.js` is deliberately absent from STEPS because the `lint`
|
|
20
|
+
* step above already runs it (see run-lint.js) — re-running it here would
|
|
21
|
+
* double-pay a gate verify already covers. `check-dead-exports.js` and
|
|
22
|
+
* `check-context-budget.js` had no such cover: they were reachable locally only
|
|
23
|
+
* via the diff-scoped `npm run quality:preview` or a direct invocation, so a
|
|
24
|
+
* clean `verify` could still hide a CI-red — the failure Story #4531 / PR #4548
|
|
25
|
+
* paid for with a full push → CI → fix → push round-trip. Both are pure-Node
|
|
26
|
+
* and baseline-aware, adding ~15s on a cold cache (dominated by knip's
|
|
27
|
+
* full-tree scan in check-dead-exports.js) to a command that already carries
|
|
28
|
+
* the full test suite.
|
|
29
|
+
*
|
|
16
30
|
* A handful of CI gates cannot be reproduced by this command (action pinning,
|
|
17
31
|
* TruffleHog secret scan, the BASELINE_SCOPE=full push-scoped maintainability
|
|
18
32
|
* run) — those are catalogued in docs/ci-contract.md.
|
|
@@ -34,6 +48,21 @@ const STEPS = [
|
|
|
34
48
|
cmd: 'node',
|
|
35
49
|
args: ['.agents/scripts/check-baselines.js'],
|
|
36
50
|
},
|
|
51
|
+
{
|
|
52
|
+
label: 'dead-exports',
|
|
53
|
+
cmd: 'node',
|
|
54
|
+
args: ['.agents/scripts/check-dead-exports.js'],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
label: 'dead-exports-production',
|
|
58
|
+
cmd: 'node',
|
|
59
|
+
args: ['.agents/scripts/check-dead-exports.js', '--production'],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
label: 'context-budget',
|
|
63
|
+
cmd: 'node',
|
|
64
|
+
args: ['.agents/scripts/check-context-budget.js'],
|
|
65
|
+
},
|
|
37
66
|
];
|
|
38
67
|
|
|
39
68
|
export function runVerifySteps({
|
|
@@ -174,13 +174,13 @@ function describeEvent(evt) {
|
|
|
174
174
|
* `process.stdout.write`). Logger is intentionally not used: the
|
|
175
175
|
* viewer's contract is "dumb terminal compatible, parseable output".
|
|
176
176
|
*
|
|
177
|
-
* @param {{
|
|
177
|
+
* @param {{ run: number | null, stories: Array<object> }} tree
|
|
178
178
|
* @param {{ storyFilter?: number | null }} [opts]
|
|
179
179
|
* @returns {void}
|
|
180
180
|
*/
|
|
181
181
|
export function renderTree(tree, opts = {}) {
|
|
182
182
|
const filter = opts.storyFilter ?? null;
|
|
183
|
-
println(`Run #${tree.
|
|
183
|
+
println(`Run #${tree.run ?? '?'}`);
|
|
184
184
|
const stories =
|
|
185
185
|
filter == null ? tree.stories : tree.stories.filter((s) => s.id === filter);
|
|
186
186
|
|
|
@@ -231,10 +231,10 @@ export async function main(argv, deps = {}) {
|
|
|
231
231
|
const buildSpanTree = deps.buildSpanTree ?? signals.buildSpanTree;
|
|
232
232
|
const config = buildConfig(tempRoot);
|
|
233
233
|
|
|
234
|
-
// The reader
|
|
235
|
-
//
|
|
234
|
+
// The reader resolves the run directory through temp-paths
|
|
235
|
+
// (`temp/run-<id>/`) keyed by the `run` id.
|
|
236
236
|
const iter = read(
|
|
237
|
-
story != null ? {
|
|
237
|
+
story != null ? { run: runId, story, config } : { run: runId, config },
|
|
238
238
|
);
|
|
239
239
|
|
|
240
240
|
// Eagerly collect to detect the missing-file case before we start
|
|
@@ -267,11 +267,10 @@ export async function main(argv, deps = {}) {
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
// Pin the run id on the tree to the requested one — `buildSpanTree`
|
|
270
|
-
// stores it in the
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
if (tree.epic == null) tree = { ...tree, epic: runId };
|
|
270
|
+
// stores it in the `run` field from the first observed event, which is
|
|
271
|
+
// normally the same, but if every event lacks that field the tree's
|
|
272
|
+
// `run` would be `null` while we still know what was requested.
|
|
273
|
+
if (tree.run == null) tree = { ...tree, run: runId };
|
|
275
274
|
|
|
276
275
|
renderTree(tree, { storyFilter: story });
|
|
277
276
|
return 0;
|
|
@@ -21,14 +21,17 @@
|
|
|
21
21
|
* the post-merge confirmation step,
|
|
22
22
|
* `single-story-confirm-merge.js`)
|
|
23
23
|
* 8. worktree-reap — drop the per-Story worktree
|
|
24
|
-
* 9. confirm-merge — Story #4428
|
|
24
|
+
* 9. confirm-merge — close-and-land (Story #4428; the DEFAULT for
|
|
25
|
+
* every run since `delivery.routing.closeAndLand`):
|
|
25
26
|
* poll the just-armed PR to merge confirmation
|
|
26
|
-
* (reusing `confirmStoryMerged`)
|
|
27
|
-
* `agent::blocked`
|
|
28
|
-
* `merge.unlanded`
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* (reusing `confirmStoryMerged`), capture the
|
|
28
|
+
* Story follow-ups, or terminate `agent::blocked`
|
|
29
|
+
* with a classified `merge.unlanded` event — or
|
|
30
|
+
* `merge.flip-failed` when the merge landed and
|
|
31
|
+
* only the label write failed. Skipped when the
|
|
32
|
+
* operator owns the merge (`--no-wait-merge`,
|
|
33
|
+
* `--no-auto-merge`, or `autoMerge: "strict"`),
|
|
34
|
+
* which rests at `agent::closing` for the human.
|
|
32
35
|
*
|
|
33
36
|
* Existing tests import the re-exported helpers
|
|
34
37
|
* (`runSingleStoryClose`, `ensurePullRequest`, `parsePrNumber`,
|
|
@@ -38,24 +41,55 @@
|
|
|
38
41
|
* Usage:
|
|
39
42
|
* node single-story-close.js --story <STORY_ID> [--cwd <main-repo>]
|
|
40
43
|
* [--skip-validation] [--skip-sync]
|
|
41
|
-
* [--no-auto-merge]
|
|
44
|
+
* [--no-auto-merge]
|
|
42
45
|
* [--wait-merge | --no-wait-merge]
|
|
43
46
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* `--no-
|
|
49
|
-
*
|
|
50
|
-
*
|
|
47
|
+
* Close-and-land is the DEFAULT for every run (Story #4428 introduced it as
|
|
48
|
+
* `--wait-merge`; `delivery.routing.closeAndLand` — default `true` — made it
|
|
49
|
+
* the default, and Story #4539 made that knob actually readable). Resolution
|
|
50
|
+
* order, highest first: `--no-wait-merge` (explicit opt-out, always wins);
|
|
51
|
+
* operator-owns-the-merge (`--no-auto-merge` or `delivery.ci.autoMerge:
|
|
52
|
+
* "strict"` — the PR was deliberately left un-armed, so there is nothing to
|
|
53
|
+
* land and the Story rests at `agent::closing`); explicit `--wait-merge`;
|
|
54
|
+
* then the config. A genuine arm FAILURE is not an opt-out — it still waits
|
|
55
|
+
* and therefore still blocks, which is what keeps the must-land contract
|
|
56
|
+
* intact.
|
|
51
57
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
58
|
+
* Every invocation emits ONE schema-validated terminal envelope
|
|
59
|
+
* (`.agents/schemas/story-deliver-terminal.schema.json`, Story #4543) on
|
|
60
|
+
* stdout between `--- STORY DELIVER TERMINAL ---` markers. Its `status` is
|
|
61
|
+
* the contract; the exit code mirrors it:
|
|
62
|
+
*
|
|
63
|
+
* 0 — `landed`: the PR merged, the Story is `agent::done`, and the
|
|
64
|
+
* post-land tail ran (follow-ups, status resync, local ref
|
|
65
|
+
* cleanup, base fast-forward).
|
|
66
|
+
* 3 — `pending`: RESUMABLE, not a failure. Either the per-invocation merge
|
|
67
|
+
* wait (`delivery.mergeWatch.maxWaitSeconds`, default 300s
|
|
68
|
+
* to fit a single host tool invocation) expired with the PR
|
|
69
|
+
* still healthy and in flight, or the operator owns the
|
|
70
|
+
* merge (`--no-wait-merge` / `--no-auto-merge` /
|
|
71
|
+
* `autoMerge: "strict"`). NO label was mutated and no
|
|
72
|
+
* `merge.unlanded` event was emitted. The envelope's
|
|
73
|
+
* `nextCommand` names the single command that resumes it,
|
|
74
|
+
* and the cumulative budget is anchored at the PR's
|
|
75
|
+
* createdAt so the resume does not restart the clock.
|
|
76
|
+
* 1 — `blocked` or `failed`: a classified hard block (the Story carries
|
|
77
|
+
* `agent::blocked` and a friction comment) or a phase crash.
|
|
78
|
+
*
|
|
79
|
+
* The distinct `pending` code is the point: before it, a close-and-land whose
|
|
80
|
+
* CI outlived the host's tool-invocation ceiling was killed mid-poll with no
|
|
81
|
+
* terminal path taken at all, and merely shrinking the budget instead would
|
|
82
|
+
* have misfiled every slow-CI run as a hard block.
|
|
54
83
|
*
|
|
55
84
|
* @see .agents/workflows/helpers/deliver-story.md
|
|
85
|
+
* @see .agents/schemas/story-deliver-terminal.schema.json
|
|
56
86
|
*/
|
|
57
87
|
|
|
88
|
+
import { parseSprintArgs } from './lib/cli-args.js';
|
|
58
89
|
import { runAsCli } from './lib/cli-utils.js';
|
|
90
|
+
import { formatCliError } from './lib/error-redactor.js';
|
|
91
|
+
import { Logger } from './lib/Logger.js';
|
|
92
|
+
import { emitTerminalFriction } from './lib/observability/runtime-friction.js';
|
|
59
93
|
import { enableAutoMergeWith } from './lib/orchestration/single-story-close/phases/auto-merge.js';
|
|
60
94
|
import {
|
|
61
95
|
buildSyncFailureCommentBody,
|
|
@@ -67,6 +101,12 @@ import {
|
|
|
67
101
|
runStoryScopeReview,
|
|
68
102
|
} from './lib/orchestration/single-story-close/phases/code-review.js';
|
|
69
103
|
import { ensurePullRequestWith } from './lib/orchestration/single-story-close/phases/pull-request.js';
|
|
104
|
+
import {
|
|
105
|
+
buildTerminalEnvelope,
|
|
106
|
+
emitTerminalEnvelope,
|
|
107
|
+
exitCodeForTerminal,
|
|
108
|
+
NEXT_COMMANDS,
|
|
109
|
+
} from './lib/orchestration/story-deliver-terminal.js';
|
|
70
110
|
|
|
71
111
|
// Story #2990 moved the `gh`-spawn boundary into the `lib/gh-exec.js`
|
|
72
112
|
// facade (the same shim the `providers/github/` gateways use). The
|
|
@@ -94,6 +134,121 @@ export async function runSingleStoryClose(opts) {
|
|
|
94
134
|
return mod.runSingleStoryClose(opts);
|
|
95
135
|
}
|
|
96
136
|
|
|
97
|
-
|
|
137
|
+
/**
|
|
138
|
+
* The close pipeline's phase order, as `setPhase` walks it. Only used to
|
|
139
|
+
* decide whether a gate had already run when a later phase died.
|
|
140
|
+
*/
|
|
141
|
+
const PHASE_ORDER = Object.freeze([
|
|
142
|
+
'init',
|
|
143
|
+
'wrong-tree-guard',
|
|
144
|
+
'close-validation',
|
|
145
|
+
'base-sync',
|
|
146
|
+
'push',
|
|
147
|
+
'pull-request',
|
|
148
|
+
'code-review',
|
|
149
|
+
'auto-merge',
|
|
150
|
+
'confirm-merge',
|
|
151
|
+
'post-land',
|
|
152
|
+
'done',
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
/** Each reported gate and the pipeline phase that decides it. */
|
|
156
|
+
const GATE_PHASES = Object.freeze([
|
|
157
|
+
['validation', 'close-validation'],
|
|
158
|
+
['baseSync', 'base-sync'],
|
|
159
|
+
['codeReview', 'code-review'],
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Report every gate's outcome for a run that died at `phase`.
|
|
164
|
+
*
|
|
165
|
+
* The schema's contract: "A gate the run skipped … reports `skipped` rather
|
|
166
|
+
* than being omitted, so a missing gate is never mistaken for a passing one."
|
|
167
|
+
* The previous shape named only the gate that died and omitted the rest
|
|
168
|
+
* entirely — exactly the ambiguity the contract forbids.
|
|
169
|
+
*
|
|
170
|
+
* Reconstructed from the phase order, which is sound because the pipeline is
|
|
171
|
+
* strictly sequential: reaching phase N means every gate before it completed.
|
|
172
|
+
* A gate whose phase the run never reached is `skipped`; one the operator
|
|
173
|
+
* turned off via `--skip-validation` / `--skip-sync` is `skipped` too (it did
|
|
174
|
+
* not pass — it never ran).
|
|
175
|
+
*
|
|
176
|
+
* @param {string} phase The phase the run died in.
|
|
177
|
+
* @param {{ skipValidation?: boolean, skipSync?: boolean }} args Parsed CLI args.
|
|
178
|
+
* @returns {Record<string, 'passed'|'failed'|'skipped'>}
|
|
179
|
+
*/
|
|
180
|
+
export function gatesForFailedPhase(phase, args = {}) {
|
|
181
|
+
const skipped = { validation: args.skipValidation, baseSync: args.skipSync };
|
|
182
|
+
const failedAt = PHASE_ORDER.indexOf(phase);
|
|
183
|
+
const gates = {};
|
|
184
|
+
for (const [gate, gatePhase] of GATE_PHASES) {
|
|
185
|
+
const at = PHASE_ORDER.indexOf(gatePhase);
|
|
186
|
+
if (gatePhase === phase) gates[gate] = 'failed';
|
|
187
|
+
else if (failedAt < 0 || at > failedAt) gates[gate] = 'skipped';
|
|
188
|
+
else gates[gate] = skipped[gate] ? 'skipped' : 'passed';
|
|
189
|
+
}
|
|
190
|
+
return gates;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Build the `failed` terminal for a phase that crashed.
|
|
195
|
+
*
|
|
196
|
+
* The runner deliberately throws rather than returning a failure (a red gate
|
|
197
|
+
* must not look like a return value), so without this the most common
|
|
198
|
+
* non-happy ending — a failing close-validation gate — would emit **no
|
|
199
|
+
* envelope at all**, exiting 1 with only a stderr line while the workflow
|
|
200
|
+
* docs promise the agent a `failed` envelope naming the phase. Every close
|
|
201
|
+
* invocation emits exactly one envelope; this is the path that keeps that
|
|
202
|
+
* true when a phase dies.
|
|
203
|
+
*
|
|
204
|
+
* `err.closePhase` is tagged by the runner's phase tracker.
|
|
205
|
+
*
|
|
206
|
+
* @param {unknown} err
|
|
207
|
+
* @returns {object|null} A validated envelope, or null when even the story id
|
|
208
|
+
* is unknown (a usage error — there is nothing to report an envelope about).
|
|
209
|
+
*/
|
|
210
|
+
function failedTerminalFor(err) {
|
|
211
|
+
const phase = err?.closePhase ?? 'init';
|
|
212
|
+
const args = parseSprintArgs();
|
|
213
|
+
const storyId = Number(args.storyId);
|
|
214
|
+
if (!Number.isInteger(storyId) || storyId <= 0) return null;
|
|
215
|
+
return buildTerminalEnvelope({
|
|
216
|
+
storyId,
|
|
217
|
+
status: 'failed',
|
|
218
|
+
phase,
|
|
219
|
+
gates: gatesForFailedPhase(phase, args),
|
|
220
|
+
failure: { reason: String(err?.message ?? err) },
|
|
221
|
+
nextCommand: NEXT_COMMANDS.recover(storyId),
|
|
222
|
+
elapsedSeconds: 0,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* CLI entry — resolves the process exit code from the terminal envelope's
|
|
228
|
+
* status rather than from a thrown/not-thrown distinction, so `pending`
|
|
229
|
+
* (resumable) is distinguishable from `blocked` (come look) without parsing
|
|
230
|
+
* stdout.
|
|
231
|
+
*/
|
|
232
|
+
async function main() {
|
|
233
|
+
try {
|
|
234
|
+
const outcome = await runSingleStoryClose();
|
|
235
|
+
return exitCodeForTerminal(outcome?.terminal ?? { status: 'failed' });
|
|
236
|
+
} catch (err) {
|
|
237
|
+
const terminal = failedTerminalFor(err);
|
|
238
|
+
if (!terminal) throw err;
|
|
239
|
+
// Mirror runAsCli's default error line (which this catch pre-empts) so the
|
|
240
|
+
// human-facing failure text is unchanged, then emit the envelope.
|
|
241
|
+
Logger.error(`[single-story-close] Fatal error: ${formatCliError(err)}`);
|
|
242
|
+
emitTerminalEnvelope(terminal);
|
|
243
|
+
// Story #4578 — a close that died before the runner could report its own
|
|
244
|
+
// terminal is exactly the friction the retro must see, so the crash path
|
|
245
|
+
// gets the same emit the happy path does. Best-effort; cannot throw.
|
|
246
|
+
await emitTerminalFriction({ envelope: terminal });
|
|
247
|
+
return exitCodeForTerminal(terminal);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
runAsCli(import.meta.url, main, {
|
|
98
252
|
source: 'single-story-close',
|
|
253
|
+
propagateExitCode: true,
|
|
99
254
|
});
|