mandrel 1.94.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +59 -73
- package/.agents/agents/story-worker.md +12 -13
- package/.agents/docs/SDLC.md +489 -1285
- package/.agents/docs/agentrc-reference.json +177 -67
- package/.agents/docs/configuration.md +104 -138
- package/.agents/docs/execution-reference.md +17 -20
- package/.agents/docs/quality-gates.md +13 -19
- package/.agents/docs/workflows.md +3 -3
- package/.agents/instructions.md +70 -81
- package/.agents/rules/ci-remediation.md +8 -12
- package/.agents/rules/git-conventions-reference.md +6 -7
- package/.agents/rules/git-conventions.md +16 -22
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +54 -214
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
- package/.agents/schemas/risk-verdict.schema.json +0 -13
- package/.agents/scripts/analyze-execution.js +1 -1
- package/.agents/scripts/audit-to-stories.js +7 -7
- package/.agents/scripts/boot-sweep.js +1 -1
- package/.agents/scripts/check-lifecycle-lint.js +6 -9
- package/.agents/scripts/check-prepush-recovery.js +1 -1
- package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
- package/.agents/scripts/lib/Logger.js +6 -10
- package/.agents/scripts/lib/audit-suite/runner.js +2 -2
- package/.agents/scripts/lib/audit-suite/selector.js +5 -5
- package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
- package/.agents/scripts/lib/baselines/kernel.js +206 -18
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -4
- package/.agents/scripts/lib/baselines/reader.js +1 -6
- package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
- package/.agents/scripts/lib/checks/core-bare-clean.js +2 -2
- package/.agents/scripts/lib/checks/index.js +2 -1
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +23 -21
- package/.agents/scripts/lib/cli/standard-args.js +13 -22
- package/.agents/scripts/lib/cli-args.js +16 -7
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/config/ci.js +6 -31
- package/.agents/scripts/lib/config/delivery-routing.js +45 -29
- package/.agents/scripts/lib/config/explain.js +55 -36
- package/.agents/scripts/lib/config/limits.js +17 -58
- package/.agents/scripts/lib/config/paths.js +0 -2
- package/.agents/scripts/lib/config/quality.js +1 -1
- package/.agents/scripts/lib/config/runners.js +17 -50
- package/.agents/scripts/lib/config/temp-paths.js +19 -14
- package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
- package/.agents/scripts/lib/config-resolver.js +2 -7
- package/.agents/scripts/lib/config-settings-schema-delivery.js +24 -148
- package/.agents/scripts/lib/config-settings-schema-quality.js +8 -14
- package/.agents/scripts/lib/config-settings-schema.js +52 -38
- package/.agents/scripts/lib/dependency-parser.js +3 -2
- package/.agents/scripts/lib/doc-tiers.js +2 -2
- package/.agents/scripts/lib/duplicate-search.js +211 -41
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +5 -5
- package/.agents/scripts/lib/framework-version.js +2 -3
- package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
- package/.agents/scripts/lib/git-branch-lifecycle.js +17 -22
- package/.agents/scripts/lib/git-utils.js +32 -6
- package/.agents/scripts/lib/github/framework-repo.js +6 -0
- package/.agents/scripts/lib/label-constants.js +10 -23
- package/.agents/scripts/lib/label-taxonomy.js +9 -43
- package/.agents/scripts/lib/observability/active-story-env.js +1 -1
- package/.agents/scripts/lib/observability/hook-heartbeat.js +20 -52
- package/.agents/scripts/lib/observability/source-classifier.js +3 -3
- package/.agents/scripts/lib/onboard/init-tail.js +1 -3
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +8 -5
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +83 -20
- package/.agents/scripts/lib/orchestration/code-review.js +20 -268
- package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +1 -1
- package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
- package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
- package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +8 -8
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +2 -2
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +17 -43
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
- package/.agents/scripts/lib/orchestration/merge-block-class.js +1 -1
- package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
- package/.agents/scripts/lib/orchestration/plan-context.js +248 -266
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +61 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +97 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +223 -854
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +361 -0
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +35 -108
- package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +13 -13
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/risk-verdict.js +3 -4
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +2 -2
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +8 -20
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -2
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
- package/.agents/scripts/lib/orchestration/preflight-cache.js +5 -5
- package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +155 -0
- package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +8 -8
- package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
- package/.agents/scripts/lib/orchestration/review-depth.js +26 -12
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
- package/.agents/scripts/lib/orchestration/run-epilogue.js +426 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +95 -41
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +16 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +40 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +11 -3
- package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
- package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
- package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -12
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +237 -0
- package/.agents/scripts/lib/orchestration/story-init-remote.js +47 -0
- package/.agents/scripts/lib/orchestration/story-plan-state.js +48 -0
- package/.agents/scripts/lib/orchestration/{epic-runner → story-progress}/story-run-progress-writer.js +3 -3
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -18
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +11 -61
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +189 -373
- package/.agents/scripts/lib/orchestration/ticket-validator.js +2 -7
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +0 -25
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +29 -26
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -5
- package/.agents/scripts/lib/planning-corpus.js +16 -11
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +36 -0
- package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
- package/.agents/scripts/lib/story-adjacency.js +11 -14
- package/.agents/scripts/lib/story-body/story-body.js +124 -70
- package/.agents/scripts/lib/story-plan.js +2 -4
- package/.agents/scripts/lib/templates/decomposer-prompts.js +46 -45
- package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
- package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +26 -26
- package/.agents/scripts/lib/validation-evidence.js +1 -1
- package/.agents/scripts/lib/wave-runner/ready-set.js +6 -6
- package/.agents/scripts/lib/workspace-provisioner.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +5 -7
- package/.agents/scripts/lint-issue-body.js +71 -40
- package/.agents/scripts/mandrel-update-preflight.js +1 -1
- package/.agents/scripts/notify.js +4 -3
- package/.agents/scripts/plan-context.js +64 -74
- package/.agents/scripts/plan-persist.js +121 -280
- package/.agents/scripts/plan-run-epilogue.js +97 -0
- package/.agents/scripts/providers/github/issues.js +17 -33
- package/.agents/scripts/providers/github/mappers.js +0 -12
- package/.agents/scripts/providers/github/tickets.js +2 -5
- package/.agents/scripts/resolve-plan-run.js +117 -0
- package/.agents/scripts/signals-view.js +24 -19
- package/.agents/scripts/single-story-close.js +11 -14
- package/.agents/scripts/single-story-confirm-merge.js +39 -23
- package/.agents/scripts/single-story-init.js +29 -20
- package/.agents/scripts/stories-wave-tick.js +6 -6
- package/.agents/scripts/story-plan.js +26 -47
- package/.agents/scripts/update-ticket-state.js +6 -15
- package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
- package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
- package/.agents/skills/core/scope-triage/SKILL.md +28 -172
- package/.agents/skills/skills.index.json +3 -43
- package/.agents/starter-agentrc.json +0 -5
- package/.agents/templates/agent-protocol.md +9 -10
- package/.agents/workflows/audit-architecture.md +3 -3
- package/.agents/workflows/audit-clean-code.md +3 -3
- package/.agents/workflows/audit-dependencies.md +3 -3
- package/.agents/workflows/audit-devops.md +3 -3
- package/.agents/workflows/audit-documentation.md +5 -5
- package/.agents/workflows/audit-lighthouse.md +3 -3
- package/.agents/workflows/audit-navigability.md +3 -2
- package/.agents/workflows/audit-performance.md +3 -3
- package/.agents/workflows/audit-privacy.md +3 -3
- package/.agents/workflows/audit-quality.md +3 -3
- package/.agents/workflows/audit-security.md +3 -3
- package/.agents/workflows/audit-seo.md +3 -3
- package/.agents/workflows/audit-sre.md +3 -3
- package/.agents/workflows/audit-to-stories.md +20 -20
- package/.agents/workflows/audit-ux-ui.md +3 -3
- package/.agents/workflows/deliver.md +122 -174
- package/.agents/workflows/git-cleanup.md +3 -4
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +16 -29
- package/.agents/workflows/helpers/code-review.md +70 -193
- package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +12 -14
- package/.agents/workflows/helpers/{single-story-deliver.md → deliver-story.md} +113 -139
- package/.agents/workflows/helpers/diagnose.md +10 -10
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/helpers/signals.md +16 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +48 -64
- package/.agents/workflows/mandrel-update.md +2 -1
- package/.agents/workflows/plan.md +112 -145
- package/.agents/workflows/qa-assist.md +24 -30
- package/.agents/workflows/qa-explore.md +29 -38
- package/.agents/workflows/qa-run.md +2 -2
- package/README.md +9 -8
- package/docs/CHANGELOG.md +30 -0
- package/lib/migrations/index.js +6 -5
- package/package.json +2 -2
- package/.agents/agents/retro.md +0 -42
- package/.agents/personas/architect.md +0 -113
- package/.agents/personas/devops-engineer.md +0 -38
- package/.agents/personas/engineer.md +0 -33
- package/.agents/personas/project-manager.md +0 -114
- package/.agents/personas/qa-engineer.md +0 -95
- package/.agents/personas/security-engineer.md +0 -111
- package/.agents/personas/technical-writer.md +0 -101
- package/.agents/schemas/dispatch-manifest.json +0 -232
- package/.agents/schemas/epic-spec.schema.json +0 -153
- package/.agents/schemas/lifecycle/slice.end.schema.json +0 -21
- package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +0 -20
- package/.agents/schemas/lifecycle/slice.start.schema.json +0 -17
- package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
- package/.agents/scripts/bookkeeping-reconcile.js +0 -117
- package/.agents/scripts/dispatcher.js +0 -295
- package/.agents/scripts/epic-audit-prepare.js +0 -497
- package/.agents/scripts/epic-audit-recheck.js +0 -274
- package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
- package/.agents/scripts/epic-deliver-preflight.js +0 -462
- package/.agents/scripts/epic-deliver-prepare.js +0 -852
- package/.agents/scripts/epic-execute-record-wave.js +0 -449
- package/.agents/scripts/epic-plan-clarity.js +0 -211
- package/.agents/scripts/epic-plan-healthcheck.js +0 -581
- package/.agents/scripts/epic-reconcile.js +0 -625
- package/.agents/scripts/lib/baseline-snapshot.js +0 -979
- package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
- package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
- package/.agents/scripts/lib/config/lifecycle.js +0 -40
- package/.agents/scripts/lib/config/preflight.js +0 -58
- package/.agents/scripts/lib/config/retro.js +0 -77
- package/.agents/scripts/lib/epic-merge-lock.js +0 -322
- package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
- package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -539
- package/.agents/scripts/lib/orchestration/deliver-route.js +0 -173
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -621
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
- package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
- package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
- package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +0 -270
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -465
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -284
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
- package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
- package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
- package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
- package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
- package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
- package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
- package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
- package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
- package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
- package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
- package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
- package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
- package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
- package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
- package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
- package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
- package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
- package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
- package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
- package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -302
- package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
- package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
- package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
- package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
- package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
- package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
- package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
- package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
- package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
- package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
- package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
- package/.agents/scripts/lib/push-epic-retry.js +0 -209
- package/.agents/scripts/lib/spec/index.js +0 -36
- package/.agents/scripts/lib/spec/loader.js +0 -425
- package/.agents/scripts/lib/spec/state.js +0 -208
- package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
- package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
- package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
- package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
- package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
- package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
- package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
- package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
- package/.agents/scripts/lib/wave-runner/tick.js +0 -754
- package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
- package/.agents/scripts/lifecycle-emit.js +0 -510
- package/.agents/scripts/retro-run.js +0 -218
- package/.agents/scripts/slice-phase.js +0 -361
- package/.agents/scripts/standalone-feedback-rollup.js +0 -188
- package/.agents/scripts/story-close.js +0 -294
- package/.agents/scripts/story-init.js +0 -599
- package/.agents/scripts/story-phase.js +0 -369
- package/.agents/scripts/wave-tick.js +0 -464
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
- package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
- package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
- package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
- package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -383
- package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
- package/.agents/workflows/helpers/deliver-epic-reference.md +0 -547
- package/.agents/workflows/helpers/deliver-epic-single.md +0 -331
- package/.agents/workflows/helpers/deliver-epic.md +0 -998
- package/.agents/workflows/helpers/deliver-stories.md +0 -450
- package/.agents/workflows/helpers/epic-audit.md +0 -189
- package/.agents/workflows/helpers/epic-deliver-story.md +0 -436
- package/.agents/workflows/helpers/epic-testing.md +0 -125
- package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
- package/.agents/workflows/helpers/plan-epic.md +0 -353
- package/.agents/workflows/helpers/plan-story.md +0 -251
- package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
- /package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +0 -0
|
@@ -1,673 +0,0 @@
|
|
|
1
|
-
// .agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js
|
|
2
|
-
/**
|
|
3
|
-
* Finalizer — lifecycle listener that owns the finalize-phase side
|
|
4
|
-
* effects gated on a successful acceptance reconciliation. Story #2253
|
|
5
|
-
* / Task #2254 (Epic #2172); promoted to a fully bus-owned writer in
|
|
6
|
-
* Story #2894 / Task #2917 (Epic #2880).
|
|
7
|
-
*
|
|
8
|
-
* Subscribes to:
|
|
9
|
-
* - `acceptance.reconcile.ok` → run finalize.
|
|
10
|
-
* - `acceptance.reconcile.waived` → run finalize. Story #2893 split
|
|
11
|
-
* the `acceptance::n-a` waiver path out of `.skipped` so the
|
|
12
|
-
* Finalizer can route waived Epics through to PR creation while
|
|
13
|
-
* `.skipped` (empty-spec) still terminates without a PR.
|
|
14
|
-
* `.failed` is already routed to `epic.blocked` via the
|
|
15
|
-
* AcceptanceReconciler and never reaches the Finalizer.
|
|
16
|
-
*
|
|
17
|
-
* Side effects executed inside `handle()`:
|
|
18
|
-
* 1. Emit `epic.finalize.start`.
|
|
19
|
-
* 2. Auto-graduate non-blocking findings in a SINGLE pass over the
|
|
20
|
-
* unified `verification-results` comment (best-effort; never throws).
|
|
21
|
-
* Story #4411 folded the former code-review + audit-results
|
|
22
|
-
* structured-comment contracts into one comment; the lens-aware
|
|
23
|
-
* audit-results graduator is the single canonical reader (its
|
|
24
|
-
* 🟢→`suggestion` severity vocabulary matches `findings-renderer.js`).
|
|
25
|
-
* Running a second code-review pass over the same comment would file
|
|
26
|
-
* every non-blocking finding twice, so only the audit-results
|
|
27
|
-
* graduation runs here.
|
|
28
|
-
* 3. Idempotency probe — `gh pr list --head epic/<id>` returns any
|
|
29
|
-
* existing PR URL. If one exists, short-circuit to `pr.created`
|
|
30
|
-
* + `epic.finalize.end` carrying the existing URL.
|
|
31
|
-
* 4. Otherwise, invoke `runFinalizeFn`. The production default
|
|
32
|
-
* (`composeBusOwnedFinalize`) chains
|
|
33
|
-
* a. `openOrLocatePr({ epicId, headBranch, baseBranch })`
|
|
34
|
-
* b. `markPrReady({ pr })` when `delivery.ci.earlyPr` is on
|
|
35
|
-
* (Story #4359) — the wave-1 draft is flipped ready-for-review;
|
|
36
|
-
* skipped when `earlyPr` is off (the PR was opened here, never a
|
|
37
|
-
* draft).
|
|
38
|
-
* c. `postHandoffComment({ epicId, prNumber, prUrl, provider })`
|
|
39
|
-
* and returns `{ prNumber, prUrl, handoff }`. (Story #4324 retired
|
|
40
|
-
* the `closePlanningTickets` sweep with the context-ticket classes —
|
|
41
|
-
* there are no planning tickets to close.)
|
|
42
|
-
* 5. Emit `pr.created` then `epic.finalize.end`.
|
|
43
|
-
*
|
|
44
|
-
* Why the Finalizer does NOT emit `epic.merge.ready` (Story #3367):
|
|
45
|
-
* `epic.merge.ready` is the SOLE trigger AutomergeArmer subscribes to,
|
|
46
|
-
* and AutomergeArmer's `epic.merge.armed` cascades synchronously
|
|
47
|
-
* through MergeWatcher → Cleaner → BranchCleaner (branch reap). If the
|
|
48
|
-
* Finalizer emitted `epic.merge.ready` directly, firing `epic.close.end`
|
|
49
|
-
* would run that entire destructive cascade in one synchronous pass —
|
|
50
|
-
* arming auto-merge and reaping the `epic/<id>` branch BEFORE the PR is
|
|
51
|
-
* merged, and BYPASSING the AutomergePredicate disqualification gate
|
|
52
|
-
* (which only ever fires on `epic.watch.end`). The Finalizer's job ends
|
|
53
|
-
* at opening the PR (`pr.created` + `epic.finalize.end`). The auto-merge
|
|
54
|
-
* arm flows ONLY through the gated path: `pr.created` → `Watcher` →
|
|
55
|
-
* `epic.watch.end` → `AutomergePredicate` → `epic.merge.ready` →
|
|
56
|
-
* `AutomergeArmer`. AutomergePredicate is the SOLE emitter of
|
|
57
|
-
* `epic.merge.ready` (merge-gate-ordering invariant).
|
|
58
|
-
*
|
|
59
|
-
* Idempotency contract (AC-10): two-layer defence.
|
|
60
|
-
* 1. Per-instance `Set<string>` of `${event}:${seqId}` keys — bus-
|
|
61
|
-
* level replays short-circuit.
|
|
62
|
-
* 2. The `gh pr list --head` probe + `openOrLocatePr`'s internal
|
|
63
|
-
* locate path — both defend against cross-process re-runs
|
|
64
|
-
* (`/deliver` restarted on the same branch after a crash).
|
|
65
|
-
*
|
|
66
|
-
* Side-effect firewall: the listener emits on the bus, shells out to
|
|
67
|
-
* `gh`/`git` (via the helpers), and upserts the `epic-handoff`
|
|
68
|
-
* structured comment via `postHandoffComment`. It does NOT mutate Epic
|
|
69
|
-
* state labels or call `notify` directly — those listeners receive
|
|
70
|
-
* `pr.created` / `epic.finalize.end` and own their own side effects.
|
|
71
|
-
*/
|
|
72
|
-
|
|
73
|
-
import { spawnSync } from 'node:child_process';
|
|
74
|
-
import { getCiDelivery } from '../../../config/ci.js';
|
|
75
|
-
import {
|
|
76
|
-
graduateAuditResults as defaultGraduateAuditResults,
|
|
77
|
-
isAutoFileEnabled as isAuditResultsAutoFileEnabled,
|
|
78
|
-
} from '../../../feedback-loop/audit-results-graduator.js';
|
|
79
|
-
import { parsePrNumberFromUrl } from '../../../github-url.js';
|
|
80
|
-
import {
|
|
81
|
-
markPrReady as defaultMarkPrReady,
|
|
82
|
-
openOrLocatePr as defaultOpenOrLocatePr,
|
|
83
|
-
} from '../../finalize/open-or-locate-pr.js';
|
|
84
|
-
import { postHandoffComment as defaultPostHandoffComment } from '../../finalize/post-handoff-comment.js';
|
|
85
|
-
import { probeRemoteBranch as defaultProbeRemoteBranch } from '../../remote-verifier.js';
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Build the production default `runFinalizeFn` that composes the
|
|
89
|
-
* bus-owned finalize helpers. Exported as a factory so the listener can
|
|
90
|
-
* inject the provider (which the default needs but tests inject by
|
|
91
|
-
* supplying their own `runFinalizeFn` outright).
|
|
92
|
-
*
|
|
93
|
-
* Returns `{ prNumber, prUrl, handoff }` on success, or
|
|
94
|
-
* `{ blocker: { reason, detail } }` when a step fails with an
|
|
95
|
-
* unrecoverable error that should keep the Epic at `agent::blocked`.
|
|
96
|
-
*
|
|
97
|
-
* Story #4359 (Epic #4355) — early-PR draft mode. When `earlyPr` is on
|
|
98
|
-
* (the default resolved via `getCiDelivery`), the Epic PR already exists
|
|
99
|
-
* as a draft (opened at wave 1). Finalize then **locates** it (the
|
|
100
|
-
* `openOrLocatePr` probe short-circuits to `created: false`) and flips it
|
|
101
|
-
* ready-for-review via `markPrReady` rather than opening the draft as a
|
|
102
|
-
* `--draft`. When `earlyPr` is off, finalize opens the PR here with no
|
|
103
|
-
* draft — the pre-Story close-time timing. In both modes the title/body
|
|
104
|
-
* contract is identical and `markPrReady` is a no-op on an already-ready
|
|
105
|
-
* PR, so replay stays idempotent.
|
|
106
|
-
*
|
|
107
|
-
* Issue #4483 — deterministic land-or-block backstop. Before opening (or
|
|
108
|
-
* readying) the PR, finalize asserts the delivery branch `epic/<id>`
|
|
109
|
-
* actually exists on origin. A delivery that was never pushed — e.g. an
|
|
110
|
-
* agent that built the Epic inline on local `main` and skipped the
|
|
111
|
-
* orchestration — MUST surface as an explicit
|
|
112
|
-
* `delivery-branch-missing-on-origin` blocker (which keeps the Epic at
|
|
113
|
-
* `agent::blocked`), never a declared success. The probe is bounded
|
|
114
|
-
* (timeout + SIGKILL) so a hung remote degrades to a blocker too.
|
|
115
|
-
*
|
|
116
|
-
* @param {{
|
|
117
|
-
* provider?: object|null,
|
|
118
|
-
* earlyPr?: boolean,
|
|
119
|
-
* openOrLocatePrFn?: typeof defaultOpenOrLocatePr,
|
|
120
|
-
* markPrReadyFn?: typeof defaultMarkPrReady,
|
|
121
|
-
* postHandoffCommentFn?: typeof defaultPostHandoffComment,
|
|
122
|
-
* probeRemoteBranchFn?: typeof defaultProbeRemoteBranch,
|
|
123
|
-
* }} deps
|
|
124
|
-
*/
|
|
125
|
-
export function composeBusOwnedFinalize(deps = {}) {
|
|
126
|
-
const openOrLocatePrFn = deps.openOrLocatePrFn ?? defaultOpenOrLocatePr;
|
|
127
|
-
const markPrReadyFn = deps.markPrReadyFn ?? defaultMarkPrReady;
|
|
128
|
-
const postHandoffCommentFn =
|
|
129
|
-
deps.postHandoffCommentFn ?? defaultPostHandoffComment;
|
|
130
|
-
const probeRemoteBranchFn =
|
|
131
|
-
deps.probeRemoteBranchFn ?? defaultProbeRemoteBranch;
|
|
132
|
-
const provider = deps.provider ?? null;
|
|
133
|
-
const earlyPr = deps.earlyPr !== false;
|
|
134
|
-
|
|
135
|
-
return async function runBusOwnedFinalize({ epicId, cwd } = {}) {
|
|
136
|
-
if (!Number.isInteger(epicId) || epicId < 1) {
|
|
137
|
-
return {
|
|
138
|
-
blocker: {
|
|
139
|
-
reason: 'invalid-epicId',
|
|
140
|
-
detail: `runFinalizeFn called with invalid epicId ${epicId}`,
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Issue #4483 backstop — the delivery branch MUST be on origin before
|
|
146
|
-
// finalize declares any success. A never-pushed branch is the silent
|
|
147
|
-
// local-main failure shape; block explicitly with the probe detail.
|
|
148
|
-
let branchProbe;
|
|
149
|
-
try {
|
|
150
|
-
branchProbe = probeRemoteBranchFn({ branch: `epic/${epicId}`, cwd });
|
|
151
|
-
} catch (err) {
|
|
152
|
-
branchProbe = { exists: false, detail: err?.message ?? String(err) };
|
|
153
|
-
}
|
|
154
|
-
if (!branchProbe.exists) {
|
|
155
|
-
return {
|
|
156
|
-
blocker: {
|
|
157
|
-
reason: 'delivery-branch-missing-on-origin',
|
|
158
|
-
detail: `epic/${epicId} is not on origin — the delivery was never pushed; refusing to finalize (issue #4483). Probe: ${branchProbe.detail}`,
|
|
159
|
-
},
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
let openResult;
|
|
164
|
-
try {
|
|
165
|
-
openResult = await openOrLocatePrFn({
|
|
166
|
-
epicId,
|
|
167
|
-
headBranch: `epic/${epicId}`,
|
|
168
|
-
baseBranch: 'main',
|
|
169
|
-
cwd,
|
|
170
|
-
});
|
|
171
|
-
} catch (err) {
|
|
172
|
-
return {
|
|
173
|
-
blocker: {
|
|
174
|
-
reason: 'open-or-locate-pr-failed',
|
|
175
|
-
detail: err?.message ?? String(err),
|
|
176
|
-
},
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
if (
|
|
180
|
-
!openResult ||
|
|
181
|
-
!Number.isInteger(openResult.prNumber) ||
|
|
182
|
-
typeof openResult.url !== 'string'
|
|
183
|
-
) {
|
|
184
|
-
return {
|
|
185
|
-
blocker: {
|
|
186
|
-
reason: 'open-or-locate-pr-empty',
|
|
187
|
-
detail: 'openOrLocatePr returned no { prNumber, url } envelope',
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Story #4359: when earlyPr is on, the located PR is the wave-1 draft —
|
|
193
|
-
// flip it ready-for-review. `gh pr ready` on an already-ready PR is a
|
|
194
|
-
// no-op, so a replay (or an earlyPr-off PR that was never a draft) is
|
|
195
|
-
// safe. A failure here is a hard blocker: leaving the PR a draft would
|
|
196
|
-
// silently park the merge gate.
|
|
197
|
-
if (earlyPr) {
|
|
198
|
-
try {
|
|
199
|
-
await markPrReadyFn({ pr: openResult.url, cwd });
|
|
200
|
-
} catch (err) {
|
|
201
|
-
return {
|
|
202
|
-
blocker: {
|
|
203
|
-
reason: 'mark-pr-ready-failed',
|
|
204
|
-
detail: err?.message ?? String(err),
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// The handoff comment requires a provider. The lifecycle-emit CLI
|
|
211
|
-
// may construct the Finalizer without one; in that case the step
|
|
212
|
-
// short-circuits (the run itself still succeeds — the PR is open and
|
|
213
|
-
// the bus can arm auto-merge).
|
|
214
|
-
let handoff = null;
|
|
215
|
-
if (provider) {
|
|
216
|
-
try {
|
|
217
|
-
handoff = await postHandoffCommentFn({
|
|
218
|
-
epicId,
|
|
219
|
-
prNumber: openResult.prNumber,
|
|
220
|
-
prUrl: openResult.url,
|
|
221
|
-
provider,
|
|
222
|
-
});
|
|
223
|
-
} catch (err) {
|
|
224
|
-
// Handoff is best-effort — failing to upsert the marker comment
|
|
225
|
-
// should not roll back the PR open. Surface as a non-blocker so
|
|
226
|
-
// the Finalizer still emits pr.created and arms downstream.
|
|
227
|
-
handoff = {
|
|
228
|
-
marker: 'epic-handoff',
|
|
229
|
-
commentId: null,
|
|
230
|
-
error: err?.message ?? String(err),
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return {
|
|
236
|
-
epicId,
|
|
237
|
-
prNumber: openResult.prNumber,
|
|
238
|
-
prUrl: openResult.url,
|
|
239
|
-
created: openResult.created,
|
|
240
|
-
handoff,
|
|
241
|
-
};
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Parse `gh pr list --head <branch> --json url --jq '.[0].url'` output
|
|
247
|
-
* into a PR URL or null. Pure — exported for tests so the regex pin is
|
|
248
|
-
* explicit and reviewable.
|
|
249
|
-
*
|
|
250
|
-
* Accepted forms:
|
|
251
|
-
* - `https://github.com/owner/repo/pull/123\n` — typical happy path.
|
|
252
|
-
* - empty / whitespace — no PR open.
|
|
253
|
-
* - JSON array `[{"url":"…"}]` — when the caller did not pass `--jq`.
|
|
254
|
-
*/
|
|
255
|
-
export function extractPrUrl(stdout) {
|
|
256
|
-
const trimmed = String(stdout || '').trim();
|
|
257
|
-
if (trimmed.length === 0) return null;
|
|
258
|
-
if (trimmed.startsWith('[')) {
|
|
259
|
-
try {
|
|
260
|
-
const parsed = JSON.parse(trimmed);
|
|
261
|
-
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
262
|
-
const url = parsed[0]?.url;
|
|
263
|
-
if (typeof url === 'string' && /^https:\/\/github\.com\//.test(url)) {
|
|
264
|
-
return url;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return null;
|
|
268
|
-
} catch {
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
const match = trimmed.match(/^https:\/\/github\.com\/[^\s]+\/pull\/(\d+)/);
|
|
273
|
-
return match ? match[0] : null;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Default bounded timeout for the finalize idempotency probe. Story #4415
|
|
278
|
-
* (Epic #4406) — a hung `gh pr list` spawn previously blocked finalize
|
|
279
|
-
* forever; the probe now SIGKILLs the child at this bound so a stalled
|
|
280
|
-
* `gh` cannot park the merge gate.
|
|
281
|
-
*/
|
|
282
|
-
export const GH_PR_LIST_TIMEOUT_MS = 30000;
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Default `gh` spawn used by the listener's idempotency probe.
|
|
286
|
-
* Mirrors the `shell: false` contract `openOrLocatePr` and the other
|
|
287
|
-
* listener helpers use so a future Windows audit doesn't have to grep
|
|
288
|
-
* across two modules. Bounded by `timeoutMs` (default
|
|
289
|
-
* {@link GH_PR_LIST_TIMEOUT_MS}) + `killSignal: 'SIGKILL'` so a hung `gh`
|
|
290
|
-
* spawn cannot block finalize indefinitely (Story #4415). Exported so
|
|
291
|
-
* tests can stub.
|
|
292
|
-
*/
|
|
293
|
-
export function ghPrListHead({
|
|
294
|
-
epicBranch,
|
|
295
|
-
cwd,
|
|
296
|
-
spawnFn = spawnSync,
|
|
297
|
-
timeoutMs = GH_PR_LIST_TIMEOUT_MS,
|
|
298
|
-
}) {
|
|
299
|
-
const result = spawnFn(
|
|
300
|
-
'gh',
|
|
301
|
-
['pr', 'list', '--head', epicBranch, '--json', 'url', '--jq', '.[0].url'],
|
|
302
|
-
{
|
|
303
|
-
cwd,
|
|
304
|
-
encoding: 'utf-8',
|
|
305
|
-
shell: false,
|
|
306
|
-
timeout: timeoutMs,
|
|
307
|
-
killSignal: 'SIGKILL',
|
|
308
|
-
},
|
|
309
|
-
);
|
|
310
|
-
return {
|
|
311
|
-
status: result.status ?? 1,
|
|
312
|
-
stdout: result.stdout ?? '',
|
|
313
|
-
stderr: result.stderr ?? '',
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Finalizer listener.
|
|
319
|
-
*/
|
|
320
|
-
export class Finalizer {
|
|
321
|
-
/**
|
|
322
|
-
* @param {object} opts
|
|
323
|
-
* @param {object} opts.bus
|
|
324
|
-
* @param {number} opts.epicId
|
|
325
|
-
* @param {string} [opts.cwd]
|
|
326
|
-
* @param {boolean} [opts.fullScope] forwarded to `runFinalizeFn`;
|
|
327
|
-
* defaults to false (diff-scope).
|
|
328
|
-
* @param {Function} [opts.runFinalizeFn] override of the composed
|
|
329
|
-
* bus-owned finalize default; tests inject their own stub.
|
|
330
|
-
* @param {Function} [opts.ghPrListHeadFn] override of the
|
|
331
|
-
* idempotency probe.
|
|
332
|
-
* @param {object} [opts.provider] Ticketing provider forwarded to
|
|
333
|
-
* the default `runFinalizeFn` (for `postHandoffComment`) and to the
|
|
334
|
-
* graduators.
|
|
335
|
-
* @param {object} [opts.config] Resolved agent config; forwarded to
|
|
336
|
-
* the graduators.
|
|
337
|
-
* @param {{owner:string,repo:string}} [opts.currentRepo]
|
|
338
|
-
* @param {{owner:string,repo:string}} [opts.frameworkRepo]
|
|
339
|
-
* @param {Function} [opts.graduateAuditResultsFn]
|
|
340
|
-
* @param {{ info?: Function, warn?: Function, debug?: Function }} [opts.logger]
|
|
341
|
-
*/
|
|
342
|
-
constructor(opts = {}) {
|
|
343
|
-
if (
|
|
344
|
-
!opts.bus ||
|
|
345
|
-
typeof opts.bus.on !== 'function' ||
|
|
346
|
-
typeof opts.bus.emit !== 'function'
|
|
347
|
-
) {
|
|
348
|
-
throw new TypeError('Finalizer requires a bus with on() and emit()');
|
|
349
|
-
}
|
|
350
|
-
if (!Number.isInteger(opts.epicId) || opts.epicId < 1) {
|
|
351
|
-
throw new TypeError('Finalizer requires a numeric epicId');
|
|
352
|
-
}
|
|
353
|
-
this.bus = opts.bus;
|
|
354
|
-
this.epicId = opts.epicId;
|
|
355
|
-
this.cwd = opts.cwd ?? process.cwd();
|
|
356
|
-
this.fullScope = opts.fullScope === true;
|
|
357
|
-
this.provider = opts.provider ?? null;
|
|
358
|
-
// Story #4359: gate the finalize PR-open/ready branch on
|
|
359
|
-
// `delivery.ci.earlyPr` (default true) resolved via getCiDelivery.
|
|
360
|
-
// On → the wave-1 draft is located and marked ready; off → the PR is
|
|
361
|
-
// opened here at close time (pre-Story timing).
|
|
362
|
-
const earlyPr = getCiDelivery(opts.config ?? null).earlyPr;
|
|
363
|
-
this.runFinalizeFn =
|
|
364
|
-
opts.runFinalizeFn ??
|
|
365
|
-
composeBusOwnedFinalize({ provider: this.provider, earlyPr });
|
|
366
|
-
this.ghPrListHeadFn = opts.ghPrListHeadFn ?? ghPrListHead;
|
|
367
|
-
// ultrareview bug_007: the existing-PR short-circuit must run the
|
|
368
|
-
// handoff-comment upsert (idempotent) so crash-recovery replays
|
|
369
|
-
// don't skip it. Stored as an instance override so tests can swap
|
|
370
|
-
// it with a no-op stub.
|
|
371
|
-
this.postHandoffCommentFn =
|
|
372
|
-
opts.postHandoffCommentFn ?? defaultPostHandoffComment;
|
|
373
|
-
this.config = opts.config ?? null;
|
|
374
|
-
this.currentRepo = opts.currentRepo ?? null;
|
|
375
|
-
this.frameworkRepo = opts.frameworkRepo ?? null;
|
|
376
|
-
this.graduateAuditResultsFn =
|
|
377
|
-
opts.graduateAuditResultsFn ?? defaultGraduateAuditResults;
|
|
378
|
-
this.logger = opts.logger ?? console;
|
|
379
|
-
/** @type {Set<string>} `${event}:${seqId}` idempotency cache. */
|
|
380
|
-
this._seen = new Set();
|
|
381
|
-
/**
|
|
382
|
-
* Classification log — every `acceptance.reconcile.ok` or `.waived`
|
|
383
|
-
* we observe lands here with the outcome (`opened`, `existing`,
|
|
384
|
-
* `failed`, `skipped-duplicate`). Mirrors the BlockerHandler /
|
|
385
|
-
* Reconciler "no silent skip" surface.
|
|
386
|
-
*/
|
|
387
|
-
this.classifications = [];
|
|
388
|
-
this.events = Object.freeze([
|
|
389
|
-
'acceptance.reconcile.ok',
|
|
390
|
-
'acceptance.reconcile.waived',
|
|
391
|
-
]);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
register() {
|
|
395
|
-
return this.events.map((event) =>
|
|
396
|
-
this.bus.on(event, async (ctx) => this.handle(ctx)),
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
async handle({ event, seqId, payload: _payload }) {
|
|
401
|
-
const key = `${event}:${seqId}`;
|
|
402
|
-
if (this._seen.has(key)) {
|
|
403
|
-
this.classifications.push({
|
|
404
|
-
event,
|
|
405
|
-
seqId,
|
|
406
|
-
outcome: 'skipped',
|
|
407
|
-
reason: 'duplicate-seqId',
|
|
408
|
-
});
|
|
409
|
-
this.logger.debug?.(`[Finalizer] skip duplicate ${key} (idempotent)`);
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
this._seen.add(key);
|
|
413
|
-
|
|
414
|
-
const epicId = this.epicId;
|
|
415
|
-
const epicBranch = `epic/${epicId}`;
|
|
416
|
-
|
|
417
|
-
// 1. Announce finalize.start.
|
|
418
|
-
try {
|
|
419
|
-
await this.bus.emit('epic.finalize.start', { epicId });
|
|
420
|
-
} catch (err) {
|
|
421
|
-
this.classifications.push({
|
|
422
|
-
event,
|
|
423
|
-
seqId,
|
|
424
|
-
outcome: 'failed',
|
|
425
|
-
reason: `start-emit-failed:${err?.message ?? err}`,
|
|
426
|
-
});
|
|
427
|
-
this.logger.warn?.(
|
|
428
|
-
`[Finalizer] epic.finalize.start emit failed: ${err?.message ?? err}`,
|
|
429
|
-
);
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// 1b. Auto-graduate non-blocking findings in a SINGLE pass over the
|
|
434
|
-
// unified `verification-results` comment (Story #2615; unified in
|
|
435
|
-
// Story #4411). The lens-aware audit-results graduator is the sole
|
|
436
|
-
// canonical reader — a second code-review pass over the same comment
|
|
437
|
-
// would file every non-blocking finding twice.
|
|
438
|
-
await this._runAuditResultsGraduation();
|
|
439
|
-
|
|
440
|
-
// 2. Idempotency probe — does a PR already exist on the head
|
|
441
|
-
// branch? If yes, short-circuit to a pr.created emit with the
|
|
442
|
-
// existing URL. This is the AC-10 contract for the most-risky
|
|
443
|
-
// non-trivial idempotency case (cross-process re-run after a
|
|
444
|
-
// crash between `gh pr create` and `pr.created` emit). The
|
|
445
|
-
// bus-owned default `runFinalizeFn` also re-checks via
|
|
446
|
-
// `openOrLocatePr`; the listener-level probe is defence in
|
|
447
|
-
// depth and keeps the existing test surface stable.
|
|
448
|
-
const probe = this.ghPrListHeadFn({ epicBranch, cwd: this.cwd });
|
|
449
|
-
if (probe.status === 0) {
|
|
450
|
-
const existingUrl = extractPrUrl(probe.stdout);
|
|
451
|
-
if (existingUrl) {
|
|
452
|
-
this.logger.info?.(
|
|
453
|
-
`[Finalizer] PR already open for ${epicBranch}: ${existingUrl} — short-circuiting create.`,
|
|
454
|
-
);
|
|
455
|
-
const prNumber = parsePrNumberFromUrl(existingUrl);
|
|
456
|
-
// ultrareview bug_007: even on the short-circuit path we MUST
|
|
457
|
-
// run the handoff-comment upsert. The helper is idempotent —
|
|
458
|
-
// postHandoffComment edits an existing marker comment in place.
|
|
459
|
-
// Without this call, a crash-recovery replay against an
|
|
460
|
-
// already-open PR never posts the handoff comment, even though
|
|
461
|
-
// epic.merge.ready is still emitted.
|
|
462
|
-
try {
|
|
463
|
-
await this.postHandoffCommentFn({
|
|
464
|
-
epicId: this.epicId,
|
|
465
|
-
prNumber,
|
|
466
|
-
prUrl: existingUrl,
|
|
467
|
-
provider: this.provider,
|
|
468
|
-
cwd: this.cwd,
|
|
469
|
-
});
|
|
470
|
-
} catch (err) {
|
|
471
|
-
this.logger.warn?.(
|
|
472
|
-
`[Finalizer] postHandoffComment on short-circuit failed (swallowed; replay will retry): ${err?.message ?? err}`,
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
await this._emitFinalize({
|
|
476
|
-
event,
|
|
477
|
-
seqId,
|
|
478
|
-
prUrl: existingUrl,
|
|
479
|
-
prNumber,
|
|
480
|
-
epicBranch,
|
|
481
|
-
base: this._resolveBase(),
|
|
482
|
-
outcome: 'existing',
|
|
483
|
-
});
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
} else {
|
|
487
|
-
this.logger.warn?.(
|
|
488
|
-
`[Finalizer] gh pr list probe failed (status=${probe.status}): ${probe.stderr} — proceeding with create.`,
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// 3. Run the composed bus-owned finalize (or the test-injected
|
|
493
|
-
// `runFinalizeFn`). The default chains openOrLocatePr →
|
|
494
|
-
// postHandoffComment in order and returns
|
|
495
|
-
// `{ prNumber, prUrl, handoff }`.
|
|
496
|
-
let finalize;
|
|
497
|
-
try {
|
|
498
|
-
finalize = await this.runFinalizeFn({
|
|
499
|
-
epicId,
|
|
500
|
-
cwd: this.cwd,
|
|
501
|
-
fullScope: this.fullScope,
|
|
502
|
-
loggerImpl: this.logger,
|
|
503
|
-
});
|
|
504
|
-
} catch (err) {
|
|
505
|
-
this.classifications.push({
|
|
506
|
-
event,
|
|
507
|
-
seqId,
|
|
508
|
-
outcome: 'failed',
|
|
509
|
-
reason: `finalize-threw:${err?.message ?? err}`,
|
|
510
|
-
});
|
|
511
|
-
this.logger.warn?.(
|
|
512
|
-
`[Finalizer] runFinalizeFn threw (swallowed): ${err?.message ?? err}`,
|
|
513
|
-
);
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
if (finalize?.blocker) {
|
|
518
|
-
this.classifications.push({
|
|
519
|
-
event,
|
|
520
|
-
seqId,
|
|
521
|
-
outcome: 'failed',
|
|
522
|
-
reason: `blocker:${finalize.blocker.reason}`,
|
|
523
|
-
});
|
|
524
|
-
this.logger.warn?.(
|
|
525
|
-
`[Finalizer] finalize reported blocker (${finalize.blocker.reason}): ${finalize.blocker.detail ?? ''}`,
|
|
526
|
-
);
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
const prUrl = finalize?.prUrl;
|
|
530
|
-
if (typeof prUrl !== 'string' || prUrl.length === 0) {
|
|
531
|
-
this.classifications.push({
|
|
532
|
-
event,
|
|
533
|
-
seqId,
|
|
534
|
-
outcome: 'failed',
|
|
535
|
-
reason: 'no-pr-url',
|
|
536
|
-
});
|
|
537
|
-
this.logger.warn?.(
|
|
538
|
-
'[Finalizer] runFinalizeFn returned no prUrl — cannot emit pr.created.',
|
|
539
|
-
);
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
const prNumber =
|
|
543
|
-
typeof finalize?.prNumber === 'number'
|
|
544
|
-
? finalize.prNumber
|
|
545
|
-
: parsePrNumberFromUrl(prUrl);
|
|
546
|
-
|
|
547
|
-
await this._emitFinalize({
|
|
548
|
-
event,
|
|
549
|
-
seqId,
|
|
550
|
-
prUrl,
|
|
551
|
-
prNumber,
|
|
552
|
-
epicBranch,
|
|
553
|
-
base: this._resolveBase(),
|
|
554
|
-
outcome: 'opened',
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Invoke the audit-results graduator best-effort — the SINGLE canonical
|
|
560
|
-
* graduation pass over the unified `verification-results` comment. Wired
|
|
561
|
-
* into finalize so that non-blocking findings (high/medium/low/suggestion)
|
|
562
|
-
* get auto-filed as routed follow-up issues — Story #2615 / Epic #2586,
|
|
563
|
-
* unified into one pass in Story #4411 / Epic #4405. The lens-aware
|
|
564
|
-
* audit-results graduator is canonical because its 🟢→`suggestion`
|
|
565
|
-
* severity vocabulary matches `findings-renderer.js`; running a parallel
|
|
566
|
-
* code-review pass over the same comment would double-file every finding.
|
|
567
|
-
*/
|
|
568
|
-
async _runAuditResultsGraduation() {
|
|
569
|
-
if (!this.provider || !this.currentRepo) {
|
|
570
|
-
this.logger.debug?.(
|
|
571
|
-
'[Finalizer] audit-results graduation skipped: provider or currentRepo not wired',
|
|
572
|
-
);
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
if (!isAuditResultsAutoFileEnabled(this.config)) {
|
|
576
|
-
this.logger.debug?.(
|
|
577
|
-
'[Finalizer] audit-results graduation skipped: auditResultsAutoFile toggle disabled',
|
|
578
|
-
);
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
try {
|
|
582
|
-
const summary = await this.graduateAuditResultsFn({
|
|
583
|
-
epicId: this.epicId,
|
|
584
|
-
provider: this.provider,
|
|
585
|
-
config: this.config,
|
|
586
|
-
currentRepo: this.currentRepo,
|
|
587
|
-
frameworkRepo: this.frameworkRepo,
|
|
588
|
-
cwd: this.cwd,
|
|
589
|
-
logger: this.logger,
|
|
590
|
-
});
|
|
591
|
-
const filed = Array.isArray(summary?.filed) ? summary.filed.length : 0;
|
|
592
|
-
const skipped = Array.isArray(summary?.skipped)
|
|
593
|
-
? summary.skipped.length
|
|
594
|
-
: 0;
|
|
595
|
-
const errors = Array.isArray(summary?.errors) ? summary.errors.length : 0;
|
|
596
|
-
this.logger.info?.(
|
|
597
|
-
`[Finalizer] audit-results graduation: filed=${filed} skipped=${skipped} errors=${errors}`,
|
|
598
|
-
);
|
|
599
|
-
if (errors > 0) {
|
|
600
|
-
this.logger.warn?.(
|
|
601
|
-
`[Finalizer] audit-results graduator errors: ${summary.errors.join('; ')}`,
|
|
602
|
-
);
|
|
603
|
-
}
|
|
604
|
-
} catch (err) {
|
|
605
|
-
this.logger.warn?.(
|
|
606
|
-
`[Finalizer] audit-results graduator threw (swallowed): ${err?.message ?? err}`,
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
/**
|
|
612
|
-
* Emit the canonical finalize-success pair: `pr.created` →
|
|
613
|
-
* `epic.finalize.end` in strict order. Helper carved out so the
|
|
614
|
-
* existing-PR short-circuit and the freshly-opened path share the
|
|
615
|
-
* same emit sequence.
|
|
616
|
-
*
|
|
617
|
-
* The Finalizer deliberately STOPS at `epic.finalize.end` and never
|
|
618
|
-
* emits `epic.merge.ready` (Story #3367). Emitting `epic.merge.ready`
|
|
619
|
-
* here would let `epic.close.end` cascade synchronously into the
|
|
620
|
-
* auto-merge arm + branch reap, bypassing the AutomergePredicate gate.
|
|
621
|
-
* The auto-merge arm flows ONLY through `pr.created` → Watcher →
|
|
622
|
-
* `epic.watch.end` → AutomergePredicate → `epic.merge.ready`.
|
|
623
|
-
*
|
|
624
|
-
* `prNumber` stays in the classification log for trace/audit even
|
|
625
|
-
* though it is no longer threaded onto an `epic.merge.ready` payload.
|
|
626
|
-
*/
|
|
627
|
-
async _emitFinalize({
|
|
628
|
-
event,
|
|
629
|
-
seqId,
|
|
630
|
-
prUrl,
|
|
631
|
-
prNumber,
|
|
632
|
-
epicBranch,
|
|
633
|
-
base,
|
|
634
|
-
outcome,
|
|
635
|
-
}) {
|
|
636
|
-
this.classifications.push({ event, seqId, outcome, prUrl, prNumber });
|
|
637
|
-
try {
|
|
638
|
-
await this.bus.emit('pr.created', {
|
|
639
|
-
prUrl,
|
|
640
|
-
head: epicBranch,
|
|
641
|
-
base,
|
|
642
|
-
});
|
|
643
|
-
} catch (err) {
|
|
644
|
-
this.logger.warn?.(
|
|
645
|
-
`[Finalizer] pr.created emit failed (swallowed): ${err?.message ?? err}`,
|
|
646
|
-
);
|
|
647
|
-
}
|
|
648
|
-
try {
|
|
649
|
-
await this.bus.emit('epic.finalize.end', {
|
|
650
|
-
epicId: this.epicId,
|
|
651
|
-
prUrl,
|
|
652
|
-
});
|
|
653
|
-
} catch (err) {
|
|
654
|
-
this.logger.warn?.(
|
|
655
|
-
`[Finalizer] epic.finalize.end emit failed (swallowed): ${err?.message ?? err}`,
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
/**
|
|
661
|
-
* Resolve the base branch for the PR. Always `main` today; pulled
|
|
662
|
-
* into a helper so the listener owns the decision (and tests can
|
|
663
|
-
* stub when a non-`main` base is wired in).
|
|
664
|
-
*/
|
|
665
|
-
_resolveBase() {
|
|
666
|
-
return 'main';
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
reset() {
|
|
670
|
-
this._seen.clear();
|
|
671
|
-
this.classifications = [];
|
|
672
|
-
}
|
|
673
|
-
}
|