mandrel 1.94.0 → 2.1.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 +116 -99
- package/.agents/agents/acceptance-critic.md +9 -7
- package/.agents/agents/story-worker.md +45 -51
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +498 -1287
- package/.agents/docs/agentrc-reference.json +185 -80
- package/.agents/docs/configuration.md +132 -190
- package/.agents/docs/execution-reference.md +51 -25
- package/.agents/docs/quality-gates.md +25 -33
- package/.agents/docs/workflows.md +8 -8
- package/.agents/instructions.md +113 -139
- package/.agents/rules/ci-remediation.md +11 -15
- package/.agents/rules/git-conventions-reference.md +48 -58
- package/.agents/rules/git-conventions.md +16 -22
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +83 -254
- package/.agents/schemas/audit-rules.json +59 -1
- package/.agents/schemas/audit-rules.schema.json +33 -1
- package/.agents/schemas/lifecycle/README.md +1 -2
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
- package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
- package/.agents/schemas/signal-event.schema.json +3 -3
- package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +22 -66
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/audit-to-stories.js +7 -7
- package/.agents/scripts/boot-sweep.js +1 -1
- package/.agents/scripts/bootstrap.js +3 -3
- package/.agents/scripts/check-dead-exports.js +43 -104
- package/.agents/scripts/check-doc-links.js +2 -2
- package/.agents/scripts/check-lifecycle-lint.js +7 -10
- package/.agents/scripts/check-workflow-cli-lint.js +91 -0
- package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
- package/.agents/scripts/deliver-recover.js +122 -0
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/evidence-gate.js +20 -50
- package/.agents/scripts/generate-skills-index.js +17 -1
- package/.agents/scripts/generate-workflows-doc.js +4 -4
- package/.agents/scripts/lib/ITicketingProvider.js +1 -19
- package/.agents/scripts/lib/Logger.js +6 -10
- package/.agents/scripts/lib/audit-suite/runner.js +2 -2
- package/.agents/scripts/lib/audit-suite/selector.js +328 -28
- 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 -15
- package/.agents/scripts/lib/baselines/reader.js +1 -6
- package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
- package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +6 -3
- package/.agents/scripts/lib/checks/index.js +3 -2
- package/.agents/scripts/lib/checks/loop-health.js +12 -11
- package/.agents/scripts/lib/checks/state.js +17 -248
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +26 -24
- package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
- package/.agents/scripts/lib/cli/standard-args.js +13 -22
- package/.agents/scripts/lib/cli-args.js +39 -9
- package/.agents/scripts/lib/close-validation/gates.js +15 -15
- package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
- package/.agents/scripts/lib/close-validation/runner.js +13 -21
- package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
- package/.agents/scripts/lib/config/ci.js +6 -31
- package/.agents/scripts/lib/config/delivery-routing.js +52 -35
- package/.agents/scripts/lib/config/explain.js +61 -48
- package/.agents/scripts/lib/config/github.js +7 -5
- package/.agents/scripts/lib/config/limits.js +29 -80
- package/.agents/scripts/lib/config/paths.js +0 -2
- package/.agents/scripts/lib/config/quality.js +12 -15
- package/.agents/scripts/lib/config/runners.js +20 -66
- package/.agents/scripts/lib/config/temp-paths.js +30 -63
- 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 +55 -161
- package/.agents/scripts/lib/config-settings-schema-quality.js +17 -16
- package/.agents/scripts/lib/config-settings-schema.js +100 -60
- package/.agents/scripts/lib/dead-exports-knip.js +105 -0
- package/.agents/scripts/lib/dead-exports-mode.js +51 -0
- 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 +242 -41
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +23 -14
- package/.agents/scripts/lib/format-generated-json.js +97 -0
- package/.agents/scripts/lib/framework-version.js +19 -190
- package/.agents/scripts/lib/gh-exec.js +8 -0
- package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
- package/.agents/scripts/lib/git-branch-lifecycle.js +17 -180
- package/.agents/scripts/lib/git-utils.js +32 -20
- package/.agents/scripts/lib/github/framework-repo.js +6 -0
- package/.agents/scripts/lib/json-utils.js +1 -2
- package/.agents/scripts/lib/label-constants.js +10 -38
- package/.agents/scripts/lib/label-taxonomy.js +10 -55
- package/.agents/scripts/lib/observability/active-story-env.js +44 -165
- package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
- package/.agents/scripts/lib/observability/signal-validator.js +4 -4
- package/.agents/scripts/lib/observability/signals-writer.js +6 -82
- package/.agents/scripts/lib/observability/source-classifier.js +5 -5
- package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
- package/.agents/scripts/lib/onboard/init-tail.js +1 -3
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +105 -44
- package/.agents/scripts/lib/orchestration/code-review.js +78 -436
- package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +4 -4
- package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
- package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
- package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
- package/.agents/scripts/lib/orchestration/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/git-cleanup/phases/fast-forward.js +34 -0
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
- package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +17 -19
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +21 -43
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
- package/.agents/scripts/lib/orchestration/merge-block-class.js +77 -21
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
- package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
- package/.agents/scripts/lib/orchestration/plan-context.js +354 -282
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +4 -8
- package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
- package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +71 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +245 -0
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +432 -858
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +778 -0
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +47 -115
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
- 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 +52 -51
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
- package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
- package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
- package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
- package/.agents/scripts/lib/orchestration/review-depth.js +126 -47
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
- package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
- package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
- package/.agents/scripts/lib/orchestration/run-epilogue.js +784 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +25 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +86 -41
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +77 -32
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +44 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +275 -46
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- 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/format-autofix.js +10 -10
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +28 -42
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +298 -0
- package/.agents/scripts/lib/orchestration/story-init-remote.js +51 -0
- package/.agents/scripts/lib/orchestration/story-plan-state.js +33 -0
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +60 -25
- package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +130 -75
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +192 -377
- package/.agents/scripts/lib/orchestration/ticket-validator.js +123 -25
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -72
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +45 -55
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +66 -6
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
- package/.agents/scripts/lib/planning-corpus.js +12 -281
- package/.agents/scripts/lib/preflight-runner.js +4 -4
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
- package/.agents/scripts/lib/signals/index.js +4 -17
- package/.agents/scripts/lib/signals/read.js +35 -35
- package/.agents/scripts/lib/signals/schema.js +8 -11
- package/.agents/scripts/lib/signals/span-tree.js +7 -7
- package/.agents/scripts/lib/signals/write.js +0 -1
- package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
- package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
- package/.agents/scripts/lib/skills/parse-skill.js +16 -3
- package/.agents/scripts/lib/story-adjacency.js +17 -19
- package/.agents/scripts/lib/story-body/story-body.js +130 -75
- package/.agents/scripts/lib/story-plan.js +2 -4
- package/.agents/scripts/lib/templates/decomposer-prompts.js +51 -46
- package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
- package/.agents/scripts/lib/test-env.js +14 -1
- package/.agents/scripts/lib/test-tiers.js +0 -3
- package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +25 -39
- package/.agents/scripts/lib/validation-evidence.js +32 -60
- package/.agents/scripts/lib/wave-runner/ready-set.js +38 -12
- package/.agents/scripts/lib/workspace-provisioner.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +72 -25
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
- 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 +102 -80
- package/.agents/scripts/plan-persist.js +230 -279
- package/.agents/scripts/plan-run-epilogue.js +142 -0
- package/.agents/scripts/post-structured-comment.js +0 -38
- package/.agents/scripts/pr-watch-with-update.js +43 -22
- package/.agents/scripts/providers/github/compose.js +0 -1
- package/.agents/scripts/providers/github/errors.js +0 -19
- package/.agents/scripts/providers/github/issues.js +13 -39
- package/.agents/scripts/providers/github/mappers.js +5 -12
- package/.agents/scripts/providers/github/sub-issues.js +0 -47
- package/.agents/scripts/providers/github/tickets.js +33 -156
- package/.agents/scripts/providers/github.js +17 -6
- package/.agents/scripts/resolve-stories.js +236 -0
- package/.agents/scripts/run-coverage.js +4 -1
- package/.agents/scripts/run-lint.js +2 -2
- package/.agents/scripts/run-verify.js +31 -2
- package/.agents/scripts/signals-view.js +25 -21
- package/.agents/scripts/single-story-close.js +178 -26
- package/.agents/scripts/single-story-confirm-merge.js +313 -24
- package/.agents/scripts/single-story-init.js +35 -30
- package/.agents/scripts/stories-wave-tick.js +85 -10
- package/.agents/scripts/story-plan.js +28 -49
- package/.agents/scripts/update-ticket-state.js +14 -65
- package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
- package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
- package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
- package/.agents/skills/core/idea-refinement/SKILL.md +9 -9
- package/.agents/skills/core/scope-triage/SKILL.md +31 -172
- package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
- package/.agents/skills/core/security-and-hardening/reference.md +375 -0
- package/.agents/skills/skills.index.json +3 -53
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
- package/.agents/starter-agentrc.json +0 -5
- package/.agents/templates/agent-protocol.md +9 -10
- package/.agents/workflows/audit-architecture.md +6 -7
- package/.agents/workflows/audit-clean-code.md +7 -7
- package/.agents/workflows/audit-dependencies.md +3 -3
- package/.agents/workflows/audit-devops.md +3 -3
- package/.agents/workflows/audit-documentation.md +9 -10
- package/.agents/workflows/audit-lighthouse.md +11 -3
- package/.agents/workflows/audit-navigability.md +13 -2
- package/.agents/workflows/audit-performance.md +5 -6
- package/.agents/workflows/audit-privacy.md +3 -3
- package/.agents/workflows/audit-quality.md +11 -12
- package/.agents/workflows/audit-security.md +4 -5
- package/.agents/workflows/audit-seo.md +13 -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 +10 -3
- package/.agents/workflows/deliver.md +177 -176
- package/.agents/workflows/git-cleanup.md +5 -6
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +35 -40
- package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
- package/.agents/workflows/helpers/code-review.md +75 -196
- package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +83 -44
- package/.agents/workflows/helpers/deliver-story.md +606 -0
- package/.agents/workflows/helpers/diagnose.md +10 -10
- package/.agents/workflows/helpers/parallel-tooling.md +3 -3
- package/.agents/workflows/helpers/signals.md +16 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +66 -86
- package/.agents/workflows/mandrel-update.md +2 -1
- package/.agents/workflows/plan.md +277 -145
- package/.agents/workflows/qa-assist.md +27 -33
- package/.agents/workflows/qa-explore.md +29 -38
- package/.agents/workflows/qa-run.md +2 -6
- package/README.md +9 -8
- package/bin/mandrel.js +12 -1
- package/docs/CHANGELOG.md +70 -0
- package/lib/cli/registry.js +262 -19
- package/lib/cli/sync-agents.js +157 -0
- package/lib/cli/sync-commands.js +115 -6
- package/lib/cli/sync.js +168 -6
- package/lib/cli/update.js +105 -8
- package/lib/cli/version-helpers.js +131 -0
- package/lib/migrations/README.md +7 -5
- package/lib/migrations/index.js +12 -8
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
- package/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-perf-report.schema.json +0 -89
- package/.agents/schemas/epic-spec.schema.json +0 -153
- package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
- package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
- package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
- package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
- package/.agents/schemas/lifecycle/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/schemas/lifecycle/story.heartbeat.schema.json +0 -20
- package/.agents/schemas/risk-verdict.schema.json +0 -66
- package/.agents/schemas/story-perf-summary.schema.json +0 -73
- package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
- package/.agents/scripts/analyze-execution.js +0 -444
- package/.agents/scripts/bookkeeping-reconcile.js +0 -117
- package/.agents/scripts/check-prepush-recovery.js +0 -90
- 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/git-merge-orchestrator.js +0 -261
- package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
- package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -219
- package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
- package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
- package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -270
- 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/risk-verdict.js +0 -105
- 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/story-run-progress-writer.js +0 -400
- 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/error-journal.js +0 -139
- 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/emit-story-heartbeat.js +0 -155
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
- 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/merge-watcher.js +0 -665
- 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/plan-review-routing.js +0 -63
- package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
- package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -33
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
- package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -396
- package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
- package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
- 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-perf-heuristics.js +0 -275
- 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/analyze-execution/SKILL.md +0 -98
- 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/workflows/helpers/single-story-deliver.md +0 -557
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run-epilogue.js — real per-run closeout for `/deliver --run`.
|
|
3
|
+
*
|
|
4
|
+
* After the last Story in a multi-Story plan-run lands, this module:
|
|
5
|
+
* 1. Selects the cross-Story audit lens roster over the combined landed
|
|
6
|
+
* tip vs base (deterministic `selectAudits` — host walks lenses).
|
|
7
|
+
* 2. Rolls up friction follow-ups across every Story in the run and
|
|
8
|
+
* files/posts them on the primary Story.
|
|
9
|
+
* 3. Checks sibling Spec/acceptance coherence across Story bodies.
|
|
10
|
+
*
|
|
11
|
+
* There is no inert planner-only path: `planRunEpilogue` enumerates steps
|
|
12
|
+
* and `runPlanRunEpilogue` executes them. Single-Story runs skip the
|
|
13
|
+
* epilogue (`applicable: false`).
|
|
14
|
+
*
|
|
15
|
+
* @module lib/orchestration/run-epilogue
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { selectAudits } from '../audit-suite/index.js';
|
|
19
|
+
import { graduateRetroProposals } from '../feedback-loop/retro-proposals-graduator.js';
|
|
20
|
+
import { gitSpawn } from '../git-utils.js';
|
|
21
|
+
import { Logger } from '../Logger.js';
|
|
22
|
+
import { forEachLine } from '../observability/signals-writer.js';
|
|
23
|
+
import { composeRoutedProposals } from './retro-proposals.js';
|
|
24
|
+
import {
|
|
25
|
+
buildFollowUpsCommentBody,
|
|
26
|
+
resolveFollowUpRepos,
|
|
27
|
+
} from './story-follow-ups.js';
|
|
28
|
+
import { upsertStructuredComment } from './ticketing.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Canonical epilogue step kinds, in execution order.
|
|
32
|
+
* @type {readonly ['audit-roster', 'follow-up-rollup', 'sibling-coherence']}
|
|
33
|
+
*/
|
|
34
|
+
export const RUN_EPILOGUE_STEP_KINDS = Object.freeze([
|
|
35
|
+
'audit-roster',
|
|
36
|
+
'follow-up-rollup',
|
|
37
|
+
'sibling-coherence',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {string|number|{ id?: string|number, slug?: string }} entry
|
|
42
|
+
* @returns {string|null}
|
|
43
|
+
*/
|
|
44
|
+
function normalizeStoryId(entry) {
|
|
45
|
+
if (typeof entry === 'string') return entry.trim() || null;
|
|
46
|
+
if (typeof entry === 'number' && Number.isInteger(entry)) {
|
|
47
|
+
return String(entry);
|
|
48
|
+
}
|
|
49
|
+
if (!entry || typeof entry !== 'object') return null;
|
|
50
|
+
if (typeof entry.id === 'string' || Number.isInteger(entry.id)) {
|
|
51
|
+
return String(entry.id).trim() || null;
|
|
52
|
+
}
|
|
53
|
+
return typeof entry.slug === 'string' ? entry.slug.trim() || null : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {Array<string|number|{ id?: string|number, slug?: string }>} stories
|
|
58
|
+
* @returns {string[]}
|
|
59
|
+
*/
|
|
60
|
+
function normalizeStoryIds(stories) {
|
|
61
|
+
const list = Array.isArray(stories) ? stories : [];
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
const ids = [];
|
|
64
|
+
for (const entry of list) {
|
|
65
|
+
const id = normalizeStoryId(entry);
|
|
66
|
+
if (!id || seen.has(id)) continue;
|
|
67
|
+
seen.add(id);
|
|
68
|
+
ids.push(id);
|
|
69
|
+
}
|
|
70
|
+
return ids;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Enumerate the ordered epilogue steps for a completed run.
|
|
75
|
+
*
|
|
76
|
+
* @param {object} args
|
|
77
|
+
* @param {string} args.planRunId
|
|
78
|
+
* @param {Array<string|number|{ id?: string|number, slug?: string }>} args.stories
|
|
79
|
+
* @returns {object}
|
|
80
|
+
*/
|
|
81
|
+
export function planRunEpilogue({ planRunId, stories } = {}) {
|
|
82
|
+
const ids = normalizeStoryIds(stories);
|
|
83
|
+
const runId =
|
|
84
|
+
typeof planRunId === 'string' && planRunId.trim() !== ''
|
|
85
|
+
? planRunId.trim()
|
|
86
|
+
: null;
|
|
87
|
+
|
|
88
|
+
if (ids.length <= 1) {
|
|
89
|
+
return {
|
|
90
|
+
applicable: false,
|
|
91
|
+
planRunId: runId,
|
|
92
|
+
stories: ids,
|
|
93
|
+
steps: [],
|
|
94
|
+
reason:
|
|
95
|
+
ids.length === 0
|
|
96
|
+
? 'no Stories in run'
|
|
97
|
+
: 'single-Story run — per-Story close is the end; no run-scoped epilogue',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Positional `/deliver 101 102` has no plan-run label. Synthesize a
|
|
102
|
+
// stable adhoc id from the sorted Story set so the epilogue still
|
|
103
|
+
// anchors comments / audit roster without requiring `--run`.
|
|
104
|
+
const effectiveRunId =
|
|
105
|
+
runId ??
|
|
106
|
+
`adhoc-${[...ids].sort((a, b) => Number(a) - Number(b)).join('-')}`;
|
|
107
|
+
|
|
108
|
+
const steps = [
|
|
109
|
+
{
|
|
110
|
+
kind: 'audit-roster',
|
|
111
|
+
description: `Select cross-Story audit lenses for run ${effectiveRunId}`,
|
|
112
|
+
stories: ids,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
kind: 'follow-up-rollup',
|
|
116
|
+
description: `Friction follow-up roll-up for run ${effectiveRunId}`,
|
|
117
|
+
stories: ids,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
kind: 'sibling-coherence',
|
|
121
|
+
description: `Sibling-coherence check across the ${ids.length} Story specs of run ${effectiveRunId}`,
|
|
122
|
+
stories: ids,
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
applicable: true,
|
|
128
|
+
planRunId: effectiveRunId,
|
|
129
|
+
stories: ids,
|
|
130
|
+
steps,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* How many first-parent commits of the base ref to scan when looking for the
|
|
136
|
+
* run's landed squash-merges. The epilogue fires immediately after the run's
|
|
137
|
+
* last Story lands, so the run's merges sit within the first handful of
|
|
138
|
+
* commits; the limit only bounds the pathological case.
|
|
139
|
+
* @type {number}
|
|
140
|
+
*/
|
|
141
|
+
const BASE_SCAN_LIMIT = 500;
|
|
142
|
+
|
|
143
|
+
/** ASCII unit separator — cannot occur in a git commit subject. */
|
|
144
|
+
const FIELD_SEP = '\x1f';
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Read the base ref's first-parent history as `{ sha, parents, subject }`
|
|
148
|
+
* records, newest-first.
|
|
149
|
+
*
|
|
150
|
+
* @param {object} args
|
|
151
|
+
* @param {string} args.cwd
|
|
152
|
+
* @param {string} args.baseRef
|
|
153
|
+
* @param {number} args.scanLimit
|
|
154
|
+
* @param {{ gitSpawn: Function }} args.git
|
|
155
|
+
* @returns {{ ok: true, commits: Array<{sha: string, parents: string[], subject: string}> }
|
|
156
|
+
* | { ok: false, reason: string }}
|
|
157
|
+
*/
|
|
158
|
+
function readFirstParentHistory({ cwd, baseRef, scanLimit, git }) {
|
|
159
|
+
let result;
|
|
160
|
+
try {
|
|
161
|
+
result = git.gitSpawn(
|
|
162
|
+
cwd,
|
|
163
|
+
'log',
|
|
164
|
+
'--first-parent',
|
|
165
|
+
baseRef,
|
|
166
|
+
`--max-count=${scanLimit}`,
|
|
167
|
+
`--format=%H${FIELD_SEP}%P${FIELD_SEP}%s`,
|
|
168
|
+
);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
reason: `\`git log ${baseRef}\` could not be spawned: ${err?.message ?? String(err)}`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (result?.status !== 0) {
|
|
176
|
+
const detail =
|
|
177
|
+
String(result?.stderr ?? '').split('\n')[0] || 'unknown error';
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
reason: `\`git log ${baseRef}\` failed (is \`${baseRef}\` fetched?): ${detail}`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const commits = String(result.stdout ?? '')
|
|
184
|
+
.split('\n')
|
|
185
|
+
.map((line) => line.trim())
|
|
186
|
+
.filter(Boolean)
|
|
187
|
+
.map((line) => {
|
|
188
|
+
const [sha, parents, ...rest] = line.split(FIELD_SEP);
|
|
189
|
+
return {
|
|
190
|
+
sha,
|
|
191
|
+
parents: parents ? parents.split(' ').filter(Boolean) : [],
|
|
192
|
+
subject: rest.join(FIELD_SEP),
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
return { ok: true, commits };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* One trailing `(#<n>)` / `(refs #<n>)` marker, anchored at the end of what
|
|
200
|
+
* is left of a subject after the markers to its right have been peeled off.
|
|
201
|
+
*/
|
|
202
|
+
const TRAILING_MARKER_RE = /\s*\((?:refs\s+)?#(\d+)\)$/;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Peel the **trailing run** of `(#<n>)` markers off a squash subject.
|
|
206
|
+
*
|
|
207
|
+
* The shape this parses is fixed by the close pipeline plus GitHub: the PR
|
|
208
|
+
* title `normalizePrTitle` writes ends with `(#<storyId>)`, and GitHub's
|
|
209
|
+
* squash appends ` (#<prNumber>)` to it — so a landed Story merge reads
|
|
210
|
+
* `<subject> (#<storyId>) (#<prNumber>)` and the Story's own marker is the
|
|
211
|
+
* *second-to-last* marker, not the last one. Both are returned; the caller
|
|
212
|
+
* decides which ids it cares about.
|
|
213
|
+
*
|
|
214
|
+
* Why the trailing run rather than a substring scan (the bug this replaces):
|
|
215
|
+
* `subject.includes('(#101)')` matched the id **anywhere**, so an unrelated
|
|
216
|
+
* later commit quoting an old marker — canonically a revert, whose subject
|
|
217
|
+
* embeds the reverted title verbatim: `revert: "fix: x (#101) (#900)" (#950)`
|
|
218
|
+
* — anchored the run on a far-older commit and inflated the roster diff with
|
|
219
|
+
* everything in between. Peeling from the right stops at the first character
|
|
220
|
+
* that is not part of a marker (the closing quote, above), so a quoted marker
|
|
221
|
+
* is structurally out of reach.
|
|
222
|
+
*
|
|
223
|
+
* The `refs #` form is accepted because it is the other PR-title shape this
|
|
224
|
+
* repo's own history carries (`feat(x): … (refs #4575) (#4582)`) — the
|
|
225
|
+
* `refs #<id>` convention from `rules/git-conventions.md`. A plain substring
|
|
226
|
+
* scan for `(#4575)` never matched those at all.
|
|
227
|
+
*
|
|
228
|
+
* @param {string} subject
|
|
229
|
+
* @returns {number[]} Marker ids, right-to-left (PR number first).
|
|
230
|
+
*/
|
|
231
|
+
function trailingMarkerIds(subject) {
|
|
232
|
+
const ids = [];
|
|
233
|
+
let rest = typeof subject === 'string' ? subject.trimEnd() : '';
|
|
234
|
+
for (;;) {
|
|
235
|
+
const match = TRAILING_MARKER_RE.exec(rest);
|
|
236
|
+
if (!match) return ids;
|
|
237
|
+
ids.push(Number(match[1]));
|
|
238
|
+
rest = rest.slice(0, match.index).trimEnd();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Resolve the **pre-run base sha**: the commit the base branch pointed at
|
|
244
|
+
* before the run's first Story landed.
|
|
245
|
+
*
|
|
246
|
+
* Why not `origin/main...HEAD` (the bug this replaces): the epilogue runs
|
|
247
|
+
* *after* the run's last Story lands, so HEAD in the main checkout is either
|
|
248
|
+
* `origin/main` itself or an ancestor of it. The three-dot merge-base is then
|
|
249
|
+
* HEAD, and the diff is empty **by construction** — it never reported the
|
|
250
|
+
* run's real diff. Rolling HEAD back does not help, so branch-reaping /
|
|
251
|
+
* cleanup ordering is not the cause; the refs being compared are.
|
|
252
|
+
*
|
|
253
|
+
* The derivation walks the base ref's first-parent history for the run's
|
|
254
|
+
* landed squash-merges. Every Story PR title carries a `(#<storyId>)` suffix
|
|
255
|
+
* (guaranteed by `normalizePrTitle` in the close pipeline) and GitHub uses the
|
|
256
|
+
* PR title as the squash subject, so the marker is a reliable, offline handle
|
|
257
|
+
* that does **not** depend on the Story branches still existing. The earliest
|
|
258
|
+
* such merge's first parent is the pre-run base.
|
|
259
|
+
*
|
|
260
|
+
* @param {object} args
|
|
261
|
+
* @param {Array<string|number>} args.stories - Story ids in the run.
|
|
262
|
+
* @param {string} args.cwd
|
|
263
|
+
* @param {string} [args.baseRef] - Remote-tracking base ref, e.g. `origin/main`.
|
|
264
|
+
* @param {number} [args.scanLimit]
|
|
265
|
+
* @param {{ gitSpawn: Function }} [args.git]
|
|
266
|
+
* @returns {{ resolved: true, baseSha: string, mergeSha: string, storyId: number, baseRef: string }
|
|
267
|
+
* | { resolved: false, reason: string, baseRef: string }}
|
|
268
|
+
*/
|
|
269
|
+
export function resolveRunBaseSha({
|
|
270
|
+
stories,
|
|
271
|
+
cwd,
|
|
272
|
+
baseRef = 'origin/main',
|
|
273
|
+
scanLimit = BASE_SCAN_LIMIT,
|
|
274
|
+
git = { gitSpawn },
|
|
275
|
+
} = {}) {
|
|
276
|
+
const ids = (Array.isArray(stories) ? stories : [])
|
|
277
|
+
.map(Number)
|
|
278
|
+
.filter((n) => Number.isInteger(n) && n > 0);
|
|
279
|
+
if (ids.length === 0) {
|
|
280
|
+
return {
|
|
281
|
+
resolved: false,
|
|
282
|
+
baseRef,
|
|
283
|
+
reason:
|
|
284
|
+
'the run carries no numeric Story ids to match landed merges against',
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const history = readFirstParentHistory({ cwd, baseRef, scanLimit, git });
|
|
289
|
+
if (!history.ok) {
|
|
290
|
+
return { resolved: false, baseRef, reason: history.reason };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// `git log` is newest-first; walk backwards so the first hit is the
|
|
294
|
+
// *earliest* merge belonging to the run.
|
|
295
|
+
const wanted = new Set(ids);
|
|
296
|
+
for (let i = history.commits.length - 1; i >= 0; i -= 1) {
|
|
297
|
+
const commit = history.commits[i];
|
|
298
|
+
const hit = trailingMarkerIds(commit.subject).find((id) => wanted.has(id));
|
|
299
|
+
if (hit === undefined) continue;
|
|
300
|
+
const baseSha = commit.parents[0];
|
|
301
|
+
if (!baseSha) {
|
|
302
|
+
return {
|
|
303
|
+
resolved: false,
|
|
304
|
+
baseRef,
|
|
305
|
+
reason: `the earliest landed merge for the run (${commit.sha}, Story #${hit}) is a root commit — it has no first parent to use as the pre-run base`,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
resolved: true,
|
|
310
|
+
baseRef,
|
|
311
|
+
baseSha,
|
|
312
|
+
mergeSha: commit.sha,
|
|
313
|
+
storyId: hit,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
resolved: false,
|
|
319
|
+
baseRef,
|
|
320
|
+
reason: `no landed squash-merge carrying a \`(#<storyId>)\` marker for ${ids
|
|
321
|
+
.map((id) => `#${id}`)
|
|
322
|
+
.join(
|
|
323
|
+
', ',
|
|
324
|
+
)} was found in the last ${scanLimit} first-parent commits of \`${baseRef}\` — has the run landed?`,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* List the files the run changed: `<pre-run base>...<baseRef>`.
|
|
330
|
+
*
|
|
331
|
+
* @returns {{ ok: true, files: string[] } | { ok: false, reason: string }}
|
|
332
|
+
*/
|
|
333
|
+
function listChangedFiles({ cwd, baseSha, headRef, git }) {
|
|
334
|
+
const range = `${baseSha}...${headRef}`;
|
|
335
|
+
let result;
|
|
336
|
+
try {
|
|
337
|
+
result = git.gitSpawn(cwd, 'diff', '--name-only', range);
|
|
338
|
+
} catch (err) {
|
|
339
|
+
return {
|
|
340
|
+
ok: false,
|
|
341
|
+
reason: `\`git diff ${range}\` could not be spawned: ${err?.message ?? String(err)}`,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (result?.status !== 0) {
|
|
345
|
+
const detail =
|
|
346
|
+
String(result?.stderr ?? '').split('\n')[0] || 'unknown error';
|
|
347
|
+
return { ok: false, reason: `\`git diff ${range}\` failed: ${detail}` };
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
ok: true,
|
|
351
|
+
files: String(result.stdout ?? '')
|
|
352
|
+
.split('\n')
|
|
353
|
+
.map((line) => line.trim())
|
|
354
|
+
.filter(Boolean),
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Resolve the run's combined landed diff, or an explicit reason it could not
|
|
360
|
+
* be computed. Never conflates "could not compute" with "zero files changed".
|
|
361
|
+
*
|
|
362
|
+
* @returns {{ resolved: boolean, changedFiles: string[], baseSha: string|null,
|
|
363
|
+
* mergeSha: string|null, baseRef: string, reason: string|null }}
|
|
364
|
+
*/
|
|
365
|
+
function resolveCombinedDiff({ stories, cwd, baseRef, git }) {
|
|
366
|
+
const base = resolveRunBaseSha({ stories, cwd, baseRef, git });
|
|
367
|
+
if (!base.resolved) {
|
|
368
|
+
return {
|
|
369
|
+
resolved: false,
|
|
370
|
+
changedFiles: [],
|
|
371
|
+
baseSha: null,
|
|
372
|
+
mergeSha: null,
|
|
373
|
+
baseRef,
|
|
374
|
+
reason: base.reason,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const diff = listChangedFiles({
|
|
378
|
+
cwd,
|
|
379
|
+
baseSha: base.baseSha,
|
|
380
|
+
headRef: baseRef,
|
|
381
|
+
git,
|
|
382
|
+
});
|
|
383
|
+
if (!diff.ok) {
|
|
384
|
+
return {
|
|
385
|
+
resolved: false,
|
|
386
|
+
changedFiles: [],
|
|
387
|
+
baseSha: base.baseSha,
|
|
388
|
+
mergeSha: base.mergeSha,
|
|
389
|
+
baseRef,
|
|
390
|
+
reason: diff.reason,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
resolved: true,
|
|
395
|
+
changedFiles: diff.files,
|
|
396
|
+
baseSha: base.baseSha,
|
|
397
|
+
mergeSha: base.mergeSha,
|
|
398
|
+
baseRef,
|
|
399
|
+
reason: null,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* The diff line of the roster comment. An unresolved base MUST read as a
|
|
405
|
+
* loud failure, never as `Changed files considered: 0`.
|
|
406
|
+
*
|
|
407
|
+
* @param {ReturnType<typeof resolveCombinedDiff>} diff
|
|
408
|
+
* @returns {string[]}
|
|
409
|
+
*/
|
|
410
|
+
function renderDiffLines(diff) {
|
|
411
|
+
if (!diff.resolved) {
|
|
412
|
+
return [
|
|
413
|
+
'> ⚠️ **Combined landed diff unavailable — this is NOT "zero files changed".**',
|
|
414
|
+
`> The pre-run base sha could not be resolved: ${diff.reason}`,
|
|
415
|
+
'> Walk the lenses below against the run diff determined by hand.',
|
|
416
|
+
'> Lens selection was **keyword-only**: with no change set, no lens',
|
|
417
|
+
'> `filePatterns` trigger could fire, so the roster below reflects the',
|
|
418
|
+
"> primary Story's prose rather than what the run touched. Treat it as a",
|
|
419
|
+
'> starting point, not a roster.',
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
return [
|
|
423
|
+
`Combined landed diff \`${diff.baseSha}...${diff.baseRef}\` — ` +
|
|
424
|
+
`**${diff.changedFiles.length}** changed file(s).`,
|
|
425
|
+
];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* @param {object} config
|
|
430
|
+
* @returns {string} the remote-tracking base ref, e.g. `origin/main`.
|
|
431
|
+
*/
|
|
432
|
+
function resolveBaseRef(config) {
|
|
433
|
+
const baseBranch = config?.project?.baseBranch;
|
|
434
|
+
const branch =
|
|
435
|
+
typeof baseBranch === 'string' && baseBranch.trim() !== ''
|
|
436
|
+
? baseBranch.trim()
|
|
437
|
+
: 'main';
|
|
438
|
+
return `origin/${branch}`;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function executeAuditRoster({
|
|
442
|
+
planRunId,
|
|
443
|
+
stories,
|
|
444
|
+
cwd,
|
|
445
|
+
provider,
|
|
446
|
+
config,
|
|
447
|
+
git,
|
|
448
|
+
selectAuditsFn,
|
|
449
|
+
}) {
|
|
450
|
+
const primaryId = Number(stories[0]);
|
|
451
|
+
const diff = resolveCombinedDiff({
|
|
452
|
+
stories,
|
|
453
|
+
cwd,
|
|
454
|
+
baseRef: resolveBaseRef(config),
|
|
455
|
+
git,
|
|
456
|
+
});
|
|
457
|
+
// Hand `selectAudits` the change set we just resolved — never a git range for
|
|
458
|
+
// it to re-derive. This function runs in the main checkout *after* the run's
|
|
459
|
+
// Stories merged, so every range it could name (`main...HEAD`) is empty by
|
|
460
|
+
// construction; asking for one is how the roster came to select lenses from
|
|
461
|
+
// zero files while printing the correct file list beside them (Story #4571).
|
|
462
|
+
const lensGrounding = diff.resolved ? 'diff' : 'keyword-only';
|
|
463
|
+
let selectedAudits = [];
|
|
464
|
+
if (Number.isInteger(primaryId) && primaryId > 0) {
|
|
465
|
+
const selected = await selectAuditsFn({
|
|
466
|
+
ticketId: primaryId,
|
|
467
|
+
gate: 'gate3',
|
|
468
|
+
provider,
|
|
469
|
+
changedFiles: diff.resolved ? diff.changedFiles : [],
|
|
470
|
+
});
|
|
471
|
+
selectedAudits = Array.isArray(selected?.selectedAudits)
|
|
472
|
+
? selected.selectedAudits
|
|
473
|
+
: Array.isArray(selected)
|
|
474
|
+
? selected
|
|
475
|
+
: [];
|
|
476
|
+
}
|
|
477
|
+
if (!diff.resolved) {
|
|
478
|
+
Logger.warn(
|
|
479
|
+
`[run-epilogue] plan-run ${planRunId}: combined landed diff unavailable — ${diff.reason}`,
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
const body = [
|
|
483
|
+
'### plan-run-audit-roster',
|
|
484
|
+
'',
|
|
485
|
+
`Cross-Story audit roster for plan-run \`${planRunId}\`.`,
|
|
486
|
+
'',
|
|
487
|
+
...renderDiffLines(diff),
|
|
488
|
+
'',
|
|
489
|
+
`**Selected lenses** (host MUST walk each against the combined landed diff) — ` +
|
|
490
|
+
`grounding: \`${lensGrounding}\`:`,
|
|
491
|
+
...(selectedAudits.length > 0
|
|
492
|
+
? selectedAudits.map((lens) => `- \`${lens}\``)
|
|
493
|
+
: ['- _(none — docs-only or no matching change-set lenses)_']),
|
|
494
|
+
'',
|
|
495
|
+
'```json',
|
|
496
|
+
JSON.stringify(
|
|
497
|
+
{
|
|
498
|
+
planRunId,
|
|
499
|
+
stories: stories.map(Number),
|
|
500
|
+
baseResolution: {
|
|
501
|
+
resolved: diff.resolved,
|
|
502
|
+
baseRef: diff.baseRef,
|
|
503
|
+
baseSha: diff.baseSha,
|
|
504
|
+
mergeSha: diff.mergeSha,
|
|
505
|
+
reason: diff.reason,
|
|
506
|
+
},
|
|
507
|
+
changedFiles: diff.resolved ? diff.changedFiles : null,
|
|
508
|
+
lensGrounding,
|
|
509
|
+
selectedAudits,
|
|
510
|
+
},
|
|
511
|
+
null,
|
|
512
|
+
2,
|
|
513
|
+
),
|
|
514
|
+
'```',
|
|
515
|
+
].join('\n');
|
|
516
|
+
if (Number.isInteger(primaryId) && primaryId > 0) {
|
|
517
|
+
await upsertStructuredComment(
|
|
518
|
+
provider,
|
|
519
|
+
primaryId,
|
|
520
|
+
'plan-run-audit-roster',
|
|
521
|
+
body,
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
kind: 'audit-roster',
|
|
526
|
+
selectedAudits,
|
|
527
|
+
// Whether the lenses above were chosen from the run's landed files
|
|
528
|
+
// (`diff`) or, with no resolvable base, from the primary Story's prose
|
|
529
|
+
// alone (`keyword-only`). The two are not interchangeable: a keyword-only
|
|
530
|
+
// roster cannot select a lens that declares no keywords, so its silence
|
|
531
|
+
// about a lens says nothing about the code.
|
|
532
|
+
lensGrounding,
|
|
533
|
+
// `null` — not `0` — when unresolved: a zero count must only ever mean
|
|
534
|
+
// "the run genuinely changed nothing".
|
|
535
|
+
changedFileCount: diff.resolved ? diff.changedFiles.length : null,
|
|
536
|
+
changedFiles: diff.resolved ? diff.changedFiles : null,
|
|
537
|
+
baseResolution: {
|
|
538
|
+
resolved: diff.resolved,
|
|
539
|
+
baseRef: diff.baseRef,
|
|
540
|
+
baseSha: diff.baseSha,
|
|
541
|
+
mergeSha: diff.mergeSha,
|
|
542
|
+
reason: diff.reason,
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
async function executeFollowUpRollup({
|
|
548
|
+
planRunId,
|
|
549
|
+
stories,
|
|
550
|
+
provider,
|
|
551
|
+
config,
|
|
552
|
+
cwd,
|
|
553
|
+
}) {
|
|
554
|
+
const signals = [];
|
|
555
|
+
for (const raw of stories) {
|
|
556
|
+
const sid = Number(raw);
|
|
557
|
+
if (!Number.isInteger(sid) || sid <= 0) continue;
|
|
558
|
+
await forEachLine(
|
|
559
|
+
null,
|
|
560
|
+
sid,
|
|
561
|
+
(parsed) => {
|
|
562
|
+
if (!parsed || typeof parsed !== 'object') return;
|
|
563
|
+
const category =
|
|
564
|
+
typeof parsed.category === 'string' ? parsed.category.trim() : '';
|
|
565
|
+
if (!category) return;
|
|
566
|
+
const source = parsed.source === 'framework' ? 'framework' : 'consumer';
|
|
567
|
+
signals.push({ category, source });
|
|
568
|
+
},
|
|
569
|
+
config,
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
const repos = resolveFollowUpRepos(config);
|
|
573
|
+
const primaryId = Number(stories[0]);
|
|
574
|
+
const proposals = composeRoutedProposals({
|
|
575
|
+
anchorId: Number.isInteger(primaryId) ? primaryId : 1,
|
|
576
|
+
anchorKind: 'run',
|
|
577
|
+
frameworkRepo: repos.frameworkRepo,
|
|
578
|
+
consumerRepo: repos.consumerRepo,
|
|
579
|
+
signals,
|
|
580
|
+
unresolvedBlockedEvents: [],
|
|
581
|
+
});
|
|
582
|
+
// Patch titles to mention the plan-run token (anchorKind run uses numeric id).
|
|
583
|
+
for (const item of [...proposals.framework, ...proposals.consumer]) {
|
|
584
|
+
item.title = item.title.replace(/plan-run \d+/, `plan-run ${planRunId}`);
|
|
585
|
+
item.body = item.body.replace(/plan-run \d+/g, `plan-run ${planRunId}`);
|
|
586
|
+
}
|
|
587
|
+
const graduated = await graduateRetroProposals({
|
|
588
|
+
epicId: primaryId,
|
|
589
|
+
provider,
|
|
590
|
+
config,
|
|
591
|
+
currentRepo: repos.currentRepo,
|
|
592
|
+
frameworkRepo: (() => {
|
|
593
|
+
const [owner, repo] = repos.frameworkRepo.split('/');
|
|
594
|
+
return { owner, repo };
|
|
595
|
+
})(),
|
|
596
|
+
routedProposals: proposals,
|
|
597
|
+
cwd,
|
|
598
|
+
});
|
|
599
|
+
if (Number.isInteger(primaryId) && primaryId > 0) {
|
|
600
|
+
const body = buildFollowUpsCommentBody({
|
|
601
|
+
storyId: primaryId,
|
|
602
|
+
proposals,
|
|
603
|
+
graduated,
|
|
604
|
+
// Story #4578 — the run's Story count is what lets an empty roll-up
|
|
605
|
+
// render as a flagged claim ("0 signals across N Stories") rather than
|
|
606
|
+
// as "nothing to follow up".
|
|
607
|
+
storyCount: stories.length,
|
|
608
|
+
}).replace(
|
|
609
|
+
`from Story #${primaryId}`,
|
|
610
|
+
`from plan-run \`${planRunId}\` (primary Story #${primaryId})`,
|
|
611
|
+
);
|
|
612
|
+
await upsertStructuredComment(provider, primaryId, 'follow-ups', body);
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
kind: 'follow-up-rollup',
|
|
616
|
+
signalCount: signals.length,
|
|
617
|
+
storyCount: stories.length,
|
|
618
|
+
filed: graduated.filed?.length ?? 0,
|
|
619
|
+
// Story #4578 — zero signals across a multi-Story run is a claim, not a
|
|
620
|
+
// clean bill of health. Surfaced on the step result so the CLI can warn
|
|
621
|
+
// the operator without re-deriving it from the comment prose.
|
|
622
|
+
emptyRollupSuspect: signals.length === 0 && stories.length > 1,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function extractSection(body, heading) {
|
|
627
|
+
if (typeof body !== 'string') return '';
|
|
628
|
+
const re = new RegExp(
|
|
629
|
+
`(?:^|\\n)## ${heading}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`,
|
|
630
|
+
'i',
|
|
631
|
+
);
|
|
632
|
+
const match = body.match(re);
|
|
633
|
+
return match ? match[1].trim() : '';
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async function executeSiblingCoherence({ planRunId, stories, provider }) {
|
|
637
|
+
const findings = [];
|
|
638
|
+
const bodies = [];
|
|
639
|
+
for (const raw of stories) {
|
|
640
|
+
const sid = Number(raw);
|
|
641
|
+
if (!Number.isInteger(sid) || sid <= 0) continue;
|
|
642
|
+
const ticket = await provider.getTicket(sid);
|
|
643
|
+
bodies.push({
|
|
644
|
+
id: sid,
|
|
645
|
+
title: ticket?.title ?? '',
|
|
646
|
+
acceptance: extractSection(ticket?.body ?? '', 'Acceptance'),
|
|
647
|
+
spec: extractSection(ticket?.body ?? '', 'Spec'),
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
const withAcceptance = bodies.filter((b) => b.acceptance.length > 0);
|
|
651
|
+
if (withAcceptance.length > 0 && withAcceptance.length < bodies.length) {
|
|
652
|
+
const missing = bodies
|
|
653
|
+
.filter((b) => b.acceptance.length === 0)
|
|
654
|
+
.map((b) => `#${b.id}`);
|
|
655
|
+
findings.push(
|
|
656
|
+
`Stories missing ## Acceptance while siblings declare ACs: ${missing.join(', ')}`,
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
// Detect identical non-empty Spec blobs (likely copy-paste drift).
|
|
660
|
+
const specMap = new Map();
|
|
661
|
+
for (const b of bodies) {
|
|
662
|
+
if (!b.spec) continue;
|
|
663
|
+
const key = b.spec.replace(/\s+/g, ' ').slice(0, 400);
|
|
664
|
+
if (!specMap.has(key)) specMap.set(key, []);
|
|
665
|
+
specMap.get(key).push(b.id);
|
|
666
|
+
}
|
|
667
|
+
for (const ids of specMap.values()) {
|
|
668
|
+
if (ids.length > 1) {
|
|
669
|
+
findings.push(
|
|
670
|
+
`Duplicate ## Spec prose across Stories ${ids.map((id) => `#${id}`).join(', ')} — split or dedupe.`,
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const primaryId = Number(stories[0]);
|
|
675
|
+
const body = [
|
|
676
|
+
'### plan-run-sibling-coherence',
|
|
677
|
+
'',
|
|
678
|
+
`Sibling-coherence check for plan-run \`${planRunId}\`.`,
|
|
679
|
+
'',
|
|
680
|
+
findings.length === 0
|
|
681
|
+
? '_No coherence findings._'
|
|
682
|
+
: findings.map((f) => `- ${f}`).join('\n'),
|
|
683
|
+
'',
|
|
684
|
+
'```json',
|
|
685
|
+
JSON.stringify(
|
|
686
|
+
{ planRunId, stories: stories.map(Number), findings },
|
|
687
|
+
null,
|
|
688
|
+
2,
|
|
689
|
+
),
|
|
690
|
+
'```',
|
|
691
|
+
].join('\n');
|
|
692
|
+
if (Number.isInteger(primaryId) && primaryId > 0) {
|
|
693
|
+
await upsertStructuredComment(
|
|
694
|
+
provider,
|
|
695
|
+
primaryId,
|
|
696
|
+
'plan-run-sibling-coherence',
|
|
697
|
+
body,
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
return { kind: 'sibling-coherence', findings };
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Execute the per-run epilogue. Throws only on programmer misuse; step
|
|
705
|
+
* failures are collected into `errors[]`.
|
|
706
|
+
*
|
|
707
|
+
* @param {object} args
|
|
708
|
+
* @param {string} args.planRunId
|
|
709
|
+
* @param {Array<string|number>} args.stories
|
|
710
|
+
* @param {object} args.provider
|
|
711
|
+
* @param {object} [args.config]
|
|
712
|
+
* @param {string} [args.cwd]
|
|
713
|
+
* @param {{ gitSpawn: Function }} [args.git] - Injection seam for tests.
|
|
714
|
+
* @param {typeof selectAudits} [args.selectAuditsFn] - Injection seam for tests.
|
|
715
|
+
* @returns {Promise<object>}
|
|
716
|
+
*/
|
|
717
|
+
export async function runPlanRunEpilogue({
|
|
718
|
+
planRunId,
|
|
719
|
+
stories,
|
|
720
|
+
provider,
|
|
721
|
+
config,
|
|
722
|
+
cwd = process.cwd(),
|
|
723
|
+
git = { gitSpawn },
|
|
724
|
+
selectAuditsFn = selectAudits,
|
|
725
|
+
} = {}) {
|
|
726
|
+
const plan = planRunEpilogue({ planRunId, stories });
|
|
727
|
+
if (!plan.applicable) {
|
|
728
|
+
return { ...plan, results: [], errors: [] };
|
|
729
|
+
}
|
|
730
|
+
if (!provider || typeof provider.getTicket !== 'function') {
|
|
731
|
+
throw new TypeError('runPlanRunEpilogue requires a ticketing provider');
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const results = [];
|
|
735
|
+
const errors = [];
|
|
736
|
+
for (const step of plan.steps) {
|
|
737
|
+
try {
|
|
738
|
+
if (step.kind === 'audit-roster') {
|
|
739
|
+
results.push(
|
|
740
|
+
await executeAuditRoster({
|
|
741
|
+
planRunId: plan.planRunId,
|
|
742
|
+
stories: plan.stories,
|
|
743
|
+
cwd,
|
|
744
|
+
provider,
|
|
745
|
+
config,
|
|
746
|
+
git,
|
|
747
|
+
selectAuditsFn,
|
|
748
|
+
}),
|
|
749
|
+
);
|
|
750
|
+
} else if (step.kind === 'follow-up-rollup') {
|
|
751
|
+
results.push(
|
|
752
|
+
await executeFollowUpRollup({
|
|
753
|
+
planRunId: plan.planRunId,
|
|
754
|
+
stories: plan.stories,
|
|
755
|
+
provider,
|
|
756
|
+
config,
|
|
757
|
+
cwd,
|
|
758
|
+
}),
|
|
759
|
+
);
|
|
760
|
+
} else if (step.kind === 'sibling-coherence') {
|
|
761
|
+
results.push(
|
|
762
|
+
await executeSiblingCoherence({
|
|
763
|
+
planRunId: plan.planRunId,
|
|
764
|
+
stories: plan.stories,
|
|
765
|
+
provider,
|
|
766
|
+
}),
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
} catch (err) {
|
|
770
|
+
const message = err?.message ?? String(err);
|
|
771
|
+
Logger.warn(`[run-epilogue] step ${step.kind} failed: ${message}`);
|
|
772
|
+
errors.push({ kind: step.kind, message });
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return {
|
|
777
|
+
applicable: true,
|
|
778
|
+
planRunId: plan.planRunId,
|
|
779
|
+
stories: plan.stories,
|
|
780
|
+
steps: plan.steps,
|
|
781
|
+
results,
|
|
782
|
+
errors,
|
|
783
|
+
};
|
|
784
|
+
}
|