mandrel 2.31.0 → 2.33.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 +13 -17
- package/.agents/agents/acceptance-critic.md +1 -2
- package/.agents/docs/SDLC.md +12 -9
- package/.agents/docs/agentrc-reference.json +63 -58
- package/.agents/docs/configuration.md +275 -227
- package/.agents/docs/execution-reference.md +13 -14
- package/.agents/docs/quality-gates.md +195 -23
- package/.agents/instructions.md +2 -5
- package/.agents/rules/git-conventions-reference.md +27 -27
- package/.agents/rules/git-conventions.md +4 -2
- package/.agents/rules/known-tooling-behavior.md +66 -30
- package/.agents/rules/testing-standards.md +35 -71
- package/.agents/runtime-deps.json +2 -2
- package/.agents/schemas/agentrc.schema.json +1955 -1410
- package/.agents/schemas/lifecycle/README.md +21 -14
- package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/scripts/README.md +16 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- package/.agents/scripts/audit-to-stories.js +160 -41
- package/.agents/scripts/bootstrap.js +28 -26
- package/.agents/scripts/check-baseline-drift.js +73 -13
- package/.agents/scripts/check-baseline-scope.js +362 -0
- package/.agents/scripts/check-dead-exports.js +9 -1
- package/.agents/scripts/check-gherkin-corpus.js +508 -0
- package/.agents/scripts/check-knip-entries.js +159 -0
- package/.agents/scripts/check-lifecycle-lint.js +108 -124
- package/.agents/scripts/check-schema-references.js +1 -1
- package/.agents/scripts/diagnose-friction.js +7 -4
- package/.agents/scripts/generate-config-docs.js +263 -171
- package/.agents/scripts/install-matrix-assert.js +0 -1
- package/.agents/scripts/lib/ITicketingProvider.js +0 -58
- package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
- package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
- package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +81 -35
- package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +185 -0
- package/.agents/scripts/lib/baselines/envelope.js +41 -60
- package/.agents/scripts/lib/baselines/git-base.js +30 -37
- package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
- package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
- package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
- package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
- package/.agents/scripts/lib/bdd-step-index.js +326 -0
- package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
- package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
- package/.agents/scripts/lib/checks/loop-health.js +9 -37
- package/.agents/scripts/lib/child-exec.js +193 -0
- package/.agents/scripts/lib/cli/standard-args.js +1 -1
- package/.agents/scripts/lib/cli-args.js +64 -0
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/close-validation/runner.js +3 -3
- package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
- package/.agents/scripts/lib/config/commands.js +3 -5
- package/.agents/scripts/lib/config/explain.js +5 -7
- package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
- package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
- package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
- package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
- package/.agents/scripts/lib/config/gates/index.js +5 -2
- package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
- package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
- package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
- package/.agents/scripts/lib/config/gates/shared.js +58 -9
- package/.agents/scripts/lib/config/github.js +0 -1
- package/.agents/scripts/lib/config/limits.js +3 -48
- package/.agents/scripts/lib/config/qa.js +105 -0
- package/.agents/scripts/lib/config/runners.js +38 -16
- package/.agents/scripts/lib/config/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +245 -56
- package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
- package/.agents/scripts/lib/config-settings-schema.js +386 -39
- package/.agents/scripts/lib/crap-baseline-join.js +126 -9
- package/.agents/scripts/lib/crap-utils.js +84 -520
- package/.agents/scripts/lib/dead-exports-knip.js +79 -10
- package/.agents/scripts/lib/degraded-mode.js +2 -2
- package/.agents/scripts/lib/dependency-parser.js +20 -7
- package/.agents/scripts/lib/doc-tiers.js +3 -3
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
- package/.agents/scripts/lib/findings/provenance-field.js +135 -0
- package/.agents/scripts/lib/findings/route-finding.js +57 -8
- package/.agents/scripts/lib/fs-walk.js +52 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/git-utils.js +16 -36
- package/.agents/scripts/lib/knip-config-resolver.js +181 -0
- package/.agents/scripts/lib/knip-entry-sync.js +508 -0
- package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
- package/.agents/scripts/lib/observability/signal-validator.js +5 -85
- package/.agents/scripts/lib/observability/signals-writer.js +19 -62
- package/.agents/scripts/lib/observability/source-classifier.js +5 -7
- package/.agents/scripts/lib/observability/terse-result.js +3 -3
- package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
- package/.agents/scripts/lib/orchestration/code-review.js +2 -2
- package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
- package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
- package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
- package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +7 -26
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +69 -5
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +69 -13
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +49 -0
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +77 -50
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
- package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +116 -1
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-validator.js +38 -0
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
- package/.agents/scripts/lib/signals/detectors/common.js +1 -1
- package/.agents/scripts/lib/signals/index.js +8 -6
- package/.agents/scripts/lib/signals/schema.js +20 -25
- package/.agents/scripts/lib/signals/write.js +8 -8
- package/.agents/scripts/lib/story-body/footer-block.js +97 -0
- package/.agents/scripts/lib/story-body/story-body.js +18 -81
- package/.agents/scripts/lib/temp-retention.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
- package/.agents/scripts/lib/ticket-body-sections.js +4 -5
- package/.agents/scripts/lib/wave-runner/footprint.js +306 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +198 -181
- package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
- package/.agents/scripts/lib/worktree-manager.js +2 -3
- package/.agents/scripts/lint-label-vocabulary.js +2 -24
- package/.agents/scripts/pr-watch-with-update.js +7 -5
- package/.agents/scripts/providers/github/blocked-by-add.js +25 -10
- package/.agents/scripts/providers/github/cache.js +2 -2
- package/.agents/scripts/providers/github/comments.js +6 -28
- package/.agents/scripts/providers/github/compose.js +0 -15
- package/.agents/scripts/providers/github/errors.js +10 -27
- package/.agents/scripts/providers/github/request-helpers.js +1 -2
- package/.agents/scripts/providers/github/sub-issues.js +10 -218
- package/.agents/scripts/providers/github.js +4 -7
- package/.agents/scripts/prune-baseline-orphans.js +181 -0
- package/.agents/scripts/resolve-stories.js +21 -7
- package/.agents/scripts/run-lint.js +61 -61
- package/.agents/scripts/run-test-profile.js +6 -6
- package/.agents/scripts/run-verify.js +48 -30
- package/.agents/scripts/single-story-close.js +20 -0
- package/.agents/scripts/single-story-init.js +12 -35
- package/.agents/scripts/stories-wave-tick.js +192 -9
- package/.agents/scripts/update-dead-exports-baseline.js +321 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
- package/.agents/skills/skills.index.json +1 -11
- package/.agents/workflows/audit-documentation.md +5 -6
- package/.agents/workflows/audit-to-stories.md +28 -2
- package/.agents/workflows/helpers/audit-lens-core.md +11 -12
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
- package/.agents/workflows/helpers/code-review.md +3 -8
- package/.agents/workflows/helpers/deliver-reference.md +30 -2
- package/.agents/workflows/helpers/deliver-story-reference.md +84 -16
- package/.agents/workflows/helpers/plan-reference.md +76 -0
- package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
- package/.agents/workflows/mandrel-update.md +10 -10
- package/.agents/workflows/qa-assist.md +15 -20
- package/.agents/workflows/qa-explore.md +9 -8
- package/README.md +1 -1
- package/docs/CHANGELOG.md +58 -0
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
- package/package.json +14 -5
- package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
- package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
- package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
- package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
- package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
- package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
- package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
- package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
- package/.agents/scripts/check-gherkin-placeholders.js +0 -663
- package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
- package/.agents/scripts/lib/audit-suite/cli.js +0 -64
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
- package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
- package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
- package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
- package/.agents/scripts/lib/crap-baseline-index.js +0 -46
- package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
- package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
- package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
- package/.agents/scripts/lib/mutation/config-detector.js +0 -119
- package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
- package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
- package/.agents/scripts/lib/observability/active-story-env.js +0 -170
- package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
- package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
- package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
- package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
- package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
- package/.agents/scripts/lib/planning-corpus.js +0 -37
- package/.agents/scripts/lib/qa/coverage-report.js +0 -181
- package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
- package/.agents/scripts/lib/signals/detectors/index.js +0 -14
- package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
- package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
- package/.agents/scripts/lib/signals/read.js +0 -268
- package/.agents/scripts/lib/signals/span-tree.js +0 -291
- package/.agents/scripts/lib/story-lifecycle.js +0 -194
- package/.agents/scripts/lib/story-plan.js +0 -379
- package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
- package/.agents/scripts/lib/util/phase-timer.js +0 -163
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
- package/.agents/scripts/lint-baseline.js +0 -507
- package/.agents/scripts/providers/github/prs.js +0 -103
- package/.agents/scripts/signals-view.js +0 -309
- package/.agents/scripts/story-plan.js +0 -370
- package/.agents/scripts/sync-branch-from-base.js +0 -149
- package/.agents/scripts/validate-docs-freshness.js +0 -314
- package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
- package/.agents/workflows/helpers/signals.md +0 -112
|
@@ -182,9 +182,16 @@ export function storyFootprintPaths(body, id, warn) {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
|
-
* Build the DAG nodes. `dependsOn` is the union the
|
|
186
|
-
*
|
|
187
|
-
*
|
|
185
|
+
* Build the DAG nodes. `dependsOn` is the **union of the two declared-edge
|
|
186
|
+
* channels**: the Story body's `---` footer (`blocked by #N`) and the native
|
|
187
|
+
* GitHub `blocked_by` relations threaded in via `nativeEdges`. `files` is a
|
|
188
|
+
* plain `string[]`.
|
|
189
|
+
*
|
|
190
|
+
* The body channel is footer-scoped and strict (`parseBlockedBy`, Story
|
|
191
|
+
* #5046) — a `blocked by #123` mention in prose no longer mints a dispatch
|
|
192
|
+
* gate. Only `{ id, dependsOn }` is handed to the adjacency builder, never the
|
|
193
|
+
* body: the edge set is decided here, once, so the builder's own body parse
|
|
194
|
+
* cannot re-derive a different one behind this function's back.
|
|
188
195
|
*
|
|
189
196
|
* @param {object[]} stories
|
|
190
197
|
* @param {Map<number, number[]>} [nativeEdges]
|
|
@@ -193,7 +200,7 @@ export function storyFootprintPaths(body, id, warn) {
|
|
|
193
200
|
*/
|
|
194
201
|
export function storiesToDag(stories, nativeEdges = new Map(), warn) {
|
|
195
202
|
const withNative = stories.map((s) => ({
|
|
196
|
-
|
|
203
|
+
id: s.id,
|
|
197
204
|
dependsOn: [
|
|
198
205
|
...new Set([
|
|
199
206
|
...parseBlockedBy(s.body ?? ''),
|
|
@@ -222,28 +229,39 @@ export function storiesToDag(stories, nativeEdges = new Map(), warn) {
|
|
|
222
229
|
* matching no Story, foreign to the set, never satisfiable, and (because
|
|
223
230
|
* foreign edges are real gates) a silent permanent wedge.
|
|
224
231
|
*
|
|
225
|
-
*
|
|
226
|
-
* is not this repo's #4530, and treating it as one could
|
|
227
|
-
* is still open.
|
|
232
|
+
* A cross-repo blocker is **dropped with a loud warning**, never matched:
|
|
233
|
+
* another repo's #4530 is not this repo's #4530, and treating it as one could
|
|
234
|
+
* satisfy a gate that is still open. It used to throw, which failed the WHOLE
|
|
235
|
+
* resolution — one Story's unsupported edge took every sibling down with it
|
|
236
|
+
* (Story #5046). The degrade is now scoped to the Story carrying the edge:
|
|
237
|
+
* its siblings resolve normally, and the operator is told, by number, which
|
|
238
|
+
* Story lost which edge.
|
|
228
239
|
*
|
|
229
240
|
* @param {unknown} data Parsed API response.
|
|
230
|
-
* @param {{ owner: string, repo: string, issueNumber: number }} ctx
|
|
241
|
+
* @param {{ owner: string, repo: string, issueNumber: number, warn?: (msg: string) => void }} ctx
|
|
231
242
|
* @returns {number[]}
|
|
232
243
|
*/
|
|
233
|
-
export function nativeBlockedByNumbers(
|
|
244
|
+
export function nativeBlockedByNumbers(
|
|
245
|
+
data,
|
|
246
|
+
{ owner, repo, issueNumber, warn },
|
|
247
|
+
) {
|
|
234
248
|
if (!Array.isArray(data)) return [];
|
|
235
249
|
const out = [];
|
|
236
250
|
for (const item of data) {
|
|
237
251
|
const repoUrl = item?.repository_url ?? item?.repository?.url ?? null;
|
|
238
|
-
if (
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
252
|
+
if (
|
|
253
|
+
typeof repoUrl === 'string' &&
|
|
254
|
+
repoUrl.length > 0 &&
|
|
255
|
+
!repoUrl.endsWith(`/repos/${owner}/${repo}`)
|
|
256
|
+
) {
|
|
257
|
+
warn?.(
|
|
258
|
+
`[resolve-stories] #${issueNumber} declares a native blocked_by edge on an issue in ` +
|
|
259
|
+
`another repository (${repoUrl}). Cross-repo edges are not supported — its number ` +
|
|
260
|
+
`cannot be matched against this repo's Stories without risking a false match, so the ` +
|
|
261
|
+
`edge is DROPPED for #${issueNumber} only. Its siblings resolve normally; re-declare ` +
|
|
262
|
+
`the ordering in this repo if #${issueNumber} must wait.`,
|
|
263
|
+
);
|
|
264
|
+
continue;
|
|
247
265
|
}
|
|
248
266
|
const number = Number(item?.number);
|
|
249
267
|
if (Number.isInteger(number) && number > 0) out.push(number);
|
|
@@ -252,17 +270,33 @@ export function nativeBlockedByNumbers(data, { owner, repo, issueNumber }) {
|
|
|
252
270
|
}
|
|
253
271
|
|
|
254
272
|
/**
|
|
255
|
-
* Read an issue's native `blocked_by` edges as issue numbers
|
|
273
|
+
* Read an issue's native `blocked_by` edges as issue numbers, **paginated to
|
|
274
|
+
* exhaustion**.
|
|
275
|
+
*
|
|
276
|
+
* The read used to take the first page only, so a Story with more than a
|
|
277
|
+
* page of blockers silently lost every edge past the boundary — the exact
|
|
278
|
+
* failure this function's fail-loud contract exists to prevent, arriving
|
|
279
|
+
* through the one door that never raised (Story #5046). `paginate` is
|
|
280
|
+
* injected (the CLI passes `paginateRest`) so the lib layer stays free of a
|
|
281
|
+
* provider import and the page walk stays testable without a live round-trip.
|
|
256
282
|
*
|
|
257
|
-
* **Fails loud**, deliberately inverting the write path's
|
|
258
|
-
* A dropped write-side edge is cosmetic (the ordering
|
|
259
|
-
* `blocked by #N` body footer); a dropped READ-side edge
|
|
260
|
-
* dispatch gate, so
|
|
261
|
-
*
|
|
262
|
-
* against unlanded blockers. A 404 means "no dependencies on this issue" and
|
|
263
|
-
* is a legitimate empty result.
|
|
283
|
+
* **Fails loud on every non-OK read**, deliberately inverting the write path's
|
|
284
|
+
* non-fatal contract. A dropped write-side edge is cosmetic (the ordering
|
|
285
|
+
* still lives in the `blocked by #N` body footer); a dropped READ-side edge
|
|
286
|
+
* silently removes a dispatch gate, so one failure would erase every native
|
|
287
|
+
* edge at once and co-dispatch the run against unlanded blockers.
|
|
264
288
|
*
|
|
265
|
-
*
|
|
289
|
+
* **A 404 is not an empty result.** It used to be treated as "this issue has
|
|
290
|
+
* no dependencies", which is how GitHub answers an issue that genuinely has
|
|
291
|
+
* none — but it is *also* how GitHub answers a token that cannot see the
|
|
292
|
+
* dependencies API at all. Reading the second as the first erases every
|
|
293
|
+
* native edge in the run under a mis-scoped token, silently, with a clean
|
|
294
|
+
* exit code. An issue with no dependencies returns `200 []`, so the empty
|
|
295
|
+
* case needs no 404 escape hatch and the ambiguity resolves loud.
|
|
296
|
+
*
|
|
297
|
+
* @param {{ gh: object, owner: string, repo: string, issueNumber: number,
|
|
298
|
+
* paginate: (gh: object, endpoint: string, opts?: object) => Promise<unknown[]>,
|
|
299
|
+
* warn?: (msg: string) => void }} opts
|
|
266
300
|
* @returns {Promise<number[]>}
|
|
267
301
|
*/
|
|
268
302
|
export async function readNativeBlockedBy({
|
|
@@ -270,27 +304,30 @@ export async function readNativeBlockedBy({
|
|
|
270
304
|
owner,
|
|
271
305
|
repo,
|
|
272
306
|
issueNumber,
|
|
273
|
-
|
|
307
|
+
paginate,
|
|
308
|
+
warn,
|
|
274
309
|
}) {
|
|
275
|
-
|
|
310
|
+
const endpoint = `/repos/${owner}/${repo}/issues/${issueNumber}/dependencies/blocked_by`;
|
|
311
|
+
let items;
|
|
276
312
|
try {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
endpoint: `/repos/${owner}/${repo}/issues/${issueNumber}/dependencies/blocked_by`,
|
|
313
|
+
items = await paginate(gh, endpoint, {
|
|
314
|
+
label: `[resolve-stories] blocked_by #${issueNumber}`,
|
|
280
315
|
});
|
|
281
316
|
} catch (err) {
|
|
282
317
|
const detail = String(err?.message ?? err);
|
|
283
|
-
if (/404|not found/i.test(detail)) return [];
|
|
284
318
|
throw new Error(
|
|
285
319
|
`[resolve-stories] Could not read native blocked_by edges for #${issueNumber}: ${detail}. ` +
|
|
286
320
|
`Refusing to continue: a dropped dependency edge would silently remove a dispatch gate ` +
|
|
287
|
-
`and co-dispatch this Story against an unlanded blocker
|
|
321
|
+
`and co-dispatch this Story against an unlanded blocker. A 404 here is NOT "no ` +
|
|
322
|
+
`dependencies" (that answers 200 with an empty list) — check the token's scopes and ` +
|
|
323
|
+
`that the dependencies API is enabled for ${owner}/${repo}.`,
|
|
288
324
|
);
|
|
289
325
|
}
|
|
290
|
-
return nativeBlockedByNumbers(
|
|
326
|
+
return nativeBlockedByNumbers(items, {
|
|
291
327
|
owner,
|
|
292
328
|
repo,
|
|
293
329
|
issueNumber,
|
|
330
|
+
warn,
|
|
294
331
|
});
|
|
295
332
|
}
|
|
296
333
|
|
|
@@ -301,9 +338,6 @@ export async function readNativeBlockedBy({
|
|
|
301
338
|
* @param {Map<number, number[]>} nativeEdges
|
|
302
339
|
* @param {number[]} foreignDone Ids outside the set already satisfied.
|
|
303
340
|
* @param {(msg: string) => void} [warn]
|
|
304
|
-
* @param {object} [injectedRules] Test seam forwarded to the shape
|
|
305
|
-
* derivation — skips the `audit-rules.json` disk read. Production callers
|
|
306
|
-
* omit it (the real manifest, memoized per process, is the default).
|
|
307
341
|
* @returns {{ kind: string, stories: object[], dag: object[], done: number[] }}
|
|
308
342
|
*/
|
|
309
343
|
export function buildStoriesEnvelope({
|
|
@@ -311,8 +345,6 @@ export function buildStoriesEnvelope({
|
|
|
311
345
|
nativeEdges = new Map(),
|
|
312
346
|
foreignDone = [],
|
|
313
347
|
warn,
|
|
314
|
-
config,
|
|
315
|
-
injectedRules,
|
|
316
348
|
}) {
|
|
317
349
|
const sorted = [...stories].sort((a, b) => a.id - b.id);
|
|
318
350
|
const inSetDone = sorted.filter(isSatisfiedBlocker).map((s) => s.id);
|
|
@@ -324,8 +356,8 @@ export function buildStoriesEnvelope({
|
|
|
324
356
|
// acceptance-critic sub-agent boots) or `subagent` (the conservative
|
|
325
357
|
// default). Model-side fan-out only; close gates are untouched.
|
|
326
358
|
//
|
|
327
|
-
// `storyCount` is the premise that decides it, and it is this call
|
|
328
|
-
//
|
|
359
|
+
// `storyCount` is the ONLY premise that decides it, and it is this call
|
|
360
|
+
// site's whole argument: `inline` names the router's ONE session, so it is
|
|
329
361
|
// granted only to a run resolving exactly ONE Story, which has no
|
|
330
362
|
// concurrent sibling to share that session with. Passing the resolved set
|
|
331
363
|
// size here is therefore what makes the envelope self-consistent with the
|
|
@@ -336,19 +368,14 @@ export function buildStoriesEnvelope({
|
|
|
336
368
|
// a caller reads for a given `--ids` list never changes as siblings land
|
|
337
369
|
// mid-run. The `route::lite` label is a human-visible hint only, never the
|
|
338
370
|
// control signal.
|
|
339
|
-
stories: sorted.map(({ id, title,
|
|
371
|
+
stories: sorted.map(({ id, title, url, labels, state }) => ({
|
|
340
372
|
id,
|
|
341
373
|
title,
|
|
342
374
|
url,
|
|
343
375
|
labels,
|
|
344
376
|
state,
|
|
345
|
-
dispatchMode: resolveStoryDispatchMode({
|
|
346
|
-
|
|
347
|
-
labels,
|
|
348
|
-
config,
|
|
349
|
-
storyCount: sorted.length,
|
|
350
|
-
injectedRules,
|
|
351
|
-
}).mode,
|
|
377
|
+
dispatchMode: resolveStoryDispatchMode({ storyCount: sorted.length })
|
|
378
|
+
.mode,
|
|
352
379
|
})),
|
|
353
380
|
dag: storiesToDag(sorted, nativeEdges, warn),
|
|
354
381
|
done: [...new Set([...inSetDone, ...foreignDone])].sort((a, b) => a - b),
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* `provider: native` explicitly; the probe is the only thing that
|
|
20
20
|
* routes between "configured backend present" and "configured backend
|
|
21
21
|
* missing". The adapter never consults a GitHub provider — the
|
|
22
|
-
* orchestrator owns posting
|
|
22
|
+
* orchestrator owns posting and upserting.
|
|
23
23
|
*
|
|
24
24
|
* @typedef {import('./types.js').Finding} Finding
|
|
25
25
|
* @typedef {import('./types.js').ReviewInput} ReviewInput
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* review-providers/mi-exemptions.js — the maintainability gate's exemption list,
|
|
3
|
+
* as the native review provider reads it.
|
|
4
|
+
*
|
|
5
|
+
* `delivery.quality.gates.maintainability.ignoreGlobs` is the one declared
|
|
6
|
+
* answer to "which files is the maintainability index meaningless for" —
|
|
7
|
+
* declarative schema blobs, generated code, vendored trees. Every other MI
|
|
8
|
+
* consumer already honours it: the baseline writer excludes those files
|
|
9
|
+
* (`update-maintainability-baseline.js`), so the `check-baselines.js` ratchet
|
|
10
|
+
* and the pre-merge MI advisory never see them either.
|
|
11
|
+
*
|
|
12
|
+
* `review-providers/native.js` did not, and the split produced a live
|
|
13
|
+
* contradiction on Story #5007 / PR #5022: the ratchet PASSED while the review
|
|
14
|
+
* lens raised a **critical blocker** on three exempted
|
|
15
|
+
* `config-settings-schema*.js` modules in the same close run. Because a critical
|
|
16
|
+
* finding halts `single-story-close.js` before auto-merge, the only way to land
|
|
17
|
+
* legitimate work was to merge the PR by hand. A gate that must be
|
|
18
|
+
* hand-bypassed to ship is not a gate.
|
|
19
|
+
*
|
|
20
|
+
* This module is that reconciliation, kept separate from the provider so the
|
|
21
|
+
* exemption concern has one home and the provider keeps one reason to change.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { getQuality } from '../../config/quality.js';
|
|
25
|
+
import { resolveConfig } from '../../config-resolver.js';
|
|
26
|
+
import { isIgnoredByGlobs } from '../../maintainability-utils.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read `delivery.quality.gates.maintainability.ignoreGlobs` so a review's
|
|
30
|
+
* maintainability dimension scores the same file set the ratchet does.
|
|
31
|
+
*
|
|
32
|
+
* Best-effort and total. A config that cannot be resolved yields `[]`, which
|
|
33
|
+
* scores every changed JS file. That direction is deliberate: degrading to
|
|
34
|
+
* "score everything" can only produce an advisory the operator must read,
|
|
35
|
+
* whereas degrading to "score nothing" would silently retire the dimension.
|
|
36
|
+
*
|
|
37
|
+
* @param {{ resolveConfigFn?: typeof resolveConfig, getQualityFn?: typeof getQuality }} [deps]
|
|
38
|
+
* @returns {string[]} minimatch patterns; `[]` when unset or unresolvable.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveMaintainabilityIgnoreGlobs({
|
|
41
|
+
resolveConfigFn = resolveConfig,
|
|
42
|
+
getQualityFn = getQuality,
|
|
43
|
+
} = {}) {
|
|
44
|
+
try {
|
|
45
|
+
const globs = getQualityFn(resolveConfigFn())?.maintainability?.ignoreGlobs;
|
|
46
|
+
return Array.isArray(globs) ? globs.slice() : [];
|
|
47
|
+
} catch {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Pure: split a changed-file list into the set to score and the set the
|
|
54
|
+
* maintainability gate exempts.
|
|
55
|
+
*
|
|
56
|
+
* Matching funnels through `maintainability-utils.js#isIgnoredByGlobs` — the
|
|
57
|
+
* declared single source of truth for how the MI scorer decides a file is
|
|
58
|
+
* ignored — so an exempted file is excluded here by exactly the same rule that
|
|
59
|
+
* kept it out of the baseline. Re-implementing the match with a local
|
|
60
|
+
* `minimatch` call is what let the two surfaces disagree in the first place.
|
|
61
|
+
*
|
|
62
|
+
* Module-local: {@link scopeMaintainabilityFiles} is the single door, and its
|
|
63
|
+
* `scored` / `ignored` split is where this behaviour is observable.
|
|
64
|
+
*
|
|
65
|
+
* @param {string[]} files
|
|
66
|
+
* @param {string[]} ignoreGlobs
|
|
67
|
+
* @param {string} cwd root for repo-relative glob resolution
|
|
68
|
+
* @returns {{ scored: string[], ignored: string[] }}
|
|
69
|
+
*/
|
|
70
|
+
function partitionByIgnoreGlobs(files, ignoreGlobs, cwd) {
|
|
71
|
+
if (!Array.isArray(ignoreGlobs) || ignoreGlobs.length === 0) {
|
|
72
|
+
return { scored: files, ignored: [] };
|
|
73
|
+
}
|
|
74
|
+
const scored = [];
|
|
75
|
+
const ignored = [];
|
|
76
|
+
for (const relPath of files) {
|
|
77
|
+
if (isIgnoredByGlobs(relPath, ignoreGlobs, cwd)) ignored.push(relPath);
|
|
78
|
+
else scored.push(relPath);
|
|
79
|
+
}
|
|
80
|
+
return { scored, ignored };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Render the operator-facing notice naming the files the gate exempted, or
|
|
85
|
+
* `null` when nothing was exempted so the caller can `if` past the log call.
|
|
86
|
+
*
|
|
87
|
+
* The notice exists because silence is ambiguous: an operator reading a review
|
|
88
|
+
* that says nothing about three changed schema modules cannot tell "scored and
|
|
89
|
+
* healthy" from "never scored".
|
|
90
|
+
*
|
|
91
|
+
* Module-local: reached through {@link scopeMaintainabilityFiles}'s `notice`.
|
|
92
|
+
*
|
|
93
|
+
* @param {string[]|undefined} ignoredFiles
|
|
94
|
+
* @returns {string|null}
|
|
95
|
+
*/
|
|
96
|
+
function formatExemptionNotice(ignoredFiles) {
|
|
97
|
+
const files = Array.isArray(ignoredFiles) ? ignoredFiles : [];
|
|
98
|
+
if (files.length === 0) return null;
|
|
99
|
+
return (
|
|
100
|
+
`[native-review] Maintainability: ${files.length} changed file(s) exempt via ` +
|
|
101
|
+
`delivery.quality.gates.maintainability.ignoreGlobs — not scored: ${files.join(', ')}.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Resolve the exemption list and split a review's changed-file set into the
|
|
107
|
+
* paths whose maintainability should be scored and the paths the gate exempts.
|
|
108
|
+
*
|
|
109
|
+
* This is the one door the native provider uses: it keeps the resolve → match →
|
|
110
|
+
* report sequence here rather than spread across the provider, so the provider
|
|
111
|
+
* carries no knowledge of how an exemption is decided.
|
|
112
|
+
*
|
|
113
|
+
* @param {string[]} changedFiles
|
|
114
|
+
* @param {{
|
|
115
|
+
* cwd: string,
|
|
116
|
+
* resolveIgnoreGlobsFn?: typeof resolveMaintainabilityIgnoreGlobs,
|
|
117
|
+
* }} opts
|
|
118
|
+
* @returns {{ scored: string[], ignored: string[], notice: string|null }}
|
|
119
|
+
*/
|
|
120
|
+
export function scopeMaintainabilityFiles(
|
|
121
|
+
changedFiles,
|
|
122
|
+
{ cwd, resolveIgnoreGlobsFn = resolveMaintainabilityIgnoreGlobs } = {},
|
|
123
|
+
) {
|
|
124
|
+
const { scored, ignored } = partitionByIgnoreGlobs(
|
|
125
|
+
changedFiles,
|
|
126
|
+
resolveIgnoreGlobsFn(),
|
|
127
|
+
cwd,
|
|
128
|
+
);
|
|
129
|
+
return { scored, ignored, notice: formatExemptionNotice(ignored) };
|
|
130
|
+
}
|
|
@@ -7,14 +7,24 @@
|
|
|
7
7
|
*
|
|
8
8
|
* 1. Diffs `headRef` against `baseRef` to enumerate changed files.
|
|
9
9
|
* 2. Runs scoped lint (biome + markdownlint) over the changed surface.
|
|
10
|
-
* 3. Computes per-file maintainability reports for changed JS files
|
|
10
|
+
* 3. Computes per-file maintainability reports for changed JS files, minus
|
|
11
|
+
* the files the maintainability gate exempts (see below).
|
|
11
12
|
* 4. Maps each signal to a `Finding` with a `severity` ∈ {critical, high,
|
|
12
13
|
* medium, suggestion}.
|
|
13
14
|
*
|
|
14
|
-
* The
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* **The maintainability dimension honours the gate's exemption list**, read via
|
|
16
|
+
* [`mi-exemptions.js`](mi-exemptions.js) — see that module for why this
|
|
17
|
+
* provider disagreeing with the ratchet was a live delivery blocker. Exempted
|
|
18
|
+
* files are named on the log rather than silently dropped.
|
|
19
|
+
*
|
|
20
|
+
* The lint dimension is deliberately NOT filtered through the same list:
|
|
21
|
+
* lint carries its own exclusion surface (biome's `files.includes`,
|
|
22
|
+
* `.markdownlintignore`), and a quality-gate ignore glob makes no claim about
|
|
23
|
+
* whether a file should parse or format cleanly.
|
|
24
|
+
*
|
|
25
|
+
* The adapter does NOT post to GitHub and does NOT render a markdown body.
|
|
26
|
+
* Those concerns belong to `runCodeReview()`, which calls the renderer + the
|
|
27
|
+
* structured-comment upserter.
|
|
18
28
|
*
|
|
19
29
|
* Construction is intentionally zero-arg so the factory can instantiate
|
|
20
30
|
* it without threading config through every call. Per-invocation config
|
|
@@ -53,6 +63,10 @@ import {
|
|
|
53
63
|
} from '../../observability/runtime-friction.js';
|
|
54
64
|
import { PROJECT_ROOT } from '../../project-root.js';
|
|
55
65
|
import { transpileIfNeeded } from '../../transpile.js';
|
|
66
|
+
import {
|
|
67
|
+
resolveMaintainabilityIgnoreGlobs,
|
|
68
|
+
scopeMaintainabilityFiles,
|
|
69
|
+
} from './mi-exemptions.js';
|
|
56
70
|
import {
|
|
57
71
|
parseLintOutput,
|
|
58
72
|
partitionFilesForLint,
|
|
@@ -389,16 +403,6 @@ export function buildLintFindings(lintSummary) {
|
|
|
389
403
|
return findings;
|
|
390
404
|
}
|
|
391
405
|
|
|
392
|
-
function _emptyResults() {
|
|
393
|
-
return {
|
|
394
|
-
totalFiles: 0,
|
|
395
|
-
jsFiles: 0,
|
|
396
|
-
maintainability: [],
|
|
397
|
-
criticalFindings: [],
|
|
398
|
-
mediumFindings: [],
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
|
|
402
406
|
async function runLintPhase({
|
|
403
407
|
scopeLint,
|
|
404
408
|
changedFiles,
|
|
@@ -467,6 +471,7 @@ function buildLintDegradations(lintSummary) {
|
|
|
467
471
|
* analyzeChangedFilesFn?: typeof analyzeChangedFiles,
|
|
468
472
|
* buildLintFindingsFn?: typeof buildLintFindings,
|
|
469
473
|
* emitToolDegradationFn?: typeof emitRuntimeFriction,
|
|
474
|
+
* resolveIgnoreGlobsFn?: typeof resolveMaintainabilityIgnoreGlobs,
|
|
470
475
|
* logger?: { info?: Function, warn?: Function, error?: Function },
|
|
471
476
|
* scopeLint?: 'changed-only'|'off',
|
|
472
477
|
* }} [deps]
|
|
@@ -479,6 +484,10 @@ export function createNativeProvider(deps = {}) {
|
|
|
479
484
|
analyzeChangedFilesFn = analyzeChangedFiles,
|
|
480
485
|
buildLintFindingsFn = buildLintFindings,
|
|
481
486
|
emitToolDegradationFn = emitRuntimeFriction,
|
|
487
|
+
// The maintainability-gate exemption seam. The resolution itself — gate-key
|
|
488
|
+
// read and fail-open — is unit-tested in `mi-exemptions.js`; this dep is
|
|
489
|
+
// here so a provider test can pin the WIRING without a config on disk.
|
|
490
|
+
resolveIgnoreGlobsFn = resolveMaintainabilityIgnoreGlobs,
|
|
482
491
|
logger,
|
|
483
492
|
scopeLint = 'changed-only',
|
|
484
493
|
} = deps;
|
|
@@ -551,7 +560,12 @@ export function createNativeProvider(deps = {}) {
|
|
|
551
560
|
logger?.info?.(
|
|
552
561
|
`[native-review] Analyzing ${changedFiles.length} changed file(s)...`,
|
|
553
562
|
);
|
|
554
|
-
const
|
|
563
|
+
const mi = scopeMaintainabilityFiles(changedFiles, {
|
|
564
|
+
cwd: PROJECT_ROOT,
|
|
565
|
+
resolveIgnoreGlobsFn,
|
|
566
|
+
});
|
|
567
|
+
if (mi.notice) logger?.info?.(mi.notice);
|
|
568
|
+
const results = await analyzeChangedFilesFn(mi.scored, {
|
|
555
569
|
headRef,
|
|
556
570
|
gitSpawnFn,
|
|
557
571
|
});
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* the caller raises that to a thrown error so auto-merge is not enabled.
|
|
22
22
|
*
|
|
23
23
|
* Delegates the `runCodeReview` invocation to `runStoryReviewCore`
|
|
24
|
-
* (exported from `story-close/phases/
|
|
24
|
+
* (exported from `story-close/phases/review-core.js`) so the close path
|
|
25
25
|
* shares a single invocation pattern (Story #3653). Review depth needs no
|
|
26
26
|
* input here: it is derived from this Story's own diff inside `runCodeReview`
|
|
27
27
|
* (Story #4542).
|
|
@@ -96,6 +96,7 @@ import {
|
|
|
96
96
|
readPrMergeState as defaultReadPrMergeState,
|
|
97
97
|
} from '../../../single-story/confirm-merge.js';
|
|
98
98
|
import { pollUntil } from '../../../util/poll-loop.js';
|
|
99
|
+
import { applyBehindUpdate } from '../../behind-recovery.js';
|
|
99
100
|
import {
|
|
100
101
|
emitMergeFlipFailed as defaultEmitMergeFlipFailed,
|
|
101
102
|
MERGED_FLIP_FAILED_BLOCK_CLASS,
|
|
@@ -635,7 +636,15 @@ async function blockOnUnlanded({
|
|
|
635
636
|
/**
|
|
636
637
|
* Bring a BEHIND PR up to date, bounded by `updateAttempts`. Best-effort:
|
|
637
638
|
* a failed update is not itself a terminal — the next poll re-reads the
|
|
638
|
-
* real state and lets the normal classification decide
|
|
639
|
+
* real state and lets the normal classification decide, which is why a
|
|
640
|
+
* failed attempt still counts against the wait's tick.
|
|
641
|
+
*
|
|
642
|
+
* The BEHIND / budget / did-it-land decision itself lives in the shared
|
|
643
|
+
* {@link applyBehindUpdate} (Story #5006) — the CI-watch loop in
|
|
644
|
+
* `lib/orchestration/pr-watch.js` runs the same one. This wrapper supplies
|
|
645
|
+
* the merge wait's probe source, its `gh` facade (bounded by
|
|
646
|
+
* {@link withGhTimeout}, so a wedged child cannot strand an unattended
|
|
647
|
+
* async-mode wait), and its operator wording.
|
|
639
648
|
*
|
|
640
649
|
* @returns {Promise<boolean>} whether an update was actually attempted.
|
|
641
650
|
*/
|
|
@@ -648,31 +657,33 @@ async function maybeUpdateBehindPr({
|
|
|
648
657
|
ghTimeoutMs = MERGE_WAIT_GH_TIMEOUT_MS,
|
|
649
658
|
progress,
|
|
650
659
|
}) {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
660
|
+
const recovery = await applyBehindUpdate({
|
|
661
|
+
mergeStateStatus: probe.mergeStateStatus,
|
|
662
|
+
updatesUsed,
|
|
663
|
+
maxUpdates: updateAttempts,
|
|
664
|
+
updateBranch: () =>
|
|
665
|
+
withGhTimeout(
|
|
666
|
+
(gh ?? defaultGh).pr.updateBranch(prNumber),
|
|
667
|
+
ghTimeoutMs,
|
|
668
|
+
`gh pr update-branch ${prNumber}`,
|
|
669
|
+
),
|
|
670
|
+
onBudgetSpent: () =>
|
|
671
|
+
progress?.(
|
|
672
|
+
'CONFIRM',
|
|
673
|
+
`⚠️ PR #${prNumber} is BEHIND but the update budget (${updateAttempts}) is spent — not updating again.`,
|
|
674
|
+
),
|
|
675
|
+
onUpdated: () =>
|
|
676
|
+
progress?.(
|
|
677
|
+
'CONFIRM',
|
|
678
|
+
`⏫ PR #${prNumber} was BEHIND its base — updated (attempt ${updatesUsed + 1}/${updateAttempts}).`,
|
|
679
|
+
),
|
|
680
|
+
onUpdateFailed: (detail) =>
|
|
681
|
+
progress?.(
|
|
682
|
+
'CONFIRM',
|
|
683
|
+
`⚠️ gh pr update-branch failed (continuing): ${detail}`,
|
|
684
|
+
),
|
|
685
|
+
});
|
|
686
|
+
return recovery.attempted;
|
|
676
687
|
}
|
|
677
688
|
|
|
678
689
|
/**
|