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
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
import { createHash } from 'node:crypto';
|
|
19
19
|
import { applyBlockedByDependencies } from '../../../providers/github/blocked-by-add.js';
|
|
20
|
+
import {
|
|
21
|
+
normalizeOwnedProvenance,
|
|
22
|
+
ownedProvenanceSource,
|
|
23
|
+
} from '../../findings/provenance-field.js';
|
|
20
24
|
import { carryProvenanceFooters } from '../../findings/route-finding.js';
|
|
21
25
|
import { Logger } from '../../Logger.js';
|
|
22
26
|
import { AGENT_LABELS, TYPE_LABELS } from '../../label-constants.js';
|
|
@@ -273,7 +277,6 @@ function bodyObjectFromTicket(ticket) {
|
|
|
273
277
|
wide: ticket.wide ?? null,
|
|
274
278
|
reason_to_exist: ticket.reason_to_exist ?? null,
|
|
275
279
|
depends_on: ticket.depends_on ?? [],
|
|
276
|
-
estimated_test_files: ticket.estimated_test_files ?? null,
|
|
277
280
|
}).body;
|
|
278
281
|
}
|
|
279
282
|
|
|
@@ -320,8 +323,12 @@ function syncContractFieldFromTopLevel(ticket, bodyObject, field) {
|
|
|
320
323
|
* bookkeeping for the `--tickets` source issues, not part of the Story's
|
|
321
324
|
* executable body, so it is deliberately not serialized into the markdown.
|
|
322
325
|
*
|
|
326
|
+
* `provenance` is likewise top-level-only (Story #5045) — it names the audit
|
|
327
|
+
* identities *this* Story owns, and is stamped into the body as footers rather
|
|
328
|
+
* than serialized as a section.
|
|
329
|
+
*
|
|
323
330
|
* @param {object} ticket
|
|
324
|
-
* @returns {{ slug: string, title: string, bodyObject: object, depends_on: string[], labels: string[], supersedes: Array<{ id: number, note: string|null }
|
|
331
|
+
* @returns {{ slug: string, title: string, bodyObject: object, depends_on: string[], labels: string[], supersedes: Array<{ id: number, note: string|null }>, provenance: { fingerprints: string[], semanticKeys: string[] }|null }}
|
|
325
332
|
*/
|
|
326
333
|
export function normalizeStoryTicket(ticket) {
|
|
327
334
|
if (!ticket || typeof ticket !== 'object') {
|
|
@@ -346,8 +353,22 @@ export function normalizeStoryTicket(ticket) {
|
|
|
346
353
|
const depends_on = normalizeDependsOn(ticket, bodyObject);
|
|
347
354
|
const supersedes = normalizeSupersedes(ticket, slug);
|
|
348
355
|
const labels = sanitizeAuthoredLabels(ticket.labels, slug);
|
|
356
|
+
let provenance;
|
|
357
|
+
try {
|
|
358
|
+
provenance = normalizeOwnedProvenance(ticket.provenance, slug);
|
|
359
|
+
} catch (err) {
|
|
360
|
+
throw new Error(`[plan-persist] ${err.message}`);
|
|
361
|
+
}
|
|
349
362
|
|
|
350
|
-
return {
|
|
363
|
+
return {
|
|
364
|
+
slug,
|
|
365
|
+
title,
|
|
366
|
+
bodyObject,
|
|
367
|
+
depends_on,
|
|
368
|
+
labels,
|
|
369
|
+
supersedes,
|
|
370
|
+
provenance,
|
|
371
|
+
};
|
|
351
372
|
}
|
|
352
373
|
|
|
353
374
|
/**
|
|
@@ -389,24 +410,59 @@ export function foldSpecIntoStoryBody(bodyObject, slug, opts = {}) {
|
|
|
389
410
|
return { bodyObject: next };
|
|
390
411
|
}
|
|
391
412
|
|
|
413
|
+
/**
|
|
414
|
+
* Resolve the provenance source one Story is stamped from (Story #5045).
|
|
415
|
+
*
|
|
416
|
+
* Two channels, and the precedence between them is the whole contract:
|
|
417
|
+
*
|
|
418
|
+
* - **Attributed** — the Story authored a `provenance` field naming the audit
|
|
419
|
+
* identities *it* owns. Exactly those are stamped. This is what makes the
|
|
420
|
+
* footers answer "which Story tracks this finding?" instead of "which sweep
|
|
421
|
+
* planned it?": under the union every sibling carried every key, so the next
|
|
422
|
+
* sweep's confirmation pass could only pick an arbitrary open Story, and a
|
|
423
|
+
* key whose owner had since closed was masked by any open neighbour.
|
|
424
|
+
* - **Union fallback** — no `provenance` field, so the whole seed's footers are
|
|
425
|
+
* carried, exactly as before. This is **not** dead weight to be tidied away:
|
|
426
|
+
* hand-carried provenance is the failure Stories #4626 / #4877 measured, and
|
|
427
|
+
* the union is what closed it. Attribution is additive and recall-safe;
|
|
428
|
+
* deleting the fallback would re-open that hole for every plan that does not
|
|
429
|
+
* attribute.
|
|
430
|
+
*
|
|
431
|
+
* @param {{ fingerprints: string[], semanticKeys: string[] }|null} provenance
|
|
432
|
+
* @param {object} opts Assembly options carrying `provenanceSource`.
|
|
433
|
+
* @returns {string}
|
|
434
|
+
*/
|
|
435
|
+
function resolveProvenanceSource(provenance, opts) {
|
|
436
|
+
if (provenance !== null) return ownedProvenanceSource(provenance);
|
|
437
|
+
return opts.provenanceSource ?? '';
|
|
438
|
+
}
|
|
439
|
+
|
|
392
440
|
function assembleOnePlanStory(ticket, opts) {
|
|
393
|
-
const {
|
|
394
|
-
|
|
441
|
+
const {
|
|
442
|
+
slug,
|
|
443
|
+
title,
|
|
444
|
+
bodyObject,
|
|
445
|
+
depends_on,
|
|
446
|
+
labels,
|
|
447
|
+
supersedes,
|
|
448
|
+
provenance,
|
|
449
|
+
} = normalizeStoryTicket(ticket);
|
|
395
450
|
const { bodyObject: folded } = foldSpecIntoStoryBody(bodyObject, slug, {
|
|
396
451
|
sharedSpec: opts.sharedSpec ?? null,
|
|
397
452
|
});
|
|
398
453
|
// Body first: the fingerprint is an identity over the *assembled* content,
|
|
399
454
|
// so it cannot be computed until that content exists.
|
|
400
455
|
const serialized = serializeStoryBody({ ...folded, depends_on });
|
|
401
|
-
// Carry audit dedup provenance
|
|
402
|
-
//
|
|
403
|
-
// `audit-
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
//
|
|
456
|
+
// Carry audit dedup provenance into the persisted body (Story #4877). The
|
|
457
|
+
// audit sweep's Single-plan path stamps the `audit-fingerprints` /
|
|
458
|
+
// `audit-semantic-keys` footers into the seed it hands `/plan`; without this
|
|
459
|
+
// the persisted Story carries no provenance and the next sweep re-files work
|
|
460
|
+
// it already planned. Mechanical on purpose — the authoring agent is not
|
|
461
|
+
// asked to notice HTML comments in a one-pager. A non-audit seed carries no
|
|
462
|
+
// footers, so this is a no-op there. Which identities reach *this* Story is
|
|
463
|
+
// `resolveProvenanceSource`'s call (Story #5045).
|
|
408
464
|
const { body } = carryProvenanceFooters({
|
|
409
|
-
from: opts
|
|
465
|
+
from: resolveProvenanceSource(provenance, opts),
|
|
410
466
|
into: serialized,
|
|
411
467
|
});
|
|
412
468
|
const fingerprint = planStoryFingerprint({ slug, title, body });
|
|
@@ -65,6 +65,53 @@ function renderWaveTableLines(waveTable) {
|
|
|
65
65
|
return ['| Order | Stories |', '| --- | --- |', ...rows];
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Render the shared-editor collisions beside the wave table (Story #5045).
|
|
70
|
+
*
|
|
71
|
+
* The wave table is a promise about parallelism — "these Stories can run
|
|
72
|
+
* together". `computeSharedEditorFindings` knows exactly where that promise
|
|
73
|
+
* breaks down: a path two same-wave Stories both write will conflict on every
|
|
74
|
+
* merge after the first. Until now those findings degraded to a `Logger.warn`
|
|
75
|
+
* on stderr and were discarded, so the comment carried the optimistic half of
|
|
76
|
+
* the analysis and none of the caveat. Rendering them here puts the promise
|
|
77
|
+
* and its known exceptions on one durable surface.
|
|
78
|
+
*
|
|
79
|
+
* Advisory by default and labelled as such — `planning.failOnSharedEditors`
|
|
80
|
+
* is the knob that makes a collision refuse the plan, and it stays off.
|
|
81
|
+
*
|
|
82
|
+
* @param {object[]|null} conflictFindings
|
|
83
|
+
* @returns {string[]} Lines to splice after the wave table, or `[]`.
|
|
84
|
+
*/
|
|
85
|
+
function renderSharedEditorLines(conflictFindings) {
|
|
86
|
+
const shared = (
|
|
87
|
+
Array.isArray(conflictFindings) ? conflictFindings : []
|
|
88
|
+
).filter((finding) => finding?.kind === 'shared-editor');
|
|
89
|
+
if (shared.length === 0) return [];
|
|
90
|
+
const rows = shared
|
|
91
|
+
.slice()
|
|
92
|
+
.sort((a, b) => String(a.path).localeCompare(String(b.path)))
|
|
93
|
+
.map(
|
|
94
|
+
(finding) =>
|
|
95
|
+
`| \`${finding.path}\` | ${(finding.storySlugs ?? [])
|
|
96
|
+
.map((slug) => `\`${slug}\``)
|
|
97
|
+
.join(', ')} |`,
|
|
98
|
+
);
|
|
99
|
+
return [
|
|
100
|
+
'',
|
|
101
|
+
`#### ⚠️ Known collisions (${shared.length} shared file(s))`,
|
|
102
|
+
'',
|
|
103
|
+
'| Path | Stories in the same order |',
|
|
104
|
+
'| --- | --- |',
|
|
105
|
+
...rows,
|
|
106
|
+
'',
|
|
107
|
+
'_These Stories are scheduled to run together **and** write the same ' +
|
|
108
|
+
'file — expect a merge conflict on every landing after the first. Add a ' +
|
|
109
|
+
'`depends_on` edge to serialize them, or move the shared edit into one ' +
|
|
110
|
+
'Story. Advisory: `planning.failOnSharedEditors` turns this into a ' +
|
|
111
|
+
'refusal and is off by default._',
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
|
|
68
115
|
/**
|
|
69
116
|
* Build the `plan-summary` structured-comment body.
|
|
70
117
|
*
|
|
@@ -81,6 +128,7 @@ export function buildPlanSummaryCommentBody({
|
|
|
81
128
|
mode = 'stories',
|
|
82
129
|
planMetricsLine = null,
|
|
83
130
|
stories = null,
|
|
131
|
+
conflictFindings = null,
|
|
84
132
|
// legacy unused knobs kept so older test call sites don't crash mid-migration
|
|
85
133
|
single = null,
|
|
86
134
|
amend = null,
|
|
@@ -132,6 +180,7 @@ export function buildPlanSummaryCommentBody({
|
|
|
132
180
|
'#### Delivery order (`depends_on`)',
|
|
133
181
|
'',
|
|
134
182
|
...renderWaveTableLines(waveTable),
|
|
183
|
+
...renderSharedEditorLines(conflictFindings),
|
|
135
184
|
'',
|
|
136
185
|
`_Deliver with \`${deliverCommand}\` — \`/deliver\` resolves the dependency graph from live state, so edges may point at Stories from earlier plan runs._`,
|
|
137
186
|
].join('\n');
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
// .agents/scripts/lib/orchestration/
|
|
1
|
+
// .agents/scripts/lib/orchestration/pr-watch.js
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* pr-watch.js — the required-check poll loop for an open PR.
|
|
4
|
+
* Story #2256 (Epic #2172).
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* **Not a listener, and no longer shelved among them.** This shipped as the
|
|
7
|
+
* `Watcher` lifecycle listener subscribing to `pr.created`. Story #5006
|
|
8
|
+
* deleted the class but left the plain primitive at
|
|
9
|
+
* `lifecycle/listeners/watcher.js` so its CLI consumer's import stayed
|
|
10
|
+
* stable. Story #5024 then retired the bus itself, which took the listener
|
|
11
|
+
* concept — and the `listeners/` directory — with it, so the primitive moved
|
|
12
|
+
* here beside [`merge-poll.js`](./merge-poll.js), the home Story #4545 chose
|
|
13
|
+
* for `MergeWatcher`'s surviving parts. The only production consumer,
|
|
14
|
+
* `pr-watch-with-update.js`, has always driven {@link watchPrToTerminal}
|
|
15
|
+
* directly, with no bus.
|
|
11
16
|
*
|
|
12
17
|
* Critical contract:
|
|
13
18
|
* - Required-check **names** are resolved from `gh pr checks` at
|
|
@@ -16,29 +21,18 @@
|
|
|
16
21
|
* branch-protection ruleset on GitHub is the source of truth at
|
|
17
22
|
* watch time. This guards against config drift (a config file that
|
|
18
23
|
* hasn't been updated after a protection rule changed on GitHub
|
|
19
|
-
* would otherwise cause the
|
|
24
|
+
* would otherwise cause the watch to either skip a required check
|
|
20
25
|
* or wait for a removed one indefinitely).
|
|
21
26
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* replay defence, this is sufficient — re-running `/deliver` after
|
|
26
|
-
* a crash will produce a NEW seqId and the listener legitimately
|
|
27
|
-
* re-runs the poll loop (which is itself idempotent: the outcome map
|
|
28
|
-
* always reflects the live GitHub state).
|
|
29
|
-
*
|
|
30
|
-
* Side-effect firewall: the listener shells out to `gh` and records
|
|
31
|
-
* its outcome in the in-memory `classifications` log. It does NOT
|
|
32
|
-
* mutate ticket labels, post comments, call `notify`, or emit on the
|
|
33
|
-
* bus — the production consumer (`pr-watch-with-update.js`) drives
|
|
34
|
-
* `watchPrToTerminal` directly without a bus, and the retired
|
|
35
|
-
* `epic.watch.start` / `epic.watch.end` bus events went with the
|
|
36
|
-
* Epic-orchestration stratum.
|
|
27
|
+
* Side-effect firewall: the loop shells out to `gh` through injectable
|
|
28
|
+
* ports and returns a verdict. It does NOT mutate ticket labels, post
|
|
29
|
+
* comments, call `notify`, or write any ledger.
|
|
37
30
|
*/
|
|
38
31
|
|
|
39
32
|
import { spawnSync } from 'node:child_process';
|
|
40
33
|
|
|
41
|
-
import { parsePrNumberFromUrl } from '
|
|
34
|
+
import { parsePrNumberFromUrl } from '../github-url.js';
|
|
35
|
+
import { applyBehindUpdate } from './behind-recovery.js';
|
|
42
36
|
|
|
43
37
|
/**
|
|
44
38
|
* Map `gh pr checks` `state` values to the canonical lowercase outcome
|
|
@@ -121,8 +115,8 @@ export function normalizeCheckState(raw) {
|
|
|
121
115
|
}
|
|
122
116
|
|
|
123
117
|
/**
|
|
124
|
-
* Parse a PR number out of a PR URL.
|
|
125
|
-
* `
|
|
118
|
+
* Parse a PR number out of a PR URL. Callers hand in the URL `gh pr
|
|
119
|
+
* create` returned; `gh pr checks` accepts either the URL or the
|
|
126
120
|
* number — we pass the URL through verbatim, but the helper still
|
|
127
121
|
* exists for tests asserting we never silently coerce a malformed URL.
|
|
128
122
|
*
|
|
@@ -184,9 +178,9 @@ function ghPrChecks({ prUrl, cwd, repo, spawnFn = spawnSync }) {
|
|
|
184
178
|
}
|
|
185
179
|
|
|
186
180
|
/**
|
|
187
|
-
* Default `gh pr view` spawn — probes `mergeStateStatus` so the
|
|
181
|
+
* Default `gh pr view` spawn — probes `mergeStateStatus` so the watch loop
|
|
188
182
|
* can detect the BEHIND condition (PR head is behind its base branch)
|
|
189
|
-
* AFTER every required check is green.
|
|
183
|
+
* AFTER every required check is green. Injectable so tests can stub.
|
|
190
184
|
*/
|
|
191
185
|
function ghPrView({ prUrl, cwd, repo, spawnFn = spawnSync }) {
|
|
192
186
|
const result = spawnFn(
|
|
@@ -377,8 +371,8 @@ function defaultSleep(ms) {
|
|
|
377
371
|
* an empty stdout) are logged and skipped — the outer cap eventually
|
|
378
372
|
* short-circuits if `gh` is unrecoverably broken.
|
|
379
373
|
*
|
|
380
|
-
* Exported so
|
|
381
|
-
* call
|
|
374
|
+
* Exported so {@link watchPrToTerminal}'s BEHIND-recovery outer loop can
|
|
375
|
+
* call it once per CI cycle without duplicating the inner logic.
|
|
382
376
|
*
|
|
383
377
|
* @param {object} opts
|
|
384
378
|
* @param {string} opts.prUrl
|
|
@@ -434,9 +428,10 @@ export async function pollUntilTerminal({
|
|
|
434
428
|
* commit after each. Plain async function with NO bus coupling: it
|
|
435
429
|
* shells out to `gh` (via injectable spawns) and returns the verdict.
|
|
436
430
|
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
431
|
+
* The load-bearing primitive the `pr-watch-with-update.js` CLI drives.
|
|
432
|
+
* Story #3902 introduced it so the (since-retired) `Watcher` listener and
|
|
433
|
+
* the CLI could not drift apart on polling or BEHIND-recovery; the CLI is
|
|
434
|
+
* now its only caller.
|
|
440
435
|
*
|
|
441
436
|
* @param {object} opts
|
|
442
437
|
* @param {string} opts.prUrl PR URL or number (passed to `gh` verbatim).
|
|
@@ -463,11 +458,10 @@ export async function pollUntilTerminal({
|
|
|
463
458
|
* real `setTimeout`-backed sleep; tests override with a no-op.
|
|
464
459
|
* @param {{ info?: Function, warn?: Function, debug?: Function }} opts.logger
|
|
465
460
|
* @param {{status:number,stdout:string,stderr:string}} [opts.firstProbe]
|
|
466
|
-
* Optional already-issued `gh pr checks` result.
|
|
467
|
-
*
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
* and the loop issues the first probe itself.
|
|
461
|
+
* Optional already-issued `gh pr checks` result. A caller that has
|
|
462
|
+
* already probed once to resolve the required-check names threads it
|
|
463
|
+
* here so the loop does not double-spend the first `gh pr checks` call.
|
|
464
|
+
* Omit it (the CLI path) and the loop issues the first probe itself.
|
|
471
465
|
* @returns {Promise<{
|
|
472
466
|
* outcomes: object,
|
|
473
467
|
* requiredChecks: string[],
|
|
@@ -502,8 +496,8 @@ export async function watchPrToTerminal({
|
|
|
502
496
|
firstProbe,
|
|
503
497
|
}) {
|
|
504
498
|
// First probe: resolve the required-check name set at runtime. Reuse a
|
|
505
|
-
// caller-supplied probe (
|
|
506
|
-
//
|
|
499
|
+
// caller-supplied probe (issued to resolve the required-check names) so
|
|
500
|
+
// we never double-spend the first `gh` call.
|
|
507
501
|
const first = firstProbe ?? ghPrChecksFn({ prUrl, cwd, repo });
|
|
508
502
|
// `gh` exits 8 when checks are still pending; this is expected and
|
|
509
503
|
// does not indicate failure. Any other non-zero status with no
|
|
@@ -579,6 +573,9 @@ export async function watchPrToTerminal({
|
|
|
579
573
|
// Bounded by `maxUpdates` so a racing base branch can't ping-pong
|
|
580
574
|
// indefinitely.
|
|
581
575
|
if (!allTerminal(outcomes) || !allGreen(outcomes)) break;
|
|
576
|
+
// Budget is checked before the `gh pr view` spawn so an exhausted arm
|
|
577
|
+
// costs no extra round-trip; the shared helper re-checks it as a
|
|
578
|
+
// fail-safe, and its callback therefore never fires on this path.
|
|
582
579
|
if (updatesApplied >= maxUpdates) break;
|
|
583
580
|
const view = ghPrViewFn({ prUrl, cwd, repo });
|
|
584
581
|
if (view.status !== 0) {
|
|
@@ -587,15 +584,26 @@ export async function watchPrToTerminal({
|
|
|
587
584
|
);
|
|
588
585
|
break;
|
|
589
586
|
}
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
587
|
+
const recovery = await applyBehindUpdate({
|
|
588
|
+
mergeStateStatus: parseMergeStateStatus(view.stdout),
|
|
589
|
+
updatesUsed: updatesApplied,
|
|
590
|
+
maxUpdates,
|
|
591
|
+
updateBranch: async () => {
|
|
592
|
+
const update = ghPrUpdateBranchFn({ prUrl, cwd, repo });
|
|
593
|
+
return update.status === 0
|
|
594
|
+
? { ok: true }
|
|
595
|
+
: {
|
|
596
|
+
ok: false,
|
|
597
|
+
detail: `status=${update.status}: ${update.stderr}`,
|
|
598
|
+
};
|
|
599
|
+
},
|
|
600
|
+
onUpdateFailed: (detail) =>
|
|
601
|
+
logger.warn?.(`[Watcher] gh pr update-branch failed (${detail})`),
|
|
602
|
+
});
|
|
603
|
+
// Anything but a landed fast-forward ends the arm: not BEHIND means
|
|
604
|
+
// there is nothing to recover, and a failed update must not silently
|
|
605
|
+
// re-poll as though the head moved.
|
|
606
|
+
if (!recovery.updated) break;
|
|
599
607
|
updatesApplied += 1;
|
|
600
608
|
logger.info?.(
|
|
601
609
|
`[Watcher] PR BEHIND base — issued gh pr update-branch (#${updatesApplied}/${maxUpdates}); re-polling required checks.`,
|
|
@@ -643,161 +651,3 @@ export async function watchPrToTerminal({
|
|
|
643
651
|
stillRunning,
|
|
644
652
|
};
|
|
645
653
|
}
|
|
646
|
-
|
|
647
|
-
/**
|
|
648
|
-
* Watcher listener.
|
|
649
|
-
*/
|
|
650
|
-
export class Watcher {
|
|
651
|
-
/**
|
|
652
|
-
* @param {object} opts
|
|
653
|
-
* @param {object} opts.bus
|
|
654
|
-
* @param {string} [opts.cwd]
|
|
655
|
-
* @param {number} [opts.pollIntervalMs] default 10_000.
|
|
656
|
-
* @param {number} [opts.maxPolls] safety cap on iterations; default
|
|
657
|
-
* 180 (≈30 min @ 10s).
|
|
658
|
-
* @param {number} [opts.maxUpdates] cap on `gh pr update-branch`
|
|
659
|
-
* recovery calls per `pr.created` event; default 3. Mirrors the
|
|
660
|
-
* legacy `pr-watch-with-update` cap so a racing base branch
|
|
661
|
-
* can't induce an infinite update-branch ping-pong.
|
|
662
|
-
* @param {number} [opts.maxResumes] Story #4358: how many times to
|
|
663
|
-
* re-arm the poll loop after the cap fires with checks still pending
|
|
664
|
-
* (and none failed) before declaring `still-running`; default 0.
|
|
665
|
-
* @param {Function} [opts.ghPrChecksFn] override for tests.
|
|
666
|
-
* @param {Function} [opts.ghPrViewFn] override for tests; resolves
|
|
667
|
-
* `mergeStateStatus` for the BEHIND-recovery gate.
|
|
668
|
-
* @param {Function} [opts.ghPrUpdateBranchFn] override for tests;
|
|
669
|
-
* issues the fast-forward update on the PR.
|
|
670
|
-
* @param {Function} [opts.sleepFn] override for tests.
|
|
671
|
-
* @param {{ info?: Function, warn?: Function, debug?: Function }} [opts.logger]
|
|
672
|
-
*/
|
|
673
|
-
constructor(opts = {}) {
|
|
674
|
-
if (!opts.bus || typeof opts.bus.on !== 'function') {
|
|
675
|
-
throw new TypeError('Watcher requires a bus with on()');
|
|
676
|
-
}
|
|
677
|
-
this.bus = opts.bus;
|
|
678
|
-
this.cwd = opts.cwd ?? process.cwd();
|
|
679
|
-
this.pollIntervalMs = Number.isInteger(opts.pollIntervalMs)
|
|
680
|
-
? opts.pollIntervalMs
|
|
681
|
-
: 10_000;
|
|
682
|
-
this.maxPolls = Number.isInteger(opts.maxPolls) ? opts.maxPolls : 180;
|
|
683
|
-
this.maxUpdates =
|
|
684
|
-
Number.isInteger(opts.maxUpdates) && opts.maxUpdates >= 0
|
|
685
|
-
? opts.maxUpdates
|
|
686
|
-
: 3;
|
|
687
|
-
this.maxResumes =
|
|
688
|
-
Number.isInteger(opts.maxResumes) && opts.maxResumes >= 0
|
|
689
|
-
? opts.maxResumes
|
|
690
|
-
: 0;
|
|
691
|
-
this.ghPrChecksFn = opts.ghPrChecksFn ?? ghPrChecks;
|
|
692
|
-
this.ghPrViewFn = opts.ghPrViewFn ?? ghPrView;
|
|
693
|
-
this.ghPrUpdateBranchFn = opts.ghPrUpdateBranchFn ?? ghPrUpdateBranch;
|
|
694
|
-
this.sleepFn = opts.sleepFn ?? defaultSleep;
|
|
695
|
-
this.logger = opts.logger ?? console;
|
|
696
|
-
/** @type {Set<string>} `${event}:${seqId}` idempotency cache. */
|
|
697
|
-
this._seen = new Set();
|
|
698
|
-
/**
|
|
699
|
-
* Classification log — every `pr.created` we observe lands here
|
|
700
|
-
* with the outcome (`watched`, `failed`, `skipped-duplicate`,
|
|
701
|
-
* `still-running`, `timed-out`). Mirrors the Finalizer / Reconciler
|
|
702
|
-
* "no silent skip" surface.
|
|
703
|
-
*/
|
|
704
|
-
this.classifications = [];
|
|
705
|
-
this.events = Object.freeze(['pr.created']);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
register() {
|
|
709
|
-
return this.events.map((event) =>
|
|
710
|
-
this.bus.on(event, async (ctx) => this.handle(ctx)),
|
|
711
|
-
);
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
async handle({ event, seqId, payload }) {
|
|
715
|
-
const key = `${event}:${seqId}`;
|
|
716
|
-
if (this._seen.has(key)) {
|
|
717
|
-
this.classifications.push({
|
|
718
|
-
event,
|
|
719
|
-
seqId,
|
|
720
|
-
outcome: 'skipped',
|
|
721
|
-
reason: 'duplicate-seqId',
|
|
722
|
-
});
|
|
723
|
-
this.logger.debug?.(`[Watcher] skip duplicate ${key} (idempotent)`);
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
this._seen.add(key);
|
|
727
|
-
|
|
728
|
-
const prUrl = payload?.prUrl;
|
|
729
|
-
if (typeof prUrl !== 'string' || prUrl.length === 0) {
|
|
730
|
-
this.classifications.push({
|
|
731
|
-
event,
|
|
732
|
-
seqId,
|
|
733
|
-
outcome: 'failed',
|
|
734
|
-
reason: 'no-pr-url',
|
|
735
|
-
});
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// First probe: resolve the required-check name set at runtime BEFORE
|
|
740
|
-
// the (potentially long) poll loop runs. We thread this probe into
|
|
741
|
-
// `watchPrToTerminal` (via `firstProbe`) so the shared loop reuses it
|
|
742
|
-
// instead of double-spending the first `gh pr checks` call.
|
|
743
|
-
const first = this.ghPrChecksFn({ prUrl, cwd: this.cwd });
|
|
744
|
-
const firstEntries = parseGhPrChecks(first.stdout);
|
|
745
|
-
if (firstEntries.length === 0 && first.status !== 0 && first.status !== 8) {
|
|
746
|
-
this.classifications.push({
|
|
747
|
-
event,
|
|
748
|
-
seqId,
|
|
749
|
-
outcome: 'failed',
|
|
750
|
-
reason: `gh-checks-failed:status=${first.status}`,
|
|
751
|
-
});
|
|
752
|
-
this.logger.warn?.(
|
|
753
|
-
`[Watcher] gh pr checks failed (status=${first.status}): ${first.stderr}`,
|
|
754
|
-
);
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
const requiredChecks = firstEntries.map((e) => e.name);
|
|
759
|
-
|
|
760
|
-
// Delegate the poll + BEHIND-recovery loop to the shared plain
|
|
761
|
-
// primitive so the CLI (`pr-watch-with-update.js`) and this listener
|
|
762
|
-
// run identical logic.
|
|
763
|
-
const { polls, updatesApplied, resumesApplied, terminal, stillRunning } =
|
|
764
|
-
await watchPrToTerminal({
|
|
765
|
-
prUrl,
|
|
766
|
-
cwd: this.cwd,
|
|
767
|
-
maxPolls: this.maxPolls,
|
|
768
|
-
maxUpdates: this.maxUpdates,
|
|
769
|
-
maxResumes: this.maxResumes,
|
|
770
|
-
pollIntervalMs: this.pollIntervalMs,
|
|
771
|
-
ghPrChecksFn: this.ghPrChecksFn,
|
|
772
|
-
ghPrViewFn: this.ghPrViewFn,
|
|
773
|
-
ghPrUpdateBranchFn: this.ghPrUpdateBranchFn,
|
|
774
|
-
sleepFn: this.sleepFn,
|
|
775
|
-
logger: this.logger,
|
|
776
|
-
firstProbe: first,
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
// `still-running` (slow CI, not red) is a distinct classification from
|
|
780
|
-
// a genuine `timed-out` — reserved for a check that never went
|
|
781
|
-
// terminal within the poll cap AND the resume budget while none
|
|
782
|
-
// failed. `watched` covers every terminal arm (green or red).
|
|
783
|
-
const outcome = terminal
|
|
784
|
-
? 'watched'
|
|
785
|
-
: stillRunning
|
|
786
|
-
? 'still-running'
|
|
787
|
-
: 'timed-out';
|
|
788
|
-
this.classifications.push({
|
|
789
|
-
event,
|
|
790
|
-
seqId,
|
|
791
|
-
outcome,
|
|
792
|
-
polls,
|
|
793
|
-
updatesApplied,
|
|
794
|
-
resumesApplied,
|
|
795
|
-
requiredChecks: requiredChecks.length,
|
|
796
|
-
});
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
reset() {
|
|
800
|
-
this._seen.clear();
|
|
801
|
-
this.classifications = [];
|
|
802
|
-
}
|
|
803
|
-
}
|