mandrel 2.0.0 → 2.2.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 -28
- package/.agents/agents/acceptance-critic.md +20 -9
- package/.agents/agents/story-worker.md +45 -48
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +60 -46
- package/.agents/docs/agentrc-reference.json +8 -13
- package/.agents/docs/configuration.md +33 -57
- package/.agents/docs/execution-reference.md +39 -10
- package/.agents/docs/quality-gates.md +17 -19
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +64 -79
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/gherkin-standards.md +10 -0
- package/.agents/rules/git-conventions-reference.md +42 -51
- package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
- package/.agents/schemas/agentrc.schema.json +35 -46
- 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/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 +24 -68
- package/.agents/scripts/agents-bootstrap-github.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 +1 -1
- package/.agents/scripts/check-workflow-cli-lint.js +91 -0
- 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/audit-suite/selector.js +323 -23
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -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 +4 -1
- package/.agents/scripts/lib/checks/index.js +1 -1
- 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 +3 -3
- 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-args.js +23 -2
- package/.agents/scripts/lib/close-validation/gates.js +13 -13
- 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/acceptance-eval.js +2 -2
- package/.agents/scripts/lib/config/delivery-routing.js +7 -6
- package/.agents/scripts/lib/config/explain.js +10 -16
- package/.agents/scripts/lib/config/github.js +7 -5
- package/.agents/scripts/lib/config/limits.js +15 -25
- package/.agents/scripts/lib/config/quality.js +11 -14
- package/.agents/scripts/lib/config/runners.js +8 -21
- package/.agents/scripts/lib/config/temp-paths.js +18 -56
- package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
- package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
- package/.agents/scripts/lib/config-settings-schema.js +48 -22
- package/.agents/scripts/lib/dead-exports-knip.js +105 -0
- package/.agents/scripts/lib/dead-exports-mode.js +51 -0
- package/.agents/scripts/lib/duplicate-search.js +38 -7
- 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 -189
- package/.agents/scripts/lib/gh-exec.js +8 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
- package/.agents/scripts/lib/git-utils.js +0 -14
- package/.agents/scripts/lib/json-utils.js +1 -2
- package/.agents/scripts/lib/label-constants.js +0 -15
- package/.agents/scripts/lib/label-taxonomy.js +1 -12
- package/.agents/scripts/lib/observability/active-story-env.js +42 -163
- 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 +2 -2
- package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
- 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 +43 -45
- package/.agents/scripts/lib/orchestration/change-set.js +103 -0
- package/.agents/scripts/lib/orchestration/code-review.js +70 -191
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
- 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/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 +9 -11
- 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/listeners/README.md +7 -3
- 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/merge-block-class.js +76 -20
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
- package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
- package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
- package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
- package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
- 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/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 +374 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
- 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 +4 -13
- 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 +72 -30
- 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 +12 -8
- 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 +264 -43
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
- package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
- package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
- package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
- package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
- package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
- 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 -286
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- 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/skills/parse-skill.js +16 -3
- package/.agents/scripts/lib/story-adjacency.js +8 -7
- package/.agents/scripts/lib/story-body/story-body.js +81 -13
- package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
- package/.agents/scripts/lib/test-env.js +14 -1
- package/.agents/scripts/lib/test-tiers.js +0 -3
- package/.agents/scripts/lib/ticket-body-sections.js +0 -14
- package/.agents/scripts/lib/validation-evidence.js +31 -59
- package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
- package/.agents/scripts/plan-context.js +38 -7
- package/.agents/scripts/plan-critics.js +203 -0
- package/.agents/scripts/plan-persist.js +145 -35
- package/.agents/scripts/plan-run-epilogue.js +83 -38
- 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 +1 -11
- package/.agents/scripts/providers/github/mappers.js +5 -0
- package/.agents/scripts/providers/github/sub-issues.js +0 -47
- package/.agents/scripts/providers/github/tickets.js +33 -153
- package/.agents/scripts/providers/github.js +17 -6
- package/.agents/scripts/quality-preview.js +13 -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 +9 -10
- package/.agents/scripts/single-story-close.js +173 -18
- package/.agents/scripts/single-story-confirm-merge.js +288 -15
- package/.agents/scripts/single-story-init.js +6 -10
- package/.agents/scripts/stories-wave-tick.js +380 -53
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/update-ticket-state.js +8 -50
- 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 +3 -3
- package/.agents/skills/core/scope-triage/SKILL.md +3 -0
- 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 +2 -12
- 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/workflows/audit-architecture.md +3 -4
- package/.agents/workflows/audit-clean-code.md +4 -4
- package/.agents/workflows/audit-documentation.md +4 -5
- package/.agents/workflows/audit-lighthouse.md +8 -0
- package/.agents/workflows/audit-navigability.md +10 -0
- package/.agents/workflows/audit-performance.md +2 -3
- package/.agents/workflows/audit-quality.md +8 -9
- package/.agents/workflows/audit-security.md +1 -2
- package/.agents/workflows/audit-seo.md +10 -0
- package/.agents/workflows/audit-ux-ui.md +7 -0
- package/.agents/workflows/deliver.md +133 -45
- package/.agents/workflows/git-cleanup.md +2 -2
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
- package/.agents/workflows/helpers/code-review.md +14 -12
- package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
- package/.agents/workflows/helpers/deliver-story.md +209 -118
- package/.agents/workflows/helpers/parallel-tooling.md +2 -2
- package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
- package/.agents/workflows/plan.md +239 -19
- package/.agents/workflows/qa-assist.md +6 -6
- package/.agents/workflows/qa-explore.md +3 -3
- package/.agents/workflows/qa-run.md +1 -5
- package/bin/mandrel.js +12 -1
- package/docs/CHANGELOG.md +62 -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 +17 -9
- 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/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
- package/package.json +2 -2
- package/.agents/schemas/epic-perf-report.schema.json +0 -89
- 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/story.heartbeat.schema.json +0 -20
- package/.agents/schemas/risk-verdict.schema.json +0 -53
- package/.agents/schemas/story-perf-summary.schema.json +0 -73
- package/.agents/scripts/analyze-execution.js +0 -444
- package/.agents/scripts/check-prepush-recovery.js +0 -90
- 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 -187
- 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/audit-lens-routing.js +0 -128
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
- package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
- 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/merge-watcher.js +0 -665
- package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
- package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
- 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 -21
- 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 -397
- package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
- package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
- package/.agents/scripts/resolve-plan-run.js +0 -117
- package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
|
@@ -131,30 +131,342 @@ export function planRunEpilogue({ planRunId, stories } = {}) {
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
|
|
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;
|
|
135
336
|
try {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return
|
|
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 ?? '')
|
|
139
352
|
.split('\n')
|
|
140
353
|
.map((line) => line.trim())
|
|
141
|
-
.filter(Boolean)
|
|
142
|
-
}
|
|
143
|
-
|
|
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
|
+
};
|
|
144
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}`;
|
|
145
439
|
}
|
|
146
440
|
|
|
147
|
-
async function executeAuditRoster({
|
|
441
|
+
async function executeAuditRoster({
|
|
442
|
+
planRunId,
|
|
443
|
+
stories,
|
|
444
|
+
cwd,
|
|
445
|
+
provider,
|
|
446
|
+
config,
|
|
447
|
+
git,
|
|
448
|
+
selectAuditsFn,
|
|
449
|
+
}) {
|
|
148
450
|
const primaryId = Number(stories[0]);
|
|
149
|
-
const
|
|
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';
|
|
150
463
|
let selectedAudits = [];
|
|
151
464
|
if (Number.isInteger(primaryId) && primaryId > 0) {
|
|
152
|
-
const selected = await
|
|
465
|
+
const selected = await selectAuditsFn({
|
|
153
466
|
ticketId: primaryId,
|
|
154
467
|
gate: 'gate3',
|
|
155
468
|
provider,
|
|
156
|
-
|
|
157
|
-
headRef: 'HEAD',
|
|
469
|
+
changedFiles: diff.resolved ? diff.changedFiles : [],
|
|
158
470
|
});
|
|
159
471
|
selectedAudits = Array.isArray(selected?.selectedAudits)
|
|
160
472
|
? selected.selectedAudits
|
|
@@ -162,14 +474,20 @@ async function executeAuditRoster({ planRunId, stories, cwd, provider }) {
|
|
|
162
474
|
? selected
|
|
163
475
|
: [];
|
|
164
476
|
}
|
|
477
|
+
if (!diff.resolved) {
|
|
478
|
+
Logger.warn(
|
|
479
|
+
`[run-epilogue] plan-run ${planRunId}: combined landed diff unavailable — ${diff.reason}`,
|
|
480
|
+
);
|
|
481
|
+
}
|
|
165
482
|
const body = [
|
|
166
483
|
'### plan-run-audit-roster',
|
|
167
484
|
'',
|
|
168
485
|
`Cross-Story audit roster for plan-run \`${planRunId}\`.`,
|
|
169
486
|
'',
|
|
170
|
-
|
|
487
|
+
...renderDiffLines(diff),
|
|
171
488
|
'',
|
|
172
|
-
|
|
489
|
+
`**Selected lenses** (host MUST walk each against the combined landed diff) — ` +
|
|
490
|
+
`grounding: \`${lensGrounding}\`:`,
|
|
173
491
|
...(selectedAudits.length > 0
|
|
174
492
|
? selectedAudits.map((lens) => `- \`${lens}\``)
|
|
175
493
|
: ['- _(none — docs-only or no matching change-set lenses)_']),
|
|
@@ -179,7 +497,15 @@ async function executeAuditRoster({ planRunId, stories, cwd, provider }) {
|
|
|
179
497
|
{
|
|
180
498
|
planRunId,
|
|
181
499
|
stories: stories.map(Number),
|
|
182
|
-
|
|
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,
|
|
183
509
|
selectedAudits,
|
|
184
510
|
},
|
|
185
511
|
null,
|
|
@@ -198,7 +524,23 @@ async function executeAuditRoster({ planRunId, stories, cwd, provider }) {
|
|
|
198
524
|
return {
|
|
199
525
|
kind: 'audit-roster',
|
|
200
526
|
selectedAudits,
|
|
201
|
-
|
|
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
|
+
},
|
|
202
544
|
};
|
|
203
545
|
}
|
|
204
546
|
|
|
@@ -259,6 +601,10 @@ async function executeFollowUpRollup({
|
|
|
259
601
|
storyId: primaryId,
|
|
260
602
|
proposals,
|
|
261
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,
|
|
262
608
|
}).replace(
|
|
263
609
|
`from Story #${primaryId}`,
|
|
264
610
|
`from plan-run \`${planRunId}\` (primary Story #${primaryId})`,
|
|
@@ -268,7 +614,12 @@ async function executeFollowUpRollup({
|
|
|
268
614
|
return {
|
|
269
615
|
kind: 'follow-up-rollup',
|
|
270
616
|
signalCount: signals.length,
|
|
617
|
+
storyCount: stories.length,
|
|
271
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,
|
|
272
623
|
};
|
|
273
624
|
}
|
|
274
625
|
|
|
@@ -359,6 +710,8 @@ async function executeSiblingCoherence({ planRunId, stories, provider }) {
|
|
|
359
710
|
* @param {object} args.provider
|
|
360
711
|
* @param {object} [args.config]
|
|
361
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.
|
|
362
715
|
* @returns {Promise<object>}
|
|
363
716
|
*/
|
|
364
717
|
export async function runPlanRunEpilogue({
|
|
@@ -367,6 +720,8 @@ export async function runPlanRunEpilogue({
|
|
|
367
720
|
provider,
|
|
368
721
|
config,
|
|
369
722
|
cwd = process.cwd(),
|
|
723
|
+
git = { gitSpawn },
|
|
724
|
+
selectAuditsFn = selectAudits,
|
|
370
725
|
} = {}) {
|
|
371
726
|
const plan = planRunEpilogue({ planRunId, stories });
|
|
372
727
|
if (!plan.applicable) {
|
|
@@ -387,6 +742,9 @@ export async function runPlanRunEpilogue({
|
|
|
387
742
|
stories: plan.stories,
|
|
388
743
|
cwd,
|
|
389
744
|
provider,
|
|
745
|
+
config,
|
|
746
|
+
git,
|
|
747
|
+
selectAuditsFn,
|
|
390
748
|
}),
|
|
391
749
|
);
|
|
392
750
|
} else if (step.kind === 'follow-up-rollup') {
|
|
@@ -28,6 +28,30 @@
|
|
|
28
28
|
import { gh as defaultGh } from '../../../gh-exec.js';
|
|
29
29
|
import { resolveAutoMergeArmCwd } from '../../auto-merge-cwd.js';
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Arm reasons that mean **the operator deliberately owns the merge** — the PR
|
|
33
|
+
* was never armed because it was asked not to be, not because arming failed.
|
|
34
|
+
*
|
|
35
|
+
* The distinction is load-bearing: an un-armed-by-request PR has nothing for
|
|
36
|
+
* close to land, so `resolveWaitForMerge` (`./options.js`) resolves
|
|
37
|
+
* `waitForMerge` to `false` for these reasons and the Story rests at
|
|
38
|
+
* `agent::closing` for the human. Every *other* falsy arm outcome
|
|
39
|
+
* (`pr-number-unparseable`, an `enableAutoMerge` failure) is a genuine fault
|
|
40
|
+
* and still routes through the merge-unlanded block path.
|
|
41
|
+
*/
|
|
42
|
+
const OPERATOR_MERGE_ARM_REASONS = Object.freeze([
|
|
43
|
+
'disabled-by-flag',
|
|
44
|
+
'disabled-by-policy-strict',
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string|null|undefined} reason
|
|
49
|
+
* @returns {boolean}
|
|
50
|
+
*/
|
|
51
|
+
export function isOperatorMergeReason(reason) {
|
|
52
|
+
return OPERATOR_MERGE_ARM_REASONS.includes(reason);
|
|
53
|
+
}
|
|
54
|
+
|
|
31
55
|
/**
|
|
32
56
|
* Enable GitHub native auto-merge on the PR. Non-fatal.
|
|
33
57
|
*
|
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
import { syncBranchFromBase } from '../../../git/sync-from-base.js';
|
|
19
19
|
import { Logger } from '../../../Logger.js';
|
|
20
20
|
import { AGENT_LABELS } from '../../../label-constants.js';
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
STATE_LABELS,
|
|
23
|
+
transitionTicketState,
|
|
24
|
+
upsertStructuredComment,
|
|
25
|
+
} from '../../ticketing.js';
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
28
|
* Orchestrate the pre-push base-sync step: run `syncBranchFromBase`
|
|
@@ -76,7 +80,7 @@ export async function runBaseSyncPhase({
|
|
|
76
80
|
: syncResult.stderr
|
|
77
81
|
? `: ${syncResult.stderr.slice(0, 200)}`
|
|
78
82
|
: '') +
|
|
79
|
-
`. Story transitioned to ${AGENT_LABELS.BLOCKED}; resolve in ${syncCwd} and re-run \`/
|
|
83
|
+
`. Story transitioned to ${AGENT_LABELS.BLOCKED}; resolve in ${syncCwd} and re-run \`/deliver ${storyId}\`.`,
|
|
80
84
|
);
|
|
81
85
|
}
|
|
82
86
|
progress('SYNC', `✅ Synced from origin/${baseBranch} (${syncResult.kind}).`);
|
|
@@ -126,13 +130,11 @@ export async function handleSyncFailure({
|
|
|
126
130
|
);
|
|
127
131
|
}
|
|
128
132
|
|
|
133
|
+
// Story #4539 — the canonical mutator, not a direct label write: a bare
|
|
134
|
+
// `provider.updateTicket` skips the Projects v2 column sync (Story
|
|
135
|
+
// #2548) and strands the board on the Story's prior status.
|
|
129
136
|
try {
|
|
130
|
-
await provider
|
|
131
|
-
labels: {
|
|
132
|
-
add: [AGENT_LABELS.BLOCKED],
|
|
133
|
-
remove: [AGENT_LABELS.EXECUTING, AGENT_LABELS.READY, AGENT_LABELS.DONE],
|
|
134
|
-
},
|
|
135
|
-
});
|
|
137
|
+
await transitionTicketState(provider, storyId, STATE_LABELS.BLOCKED, {});
|
|
136
138
|
progress('SYNC', `🚧 Flipped Story #${storyId} → ${AGENT_LABELS.BLOCKED}.`);
|
|
137
139
|
} catch (err) {
|
|
138
140
|
Logger.warn?.(
|
|
@@ -164,7 +166,7 @@ export function buildSyncFailureCommentBody({
|
|
|
164
166
|
const lines = [
|
|
165
167
|
`### ${heading}`,
|
|
166
168
|
'',
|
|
167
|
-
'`/
|
|
169
|
+
'`/deliver` close-validation passed, but the pre-push',
|
|
168
170
|
`sync against \`origin/${baseBranch}\` could not complete. The Story has`,
|
|
169
171
|
`been transitioned to \`agent::blocked\`. To resume:`,
|
|
170
172
|
'',
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Standalone evidence keyspace (Story #4250). Standalone Stories have no
|
|
10
10
|
* parent Epic, so they cannot scope a `validation-evidence.json` under a
|
|
11
|
-
* `temp/
|
|
11
|
+
* `temp/run-<id>/` tree. Rather than feed a null `epicId` into the
|
|
12
12
|
* Epic-keyed path (which structurally disabled the evidence cache and
|
|
13
13
|
* forced every re-run — base-sync conflict, review remediation, baseline
|
|
14
14
|
* absorb — to re-execute ALL gates including the coverage suite), the
|
|
@@ -45,7 +45,7 @@ import { runScopedFormatAutofix as defaultRunScopedFormatAutofix } from '../../s
|
|
|
45
45
|
*
|
|
46
46
|
* Gates are built from the canonical resolved config (`buildDefaultGates`
|
|
47
47
|
* reads `project.commands` and `delivery.quality.gates.crap.enabled`); the
|
|
48
|
-
* `baseBranch` is forwarded as the gate `
|
|
48
|
+
* `baseBranch` is forwarded as the gate `baseBranch` so the format gate's
|
|
49
49
|
* changed-file scope anchors on it. `standalone: true` routes the evidence
|
|
50
50
|
* cache to the storyId-anchored keyspace.
|
|
51
51
|
*
|
|
@@ -92,7 +92,7 @@ export async function runCloseValidationPhase({
|
|
|
92
92
|
cwd,
|
|
93
93
|
worktreePath,
|
|
94
94
|
storyId,
|
|
95
|
-
|
|
95
|
+
baseBranch,
|
|
96
96
|
storyBranch,
|
|
97
97
|
config,
|
|
98
98
|
logger: Logger,
|
|
@@ -127,7 +127,7 @@ export async function runCloseValidationPhase({
|
|
|
127
127
|
worktreePath,
|
|
128
128
|
gates: buildDefaultGates({
|
|
129
129
|
config,
|
|
130
|
-
|
|
130
|
+
baseBranch,
|
|
131
131
|
cwd: worktreePath || cwd,
|
|
132
132
|
log: (m) => Logger.info(m),
|
|
133
133
|
}),
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Delegates the `runCodeReview` invocation to `runStoryReviewCore`
|
|
24
24
|
* (exported from `story-close/phases/code-review.js`) so the close path
|
|
25
|
-
* shares a single invocation pattern (Story #3653).
|
|
26
|
-
*
|
|
25
|
+
* shares a single invocation pattern (Story #3653). Review depth needs no
|
|
26
|
+
* input here: it is derived from this Story's own diff inside `runCodeReview`
|
|
27
|
+
* (Story #4542).
|
|
27
28
|
*/
|
|
28
29
|
|
|
29
30
|
import { parsePrNumberFromUrl } from '../../../github-url.js';
|
|
30
|
-
import { runStoryReviewCore } from '../../story-close/phases/
|
|
31
|
-
import { resolveStoryPlanningRisk } from '../../story-plan-state.js';
|
|
31
|
+
import { runStoryReviewCore } from '../../story-close/phases/review-core.js';
|
|
32
32
|
import { postStructuredComment } from '../../ticketing/state.js';
|
|
33
33
|
|
|
34
34
|
/**
|
|
@@ -78,7 +78,6 @@ async function invokeStoryReviewCore({
|
|
|
78
78
|
baseBranch,
|
|
79
79
|
prNumber,
|
|
80
80
|
provider,
|
|
81
|
-
planningRisk,
|
|
82
81
|
runCodeReviewFn,
|
|
83
82
|
runLocalLensReviewFn,
|
|
84
83
|
progress,
|
|
@@ -89,11 +88,6 @@ async function invokeStoryReviewCore({
|
|
|
89
88
|
headRef: storyBranch,
|
|
90
89
|
commentTargetId: prNumber,
|
|
91
90
|
provider,
|
|
92
|
-
planningRisk: await resolveStoryPlanningRisk({
|
|
93
|
-
provider,
|
|
94
|
-
storyId,
|
|
95
|
-
planningRisk,
|
|
96
|
-
}),
|
|
97
91
|
progress,
|
|
98
92
|
progressTag: 'REVIEW',
|
|
99
93
|
runCodeReviewFn,
|
|
@@ -167,7 +161,6 @@ async function postStoryReviewCrossRef({
|
|
|
167
161
|
* prUrl: string,
|
|
168
162
|
* prNumber: number|null,
|
|
169
163
|
* provider: object,
|
|
170
|
-
* planningRisk?: object|null, // omit to load from story-plan-state
|
|
171
164
|
* runCodeReviewFn: Function,
|
|
172
165
|
* runLocalLensReviewFn?: Function,
|
|
173
166
|
* progress: (tag: string, msg: string) => void,
|
|
@@ -190,7 +183,6 @@ export async function runStoryScopeReview({
|
|
|
190
183
|
prUrl,
|
|
191
184
|
prNumber,
|
|
192
185
|
provider,
|
|
193
|
-
planningRisk,
|
|
194
186
|
runCodeReviewFn,
|
|
195
187
|
runLocalLensReviewFn,
|
|
196
188
|
progress,
|
|
@@ -214,7 +206,6 @@ export async function runStoryScopeReview({
|
|
|
214
206
|
baseBranch,
|
|
215
207
|
prNumber,
|
|
216
208
|
provider,
|
|
217
|
-
planningRisk,
|
|
218
209
|
runCodeReviewFn,
|
|
219
210
|
runLocalLensReviewFn,
|
|
220
211
|
progress,
|