mandrel 2.60.0 → 2.62.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 +10 -10
- package/.agents/agents/story-worker.md +1 -1
- package/.agents/docs/agentrc-reference.json +4 -82
- package/.agents/docs/configuration.md +20 -87
- package/.agents/docs/workflows.md +1 -1
- package/.agents/instructions.md +1 -3
- package/.agents/rules/gherkin-standards.md +4 -0
- package/.agents/schemas/agentrc.schema.json +11 -486
- package/.agents/schemas/story-deliver-terminal.schema.json +10 -0
- package/.agents/schemas/validation-evidence.schema.json +2 -1
- package/.agents/scripts/README.md +14 -14
- package/.agents/scripts/acceptance-eval.js +32 -164
- package/.agents/scripts/agents-bootstrap-github.js +27 -129
- package/.agents/scripts/apply-quality-bootstrap.js +4 -40
- package/.agents/scripts/audit-baselines.js +6 -27
- package/.agents/scripts/audit-labels-bootstrap.js +5 -29
- package/.agents/scripts/audit-to-stories.js +91 -361
- package/.agents/scripts/boot-sweep.js +19 -72
- package/.agents/scripts/bootstrap.js +74 -395
- package/.agents/scripts/ceremony-derive.js +9 -45
- package/.agents/scripts/check-arch-cycles.js +14 -64
- package/.agents/scripts/check-baselines.js +8 -48
- package/.agents/scripts/check-context-budget.js +46 -172
- package/.agents/scripts/check-cyclomatic.js +12 -44
- package/.agents/scripts/check-dead-exports.js +13 -64
- package/.agents/scripts/check-doc-links.js +38 -186
- package/.agents/scripts/check-gherkin-corpus.js +24 -127
- package/.agents/scripts/check-test-temp-hygiene.js +28 -169
- package/.agents/scripts/coverage-capture.js +32 -99
- package/.agents/scripts/deliver-light.js +14 -101
- package/.agents/scripts/deliver-recover.js +6 -42
- package/.agents/scripts/deliver-run.js +36 -133
- package/.agents/scripts/diagnose-friction.js +24 -116
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/evidence-gate.js +23 -85
- package/.agents/scripts/file-ci-gap.js +10 -50
- package/.agents/scripts/generate-config-docs.js +30 -170
- package/.agents/scripts/generate-lens-checklists.js +8 -52
- package/.agents/scripts/generate-skills-index.js +14 -112
- package/.agents/scripts/generate-workflows-doc.js +10 -70
- package/.agents/scripts/git-cleanup.js +2 -34
- package/.agents/scripts/lib/Graph.js +24 -81
- package/.agents/scripts/lib/ITicketingProvider.js +47 -143
- package/.agents/scripts/lib/Logger.js +14 -76
- package/.agents/scripts/lib/audit-baselines/engine.js +11 -31
- package/.agents/scripts/lib/audit-baselines/gate-surface.js +3 -17
- package/.agents/scripts/lib/audit-baselines/headroom.js +4 -20
- package/.agents/scripts/lib/audit-baselines/hotspots.js +3 -15
- package/.agents/scripts/lib/audit-baselines/kinds.js +23 -89
- package/.agents/scripts/lib/audit-baselines/outliers.js +6 -23
- package/.agents/scripts/lib/audit-baselines/read.js +3 -16
- package/.agents/scripts/lib/audit-baselines/staleness.js +7 -26
- package/.agents/scripts/lib/audit-baselines/surface-entry.js +7 -28
- package/.agents/scripts/lib/audit-baselines/trend.js +7 -20
- package/.agents/scripts/lib/audit-baselines/weights.js +9 -37
- package/.agents/scripts/lib/audit-suite/audit-rules-reader.js +3 -18
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +26 -124
- package/.agents/scripts/lib/audit-suite/dispatch-checklist.js +14 -45
- package/.agents/scripts/lib/audit-suite/findings.js +12 -52
- package/.agents/scripts/lib/audit-suite/frontmatter.js +5 -29
- package/.agents/scripts/lib/audit-suite/index.js +1 -15
- package/.agents/scripts/lib/audit-suite/lens-checklist.js +10 -45
- package/.agents/scripts/lib/audit-suite/lens-diff-floor.js +11 -76
- package/.agents/scripts/lib/audit-suite/runner.js +13 -43
- package/.agents/scripts/lib/audit-suite/selector.js +49 -349
- package/.agents/scripts/lib/audit-suite/substitutions.js +12 -40
- package/.agents/scripts/lib/audit-suite/workflow-loader.js +3 -15
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +11 -73
- package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +8 -37
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +25 -143
- package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +14 -80
- package/.agents/scripts/lib/audit-to-stories/epic-grouping-directive.js +4 -18
- package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +7 -40
- package/.agents/scripts/lib/audit-to-stories/group-findings.js +9 -39
- package/.agents/scripts/lib/audit-to-stories/issue-corpus.js +10 -66
- package/.agents/scripts/lib/audit-to-stories/issue-index.js +6 -30
- package/.agents/scripts/lib/audit-to-stories/issues-file.js +6 -36
- package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +17 -69
- package/.agents/scripts/lib/audit-to-stories/ledger-pr.js +17 -85
- package/.agents/scripts/lib/audit-to-stories/ledger-record.js +10 -48
- package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +38 -146
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +7 -42
- package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +10 -57
- package/.agents/scripts/lib/baseline-loader.js +12 -51
- package/.agents/scripts/lib/baseline-schema-registry.js +5 -22
- package/.agents/scripts/lib/baselines/component-matcher.js +3 -12
- package/.agents/scripts/lib/baselines/components.js +10 -61
- package/.agents/scripts/lib/baselines/coverage-updater-cli.js +7 -36
- package/.agents/scripts/lib/baselines/crap-preview-incremental.js +5 -21
- package/.agents/scripts/lib/baselines/crap-preview-scan.js +19 -45
- package/.agents/scripts/lib/baselines/crap-updater-cli.js +16 -65
- package/.agents/scripts/lib/baselines/diff-scope-cli.js +5 -33
- package/.agents/scripts/lib/baselines/duplication-scanner.js +11 -60
- package/.agents/scripts/lib/baselines/env-overrides.js +12 -67
- package/.agents/scripts/lib/baselines/envelope.js +13 -129
- package/.agents/scripts/lib/baselines/exit-codes.js +6 -49
- package/.agents/scripts/lib/baselines/git-base.js +23 -142
- package/.agents/scripts/lib/baselines/kernel.js +11 -111
- package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +9 -47
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +8 -51
- package/.agents/scripts/lib/baselines/kinds/_shared-metric.js +7 -62
- package/.agents/scripts/lib/baselines/kinds/bundle-size.js +8 -29
- package/.agents/scripts/lib/baselines/kinds/coverage.js +3 -15
- package/.agents/scripts/lib/baselines/kinds/crap.js +94 -415
- package/.agents/scripts/lib/baselines/kinds/duplication.js +4 -30
- package/.agents/scripts/lib/baselines/kinds/kind-factory.js +7 -53
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +15 -80
- package/.agents/scripts/lib/baselines/kinds/mutation.js +12 -72
- package/.agents/scripts/lib/baselines/maintainability-baseline-io.js +4 -11
- package/.agents/scripts/lib/baselines/merge-envelopes.js +30 -146
- package/.agents/scripts/lib/baselines/path-canon.js +19 -128
- package/.agents/scripts/lib/baselines/preview-gates.js +8 -37
- package/.agents/scripts/lib/baselines/reader.js +14 -107
- package/.agents/scripts/lib/baselines/refresh-service.js +35 -252
- package/.agents/scripts/lib/baselines/scope.js +11 -135
- package/.agents/scripts/lib/baselines/writer.js +16 -149
- package/.agents/scripts/lib/bdd-runner-detect.js +18 -113
- package/.agents/scripts/lib/bdd-scenario-budget.js +9 -36
- package/.agents/scripts/lib/bdd-scenario-scanner.js +11 -73
- package/.agents/scripts/lib/bdd-step-index.js +26 -100
- package/.agents/scripts/lib/bootstrap/baseline-merge-driver.js +23 -103
- package/.agents/scripts/lib/bootstrap/branch-protection.js +2 -5
- package/.agents/scripts/lib/bootstrap/commit-push.js +12 -50
- package/.agents/scripts/lib/bootstrap/gh-preflight.js +14 -101
- package/.agents/scripts/lib/bootstrap/hitl-confirm.js +5 -30
- package/.agents/scripts/lib/bootstrap/install-ledger.js +17 -73
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +17 -121
- package/.agents/scripts/lib/bootstrap/manifest.js +15 -86
- package/.agents/scripts/lib/bootstrap/merge-methods.js +4 -33
- package/.agents/scripts/lib/bootstrap/preflight.js +12 -61
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +34 -235
- package/.agents/scripts/lib/bootstrap/prompt.js +21 -129
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +27 -141
- package/.agents/scripts/lib/bootstrap/summary.js +1 -8
- package/.agents/scripts/lib/bootstrap/workflow-audit.js +11 -84
- package/.agents/scripts/lib/branch-name-guard.js +4 -23
- package/.agents/scripts/lib/changed-files.js +31 -143
- package/.agents/scripts/lib/checks/core-bare-clean.js +4 -24
- package/.agents/scripts/lib/checks/index.js +18 -101
- package/.agents/scripts/lib/checks/loop-health.js +11 -53
- package/.agents/scripts/lib/checks/state.js +16 -91
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +10 -61
- package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +14 -83
- package/.agents/scripts/lib/child-exec.js +39 -108
- package/.agents/scripts/lib/cli/standard-args.js +9 -116
- package/.agents/scripts/lib/cli-args.js +31 -146
- package/.agents/scripts/lib/cli-usage.js +5 -27
- package/.agents/scripts/lib/cli-utils.js +2 -12
- package/.agents/scripts/lib/close-validation/commands.js +13 -76
- package/.agents/scripts/lib/close-validation/gates.js +92 -331
- package/.agents/scripts/lib/close-validation/process.js +121 -151
- package/.agents/scripts/lib/close-validation/projections/advisories.js +5 -31
- package/.agents/scripts/lib/close-validation/projections/crap.js +19 -67
- package/.agents/scripts/lib/close-validation/projections/head-sha.js +1 -17
- package/.agents/scripts/lib/close-validation/projections/inputs.js +4 -34
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +11 -58
- package/.agents/scripts/lib/close-validation/runner.js +53 -110
- package/.agents/scripts/lib/command-header.js +6 -28
- package/.agents/scripts/lib/config/acceptance-eval.js +4 -30
- package/.agents/scripts/lib/config/baselines.js +3 -14
- package/.agents/scripts/lib/config/ci.js +8 -38
- package/.agents/scripts/lib/config/commands.js +1 -15
- package/.agents/scripts/lib/config/defaults.js +4 -33
- package/.agents/scripts/lib/config/delivery-routing.js +6 -35
- package/.agents/scripts/lib/config/explain.js +9 -80
- package/.agents/scripts/lib/config/gates/coverage.schema.js +0 -12
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +2 -30
- package/.agents/scripts/lib/config/gates/crap.schema.js +1 -34
- package/.agents/scripts/lib/config/gates/duplication.schema.js +1 -8
- package/.agents/scripts/lib/config/gates/index.js +2 -17
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +1 -20
- package/.agents/scripts/lib/config/gates/mutation.schema.js +1 -7
- package/.agents/scripts/lib/config/gates/shared.js +4 -64
- package/.agents/scripts/lib/config/github.js +4 -22
- package/.agents/scripts/lib/config/limits.js +4 -57
- package/.agents/scripts/lib/config/paths.js +2 -21
- package/.agents/scripts/lib/config/qa.js +4 -38
- package/.agents/scripts/lib/config/quality.js +76 -432
- package/.agents/scripts/lib/config/runners.js +12 -56
- package/.agents/scripts/lib/config/runtime.js +13 -57
- package/.agents/scripts/lib/config/shared.js +2 -16
- package/.agents/scripts/lib/config/sync-agentrc.js +9 -50
- package/.agents/scripts/lib/config/temp-paths.js +40 -280
- package/.agents/scripts/lib/config/validate-orchestration.js +5 -17
- package/.agents/scripts/lib/config/worktree-isolation.js +9 -28
- package/.agents/scripts/lib/config-resolver.js +10 -52
- package/.agents/scripts/lib/config-schema-shared.js +1 -4
- package/.agents/scripts/lib/config-settings-schema-delivery.js +12 -294
- package/.agents/scripts/lib/config-settings-schema-quality.js +7 -219
- package/.agents/scripts/lib/config-settings-schema.js +37 -274
- package/.agents/scripts/lib/coverage-baseline.js +20 -110
- package/.agents/scripts/lib/coverage-capture-fullscope.js +12 -37
- package/.agents/scripts/lib/coverage-capture-incremental.js +12 -43
- package/.agents/scripts/lib/coverage-capture-usage.js +4 -24
- package/.agents/scripts/lib/coverage-capture.js +99 -224
- package/.agents/scripts/lib/coverage-utils.js +14 -79
- package/.agents/scripts/lib/cpu-pool.js +17 -123
- package/.agents/scripts/lib/crap-baseline-join.js +16 -88
- package/.agents/scripts/lib/crap-coordinates.js +6 -25
- package/.agents/scripts/lib/crap-engine.js +40 -153
- package/.agents/scripts/lib/crap-method-identity.js +16 -78
- package/.agents/scripts/lib/crap-utils.js +31 -155
- package/.agents/scripts/lib/cyclomatic-ceiling.js +14 -87
- package/.agents/scripts/lib/cyclomatic-scope.js +10 -50
- package/.agents/scripts/lib/dead-exports-knip.js +18 -56
- package/.agents/scripts/lib/dead-exports-mode.js +5 -24
- package/.agents/scripts/lib/degraded-mode.js +3 -26
- package/.agents/scripts/lib/dependency-parser.js +8 -40
- package/.agents/scripts/lib/dependency-version.js +13 -35
- package/.agents/scripts/lib/detect-package-manager.js +6 -36
- package/.agents/scripts/lib/doc-tiers.js +22 -126
- package/.agents/scripts/lib/duplicate-search.js +8 -72
- package/.agents/scripts/lib/env-loader.js +5 -27
- package/.agents/scripts/lib/error-redactor.js +6 -31
- package/.agents/scripts/lib/errors/index.js +2 -22
- package/.agents/scripts/lib/escomplex-ast-compat.js +22 -163
- package/.agents/scripts/lib/escomplex-kernel.js +21 -123
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +93 -419
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +20 -98
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +27 -151
- package/.agents/scripts/lib/findings/audit-ledger.js +23 -97
- package/.agents/scripts/lib/findings/classify-finding.js +14 -69
- package/.agents/scripts/lib/findings/promote-finding.js +14 -112
- package/.agents/scripts/lib/findings/provenance-field.js +11 -52
- package/.agents/scripts/lib/findings/route-finding.js +37 -255
- package/.agents/scripts/lib/findings/semantic-issue-search.js +14 -59
- package/.agents/scripts/lib/findings/severity.js +18 -104
- package/.agents/scripts/lib/format-generated-json.js +12 -44
- package/.agents/scripts/lib/full-suite-lock.js +160 -429
- package/.agents/scripts/lib/full-suite-queue.js +213 -0
- package/.agents/scripts/lib/gates/baseline-store.js +5 -9
- package/.agents/scripts/lib/gates/friction.js +1 -3
- package/.agents/scripts/lib/generated/agentrc-validator.js +2 -2
- package/.agents/scripts/lib/gh-exec.js +31 -259
- package/.agents/scripts/lib/git/cached-fetch.js +9 -52
- package/.agents/scripts/lib/git/sync-from-base.js +15 -107
- package/.agents/scripts/lib/git-branch-cleanup.js +14 -61
- package/.agents/scripts/lib/git-branch-lifecycle.js +16 -87
- package/.agents/scripts/lib/git-utils.js +37 -161
- package/.agents/scripts/lib/github/framework-repo.js +8 -71
- package/.agents/scripts/lib/github-url.js +2 -17
- package/.agents/scripts/lib/import-graph.js +11 -38
- package/.agents/scripts/lib/install-cmd-parser.js +4 -13
- package/.agents/scripts/lib/json-utils.js +3 -18
- package/.agents/scripts/lib/label-constants.js +16 -111
- package/.agents/scripts/lib/label-taxonomy.js +4 -30
- package/.agents/scripts/lib/maintainability-engine.js +12 -80
- package/.agents/scripts/lib/maintainability-unscorable.js +5 -25
- package/.agents/scripts/lib/maintainability-utils.js +16 -92
- package/.agents/scripts/lib/mandrel-catalog.js +12 -70
- package/.agents/scripts/lib/notifications/notifier.js +13 -38
- package/.agents/scripts/lib/npm-scripts.js +4 -23
- package/.agents/scripts/lib/observability/metrics-ledger.js +16 -76
- package/.agents/scripts/lib/observability/runtime-friction.js +44 -286
- package/.agents/scripts/lib/observability/signal-validator.js +5 -37
- package/.agents/scripts/lib/observability/signals-writer.js +11 -121
- package/.agents/scripts/lib/observability/source-classifier.js +21 -203
- package/.agents/scripts/lib/observability/terse-result.js +14 -47
- package/.agents/scripts/lib/onboard/init-tail.js +13 -74
- package/.agents/scripts/lib/onboard/scaffold-docs.js +11 -34
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +25 -139
- package/.agents/scripts/lib/orchestration/auto-merge-cwd.js +11 -66
- package/.agents/scripts/lib/orchestration/behind-recovery.js +10 -55
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +5 -75
- package/.agents/scripts/lib/orchestration/change-set.js +7 -36
- package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +9 -46
- package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +11 -44
- package/.agents/scripts/lib/orchestration/check-baselines/phases/floors.js +5 -24
- package/.agents/scripts/lib/orchestration/check-baselines/phases/friction.js +3 -12
- package/.agents/scripts/lib/orchestration/check-baselines/phases/parse-args.js +5 -38
- package/.agents/scripts/lib/orchestration/check-baselines/phases/pipeline.js +3 -8
- package/.agents/scripts/lib/orchestration/check-baselines/phases/refresh-ack.js +28 -136
- package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +3 -13
- package/.agents/scripts/lib/orchestration/check-state.js +92 -0
- package/.agents/scripts/lib/orchestration/ci-gap-intake.js +37 -144
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +42 -189
- package/.agents/scripts/lib/orchestration/code-review.js +24 -128
- package/.agents/scripts/lib/orchestration/column-sync.js +23 -111
- package/.agents/scripts/lib/orchestration/complexity-gate.js +42 -214
- package/.agents/scripts/lib/orchestration/deliver-recover.js +36 -168
- package/.agents/scripts/lib/orchestration/dependency-analyzer.js +4 -35
- package/.agents/scripts/lib/orchestration/dependency-candidates.js +9 -41
- package/.agents/scripts/lib/orchestration/diff-magnitude.js +25 -115
- package/.agents/scripts/lib/orchestration/doc-reader.js +2 -6
- package/.agents/scripts/lib/orchestration/docs-digest.js +13 -51
- package/.agents/scripts/lib/orchestration/epic-candidates.js +13 -53
- package/.agents/scripts/lib/orchestration/epic-checklist.js +9 -33
- package/.agents/scripts/lib/orchestration/epic-container.js +41 -167
- package/.agents/scripts/lib/orchestration/epic-expansion.js +10 -40
- package/.agents/scripts/lib/orchestration/epic-rollup.js +55 -207
- package/.agents/scripts/lib/orchestration/file-assumption-enum.js +2 -22
- package/.agents/scripts/lib/orchestration/file-assumptions.js +45 -253
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches-detect.js +9 -46
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches-reap.js +7 -57
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/branches.js +21 -126
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +2 -6
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +1 -4
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/filters.js +1 -5
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes-ff.js +9 -34
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/git-probes.js +41 -190
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/merged-tip.js +9 -45
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/parse-args.js +1 -4
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +19 -85
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/prompts.js +3 -17
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/prune.js +1 -5
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +16 -100
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/stashes.js +1 -4
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +16 -67
- package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +9 -33
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +8 -29
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +15 -68
- package/.agents/scripts/lib/orchestration/light-backstop.js +11 -49
- package/.agents/scripts/lib/orchestration/light-escalation.js +21 -89
- package/.agents/scripts/lib/orchestration/light-suitability.js +26 -231
- package/.agents/scripts/lib/orchestration/merge-block-class.js +39 -216
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -377
- package/.agents/scripts/lib/orchestration/merge-queue.js +158 -0
- package/.agents/scripts/lib/orchestration/pinned-identifier-lint.js +17 -57
- package/.agents/scripts/lib/orchestration/plan-context.js +52 -303
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +22 -79
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +5 -31
- package/.agents/scripts/lib/orchestration/plan-metrics.js +29 -108
- package/.agents/scripts/lib/orchestration/plan-navigation.js +8 -27
- package/.agents/scripts/lib/orchestration/plan-persist/acceptance-handle-repair.js +12 -49
- package/.agents/scripts/lib/orchestration/plan-persist/audit-provenance.js +18 -73
- package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +14 -78
- package/.agents/scripts/lib/orchestration/plan-persist/cross-plan-links.js +6 -30
- package/.agents/scripts/lib/orchestration/plan-persist/epic-adoption.js +14 -65
- package/.agents/scripts/lib/orchestration/plan-persist/epic-ops.js +21 -76
- package/.agents/scripts/lib/orchestration/plan-persist/external-deps.js +6 -43
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +14 -68
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +9 -41
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +49 -248
- package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +2 -12
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +85 -368
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +8 -38
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +30 -190
- package/.agents/scripts/lib/orchestration/plan-persist/wave-collision-gate.js +9 -49
- package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +7 -28
- package/.agents/scripts/lib/orchestration/plan-reachability.js +13 -42
- package/.agents/scripts/lib/orchestration/plan-run-labels/reap.js +16 -91
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +16 -54
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +10 -52
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +14 -109
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +11 -83
- package/.agents/scripts/lib/orchestration/pr-watch.js +48 -306
- package/.agents/scripts/lib/orchestration/project-meta-cache.js +15 -80
- package/.agents/scripts/lib/orchestration/project-meta-resolver.js +10 -51
- package/.agents/scripts/lib/orchestration/reassert-status-column.js +12 -76
- package/.agents/scripts/lib/orchestration/remote-verifier.js +12 -36
- package/.agents/scripts/lib/orchestration/resolve-stories.js +33 -164
- package/.agents/scripts/lib/orchestration/retro-proposals.js +66 -361
- package/.agents/scripts/lib/orchestration/review-base-ref.js +9 -43
- package/.agents/scripts/lib/orchestration/review-depth.js +12 -94
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +10 -111
- package/.agents/scripts/lib/orchestration/review-providers/degraded-gates.js +13 -71
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +7 -51
- package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +8 -53
- package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -214
- package/.agents/scripts/lib/orchestration/review-providers/parse-findings.js +7 -52
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +4 -33
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +11 -71
- package/.agents/scripts/lib/orchestration/review-providers/scoped-lint.js +19 -131
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +6 -89
- package/.agents/scripts/lib/orchestration/review-providers/types.js +26 -56
- package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +3 -46
- package/.agents/scripts/lib/orchestration/run-epilogue.js +40 -199
- package/.agents/scripts/lib/orchestration/run-scoped-config.js +13 -81
- package/.agents/scripts/lib/orchestration/single-story-close/close-note.js +7 -34
- package/.agents/scripts/lib/orchestration/single-story-close/failed-terminal.js +16 -89
- package/.agents/scripts/lib/orchestration/single-story-close/gate-log.js +18 -109
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +190 -280
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +18 -80
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +103 -97
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +8 -64
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +183 -485
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +30 -134
- package/.agents/scripts/lib/orchestration/single-story-close/phases/graphql-preflight.js +9 -50
- package/.agents/scripts/lib/orchestration/single-story-close/phases/lock-wait-pending.js +49 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +13 -86
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +20 -95
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +46 -203
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +8 -50
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +12 -83
- package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +6 -39
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +1 -6
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-outcome.js +4 -28
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +6 -51
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +6 -34
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +23 -121
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +110 -290
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +15 -69
- package/.agents/scripts/lib/orchestration/story-body-gate.js +4 -23
- package/.agents/scripts/lib/orchestration/story-close/baseline-upward-writeback.js +50 -179
- package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +11 -41
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +23 -311
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +22 -147
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +16 -67
- package/.agents/scripts/lib/orchestration/story-deliver-terminal-schema.js +12 -68
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +42 -201
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +43 -245
- package/.agents/scripts/lib/orchestration/story-init-envelope.js +4 -23
- package/.agents/scripts/lib/orchestration/story-init-remote.js +2 -6
- package/.agents/scripts/lib/orchestration/story-reachability.js +3 -23
- package/.agents/scripts/lib/orchestration/task-body-validator.js +12 -126
- package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -131
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +15 -149
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +5 -33
- package/.agents/scripts/lib/orchestration/ticket-validator.js +39 -181
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +51 -244
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +39 -250
- package/.agents/scripts/lib/orchestration/ticketing/state.js +14 -68
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +46 -263
- package/.agents/scripts/lib/orchestration/ticketing.js +2 -26
- package/.agents/scripts/lib/orchestration/verify-credit.js +18 -79
- package/.agents/scripts/lib/orchestration/worktree-dirty.js +5 -30
- package/.agents/scripts/lib/path-security.js +3 -6
- package/.agents/scripts/lib/plan-phase-cleanup.js +9 -49
- package/.agents/scripts/lib/preflight-runner.js +13 -69
- package/.agents/scripts/lib/process-group.js +143 -0
- package/.agents/scripts/lib/project-root.js +2 -8
- package/.agents/scripts/lib/provider-factory.js +2 -25
- package/.agents/scripts/lib/qa/console-allowlist.js +10 -59
- package/.agents/scripts/lib/qa/qa-session.js +15 -69
- package/.agents/scripts/lib/qa/redact-evidence.js +18 -129
- package/.agents/scripts/lib/qa/resolve-qa-contract.js +26 -135
- package/.agents/scripts/lib/qa/resolve-selection.js +16 -84
- package/.agents/scripts/lib/reserved-test-ids.js +9 -47
- package/.agents/scripts/lib/runtime-deps/dep-resolution.js +13 -53
- package/.agents/scripts/lib/runtime-deps/ensure-installed.js +10 -44
- package/.agents/scripts/lib/runtime-deps/manifest.js +10 -30
- package/.agents/scripts/lib/runtime-deps/parser-major.js +15 -51
- package/.agents/scripts/lib/runtime-deps/preflight.js +4 -22
- package/.agents/scripts/lib/runtime-deps/scan-imports.js +11 -55
- package/.agents/scripts/lib/signals/detectors/common.js +12 -38
- package/.agents/scripts/lib/signals/index.js +2 -19
- package/.agents/scripts/lib/signals/schema.js +14 -109
- package/.agents/scripts/lib/signals/write.js +2 -11
- package/.agents/scripts/lib/single-story/confirm-merge.js +31 -72
- package/.agents/scripts/lib/single-story/story-merged-notify.js +8 -49
- package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +4 -26
- package/.agents/scripts/lib/single-story-sweep/protection.js +13 -100
- package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +60 -258
- package/.agents/scripts/lib/single-story-sweep.js +14 -97
- package/.agents/scripts/lib/skills/parse-skill.js +13 -59
- package/.agents/scripts/lib/skills/skills-index.js +8 -29
- package/.agents/scripts/lib/skills/walk-skill-files.js +17 -78
- package/.agents/scripts/lib/source-extensions.js +11 -42
- package/.agents/scripts/lib/source-text/strip-js-comments.js +8 -41
- package/.agents/scripts/lib/stdio-flush.js +9 -36
- package/.agents/scripts/lib/story-adjacency.js +8 -40
- package/.agents/scripts/lib/story-body/body-format-lints.js +22 -83
- package/.agents/scripts/lib/story-body/footer-block.js +11 -44
- package/.agents/scripts/lib/story-body/story-body.js +83 -358
- package/.agents/scripts/lib/temp-retention.js +42 -143
- package/.agents/scripts/lib/templates/decomposer-prompts.js +11 -65
- package/.agents/scripts/lib/test-env.js +12 -65
- package/.agents/scripts/lib/test-run-credit.js +10 -65
- package/.agents/scripts/lib/test-runner-contract.js +19 -74
- package/.agents/scripts/lib/test-temp.js +38 -184
- package/.agents/scripts/lib/test-tiers.js +12 -96
- package/.agents/scripts/lib/ticket-body-sections.js +21 -96
- package/.agents/scripts/lib/transpile.js +15 -74
- package/.agents/scripts/lib/util/concurrent-map.js +6 -25
- package/.agents/scripts/lib/util/parse-id-list.js +8 -34
- package/.agents/scripts/lib/util/poll-loop.js +8 -28
- package/.agents/scripts/lib/util/with-timeout.js +2 -11
- package/.agents/scripts/lib/validation-evidence.js +21 -96
- package/.agents/scripts/lib/wave-runner/footprint.js +17 -70
- package/.agents/scripts/lib/wave-runner/live-probe.js +53 -221
- package/.agents/scripts/lib/wave-runner/ready-set.js +72 -301
- package/.agents/scripts/lib/workers/crap-worker.js +11 -64
- package/.agents/scripts/lib/workers/maintainability-report-worker.js +8 -45
- package/.agents/scripts/lib/workers/maintainability-worker.js +5 -21
- package/.agents/scripts/lib/workers/serve-worker-messages.js +2 -18
- package/.agents/scripts/lib/workflow-closure.js +22 -105
- package/.agents/scripts/lib/workspace-provisioner.js +16 -54
- package/.agents/scripts/lib/worktree/git-hooks.js +15 -60
- package/.agents/scripts/lib/worktree/lifecycle/force-drain.js +20 -60
- package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +10 -62
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +22 -86
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +24 -126
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +2 -20
- package/.agents/scripts/lib/worktree/node-modules-strategy.js +46 -180
- package/.agents/scripts/lib/worktree-manager.js +15 -40
- package/.agents/scripts/lint-issue-body.js +12 -68
- package/.agents/scripts/mandrel-update-preflight.js +10 -70
- package/.agents/scripts/merge-baseline.js +30 -104
- package/.agents/scripts/nav-registry-diff.js +22 -108
- package/.agents/scripts/notify.js +12 -69
- package/.agents/scripts/plan-context.js +27 -111
- package/.agents/scripts/plan-critics.js +12 -66
- package/.agents/scripts/plan-persist.js +31 -148
- package/.agents/scripts/plan-run-epilogue.js +11 -37
- package/.agents/scripts/pr-watch-with-update.js +76 -267
- package/.agents/scripts/providers/github/auth.js +3 -8
- package/.agents/scripts/providers/github/blocked-by-add.js +14 -61
- package/.agents/scripts/providers/github/board-add.js +5 -21
- package/.agents/scripts/providers/github/branch-protection.js +11 -43
- package/.agents/scripts/providers/github/cache.js +2 -14
- package/.agents/scripts/providers/github/comments.js +7 -40
- package/.agents/scripts/providers/github/compose.js +3 -19
- package/.agents/scripts/providers/github/errors.js +22 -125
- package/.agents/scripts/providers/github/issues.js +31 -123
- package/.agents/scripts/providers/github/labels.js +24 -124
- package/.agents/scripts/providers/github/mappers.js +5 -27
- package/.agents/scripts/providers/github/merge-methods.js +3 -22
- package/.agents/scripts/providers/github/project-board.js +2 -17
- package/.agents/scripts/providers/github/projects-v2-graphql.js +3 -6
- package/.agents/scripts/providers/github/request-helpers.js +5 -32
- package/.agents/scripts/providers/github/search-budget.js +9 -39
- package/.agents/scripts/providers/github/search-query.js +5 -26
- package/.agents/scripts/providers/github/sub-issue-add.js +15 -65
- package/.agents/scripts/providers/github/sub-issues.js +7 -36
- package/.agents/scripts/providers/github/tickets.js +32 -134
- package/.agents/scripts/providers/github.js +18 -59
- package/.agents/scripts/prune-plan-run-labels.js +12 -46
- package/.agents/scripts/quality-preview.js +35 -220
- package/.agents/scripts/resolve-doc-tiers.js +3 -26
- package/.agents/scripts/resolve-stories.js +16 -67
- package/.agents/scripts/resync-status-column.js +6 -25
- package/.agents/scripts/run-tests.js +29 -104
- package/.agents/scripts/single-story-close.js +23 -135
- package/.agents/scripts/single-story-confirm-merge.js +32 -139
- package/.agents/scripts/single-story-init.js +47 -209
- package/.agents/scripts/stories-wave-tick.js +102 -472
- package/.agents/scripts/sync-agentrc.js +3 -21
- package/.agents/scripts/sync-claude-agents.js +9 -55
- package/.agents/scripts/sync-claude-commands.js +20 -110
- package/.agents/scripts/test-wrapper.js +6 -43
- package/.agents/scripts/update-coverage-baseline.js +5 -22
- package/.agents/scripts/update-crap-baseline.js +7 -30
- package/.agents/scripts/update-duplication-baseline.js +16 -92
- package/.agents/scripts/update-maintainability-baseline.js +8 -62
- package/.agents/scripts/update-ticket-state.js +1 -7
- package/.agents/scripts/validate-skills.js +6 -26
- package/.agents/templates/agent-protocol.md +2 -2
- package/.agents/templates/docs/audit-sweep-runbook.md +3 -4
- package/.agents/workflows/audit-accessibility.md +4 -7
- package/.agents/workflows/audit-adrs.md +3 -6
- package/.agents/workflows/audit-architecture.md +3 -11
- package/.agents/workflows/audit-baselines.md +6 -9
- package/.agents/workflows/audit-clean-code.md +8 -12
- package/.agents/workflows/audit-data-model.md +3 -7
- package/.agents/workflows/audit-dependencies.md +4 -7
- package/.agents/workflows/audit-devops.md +4 -8
- package/.agents/workflows/audit-documentation.md +1 -4
- package/.agents/workflows/audit-mobile.md +4 -7
- package/.agents/workflows/audit-navigability.md +3 -6
- package/.agents/workflows/audit-performance.md +2 -4
- package/.agents/workflows/audit-privacy.md +4 -7
- package/.agents/workflows/audit-quality.md +4 -7
- package/.agents/workflows/audit-security.md +4 -7
- package/.agents/workflows/audit-seo.md +4 -7
- package/.agents/workflows/audit-sre.md +4 -7
- package/.agents/workflows/audit-to-stories.md +17 -30
- package/.agents/workflows/audit-ux-ui.md +4 -7
- package/.agents/workflows/helpers/audit-lens-core.md +1 -1
- package/.agents/workflows/helpers/code-quality-guardrails.md +12 -15
- package/.agents/workflows/helpers/deliver-digest.md +6 -4
- package/.agents/workflows/helpers/deliver-reference.md +114 -188
- package/.agents/workflows/helpers/deliver-story-reference.md +189 -533
- package/.agents/workflows/helpers/plan-reference.md +86 -139
- package/.agents/workflows/helpers/qa-core.md +12 -0
- package/.agents/workflows/memory-consolidate.md +1 -1
- package/.agents/workflows/qa-assist.md +27 -71
- package/.agents/workflows/qa-explore.md +19 -59
- package/.agents/workflows/qa-run.md +11 -33
- package/README.md +1 -1
- package/bin/mandrel.js +5 -46
- package/bin/postinstall.js +18 -107
- package/docs/CHANGELOG.md +32 -0
- package/lib/cli/doctor.js +11 -71
- package/lib/cli/init.js +26 -165
- package/lib/cli/migrate.js +5 -64
- package/lib/cli/registry.js +84 -467
- package/lib/cli/sync-agents.js +4 -64
- package/lib/cli/sync-commands.js +11 -64
- package/lib/cli/sync.js +34 -213
- package/lib/cli/uninstall.js +40 -219
- package/lib/cli/update.js +105 -661
- package/lib/cli/version-check.js +8 -72
- package/lib/cli/version-helpers.js +14 -87
- package/lib/migrations/helpers/retire-agentrc-key.js +11 -52
- package/lib/migrations/index.js +15 -92
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +2 -15
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +1 -13
- package/lib/migrations/steps/2.11.0-retire-max-seed-words.js +2 -15
- package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +9 -38
- package/lib/migrations/steps/2.20.0-retire-codebase-snapshot.js +1 -13
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +2 -27
- package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +2 -23
- package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +2 -27
- package/lib/migrations/steps/2.60.0-retire-audit-results-autofile.js +2 -21
- package/lib/migrations/steps/strip-removed-agentrc-keys.js +333 -0
- package/package.json +16 -14
- package/.agents/docs/SDLC.md +0 -590
- package/.agents/docs/quality-gates.md +0 -1183
- package/.agents/rules/known-tooling-behavior.md +0 -200
- package/.agents/rules/orchestration-error-handling.md +0 -61
- package/.agents/rules/test-seams.md +0 -59
- package/.agents/schemas/baselines/lighthouse.schema.json +0 -59
- package/.agents/schemas/baselines/lint.schema.json +0 -47
- package/.agents/scripts/check-action-pinning.js +0 -260
- package/.agents/scripts/check-audit-attribution.js +0 -302
- package/.agents/scripts/check-baseline-drift.js +0 -211
- package/.agents/scripts/check-baseline-scope.js +0 -362
- package/.agents/scripts/check-generated-validator.js +0 -202
- package/.agents/scripts/check-knip-entries.js +0 -159
- package/.agents/scripts/check-lifecycle-lint.js +0 -294
- package/.agents/scripts/check-pinned-override-notes.js +0 -102
- package/.agents/scripts/check-schema-references.js +0 -368
- package/.agents/scripts/check-test-portability.js +0 -512
- package/.agents/scripts/check-workflow-citations.js +0 -218
- package/.agents/scripts/check-workflow-cli-lint.js +0 -299
- package/.agents/scripts/check-workflow-timeouts.js +0 -291
- package/.agents/scripts/install-matrix-assert.js +0 -326
- package/.agents/scripts/lib/audit-advisories.js +0 -195
- package/.agents/scripts/lib/audit-attribution.js +0 -134
- package/.agents/scripts/lib/baselines/drift-detector.js +0 -351
- package/.agents/scripts/lib/baselines/kinds/lighthouse.js +0 -87
- package/.agents/scripts/lib/baselines/kinds/lint.js +0 -184
- package/.agents/scripts/lib/baselines/orphan-pruner.js +0 -233
- package/.agents/scripts/lib/baselines/scope-assert.js +0 -223
- package/.agents/scripts/lib/baselines/scope-inventory.js +0 -314
- package/.agents/scripts/lib/c8-cli-path.js +0 -21
- package/.agents/scripts/lib/config/gates/lighthouse.schema.js +0 -51
- package/.agents/scripts/lib/config/gates/lint.schema.js +0 -18
- package/.agents/scripts/lib/dynamic-workflow/architecture-report-contract.js +0 -70
- package/.agents/scripts/lib/dynamic-workflow/audit-orchestrator.js +0 -284
- package/.agents/scripts/lib/dynamic-workflow/clean-code-report-contract.js +0 -80
- package/.agents/scripts/lib/dynamic-workflow/degraded-coverage.js +0 -81
- package/.agents/scripts/lib/dynamic-workflow/documentation-report-contract.js +0 -87
- package/.agents/scripts/lib/dynamic-workflow/performance-report-contract.js +0 -74
- package/.agents/scripts/lib/dynamic-workflow/quality-report-contract.js +0 -90
- package/.agents/scripts/lib/dynamic-workflow/report-contract-core.js +0 -43
- package/.agents/scripts/lib/dynamic-workflow/security-report-contract.js +0 -83
- package/.agents/scripts/lib/fs-walk.js +0 -52
- package/.agents/scripts/lib/knip-config-resolver.js +0 -181
- package/.agents/scripts/lib/knip-entry-sync.js +0 -452
- package/.agents/scripts/lib/pinned-override-notes.js +0 -88
- package/.agents/scripts/lib/pinned-override-resolve.js +0 -212
- package/.agents/scripts/lib/test-isolate/cli-options.js +0 -93
- package/.agents/scripts/lib/test-isolate/env-snapshot-loader.js +0 -52
- package/.agents/scripts/lib/test-isolate/list-files.js +0 -90
- package/.agents/scripts/lib/test-isolate/parse-tap.js +0 -75
- package/.agents/scripts/lib/test-isolate/progress-log.js +0 -45
- package/.agents/scripts/lib/test-isolate/render-report.js +0 -97
- package/.agents/scripts/lib/test-isolate/run-isolate.js +0 -87
- package/.agents/scripts/lib/test-isolate/runner.js +0 -483
- package/.agents/scripts/lib/test-profile/parse-tap.js +0 -136
- package/.agents/scripts/lib/test-profile/render-report.js +0 -45
- package/.agents/scripts/lint-label-vocabulary.js +0 -214
- package/.agents/scripts/post-structured-comment.js +0 -127
- package/.agents/scripts/provision-git-hooks.js +0 -85
- package/.agents/scripts/prune-baseline-orphans.js +0 -181
- package/.agents/scripts/run-coverage.js +0 -197
- package/.agents/scripts/run-lint.js +0 -133
- package/.agents/scripts/run-test-profile.js +0 -129
- package/.agents/scripts/run-verify.js +0 -125
- package/.agents/scripts/test-isolate.js +0 -55
- package/.agents/scripts/update-dead-exports-baseline.js +0 -321
|
@@ -1,93 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* phases/confirm-merge.js — the close-and-land merge wait
|
|
3
|
-
*
|
|
2
|
+
* phases/confirm-merge.js — the close-and-land merge wait, the default
|
|
3
|
+
* terminal step of every close (`--no-wait-merge` opts out).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* Two timing domains: `maxWaitSeconds` bounds THIS invocation (it must fit
|
|
6
|
+
* the host's ~10-min tool ceiling after the close gates) and expires to a
|
|
7
|
+
* resumable `pending` with no mutation; `maxBudgetSeconds` bounds the
|
|
8
|
+
* cumulative wait, anchored at the PR's `createdAt` so resumes don't restart
|
|
9
|
+
* it, and exhausting it is the real give-up (classify, emit, block). The
|
|
10
|
+
* poll is stateless and re-entrant. Async mode only shortens the
|
|
11
|
+
* per-invocation window. Checks are probed every iteration so a red required
|
|
12
|
+
* check fails fast instead of burning the budget.
|
|
11
13
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* whose CI took longer than roughly eight minutes was **killed mid-poll**
|
|
18
|
-
* with no terminal path taken — no `merge.unlanded` event, no `agent::blocked`
|
|
19
|
-
* flip, the Story parked at `agent::closing`: precisely the strand the
|
|
20
|
-
* must-land contract exists to eliminate.
|
|
21
|
-
*
|
|
22
|
-
* The fix splits the two timing domains that were conflated:
|
|
23
|
-
*
|
|
24
|
-
* - **`maxWaitSeconds`** bounds THIS invocation (default 300s, comfortably
|
|
25
|
-
* inside the host ceiling). On expiry the wait returns
|
|
26
|
-
* `terminal: 'pending'` — **no label mutation, no `merge.unlanded`
|
|
27
|
-
* event** — and the caller surfaces a resumable terminal with its own
|
|
28
|
-
* exit code. Merely shrinking `maxBudgetSeconds` instead would have been
|
|
29
|
-
* wrong: that path conflates slow CI with a hard block, so most runs
|
|
30
|
-
* would have been misfiled as blocked.
|
|
31
|
-
* - **`maxBudgetSeconds`** bounds the CUMULATIVE wait, anchored at the
|
|
32
|
-
* PR's `createdAt` rather than this invocation's start, so resumes do not
|
|
33
|
-
* restart the clock. Exhausting it is the genuine give-up: classify,
|
|
34
|
-
* emit, block. `agent::blocked` stays reserved for hard blocks.
|
|
35
|
-
*
|
|
36
|
-
* Backgrounding is not a workaround here and does not need to be: an
|
|
37
|
-
* interrupted poll is stateless and re-entrant by construction.
|
|
38
|
-
*
|
|
39
|
-
* ## Async mode (Story #4698 — a designed short probe window, not an accident)
|
|
40
|
-
*
|
|
41
|
-
* `maxWaitSeconds` (default 300s) still routinely EXPIRES on a slow-CI
|
|
42
|
-
* consumer: the median PR-create→merge time can be minutes, so nearly every
|
|
43
|
-
* close burns its whole foreground slot polling and then returns `pending`
|
|
44
|
-
* anyway. `delivery.mergeWatch.mode: "async"` makes that async confirm a
|
|
45
|
-
* designed mode rather than an expiry accident. It caps the per-invocation
|
|
46
|
-
* wait to a short probe window (`ASYNC_PROBE_WINDOW_SECONDS`, ~60s) — long
|
|
47
|
-
* enough for the loop's existing checks to catch an instant merge and, via the
|
|
48
|
-
* imported {@link decideMergeWaitFailFast} decision (Story #4695/#4710), an
|
|
49
|
-
* instantly-red required check — then returns the SAME resumable `pending`
|
|
50
|
-
* terminal, whose `nextCommand` the worker launches in the background. Nothing
|
|
51
|
-
* else changes: the cumulative `maxBudgetSeconds` anchor is untouched, and
|
|
52
|
-
* `sync` mode (the default) is byte-compatible. An explicit `--max-wait-seconds`
|
|
53
|
-
* override wins over the async cap so a headless caller can still land in one
|
|
54
|
-
* block. The clamp lives entirely in `resolveMergeWaitConfig`; the poll loop is
|
|
55
|
-
* mode-agnostic.
|
|
56
|
-
*
|
|
57
|
-
* ## The wait is not weaker than the watch it displaced
|
|
58
|
-
*
|
|
59
|
-
* The pre-#4543 poll read only `state` / `mergedAt`. A check that went red
|
|
60
|
-
* at minute one therefore burned the full hour and then classified as
|
|
61
|
-
* `branch-protection-human-required` (the exhaustion probe sees
|
|
62
|
-
* `mergeStateStatus: BLOCKED` with checks settled) — sending the operator to
|
|
63
|
-
* diagnose branch protection instead of their red check. This wait probes the
|
|
64
|
-
* checks every iteration: it fails fast on `checks-failed`, and runs a
|
|
65
|
-
* bounded `gh pr update-branch` on a BEHIND PR instead of waiting out the
|
|
66
|
-
* budget behind a base it could have caught up to.
|
|
67
|
-
*
|
|
68
|
-
* The per-iteration `provider.getTicket` is also gone. It was re-fetched
|
|
69
|
-
* every poll for an idempotence check whose answer cannot change mid-poll —
|
|
70
|
-
* ~240 reads per Story per hour. The loop now probes the PR only, and calls
|
|
71
|
-
* the shared `confirmStoryMerged` exactly once, after a merge is observed.
|
|
72
|
-
*
|
|
73
|
-
* Terminal outcomes:
|
|
74
|
-
* - `{ confirmed: true, action, tail }` — the PR merged; `confirmStoryMerged`
|
|
75
|
-
* flipped `agent::closing → agent::done` and closed the issue, and the
|
|
76
|
-
* shared post-land tail ran.
|
|
77
|
-
* - `{ confirmed: false, terminal: 'pending', waitBudget }` — this
|
|
78
|
-
* invocation's bound expired with the PR still in flight. Resumable;
|
|
79
|
-
* nothing was mutated.
|
|
80
|
-
* - `{ confirmed: false, terminal: 'blocked', blockClass, reason }` — the
|
|
81
|
-
* arm failed outright, the PR closed without merging, a required check
|
|
82
|
-
* went red, or the cumulative budget was exhausted. Classified via the
|
|
83
|
-
* shared `classifyMergeBlock`, emitted as `merge.unlanded`, friction
|
|
84
|
-
* posted, Story transitioned to `agent::blocked`.
|
|
85
|
-
* - `{ confirmed: false, terminal: 'blocked', blockClass: 'merged-flip-failed' }`
|
|
86
|
-
* — the PR merged but the `agent::done` label write failed. Its own
|
|
87
|
-
* `merge.flip-failed` event and friction wording (Story #4539): the merge
|
|
88
|
-
* landed, so attributing it to an unlanded merge would send the operator
|
|
89
|
-
* to diagnose branch protection instead of re-running the idempotent
|
|
90
|
-
* confirm.
|
|
14
|
+
* Terminals: `landed` (confirmed, post-land tail ran); `pending` (nothing
|
|
15
|
+
* mutated); `blocked` (classified, `merge.unlanded` emitted, friction posted,
|
|
16
|
+
* Story → `agent::blocked`); `blocked`/`merged-flip-failed` (merged but the
|
|
17
|
+
* `agent::done` write failed — its own event and wording, since the merge
|
|
18
|
+
* is not in question).
|
|
91
19
|
*/
|
|
92
20
|
|
|
93
21
|
import { getCiDelivery } from '../../../config/ci.js';
|
|
@@ -98,6 +26,7 @@ import {
|
|
|
98
26
|
} from '../../../single-story/confirm-merge.js';
|
|
99
27
|
import { pollUntil } from '../../../util/poll-loop.js';
|
|
100
28
|
import { applyBehindUpdate } from '../../behind-recovery.js';
|
|
29
|
+
import { isRerunPermitted } from '../../check-state.js';
|
|
101
30
|
import {
|
|
102
31
|
emitMergeFlipFailed as defaultEmitMergeFlipFailed,
|
|
103
32
|
MERGED_FLIP_FAILED_BLOCK_CLASS,
|
|
@@ -114,11 +43,13 @@ import {
|
|
|
114
43
|
deriveChecksStatus,
|
|
115
44
|
deriveRedHeadRuns,
|
|
116
45
|
deriveRequiredRunEvidence,
|
|
46
|
+
isPrMerged,
|
|
117
47
|
MERGE_WAIT_GH_TIMEOUT_MS,
|
|
118
48
|
parseWorkflowRunId,
|
|
119
49
|
readRunSummary,
|
|
120
50
|
resolveAdvisoryGateVerdict,
|
|
121
51
|
} from '../../merge-poll.js';
|
|
52
|
+
import { readMergeQueueState } from '../../merge-queue.js';
|
|
122
53
|
import { NEXT_COMMANDS } from '../../story-deliver-terminal.js';
|
|
123
54
|
import {
|
|
124
55
|
postStructuredComment,
|
|
@@ -128,77 +59,38 @@ import {
|
|
|
128
59
|
import { disarmAutoMerge } from './auto-merge.js';
|
|
129
60
|
import { runPostLandTail as defaultRunPostLandTail } from './post-land.js';
|
|
130
61
|
|
|
131
|
-
/**
|
|
132
|
-
* Per-invocation merge-wait bound. 300s fits inside a single host tool
|
|
133
|
-
* invocation (~10 min ceiling) with room for the close gates that precede
|
|
134
|
-
* the wait. A headless caller with no such ceiling raises
|
|
135
|
-
* `delivery.mergeWatch.maxWaitSeconds` to keep single-block semantics.
|
|
136
|
-
*/
|
|
62
|
+
/** Per-invocation bound; fits the host's ~10-min tool ceiling after the gates. */
|
|
137
63
|
export const DEFAULT_MAX_WAIT_SECONDS = 300;
|
|
138
64
|
|
|
139
|
-
/**
|
|
140
|
-
* Async-mode per-invocation probe window (Story #4698). When
|
|
141
|
-
* `delivery.mergeWatch.mode` is `"async"`, `resolveMergeWaitConfig` caps the
|
|
142
|
-
* per-invocation wait to this many seconds so close returns the resumable
|
|
143
|
-
* `pending` terminal fast instead of burning the foreground host slot. Sized
|
|
144
|
-
* to catch an instant merge and — via the head-anchored required-check
|
|
145
|
-
* predicate — an instantly-red required check, while staying far inside the
|
|
146
|
-
* cumulative `maxBudgetSeconds` give-up bound.
|
|
147
|
-
*/
|
|
65
|
+
/** Async-mode per-invocation cap: long enough to catch an instant merge or red check. */
|
|
148
66
|
export const ASYNC_PROBE_WINDOW_SECONDS = 60;
|
|
149
67
|
|
|
150
|
-
/** Bounded `gh pr update-branch` attempts for a BEHIND PR. */
|
|
151
68
|
export const DEFAULT_UPDATE_ATTEMPTS = 3;
|
|
152
69
|
|
|
153
70
|
/**
|
|
154
|
-
* Minimum polls before the
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* restart it — but that alone means a PR older than `maxBudgetSeconds` (1h by
|
|
158
|
-
* default) is already over budget on its very first probe. Resuming a Story
|
|
159
|
-
* the next morning, or landing a long-open PR, would then flip
|
|
160
|
-
* `agent::blocked` and emit `merge.unlanded` against a perfectly healthy PR
|
|
161
|
-
* that was seconds from merging, without ever having waited.
|
|
162
|
-
*
|
|
163
|
-
* The floor gives every invocation at least one real poll cycle before the
|
|
164
|
-
* cumulative bound can fire. A genuinely stuck PR still blocks within one
|
|
165
|
-
* interval (~30s), so the give-up bound keeps its meaning; a PR about to go
|
|
166
|
-
* green gets the chance it earned.
|
|
71
|
+
* Minimum polls before the cumulative budget may block: the clock is anchored
|
|
72
|
+
* at `createdAt`, so a long-open PR is over budget on its first probe and
|
|
73
|
+
* would otherwise block without ever having waited.
|
|
167
74
|
*/
|
|
168
75
|
export const MIN_POLLS_BEFORE_BUDGET_BLOCK = 2;
|
|
169
76
|
|
|
77
|
+
/** Verdict for a PR closed without merging, decided by the wait itself. */
|
|
78
|
+
const CLOSED_UNMERGED_BLOCK_CLASS = 'api-race-other';
|
|
79
|
+
const CLOSED_UNMERGED_REASON =
|
|
80
|
+
'PR probe error: PR closed without merging (state=CLOSED)';
|
|
81
|
+
|
|
170
82
|
function defaultSleep(ms) {
|
|
171
83
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
172
84
|
}
|
|
173
85
|
|
|
174
|
-
/**
|
|
175
|
-
* The wait's default `gh` facade, bound to a spawn-level timeout (Story
|
|
176
|
-
* #4710): every subprocess the wait launches through it carries
|
|
177
|
-
* `MERGE_WAIT_GH_TIMEOUT_MS`, so a wedged `gh` child is killed rather than
|
|
178
|
-
* stranding an unattended async-mode wait forever. Callers that inject their
|
|
179
|
-
* own `gh` (tests, the resume CLI) are bounded by {@link withGhTimeout} at
|
|
180
|
-
* the call sites instead.
|
|
181
|
-
*/
|
|
86
|
+
/** Spawn-level timeout kills a wedged `gh` child; injected `gh`s are bounded by withGhTimeout. */
|
|
182
87
|
const defaultGh = createGh(undefined, { timeoutMs: MERGE_WAIT_GH_TIMEOUT_MS });
|
|
183
88
|
|
|
184
89
|
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* and the merge wait must never hang on any of them. Rejection maps to the
|
|
190
|
-
* caller's existing error handling: the probe degrades to its conservative
|
|
191
|
-
* pending shape, the update-branch attempt logs and continues.
|
|
192
|
-
*
|
|
193
|
-
* A late settlement of the losing promise is explicitly absorbed so a
|
|
194
|
-
* post-timeout rejection cannot surface as an unhandled rejection.
|
|
195
|
-
*
|
|
196
|
-
* The timeout timer is deliberately NOT `unref`'d: when the awaited call is a
|
|
197
|
-
* promise that never settles (a hung stub, or a real gh child whose I/O has
|
|
198
|
-
* gone quiet), the timer is the ONLY handle keeping the event loop alive, so
|
|
199
|
-
* unref'ing it would let the process/test exit before the timeout ever fires —
|
|
200
|
-
* exactly the hang this guard exists to prevent. It is short-lived and always
|
|
201
|
-
* cleared in `finally`, so keeping it referenced costs nothing.
|
|
90
|
+
* Wall-clock bound for any `gh` call, including injected ones that may never
|
|
91
|
+
* settle. The losing promise's late rejection is absorbed. The timer is NOT
|
|
92
|
+
* unref'd: for a never-settling call it is the only handle keeping the event
|
|
93
|
+
* loop alive until the timeout fires.
|
|
202
94
|
*/
|
|
203
95
|
function withGhTimeout(promise, timeoutMs, label) {
|
|
204
96
|
let timer;
|
|
@@ -215,19 +107,11 @@ function withGhTimeout(promise, timeoutMs, label) {
|
|
|
215
107
|
}
|
|
216
108
|
|
|
217
109
|
/**
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* identical `typeof x === 'string'` ternaries put that one function over the
|
|
221
|
-
* CRAP ratchet the moment a seventh field was needed.
|
|
222
|
-
*
|
|
223
|
-
* An empty string counts as absent — `gh` returns `""` for a field it cannot
|
|
224
|
-
* read, and every caller treats that exactly as "not there".
|
|
110
|
+
* A string field off a `gh pr view` payload; `""` (gh's unreadable field)
|
|
111
|
+
* counts as absent.
|
|
225
112
|
*
|
|
226
113
|
* @param {unknown} value
|
|
227
|
-
* @param {null|undefined} [absent]
|
|
228
|
-
* `null` for the three fields the poll loop compares against null; the
|
|
229
|
-
* `undefined` default for the ones whose absence must not shadow a
|
|
230
|
-
* downstream default.
|
|
114
|
+
* @param {null|undefined} [absent] `undefined` where absence must not shadow a downstream default.
|
|
231
115
|
* @returns {string|null|undefined}
|
|
232
116
|
*/
|
|
233
117
|
function readString(value, absent = undefined) {
|
|
@@ -235,17 +119,32 @@ function readString(value, absent = undefined) {
|
|
|
235
119
|
}
|
|
236
120
|
|
|
237
121
|
/**
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
122
|
+
* `inMergeQueue` for a green open PR, the only state GitHub enqueues from;
|
|
123
|
+
* `undefined` otherwise, so the common poll pays no extra call. An enqueued
|
|
124
|
+
* PR has no auto-merge request and can read BLOCKED — neither is a disarm.
|
|
125
|
+
*/
|
|
126
|
+
async function readInMergeQueue({
|
|
127
|
+
view,
|
|
128
|
+
checksStatus,
|
|
129
|
+
gh,
|
|
130
|
+
ghTimeoutMs,
|
|
131
|
+
readMergeQueueStateFn,
|
|
132
|
+
}) {
|
|
133
|
+
const prNodeId = readString(view?.id);
|
|
134
|
+
if (view?.state !== 'OPEN' || checksStatus !== 'success' || !prNodeId) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
const queue = await readMergeQueueStateFn({
|
|
138
|
+
prNodeId,
|
|
139
|
+
gh,
|
|
140
|
+
timeoutMs: ghTimeoutMs,
|
|
141
|
+
});
|
|
142
|
+
return typeof queue?.inQueue === 'boolean' ? queue.inQueue : undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* One probe per poll iteration. A failed or timed-out read degrades to
|
|
147
|
+
* `{ checksStatus: 'pending', error }` so a flaky read is never a verdict.
|
|
249
148
|
*
|
|
250
149
|
* @returns {Promise<object>}
|
|
251
150
|
*/
|
|
@@ -253,38 +152,41 @@ export async function readPrWaitProbe({
|
|
|
253
152
|
prNumber,
|
|
254
153
|
gh = defaultGh,
|
|
255
154
|
ghTimeoutMs = MERGE_WAIT_GH_TIMEOUT_MS,
|
|
155
|
+
readMergeQueueStateFn = readMergeQueueState,
|
|
256
156
|
}) {
|
|
257
157
|
try {
|
|
258
158
|
const view = await withGhTimeout(
|
|
259
159
|
gh.pr.view(prNumber, [
|
|
160
|
+
'id',
|
|
260
161
|
'state',
|
|
261
162
|
'mergedAt',
|
|
262
163
|
'createdAt',
|
|
263
164
|
'mergeStateStatus',
|
|
264
165
|
'reviewDecision',
|
|
265
166
|
'statusCheckRollup',
|
|
266
|
-
// Story #5266 — the head the red advisory runs belong to, so their
|
|
267
|
-
// check-run output can be read back and classified.
|
|
268
167
|
'headRefOid',
|
|
269
168
|
]),
|
|
270
169
|
ghTimeoutMs,
|
|
271
170
|
`gh pr view ${prNumber}`,
|
|
272
171
|
);
|
|
172
|
+
const checksStatus = deriveChecksStatus(view?.statusCheckRollup);
|
|
273
173
|
return {
|
|
274
174
|
state: readString(view?.state, null),
|
|
275
175
|
mergedAt: readString(view?.mergedAt, null),
|
|
276
176
|
createdAt: readString(view?.createdAt, null),
|
|
277
177
|
mergeStateStatus: readString(view?.mergeStateStatus),
|
|
278
178
|
reviewDecision: readString(view?.reviewDecision),
|
|
279
|
-
checksStatus
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
179
|
+
checksStatus,
|
|
180
|
+
inMergeQueue: await readInMergeQueue({
|
|
181
|
+
view,
|
|
182
|
+
checksStatus,
|
|
183
|
+
gh,
|
|
184
|
+
ghTimeoutMs,
|
|
185
|
+
readMergeQueueStateFn,
|
|
186
|
+
}),
|
|
187
|
+
// Head-anchored, so superseded/pending runs don't read as red; `null`
|
|
188
|
+
// when the rollup is empty (the consecutive-probe fallback owns that).
|
|
284
189
|
requiredRunEvidence: deriveRequiredRunEvidence(view?.statusCheckRollup),
|
|
285
|
-
// The named red head runs (Story #5096), so an `advisory-gate-red`
|
|
286
|
-
// verdict can name the offending job and match the allowlist. Same
|
|
287
|
-
// red-ness test as `requiredRunEvidence`, so the two cannot disagree.
|
|
288
190
|
redHeadRuns: deriveRedHeadRuns(view?.statusCheckRollup),
|
|
289
191
|
headSha: readString(view?.headRefOid),
|
|
290
192
|
};
|
|
@@ -301,31 +203,11 @@ export async function readPrWaitProbe({
|
|
|
301
203
|
}
|
|
302
204
|
|
|
303
205
|
/**
|
|
304
|
-
* Resolve
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
* raises the per-invocation bound to keep single-block semantics without
|
|
310
|
-
* editing the consumer's config.
|
|
311
|
-
*
|
|
312
|
-
* `mode` (Story #4698) selects the close-time merge posture. `async` caps the
|
|
313
|
-
* per-invocation wait to `ASYNC_PROBE_WINDOW_SECONDS` so close returns the
|
|
314
|
-
* resumable `pending` terminal fast; `sync` (the default) is unchanged. An
|
|
315
|
-
* explicit `maxWaitSecondsOverride` still wins over the async cap — a headless
|
|
316
|
-
* caller with no host ceiling opts back into single-block waiting.
|
|
317
|
-
*
|
|
318
|
-
* `modeOverride` is the per-invocation `--merge-watch-mode` flag (Story #4949)
|
|
319
|
-
* and wins over `delivery.mergeWatch.mode` on exactly the precedence
|
|
320
|
-
* `maxWaitSecondsOverride` already uses. It exists because run topology is
|
|
321
|
-
* knowable only to the orchestrator: close sees one Story and cannot tell a
|
|
322
|
-
* solo delivery (where a foreground wait is the cheapest ending) from the Nth
|
|
323
|
-
* close of a wave (where each foreground wait is serialized dead time). The
|
|
324
|
-
* config default therefore stays `sync`, and the caller that knows better says
|
|
325
|
-
* so per invocation. The two flags remain composable — `--merge-watch-mode
|
|
326
|
-
* async --max-wait-seconds 900` selects the async posture and then overrides
|
|
327
|
-
* its probe cap, because the cap check below keys on the override's presence,
|
|
328
|
-
* not on where the mode came from.
|
|
206
|
+
* Resolve posture and budgets from `delivery.mergeWatch.*`. Both overrides
|
|
207
|
+
* (`--max-wait-seconds`, `--merge-watch-mode`) win over config; the mode flag
|
|
208
|
+
* exists because only the orchestrator knows whether a foreground wait is
|
|
209
|
+
* cheap (solo) or serialized dead time (a wave). An explicit max-wait
|
|
210
|
+
* override also beats the async cap.
|
|
329
211
|
*
|
|
330
212
|
* @param {object} [config]
|
|
331
213
|
* @param {number} [maxWaitSecondsOverride]
|
|
@@ -338,37 +220,21 @@ export function resolveMergeWaitConfig(
|
|
|
338
220
|
modeOverride,
|
|
339
221
|
) {
|
|
340
222
|
const mergeWatch = config?.delivery?.mergeWatch ?? {};
|
|
341
|
-
const int = (value, fallback
|
|
342
|
-
Number.isInteger(value) && value >=
|
|
223
|
+
const int = (value, fallback) =>
|
|
224
|
+
Number.isInteger(value) && value >= 1 ? value : fallback;
|
|
343
225
|
const requestedMode = modeOverride ?? mergeWatch.mode;
|
|
344
226
|
const mode = requestedMode === 'async' ? 'async' : 'sync';
|
|
345
227
|
const configuredMaxWait = int(
|
|
346
228
|
maxWaitSecondsOverride,
|
|
347
229
|
int(mergeWatch.maxWaitSeconds, DEFAULT_MAX_WAIT_SECONDS),
|
|
348
230
|
);
|
|
349
|
-
// Async mode caps the per-invocation wait to a short probe window so close
|
|
350
|
-
// returns `pending` fast instead of burning the foreground host slot on a
|
|
351
|
-
// merge that lands after the wait would have expired anyway. The window is
|
|
352
|
-
// long enough for the loop's existing checks to catch an instant merge and —
|
|
353
|
-
// via the imported `decideMergeWaitFailFast` decision — an instantly
|
|
354
|
-
// red required check. An explicit `--max-wait-seconds` override still wins so
|
|
355
|
-
// a headless caller with no host ceiling opts back into single-block waiting.
|
|
356
231
|
const maxWaitSeconds =
|
|
357
232
|
mode === 'async' && maxWaitSecondsOverride == null
|
|
358
233
|
? Math.min(configuredMaxWait, ASYNC_PROBE_WINDOW_SECONDS)
|
|
359
234
|
: configuredMaxWait;
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
|
|
363
|
-
// MIN_POLLS_BEFORE_BUDGET_BLOCK, and the cumulative budget would become
|
|
364
|
-
// unreachable across ANY number of resumes — a Story stuck in permanent
|
|
365
|
-
// `pending` that never escalates. Clamping the interval to the bound keeps
|
|
366
|
-
// at least one real poll cycle possible, which is what both the floor and
|
|
367
|
-
// the give-up bound depend on.
|
|
368
|
-
const intervalSeconds = Math.min(
|
|
369
|
-
int(mergeWatch.intervalSeconds, DEFAULT_INTERVAL_SECONDS),
|
|
370
|
-
maxWaitSeconds,
|
|
371
|
-
);
|
|
235
|
+
// An interval longer than the bound would expire every invocation on poll
|
|
236
|
+
// 1, so MIN_POLLS_BEFORE_BUDGET_BLOCK and the budget could never be reached.
|
|
237
|
+
const intervalSeconds = Math.min(DEFAULT_INTERVAL_SECONDS, maxWaitSeconds);
|
|
372
238
|
return {
|
|
373
239
|
mode,
|
|
374
240
|
intervalSeconds,
|
|
@@ -377,16 +243,13 @@ export function resolveMergeWaitConfig(
|
|
|
377
243
|
mergeWatch.maxBudgetSeconds,
|
|
378
244
|
DEFAULT_MAX_BUDGET_SECONDS,
|
|
379
245
|
),
|
|
380
|
-
updateAttempts:
|
|
246
|
+
updateAttempts: DEFAULT_UPDATE_ATTEMPTS,
|
|
381
247
|
};
|
|
382
248
|
}
|
|
383
249
|
|
|
384
250
|
/**
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
* probe carried no timestamp — a conservative degrade: the worst case is a
|
|
388
|
-
* resume getting a fresh cumulative budget, which is exactly the pre-#4543
|
|
389
|
-
* behaviour, never a premature block.
|
|
251
|
+
* Budget anchor; without `createdAt` it falls back to this invocation's start
|
|
252
|
+
* (worst case a fresh budget, never a premature block).
|
|
390
253
|
*
|
|
391
254
|
* @returns {number} epoch ms
|
|
392
255
|
*/
|
|
@@ -397,22 +260,9 @@ export function resolveBudgetAnchorMs({ createdAt, fallbackMs }) {
|
|
|
397
260
|
}
|
|
398
261
|
|
|
399
262
|
/**
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
* opens by telling the operator to resolve "branch protection, required
|
|
404
|
-
* checks, or a manual merge" — a diagnosis of a fault that provably does not
|
|
405
|
-
* exist here. An advisory gate blocks precisely BECAUSE GitHub reports the PR
|
|
406
|
-
* mergeable (`mergeStateStatus=UNSTABLE`) over a NON-required red check, so
|
|
407
|
-
* close refused to let native auto-merge land it. Saying so is the paragraph
|
|
408
|
-
* that stops the operator hunting a protection rule that is working fine.
|
|
409
|
-
*
|
|
410
|
-
* The two classes then part on what the evidence authorises. `inconclusive`
|
|
411
|
-
* is a job that failed WITHOUT FINISHING and reported no violation — nothing
|
|
412
|
-
* says the change is bad — so the proportionate act is a re-run, named here
|
|
413
|
-
* as `--rerun-advisory`; the permanent global exemption is deliberately
|
|
414
|
-
* mentioned last. `advisory-gate-red` reported a real violation, so the
|
|
415
|
-
* change is implicated and landing over it is a deliberate override.
|
|
263
|
+
* Advisory-gate remedy: the PR is mergeable (UNSTABLE over a non-required
|
|
264
|
+
* red), so branch protection is not the fault. `inconclusive` never finished
|
|
265
|
+
* and implicates nothing (re-run first); `red` reported a real violation.
|
|
416
266
|
*
|
|
417
267
|
* @param {{ storyId: number, blockClass: string }} args
|
|
418
268
|
* @returns {string}
|
|
@@ -444,10 +294,6 @@ function advisoryGateRemedy({ storyId, blockClass }) {
|
|
|
444
294
|
}
|
|
445
295
|
|
|
446
296
|
/**
|
|
447
|
-
* The class-specific remediation paragraph of the unlanded friction comment.
|
|
448
|
-
* Split out of {@link formatUnlandedFriction} so each class's wording is one
|
|
449
|
-
* named branch rather than a nested ternary.
|
|
450
|
-
*
|
|
451
297
|
* @param {{ storyId: number, prNumber: number|null, blockClass: string }} args
|
|
452
298
|
* @returns {string}
|
|
453
299
|
*/
|
|
@@ -473,10 +319,6 @@ function unlandedRemedy({ storyId, prNumber, blockClass }) {
|
|
|
473
319
|
);
|
|
474
320
|
}
|
|
475
321
|
|
|
476
|
-
/**
|
|
477
|
-
* Format the `friction` comment body posted alongside the `agent::blocked`
|
|
478
|
-
* transition when a landing attempt gives up without a confirmed merge.
|
|
479
|
-
*/
|
|
480
322
|
function formatUnlandedFriction({
|
|
481
323
|
storyId,
|
|
482
324
|
prNumber,
|
|
@@ -500,13 +342,7 @@ function formatUnlandedFriction({
|
|
|
500
342
|
);
|
|
501
343
|
}
|
|
502
344
|
|
|
503
|
-
/**
|
|
504
|
-
* Format the `friction` comment for a merge that **landed** while the
|
|
505
|
-
* `agent::done` label write failed. Deliberately not the unlanded wording:
|
|
506
|
-
* the merge is not in question, so pointing the operator at branch
|
|
507
|
-
* protection and required checks would send them to diagnose a fault that
|
|
508
|
-
* does not exist. Name the actual remedy instead.
|
|
509
|
-
*/
|
|
345
|
+
/** Friction for a merge that landed but whose `agent::done` write failed. */
|
|
510
346
|
function formatFlipFailedFriction({
|
|
511
347
|
storyId,
|
|
512
348
|
prNumber,
|
|
@@ -535,10 +371,7 @@ function formatFlipFailedFriction({
|
|
|
535
371
|
}
|
|
536
372
|
|
|
537
373
|
/**
|
|
538
|
-
*
|
|
539
|
-
* surfaces one. The id is the terminal envelope's `frictionCommentId`
|
|
540
|
-
* pointer, so a caller can link the operator straight at the remediation
|
|
541
|
-
* instead of telling them to go find it.
|
|
374
|
+
* Best-effort; the id becomes the envelope's `frictionCommentId`.
|
|
542
375
|
*
|
|
543
376
|
* @returns {Promise<string|null>}
|
|
544
377
|
*/
|
|
@@ -561,12 +394,7 @@ async function postFriction({ provider, storyId, body, progress }) {
|
|
|
561
394
|
}
|
|
562
395
|
}
|
|
563
396
|
|
|
564
|
-
/**
|
|
565
|
-
* Terminal for a confirmed merge whose `agent::done` flip failed. Emits
|
|
566
|
-
* `merge.flip-failed` (NOT `merge.unlanded` — the merge landed), posts the
|
|
567
|
-
* flip-failed friction, and blocks explicitly. Best-effort throughout: the
|
|
568
|
-
* caller owns the non-zero exit.
|
|
569
|
-
*/
|
|
397
|
+
/** Emits `merge.flip-failed` (not `merge.unlanded`); best-effort throughout. */
|
|
570
398
|
async function blockOnFlipFailed({
|
|
571
399
|
storyId,
|
|
572
400
|
prNumber,
|
|
@@ -628,25 +456,17 @@ async function blockOnFlipFailed({
|
|
|
628
456
|
reason,
|
|
629
457
|
frictionCommentId,
|
|
630
458
|
elapsedSeconds,
|
|
631
|
-
// The merge is
|
|
632
|
-
// envelope must say MERGED even when the probe that got us here was read
|
|
633
|
-
// before the merge landed. Reporting the stale OPEN (or null) would tell
|
|
634
|
-
// the operator to chase a merge that already happened.
|
|
459
|
+
// The merge is confirmed even if the probe was read before it landed.
|
|
635
460
|
prProbe: { ...(prProbe ?? {}), state: 'MERGED' },
|
|
636
461
|
};
|
|
637
462
|
}
|
|
638
463
|
|
|
639
464
|
/**
|
|
640
|
-
*
|
|
641
|
-
*
|
|
642
|
-
* `--rerun-advisory <n>` wins over `delivery.ci.rerunAdvisory` on exactly the
|
|
643
|
-
* precedence `--max-wait-seconds` already uses. Both default to **0**: a
|
|
644
|
-
* rerun spends CI minutes and mutates GitHub state, so close does neither
|
|
645
|
-
* unasked. A non-integer or negative override is not an instruction to guess
|
|
646
|
-
* — it degrades to the config value.
|
|
465
|
+
* `--rerun-advisory` beats `delivery.ci.rerunAdvisory`; both default 0
|
|
466
|
+
* because a rerun spends CI minutes. An invalid override falls back to config.
|
|
647
467
|
*
|
|
648
|
-
* @param {object|null} config
|
|
649
|
-
* @param {number} [override]
|
|
468
|
+
* @param {object|null} config
|
|
469
|
+
* @param {number} [override]
|
|
650
470
|
* @returns {number} allowance ≥ 0
|
|
651
471
|
*/
|
|
652
472
|
export function resolveAdvisoryRerunAllowance(config, override) {
|
|
@@ -655,10 +475,8 @@ export function resolveAdvisoryRerunAllowance(config, override) {
|
|
|
655
475
|
}
|
|
656
476
|
|
|
657
477
|
/**
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
* the conclusion), so this is what lets the wait tell a re-run's verdict apart
|
|
661
|
-
* from the stale pre-rerun snapshot it will keep seeing for a poll or two.
|
|
478
|
+
* One observation of a red run; a rerun changes `completedAt`, which tells a
|
|
479
|
+
* fresh verdict from the stale pre-rerun snapshot.
|
|
662
480
|
*
|
|
663
481
|
* @param {{name?: string|null, runId?: number, completedAt?: string}} run
|
|
664
482
|
* @returns {string}
|
|
@@ -668,17 +486,9 @@ function advisoryRunSignature(run) {
|
|
|
668
486
|
}
|
|
669
487
|
|
|
670
488
|
/**
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
* output text for a CheckRun, so on the rollup alone every red advisory run
|
|
675
|
-
* looks identical — which is the defect. The check-runs API for the head SHA
|
|
676
|
-
* carries `output.title` / `output.summary`, and ONE call for the whole head
|
|
677
|
-
* is enough to classify every red run on it.
|
|
678
|
-
*
|
|
679
|
-
* Called only on the block path (never per poll), and **fails open**: any
|
|
680
|
-
* error, timeout, or missing head SHA returns the runs unchanged, which
|
|
681
|
-
* classifies them as `advisory-gate-red` — the pre-#5266 verdict.
|
|
489
|
+
* Attach each red run's check-run output (the rollup carries none), in one
|
|
490
|
+
* call per head. Block path only; fails open to the unenriched runs, which
|
|
491
|
+
* classify as `advisory-gate-red`.
|
|
682
492
|
*
|
|
683
493
|
* @returns {Promise<Array<object>>} the runs, enriched where output was found
|
|
684
494
|
*/
|
|
@@ -720,17 +530,10 @@ async function enrichRedRunsWithOutput({
|
|
|
720
530
|
}
|
|
721
531
|
|
|
722
532
|
/**
|
|
723
|
-
*
|
|
724
|
-
* caller's remaining allowance.
|
|
533
|
+
* `rerun-failed-jobs` once per distinct workflow run (not per job).
|
|
725
534
|
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
* is both cheaper in API calls and what an operator means by "re-run it".
|
|
729
|
-
*
|
|
730
|
-
* @returns {Promise<boolean>} whether every rerun request succeeded. `false`
|
|
731
|
-
* (including "no run id to re-run") leaves the caller on the block path,
|
|
732
|
-
* because an allowance that cannot be spent must not silently suppress the
|
|
733
|
-
* gate.
|
|
535
|
+
* @returns {Promise<boolean>} `false` (incl. no run id) keeps the caller on
|
|
536
|
+
* the block path — an unspendable allowance must not suppress the gate.
|
|
734
537
|
*/
|
|
735
538
|
async function rerunAdvisoryRuns({ blockingRuns, gh, ghTimeoutMs, progress }) {
|
|
736
539
|
const runIds = [
|
|
@@ -775,16 +578,20 @@ async function rerunAdvisoryRuns({ blockingRuns, gh, ghTimeoutMs, progress }) {
|
|
|
775
578
|
}
|
|
776
579
|
|
|
777
580
|
/**
|
|
778
|
-
*
|
|
779
|
-
*
|
|
780
|
-
* pre-rerun snapshot the next poll reads does not re-block on the same job.
|
|
581
|
+
* Advisory runs are non-required by construction; a red required check
|
|
582
|
+
* fails fast as `checks-failed` and never reaches the rerun path.
|
|
781
583
|
*
|
|
782
|
-
*
|
|
783
|
-
*
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
584
|
+
* @param {{ remaining: number }} rerunState
|
|
585
|
+
* @returns {number}
|
|
586
|
+
*/
|
|
587
|
+
function advisoryRerunsLeft(rerunState) {
|
|
588
|
+
return isRerunPermitted({ required: false }) ? rerunState.remaining : 0;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Spend one rerun unit, recording each re-run's signature so the stale
|
|
593
|
+
* snapshot doesn't re-block. Does NOT disarm: a green re-run should land on
|
|
594
|
+
* its own; the armed window is what opting in to `--rerun-advisory` accepts.
|
|
788
595
|
*
|
|
789
596
|
* @returns {Promise<boolean>} `true` when the caller should keep polling.
|
|
790
597
|
*/
|
|
@@ -795,7 +602,7 @@ async function maybeRerunAdvisory({
|
|
|
795
602
|
ghTimeoutMs,
|
|
796
603
|
progress,
|
|
797
604
|
}) {
|
|
798
|
-
if (rerunState
|
|
605
|
+
if (advisoryRerunsLeft(rerunState) <= 0) return false;
|
|
799
606
|
const rerun = await rerunAdvisoryRuns({
|
|
800
607
|
blockingRuns,
|
|
801
608
|
gh,
|
|
@@ -811,22 +618,10 @@ async function maybeRerunAdvisory({
|
|
|
811
618
|
}
|
|
812
619
|
|
|
813
620
|
/**
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
* branch. `runMergePoll` is already above `check-cyclomatic`'s ceiling; the
|
|
819
|
-
* three decision points this would otherwise cost inline are a real gate
|
|
820
|
-
* regression, and they belong with the policy either way.
|
|
821
|
-
*
|
|
822
|
-
* Disarms BEFORE returning the terminal: an armed PR can merge out from under
|
|
823
|
-
* the block the caller is about to record.
|
|
824
|
-
*
|
|
825
|
-
* Story #5266 threads three more steps through the same single assignment:
|
|
826
|
-
* runs this invocation already re-ran (and has no fresh verdict for) are
|
|
827
|
-
* skipped rather than re-blocked; the survivors are enriched with their own
|
|
828
|
-
* check-run output so the class can be `advisory-gate-inconclusive`; and a
|
|
829
|
-
* remaining rerun allowance is spent before any block is recorded.
|
|
621
|
+
* Advisory-gate terminal for one poll; passes a decided `unlanded` through so
|
|
622
|
+
* the caller stays branch-free (`runMergePoll` is at the cyclomatic ceiling).
|
|
623
|
+
* Skips runs already re-ran, enriches the rest, spends any rerun allowance,
|
|
624
|
+
* and disarms BEFORE returning a block so the PR cannot merge out from under it.
|
|
830
625
|
*/
|
|
831
626
|
async function resolveAdvisoryUnlanded({
|
|
832
627
|
unlanded,
|
|
@@ -848,9 +643,6 @@ async function resolveAdvisoryUnlanded({
|
|
|
848
643
|
advisoryAllowlist,
|
|
849
644
|
});
|
|
850
645
|
if (!advisory) return null;
|
|
851
|
-
// Every blocking run is one this invocation already re-ran and has not seen
|
|
852
|
-
// a fresh verdict for yet (Story #5266) — keep polling rather than blocking
|
|
853
|
-
// on the snapshot the rerun was meant to replace.
|
|
854
646
|
const pending = advisory.blockingRuns.filter(
|
|
855
647
|
(run) => !rerunState.issued.has(advisoryRunSignature(run)),
|
|
856
648
|
);
|
|
@@ -888,11 +680,18 @@ async function resolveAdvisoryUnlanded({
|
|
|
888
680
|
}
|
|
889
681
|
|
|
890
682
|
/**
|
|
891
|
-
*
|
|
892
|
-
*
|
|
893
|
-
* is best-effort logged rather than thrown — the caller owns surfacing the
|
|
894
|
-
* non-zero exit once this returns.
|
|
683
|
+
* An enqueued PR reads BLOCKED with every check green, which the classifier
|
|
684
|
+
* would call human-required — but the queue lands it unattended.
|
|
895
685
|
*/
|
|
686
|
+
function queuedExhaustionVerdict(probe, cumulativeMs) {
|
|
687
|
+
if (probe?.inMergeQueue !== true) return {};
|
|
688
|
+
return {
|
|
689
|
+
blockClassOverride: 'checks-pending-timeout',
|
|
690
|
+
reasonOverride: `PR is still in the merge queue after ${Math.round(cumulativeMs / 1000)} seconds — GitHub merges it when the queue's checks pass; a queued base may need a larger delivery.mergeWatch.maxBudgetSeconds`,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** Classify, emit `merge.unlanded`, post friction, block — all best-effort. */
|
|
896
695
|
async function blockOnUnlanded({
|
|
897
696
|
storyId,
|
|
898
697
|
prNumber,
|
|
@@ -907,11 +706,9 @@ async function blockOnUnlanded({
|
|
|
907
706
|
blockClassOverride,
|
|
908
707
|
reasonOverride,
|
|
909
708
|
}) {
|
|
910
|
-
//
|
|
911
|
-
//
|
|
912
|
-
//
|
|
913
|
-
// condition the class names, so every classifier heuristic reads the PR as
|
|
914
|
-
// healthy.
|
|
709
|
+
// A verdict decided at detection is emitted as-is, never re-derived (the
|
|
710
|
+
// classifier reads an advisory-gate PR as healthy); the classifier runs
|
|
711
|
+
// only for arm-failure and budget exhaustion.
|
|
915
712
|
const { blockClass, reason } = blockClassOverride
|
|
916
713
|
? {
|
|
917
714
|
blockClass: blockClassOverride,
|
|
@@ -923,11 +720,7 @@ async function blockOnUnlanded({
|
|
|
923
720
|
budget,
|
|
924
721
|
});
|
|
925
722
|
const elapsedSeconds = budget?.elapsedSeconds ?? 0;
|
|
926
|
-
//
|
|
927
|
-
// `per-run` (head-anchored required-run evidence) or `consecutive-probe`
|
|
928
|
-
// (the evidence-unavailable fallback). Named on the emitted record so the
|
|
929
|
-
// `merge.unlanded` telemetry attributes the fail-fast to the path that
|
|
930
|
-
// fired it. Absent for every other block class.
|
|
723
|
+
// `per-run` | `consecutive-probe` for `checks-failed`; absent otherwise.
|
|
931
724
|
const evidencePath = prProbe?.evidencePath;
|
|
932
725
|
|
|
933
726
|
if (Number.isInteger(prNumber) && prNumber > 0) {
|
|
@@ -988,27 +781,14 @@ async function blockOnUnlanded({
|
|
|
988
781
|
reason,
|
|
989
782
|
frictionCommentId,
|
|
990
783
|
elapsedSeconds,
|
|
991
|
-
// The
|
|
992
|
-
// `pr.state` / `pr.checksStatus` from here; dropping it made every
|
|
993
|
-
// blocked envelope claim `null` for facts we had just observed — a
|
|
994
|
-
// `checks-failed` envelope reporting `checksStatus: null` contradicts
|
|
995
|
-
// itself. Schema wants "live PR facts as observed at terminal time".
|
|
784
|
+
// The envelope's `pr.state` / `pr.checksStatus` come from here.
|
|
996
785
|
prProbe,
|
|
997
786
|
};
|
|
998
787
|
}
|
|
999
788
|
|
|
1000
789
|
/**
|
|
1001
|
-
*
|
|
1002
|
-
*
|
|
1003
|
-
* real state and lets the normal classification decide, which is why a
|
|
1004
|
-
* failed attempt still counts against the wait's tick.
|
|
1005
|
-
*
|
|
1006
|
-
* The BEHIND / budget / did-it-land decision itself lives in the shared
|
|
1007
|
-
* {@link applyBehindUpdate} (Story #5006) — the CI-watch loop in
|
|
1008
|
-
* `lib/orchestration/pr-watch.js` runs the same one. This wrapper supplies
|
|
1009
|
-
* the merge wait's probe source, its `gh` facade (bounded by
|
|
1010
|
-
* {@link withGhTimeout}, so a wedged child cannot strand an unattended
|
|
1011
|
-
* async-mode wait), and its operator wording.
|
|
790
|
+
* Update a BEHIND PR, bounded by `updateAttempts`; a failed update is not a
|
|
791
|
+
* terminal (the next poll re-reads) but still counts as an attempt.
|
|
1012
792
|
*
|
|
1013
793
|
* @returns {Promise<boolean>} whether an update was actually attempted.
|
|
1014
794
|
*/
|
|
@@ -1050,11 +830,7 @@ async function maybeUpdateBehindPr({
|
|
|
1050
830
|
return recovery.attempted;
|
|
1051
831
|
}
|
|
1052
832
|
|
|
1053
|
-
/**
|
|
1054
|
-
* Handle an observed merge: run the shared `confirmStoryMerged` flip, then
|
|
1055
|
-
* the shared post-land tail. Called at most once per wait — the loop probes
|
|
1056
|
-
* the PR, not the ticket.
|
|
1057
|
-
*/
|
|
833
|
+
/** Called at most once per wait: the flip, then the post-land tail. */
|
|
1058
834
|
async function onMergeObserved({
|
|
1059
835
|
storyId,
|
|
1060
836
|
storyBranch,
|
|
@@ -1085,15 +861,13 @@ async function onMergeObserved({
|
|
|
1085
861
|
injectedGh,
|
|
1086
862
|
injectedNotify,
|
|
1087
863
|
readPrMergeStateFn,
|
|
864
|
+
// Saves confirmation a second `gh pr view`.
|
|
865
|
+
prState: prProbe,
|
|
1088
866
|
});
|
|
1089
867
|
|
|
1090
868
|
if (confirmation.merged && confirmation.action === 'flip-failed') {
|
|
1091
|
-
//
|
|
1092
|
-
//
|
|
1093
|
-
// strand the Story at agent::closing with no notification. Reporting it
|
|
1094
|
-
// as UNLANDED was not (Story #4539): the merge landed, so the
|
|
1095
|
-
// merge.unlanded event would be false and its friction would send the
|
|
1096
|
-
// operator to branch protection instead of the one-line remedy.
|
|
869
|
+
// Block explicitly (confirmed:true would strand it at agent::closing),
|
|
870
|
+
// but not as unlanded — the merge landed.
|
|
1097
871
|
progress?.(
|
|
1098
872
|
'CONFIRM',
|
|
1099
873
|
`⚠️ Story #${storyId} merge confirmed but the agent::done flip failed — blocking explicitly.`,
|
|
@@ -1128,11 +902,7 @@ async function onMergeObserved({
|
|
|
1128
902
|
terminal: 'landed',
|
|
1129
903
|
action: confirmation.action,
|
|
1130
904
|
tail,
|
|
1131
|
-
//
|
|
1132
|
-
// by admin override, or with non-required checks red, must not be reported
|
|
1133
|
-
// as a green run nobody actually saw — that is the same
|
|
1134
|
-
// report-an-outcome-you-never-checked shape the land tail's per-step
|
|
1135
|
-
// booleans exist to prevent.
|
|
905
|
+
// The observed rollup, not a stamped 'success' (admin merges exist).
|
|
1136
906
|
prProbe,
|
|
1137
907
|
};
|
|
1138
908
|
}
|
|
@@ -1152,29 +922,20 @@ async function onMergeObserved({
|
|
|
1152
922
|
* @param {string|null} args.autoMergeReason
|
|
1153
923
|
* @param {object} args.provider
|
|
1154
924
|
* @param {object} [args.config]
|
|
1155
|
-
* @param {'sync'|'async'} [args.mergeWatchMode]
|
|
1156
|
-
* `--merge-watch-mode` override (Story #4949); wins over
|
|
1157
|
-
* `delivery.mergeWatch.mode`.
|
|
925
|
+
* @param {'sync'|'async'} [args.mergeWatchMode]
|
|
1158
926
|
* @param {(tag: string, msg: string) => void} [args.progress]
|
|
1159
|
-
* @param {number} [args.rerunAdvisory]
|
|
1160
|
-
* per-invocation override of `delivery.ci.rerunAdvisory` (both default 0).
|
|
927
|
+
* @param {number} [args.rerunAdvisory]
|
|
1161
928
|
* @param {object} [args.injectedGh]
|
|
1162
929
|
* @param {Function} [args.injectedNotify]
|
|
1163
|
-
* @param {Function} [args.confirmStoryMergedFn]
|
|
1164
|
-
*
|
|
1165
|
-
*
|
|
1166
|
-
* @param {Function} [args.
|
|
1167
|
-
* @param {Function} [args.
|
|
1168
|
-
* @param {Function} [args.
|
|
1169
|
-
* @param {
|
|
1170
|
-
* @param {
|
|
1171
|
-
* @param {
|
|
1172
|
-
* suite does not actually wait.
|
|
1173
|
-
* @param {() => number} [args.nowMsFn] Test seam; returns epoch ms.
|
|
1174
|
-
* @param {number} [args.ghTimeoutMs] Wall-clock bound for each `gh` call the
|
|
1175
|
-
* wait makes (Story #4710). A framework constant
|
|
1176
|
-
* (`MERGE_WAIT_GH_TIMEOUT_MS`), overridable only as a test seam — not
|
|
1177
|
-
* config.
|
|
930
|
+
* @param {Function} [args.confirmStoryMergedFn] The one shared merged/`agent::done` implementation.
|
|
931
|
+
* @param {Function} [args.readPrWaitProbeFn]
|
|
932
|
+
* @param {Function} [args.readPrMergeStateFn]
|
|
933
|
+
* @param {Function} [args.classifyMergeBlockFn]
|
|
934
|
+
* @param {Function} [args.emitMergeUnlandedFn]
|
|
935
|
+
* @param {Function} [args.runPostLandTailFn]
|
|
936
|
+
* @param {(ms: number) => Promise<void>} [args.sleepFn]
|
|
937
|
+
* @param {() => number} [args.nowMsFn]
|
|
938
|
+
* @param {number} [args.ghTimeoutMs] Test seam only, not config.
|
|
1178
939
|
* @returns {Promise<object>}
|
|
1179
940
|
*/
|
|
1180
941
|
export async function runConfirmMergePhase({
|
|
@@ -1207,10 +968,7 @@ export async function runConfirmMergePhase({
|
|
|
1207
968
|
nowMsFn = Date.now,
|
|
1208
969
|
ghTimeoutMs = MERGE_WAIT_GH_TIMEOUT_MS,
|
|
1209
970
|
}) {
|
|
1210
|
-
//
|
|
1211
|
-
// deliberate disablement) — there is no "armed but unconfirmed" PR to
|
|
1212
|
-
// poll. An explicit terminal state is still required, so classify and
|
|
1213
|
-
// block immediately rather than resting silently.
|
|
971
|
+
// Never armed: nothing to poll, but a terminal is still required.
|
|
1214
972
|
if (!autoMergeEnabled) {
|
|
1215
973
|
progress?.(
|
|
1216
974
|
'CONFIRM',
|
|
@@ -1226,12 +984,8 @@ export async function runConfirmMergePhase({
|
|
|
1226
984
|
progress,
|
|
1227
985
|
classifyMergeBlockFn,
|
|
1228
986
|
emitMergeUnlandedFn,
|
|
1229
|
-
//
|
|
1230
|
-
//
|
|
1231
|
-
// as a generic `arm-failure`.
|
|
1232
|
-
// Story #5266 — carry the arm phase's CLASS too: a pre-arm refusal over
|
|
1233
|
-
// a scan that never finished is `advisory-gate-inconclusive`, and
|
|
1234
|
-
// hard-coding the red class here would relabel it at the terminal.
|
|
987
|
+
// Carry the arm phase's advisory verdict and class (which may be
|
|
988
|
+
// `inconclusive`) rather than a generic `arm-failure`.
|
|
1235
989
|
...(autoMergeReason === 'advisory-gate-red'
|
|
1236
990
|
? {
|
|
1237
991
|
blockClassOverride: advisoryGate?.blockClass ?? 'advisory-gate-red',
|
|
@@ -1252,11 +1006,8 @@ export async function runConfirmMergePhase({
|
|
|
1252
1006
|
maxWaitSecondsOverride,
|
|
1253
1007
|
mergeWatchModeOverride,
|
|
1254
1008
|
);
|
|
1255
|
-
// Story #5096 — the advisory-gate knobs, read once for the whole wait.
|
|
1256
1009
|
const { blockOnAdvisoryFailure, advisoryAllowlist } = getCiDelivery(config);
|
|
1257
|
-
//
|
|
1258
|
-
// wait rather than per poll, so `n` bounds the CI minutes this invocation
|
|
1259
|
-
// can cost no matter how many times the gate is observed red.
|
|
1010
|
+
// Spent across the whole wait, not per poll, so `n` bounds CI minutes.
|
|
1260
1011
|
const rerunAllowance = resolveAdvisoryRerunAllowance(
|
|
1261
1012
|
config,
|
|
1262
1013
|
rerunAdvisoryOverride,
|
|
@@ -1271,11 +1022,8 @@ export async function runConfirmMergePhase({
|
|
|
1271
1022
|
let anchorMs = startedAtMs;
|
|
1272
1023
|
let updatesUsed = 0;
|
|
1273
1024
|
let polls = 0;
|
|
1274
|
-
//
|
|
1275
|
-
//
|
|
1276
|
-
// snapshot never fail-fasts — two consecutive failing probes at least one
|
|
1277
|
-
// poll interval apart are required. Reset on any non-failing (or genuinely
|
|
1278
|
-
// evidenced) probe.
|
|
1025
|
+
// Without per-run evidence, fail fast only after two consecutive failing
|
|
1026
|
+
// probes an interval apart; reset on any other probe.
|
|
1279
1027
|
let consecutiveRequiredFailSnapshots = 0;
|
|
1280
1028
|
|
|
1281
1029
|
progress?.(
|
|
@@ -1285,19 +1033,7 @@ export async function runConfirmMergePhase({
|
|
|
1285
1033
|
`cumulative budget=${maxBudgetSeconds}s)...`,
|
|
1286
1034
|
);
|
|
1287
1035
|
|
|
1288
|
-
/**
|
|
1289
|
-
* One poll iteration. Returns `{ done: false }` to keep polling, or
|
|
1290
|
-
* `{ done: true, outcome }` with the phase's terminal. Story #4873 lifted
|
|
1291
|
-
* this body out of a bespoke unbounded loop so the cadence is owned by the
|
|
1292
|
-
* shared {@link pollUntil} primitive — the loop below sleeps, aborts, and
|
|
1293
|
-
* counts ticks in exactly one place for every wait in the codebase. Every
|
|
1294
|
-
* budget, floor, and classification decision is unchanged; only who owns the
|
|
1295
|
-
* `await sleep(...)` moved.
|
|
1296
|
-
*
|
|
1297
|
-
* A throw from any of the terminal handlers is captured rather than allowed
|
|
1298
|
-
* to escape into `pollUntil` (which treats a throwing `fn` as a non-match
|
|
1299
|
-
* and would spin forever on it); the caller re-throws it after the loop.
|
|
1300
|
-
*/
|
|
1036
|
+
/** One iteration: `{ done: false }` or `{ done: true, outcome }`. */
|
|
1301
1037
|
async function runMergePoll() {
|
|
1302
1038
|
const probe = await readPrWaitProbeFn({
|
|
1303
1039
|
prNumber,
|
|
@@ -1306,8 +1042,6 @@ export async function runConfirmMergePhase({
|
|
|
1306
1042
|
});
|
|
1307
1043
|
polls += 1;
|
|
1308
1044
|
|
|
1309
|
-
// Anchor the cumulative budget at the PR's creation the first time we
|
|
1310
|
-
// learn it, so a resumed wait continues the clock instead of restarting.
|
|
1311
1045
|
anchorMs = resolveBudgetAnchorMs({
|
|
1312
1046
|
createdAt: probe.createdAt,
|
|
1313
1047
|
fallbackMs: startedAtMs,
|
|
@@ -1322,12 +1056,7 @@ export async function runConfirmMergePhase({
|
|
|
1322
1056
|
maxBudgetSeconds,
|
|
1323
1057
|
};
|
|
1324
1058
|
|
|
1325
|
-
// Heartbeat
|
|
1326
|
-
// progress to its own output file, and between the opening banner and the
|
|
1327
|
-
// terminal there used to be NOTHING for minutes at a time — so an
|
|
1328
|
-
// orchestrator watching that file could not tell a healthy in-flight wait
|
|
1329
|
-
// from a wedged process without going back to GitHub itself. One line per
|
|
1330
|
-
// poll makes the file's own growth the liveness signal.
|
|
1059
|
+
// Heartbeat: a backgrounded close's output-file growth is its liveness signal.
|
|
1331
1060
|
progress?.(
|
|
1332
1061
|
'CONFIRM',
|
|
1333
1062
|
`⏱ poll ${polls}: PR #${prNumber} state=${probe.state ?? 'unknown'} ` +
|
|
@@ -1338,7 +1067,7 @@ export async function runConfirmMergePhase({
|
|
|
1338
1067
|
(probe.error ? ` — probe error: ${probe.error}` : ''),
|
|
1339
1068
|
);
|
|
1340
1069
|
|
|
1341
|
-
if (probe
|
|
1070
|
+
if (isPrMerged(probe)) {
|
|
1342
1071
|
return doneWith(
|
|
1343
1072
|
await onMergeObserved({
|
|
1344
1073
|
storyId,
|
|
@@ -1362,38 +1091,21 @@ export async function runConfirmMergePhase({
|
|
|
1362
1091
|
);
|
|
1363
1092
|
}
|
|
1364
1093
|
|
|
1365
|
-
//
|
|
1366
|
-
// definitive condition fills `unlanded` and the single call site at the
|
|
1367
|
-
// bottom classifies, emits, and blocks — the fail-fast tree used to
|
|
1368
|
-
// duplicate that block twice inline.
|
|
1094
|
+
// Each definitive condition fills `unlanded`; one call site below blocks.
|
|
1369
1095
|
let unlanded = null;
|
|
1370
1096
|
|
|
1371
1097
|
if (probe.state === 'CLOSED') {
|
|
1372
|
-
// Closed without merging — a definitive terminal, not a "still
|
|
1373
|
-
// pending" condition the budget should keep waiting on. checksStatus
|
|
1374
|
-
// MUST be a non-pending, non-undefined value here: the classifier's
|
|
1375
|
-
// budget-exhausted branch treats an undefined checksStatus as "still
|
|
1376
|
-
// pending", which would misclassify this definitive case as
|
|
1377
|
-
// checks-pending-timeout instead of reaching the api-race-other
|
|
1378
|
-
// reason built from prProbe.error.
|
|
1379
1098
|
unlanded = {
|
|
1380
|
-
prProbe:
|
|
1381
|
-
checksStatus: 'closed',
|
|
1382
|
-
error: 'PR closed without merging (state=CLOSED)',
|
|
1383
|
-
},
|
|
1099
|
+
prProbe: probe,
|
|
1384
1100
|
budget: {
|
|
1385
1101
|
exhausted: true,
|
|
1386
1102
|
elapsedSeconds: Math.round(waitedMs / 1000),
|
|
1387
1103
|
},
|
|
1104
|
+
blockClassOverride: CLOSED_UNMERGED_BLOCK_CLASS,
|
|
1105
|
+
reasonOverride: CLOSED_UNMERGED_REASON,
|
|
1388
1106
|
};
|
|
1389
1107
|
} else {
|
|
1390
|
-
//
|
|
1391
|
-
// #4695), decided by the extracted `decideMergeWaitFailFast` (Story
|
|
1392
|
-
// #4710): per-run evidence decides on a single probe; without evidence
|
|
1393
|
-
// two consecutive failing probes are required. No remaining budget
|
|
1394
|
-
// turns a failed check green, and waiting it out is what made the
|
|
1395
|
-
// pre-#4543 wait report the operator's red test run as a
|
|
1396
|
-
// branch-protection block.
|
|
1108
|
+
// No remaining budget turns a red required check green; fail fast.
|
|
1397
1109
|
const decision = decideMergeWaitFailFast({
|
|
1398
1110
|
probe,
|
|
1399
1111
|
consecutiveRequiredFailSnapshots,
|
|
@@ -1413,14 +1125,13 @@ export async function runConfirmMergePhase({
|
|
|
1413
1125
|
exhausted: false,
|
|
1414
1126
|
elapsedSeconds: Math.round(waitedMs / 1000),
|
|
1415
1127
|
},
|
|
1128
|
+
blockClassOverride: decision.blockClass,
|
|
1129
|
+
reasonOverride: decision.reason,
|
|
1416
1130
|
};
|
|
1417
1131
|
}
|
|
1418
1132
|
|
|
1419
|
-
//
|
|
1420
|
-
//
|
|
1421
|
-
// gate is still QUEUED, so the pre-arm refusal in `auto-merge.js` sees
|
|
1422
|
-
// nothing; the gate reddens here, mid-wait, and native auto-merge would
|
|
1423
|
-
// land the PR the moment the REQUIRED contexts go green.
|
|
1133
|
+
// Advisory gates are usually still QUEUED at arm time, so they redden
|
|
1134
|
+
// here, mid-wait, before auto-merge lands over them.
|
|
1424
1135
|
unlanded = await resolveAdvisoryUnlanded({
|
|
1425
1136
|
unlanded,
|
|
1426
1137
|
probe,
|
|
@@ -1451,11 +1162,7 @@ export async function runConfirmMergePhase({
|
|
|
1451
1162
|
updatesUsed += 1;
|
|
1452
1163
|
}
|
|
1453
1164
|
|
|
1454
|
-
// Cumulative budget exhausted
|
|
1455
|
-
// probe we already hold. Gated behind the poll floor so an
|
|
1456
|
-
// already-over-budget PR (anchored at a createdAt older than the budget
|
|
1457
|
-
// — a resume the next day, or a long-open PR) still gets a real poll
|
|
1458
|
-
// cycle instead of being blocked before this invocation waited at all.
|
|
1165
|
+
// Cumulative budget exhausted, behind the poll floor.
|
|
1459
1166
|
if (
|
|
1460
1167
|
polls >= MIN_POLLS_BEFORE_BUDGET_BLOCK &&
|
|
1461
1168
|
cumulativeMs + intervalMs > maxBudgetSeconds * 1000
|
|
@@ -1466,6 +1173,7 @@ export async function runConfirmMergePhase({
|
|
|
1466
1173
|
exhausted: true,
|
|
1467
1174
|
elapsedSeconds: Math.round(cumulativeMs / 1000),
|
|
1468
1175
|
},
|
|
1176
|
+
...queuedExhaustionVerdict(probe, cumulativeMs),
|
|
1469
1177
|
};
|
|
1470
1178
|
}
|
|
1471
1179
|
}
|
|
@@ -1485,11 +1193,7 @@ export async function runConfirmMergePhase({
|
|
|
1485
1193
|
);
|
|
1486
1194
|
}
|
|
1487
1195
|
|
|
1488
|
-
//
|
|
1489
|
-
// nothing is wrong, the run simply reached the edge of its host slot.
|
|
1490
|
-
// No label mutation, no merge.unlanded event — the caller surfaces a
|
|
1491
|
-
// resumable terminal and the next invocation continues the cumulative
|
|
1492
|
-
// clock from the PR's createdAt.
|
|
1196
|
+
// Invocation bound reached: resumable `pending`, no mutation.
|
|
1493
1197
|
if (waitedMs + intervalMs > maxWaitSeconds * 1000) {
|
|
1494
1198
|
progress?.(
|
|
1495
1199
|
'CONFIRM',
|
|
@@ -1515,25 +1219,19 @@ export async function runConfirmMergePhase({
|
|
|
1515
1219
|
try {
|
|
1516
1220
|
return await runMergePoll();
|
|
1517
1221
|
} catch (err) {
|
|
1518
|
-
//
|
|
1519
|
-
// non-match and would poll forever on it, so the throw is carried out
|
|
1520
|
-
// as a match and re-raised below.
|
|
1222
|
+
// pollUntil treats a throw as a non-match and would spin; carry it out.
|
|
1521
1223
|
return { done: true, thrown: err };
|
|
1522
1224
|
}
|
|
1523
1225
|
},
|
|
1524
1226
|
predicate: (result) => result?.done === true,
|
|
1525
1227
|
intervalMs,
|
|
1526
|
-
//
|
|
1527
|
-
// `maxBudgetSeconds` → blocked), and both are decided from the probe
|
|
1528
|
-
// inside the tick. A second, cruder wall-clock timeout here would throw
|
|
1529
|
-
// past those classifications.
|
|
1228
|
+
// No pollUntil timeout: the tick owns both bounds and their classification.
|
|
1530
1229
|
sleepFn: (ms) => sleepFn(ms),
|
|
1531
1230
|
});
|
|
1532
1231
|
if (tick.thrown) throw tick.thrown;
|
|
1533
1232
|
return tick.outcome;
|
|
1534
1233
|
}
|
|
1535
1234
|
|
|
1536
|
-
/** Wrap a phase terminal as the poll loop's match. */
|
|
1537
1235
|
function doneWith(outcome) {
|
|
1538
1236
|
return { done: true, outcome };
|
|
1539
1237
|
}
|