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
|
@@ -12,6 +12,7 @@ import path from 'node:path';
|
|
|
12
12
|
import { parseSprintArgs } from '../../../cli-args.js';
|
|
13
13
|
import { getDeliveryRouting } from '../../../config/delivery-routing.js';
|
|
14
14
|
import { PROJECT_ROOT } from '../../../project-root.js';
|
|
15
|
+
import { isOperatorMergeReason } from './auto-merge.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Resolve a flag value from an explicit override, a parsed CLI arg, or a
|
|
@@ -28,34 +29,66 @@ function resolveFlag(paramValue, parsedValue, defaultValue) {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
|
-
* Resolve the `waitForMerge`
|
|
32
|
-
* 1. `--no-wait-merge` / injected opt-out always wins (stop at
|
|
33
|
-
* `agent::closing`).
|
|
34
|
-
* 2. Explicit `--wait-merge` / injected true forces land-in-close.
|
|
35
|
-
* 3. Otherwise default from `delivery.routing.closeAndLand` (true) so
|
|
36
|
-
* attended and headless delivers share the close-and-land happy path.
|
|
32
|
+
* Resolve whether close lands the PR in-process (`waitForMerge`).
|
|
37
33
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
34
|
+
* Called by `runSingleStoryClose` **after** config resolution and the
|
|
35
|
+
* auto-merge phase, because two of its four inputs are not knowable at
|
|
36
|
+
* parse time: the resolved config (whose `cwd` this parse produces) and the
|
|
37
|
+
* actual arm outcome. Resolving it here — once, where every input exists —
|
|
38
|
+
* is what makes `delivery.routing.closeAndLand` governable at all.
|
|
39
|
+
*
|
|
40
|
+
* Precedence, highest first:
|
|
41
|
+
* 1. `--no-wait-merge` / injected opt-out — always wins.
|
|
42
|
+
* 2. **Operator owns the merge** (`--no-auto-merge`, or
|
|
43
|
+
* `delivery.ci.autoMerge: "strict"`): the PR was deliberately left
|
|
44
|
+
* un-armed, so there is nothing for close to land. Resting at
|
|
45
|
+
* `agent::closing` for the human IS the documented contract for both
|
|
46
|
+
* surfaces; waiting would only burn the poll budget and then block a
|
|
47
|
+
* perfectly healthy Story. An explicit `--wait-merge` cannot override
|
|
48
|
+
* this — you cannot land-in-one-close a PR you refused to arm — so the
|
|
49
|
+
* caller is told rather than silently ignored.
|
|
50
|
+
* 3. Explicit `--wait-merge` / injected boolean.
|
|
51
|
+
* 4. `delivery.routing.closeAndLand` (framework default `true`).
|
|
52
|
+
*
|
|
53
|
+
* @param {{
|
|
54
|
+
* waitForMergeExplicit?: boolean,
|
|
55
|
+
* noWaitForMerge?: boolean,
|
|
56
|
+
* config?: object|null,
|
|
57
|
+
* autoMergeReason?: string|null,
|
|
58
|
+
* }} args
|
|
59
|
+
* @returns {{ waitForMerge: boolean, reason: 'opt-out-flag'|'operator-merge'|'explicit-flag'|'config-close-and-land' }}
|
|
40
60
|
*/
|
|
41
|
-
function resolveWaitForMerge({
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
export function resolveWaitForMerge({
|
|
62
|
+
waitForMergeExplicit,
|
|
63
|
+
noWaitForMerge = false,
|
|
64
|
+
config = null,
|
|
65
|
+
autoMergeReason = null,
|
|
66
|
+
} = {}) {
|
|
67
|
+
if (noWaitForMerge) {
|
|
68
|
+
return { waitForMerge: false, reason: 'opt-out-flag' };
|
|
69
|
+
}
|
|
70
|
+
if (isOperatorMergeReason(autoMergeReason)) {
|
|
71
|
+
return { waitForMerge: false, reason: 'operator-merge' };
|
|
48
72
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
73
|
+
if (typeof waitForMergeExplicit === 'boolean') {
|
|
74
|
+
return { waitForMerge: waitForMergeExplicit, reason: 'explicit-flag' };
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
waitForMerge: getDeliveryRouting(config).closeAndLand,
|
|
78
|
+
reason: 'config-close-and-land',
|
|
79
|
+
};
|
|
52
80
|
}
|
|
53
81
|
|
|
54
82
|
/**
|
|
55
83
|
* Parse and resolve all CLI / injection options for `runSingleStoryClose`.
|
|
56
84
|
*
|
|
57
|
-
*
|
|
58
|
-
* @
|
|
85
|
+
* `waitForMerge` is deliberately **not** resolved here — see
|
|
86
|
+
* {@link resolveWaitForMerge}. This returns the raw operator intent
|
|
87
|
+
* (`waitForMergeExplicit` / `noWaitForMerge`) for the runner to resolve once
|
|
88
|
+
* the config and the arm outcome exist.
|
|
89
|
+
*
|
|
90
|
+
* @param {{ storyIdParam, cwdParam, skipValidationParam, skipSyncParam, noAutoMergeParam, waitForMergeParam, noWaitForMergeParam, maxWaitSecondsParam }} raw
|
|
91
|
+
* @returns {{ storyId, cwd, skipValidation, skipSync, noAutoMerge, waitForMergeExplicit, noWaitForMerge, maxWaitSeconds }}
|
|
59
92
|
*/
|
|
60
93
|
export function parseCloseOptions({
|
|
61
94
|
storyIdParam,
|
|
@@ -63,9 +96,9 @@ export function parseCloseOptions({
|
|
|
63
96
|
skipValidationParam,
|
|
64
97
|
skipSyncParam,
|
|
65
98
|
noAutoMergeParam,
|
|
66
|
-
noFullScopeCrapParam,
|
|
67
99
|
waitForMergeParam,
|
|
68
100
|
noWaitForMergeParam,
|
|
101
|
+
maxWaitSecondsParam,
|
|
69
102
|
}) {
|
|
70
103
|
const parsed =
|
|
71
104
|
storyIdParam !== undefined
|
|
@@ -75,16 +108,26 @@ export function parseCloseOptions({
|
|
|
75
108
|
skipValidation: !!skipValidationParam,
|
|
76
109
|
skipSync: !!skipSyncParam,
|
|
77
110
|
noAutoMerge: !!noAutoMergeParam,
|
|
78
|
-
noFullScopeCrap: !!noFullScopeCrapParam,
|
|
79
111
|
// Preserve undefined so resolveWaitForMerge can apply the
|
|
80
112
|
// closeAndLand config default when neither flag was injected.
|
|
81
113
|
waitForMerge: waitForMergeParam,
|
|
82
114
|
noWaitForMerge: !!noWaitForMergeParam,
|
|
115
|
+
maxWaitSeconds: maxWaitSecondsParam,
|
|
83
116
|
}
|
|
84
117
|
: parseSprintArgs();
|
|
118
|
+
const waitForMergeExplicit = waitForMergeParam ?? parsed.waitForMerge;
|
|
119
|
+
const maxWaitSeconds = maxWaitSecondsParam ?? parsed.maxWaitSeconds;
|
|
85
120
|
return {
|
|
86
121
|
storyId: parsed.storyId,
|
|
87
122
|
cwd: path.resolve(cwdParam ?? parsed.cwd ?? PROJECT_ROOT),
|
|
123
|
+
// `undefined` when unsupplied — the merge wait then reads
|
|
124
|
+
// `delivery.mergeWatch.maxWaitSeconds`. A per-run override exists so a
|
|
125
|
+
// headless caller with no host tool-invocation ceiling can keep
|
|
126
|
+
// single-block semantics without editing the consumer's config.
|
|
127
|
+
maxWaitSeconds:
|
|
128
|
+
Number.isInteger(maxWaitSeconds) && maxWaitSeconds > 0
|
|
129
|
+
? maxWaitSeconds
|
|
130
|
+
: undefined,
|
|
88
131
|
skipValidation: resolveFlag(
|
|
89
132
|
skipValidationParam,
|
|
90
133
|
parsed.skipValidation,
|
|
@@ -92,15 +135,14 @@ export function parseCloseOptions({
|
|
|
92
135
|
),
|
|
93
136
|
skipSync: resolveFlag(skipSyncParam, parsed.skipSync, false),
|
|
94
137
|
noAutoMerge: resolveFlag(noAutoMergeParam, parsed.noAutoMerge, false),
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
138
|
+
waitForMergeExplicit:
|
|
139
|
+
typeof waitForMergeExplicit === 'boolean'
|
|
140
|
+
? waitForMergeExplicit
|
|
141
|
+
: undefined,
|
|
142
|
+
noWaitForMerge: resolveFlag(
|
|
143
|
+
noWaitForMergeParam,
|
|
144
|
+
parsed.noWaitForMerge,
|
|
98
145
|
false,
|
|
99
146
|
),
|
|
100
|
-
waitForMerge: resolveWaitForMerge({
|
|
101
|
-
waitForMergeParam,
|
|
102
|
-
noWaitForMergeParam,
|
|
103
|
-
parsed,
|
|
104
|
-
}),
|
|
105
147
|
};
|
|
106
148
|
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* phases/post-land.js — the script-owned land tail (Story #4543).
|
|
3
|
+
*
|
|
4
|
+
* Everything after "the PR merged and the Story is `agent::done`" used to be
|
|
5
|
+
* prose in `helpers/deliver-story.md`: Step 5.5 resync, Step 6 cleanup, and
|
|
6
|
+
* follow-up capture, each a separate CLI an agent may or may not have run.
|
|
7
|
+
* Follow-up capture was the sharpest edge — it lived only on the standalone
|
|
8
|
+
* `single-story-confirm-merge.js` path, which close-and-land (the DEFAULT)
|
|
9
|
+
* is explicitly told to skip, so per-Story follow-ups were captured *never*
|
|
10
|
+
* on the default path, and a belated manual confirm could not backfill (the
|
|
11
|
+
* Story is already `agent::done`, so confirm short-circuits `noop` and the
|
|
12
|
+
* capture's `action === 'done'` gate never opens).
|
|
13
|
+
*
|
|
14
|
+
* This module folds all four steps into one phase both landing surfaces
|
|
15
|
+
* reach — the in-close wait (`phases/confirm-merge.js`) and the standalone
|
|
16
|
+
* `single-story-confirm-merge.js` CLI — so the two paths cannot diverge and
|
|
17
|
+
* "landed" means the whole tail ran.
|
|
18
|
+
*
|
|
19
|
+
* **Per-step booleans, not an aggregate.** {@link runPostLandTail} reports
|
|
20
|
+
* each step's outcome individually. That is not bookkeeping fastidiousness:
|
|
21
|
+
* the worktree-reap defect this repo fixed existed because a phase reported
|
|
22
|
+
* an outcome it never checked, and a single `tailOk: true` bit invites that
|
|
23
|
+
* class of bug straight back. A degraded step is visible in the terminal
|
|
24
|
+
* envelope without failing an otherwise-healthy land.
|
|
25
|
+
*
|
|
26
|
+
* **Never throws.** The merge already landed — the code is on the base
|
|
27
|
+
* branch. Failing the land because a Projects v2 mutation flaked would
|
|
28
|
+
* report a false negative about work that is demonstrably done. Every step
|
|
29
|
+
* is best-effort and records its own reason.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { gitSpawn as defaultGitSpawn } from '../../../git-utils.js';
|
|
33
|
+
import { Logger } from '../../../Logger.js';
|
|
34
|
+
import {
|
|
35
|
+
executeFastForward as defaultExecuteFastForward,
|
|
36
|
+
planFastForward as defaultPlanFastForward,
|
|
37
|
+
} from '../../git-cleanup/phases/fast-forward.js';
|
|
38
|
+
import { reassertStatusColumn as defaultReassertStatusColumn } from '../../reassert-status-column.js';
|
|
39
|
+
import { captureStoryFollowUps as defaultCaptureStoryFollowUps } from '../../story-follow-ups.js';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Run one tail step, converting any throw into a `false` + reason. Keeps
|
|
43
|
+
* each step's own body free of defensive boilerplate while guaranteeing the
|
|
44
|
+
* module-level never-throws contract.
|
|
45
|
+
*
|
|
46
|
+
* @template T
|
|
47
|
+
* @param {() => Promise<{ ok: boolean, detail?: string|null }>} run
|
|
48
|
+
* @param {{ name: string, progress?: Function }} ctx
|
|
49
|
+
* @returns {Promise<{ ok: boolean, detail: string|null }>}
|
|
50
|
+
*/
|
|
51
|
+
async function step(run, { name, progress }) {
|
|
52
|
+
try {
|
|
53
|
+
const outcome = await run();
|
|
54
|
+
if (!outcome.ok) {
|
|
55
|
+
progress?.(
|
|
56
|
+
'POST-LAND',
|
|
57
|
+
`⚠️ ${name} degraded (land stands): ${outcome.detail ?? 'no detail'}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return { ok: Boolean(outcome.ok), detail: outcome.detail ?? null };
|
|
61
|
+
} catch (err) {
|
|
62
|
+
const detail = String(err?.message ?? err);
|
|
63
|
+
Logger.warn(`[post-land] ${name} threw (land stands): ${detail}`);
|
|
64
|
+
progress?.('POST-LAND', `⚠️ ${name} threw (land stands): ${detail}`);
|
|
65
|
+
return { ok: false, detail };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Capture the Story's follow-ups from its friction signal stream.
|
|
71
|
+
*
|
|
72
|
+
* Calls `captureStoryFollowUps` **directly** rather than through the
|
|
73
|
+
* `captureFollowUpsAfterConfirm` action-gate wrapper: by the time the tail
|
|
74
|
+
* runs, the merge is already confirmed, so re-deriving that fact from a
|
|
75
|
+
* confirmation envelope's `action` field is the exact coupling that made
|
|
76
|
+
* the default path skip capture entirely.
|
|
77
|
+
*/
|
|
78
|
+
async function stepFollowUps({
|
|
79
|
+
storyId,
|
|
80
|
+
provider,
|
|
81
|
+
config,
|
|
82
|
+
cwd,
|
|
83
|
+
progress,
|
|
84
|
+
captureStoryFollowUpsFn,
|
|
85
|
+
}) {
|
|
86
|
+
const result = await captureStoryFollowUpsFn({
|
|
87
|
+
storyId,
|
|
88
|
+
provider,
|
|
89
|
+
config,
|
|
90
|
+
cwd,
|
|
91
|
+
progress,
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
ok: result?.ok === true,
|
|
95
|
+
detail: result?.ok === true ? null : (result?.reason ?? 'capture-failed'),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Re-assert the Projects v2 Status column against the bot's late write.
|
|
101
|
+
*
|
|
102
|
+
* A `skipped` envelope (`no-project`, `not-on-project`, `no-meta`) is a
|
|
103
|
+
* **success**: the board the helper would defend does not exist, so there
|
|
104
|
+
* is nothing to get wrong. Only a genuine `drifted` outcome — the helper
|
|
105
|
+
* fired, polled, and still lost — degrades the step.
|
|
106
|
+
*/
|
|
107
|
+
async function stepStatusResync({
|
|
108
|
+
storyId,
|
|
109
|
+
provider,
|
|
110
|
+
config,
|
|
111
|
+
progress,
|
|
112
|
+
reassertStatusColumnFn,
|
|
113
|
+
}) {
|
|
114
|
+
const outcome = await reassertStatusColumnFn({
|
|
115
|
+
provider,
|
|
116
|
+
ticketId: storyId,
|
|
117
|
+
config,
|
|
118
|
+
logger: {
|
|
119
|
+
info: (m) => progress?.('POST-LAND', m),
|
|
120
|
+
warn: (m) => progress?.('POST-LAND', `⚠️ ${m}`),
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
if (outcome?.status === 'synced' || outcome?.status === 'skipped') {
|
|
124
|
+
return { ok: true, detail: null };
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
detail: `status column ${outcome?.status ?? 'unknown'} (target=${outcome?.column ?? 'n/a'}, attempts=${outcome?.attempts ?? 0})`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Reap the local `story-<id>` ref. GitHub deletes the *remote* branch on
|
|
134
|
+
* squash-merge (`--delete-branch`), but the local ref lingers in the main
|
|
135
|
+
* checkout until something prunes it.
|
|
136
|
+
*
|
|
137
|
+
* An absent ref is a success, not a failure — the sweep is idempotent and
|
|
138
|
+
* a previous run (or the init-time merged-sweep) may have already reaped it.
|
|
139
|
+
*/
|
|
140
|
+
async function stepRefCleanup({ cwd, storyBranch, progress, gitSpawnFn }) {
|
|
141
|
+
const exists = gitSpawnFn(
|
|
142
|
+
cwd,
|
|
143
|
+
'show-ref',
|
|
144
|
+
'--verify',
|
|
145
|
+
'--quiet',
|
|
146
|
+
`refs/heads/${storyBranch}`,
|
|
147
|
+
);
|
|
148
|
+
if (exists.status !== 0) {
|
|
149
|
+
progress?.('POST-LAND', `⏭ local ${storyBranch} already absent.`);
|
|
150
|
+
return { ok: true, detail: null };
|
|
151
|
+
}
|
|
152
|
+
const del = gitSpawnFn(cwd, 'branch', '-D', storyBranch);
|
|
153
|
+
if (del.status !== 0) {
|
|
154
|
+
return { ok: false, detail: `git branch -D failed: ${del.stderr ?? ''}` };
|
|
155
|
+
}
|
|
156
|
+
progress?.('POST-LAND', `🧹 reaped local ${storyBranch}.`);
|
|
157
|
+
return { ok: true, detail: null };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Fast-forward local `baseBranch` so the next Story seeds from the tip that
|
|
162
|
+
* now contains this merge. Reuses the same planner/executor pair
|
|
163
|
+
* `single-story-init.js` imports (rather than shelling out to
|
|
164
|
+
* `/git-cleanup`), which is the established in-process composition pattern.
|
|
165
|
+
*
|
|
166
|
+
* `already-up-to-date` is a success. A dirty tree is a legitimate, expected
|
|
167
|
+
* skip on a shared checkout — another worker is mid-flight — so it degrades
|
|
168
|
+
* the step's report without pretending the base moved.
|
|
169
|
+
*/
|
|
170
|
+
async function stepBaseFastForward({
|
|
171
|
+
cwd,
|
|
172
|
+
baseBranch,
|
|
173
|
+
progress,
|
|
174
|
+
planFastForwardFn,
|
|
175
|
+
executeFastForwardFn,
|
|
176
|
+
}) {
|
|
177
|
+
const plan = planFastForwardFn({ cwd, baseBranch });
|
|
178
|
+
if (!plan.runnable && plan.reason === 'already-up-to-date') {
|
|
179
|
+
progress?.('POST-LAND', `⏭ local ${baseBranch} already up to date.`);
|
|
180
|
+
return { ok: true, detail: null };
|
|
181
|
+
}
|
|
182
|
+
const ff = executeFastForwardFn({
|
|
183
|
+
cwd,
|
|
184
|
+
baseBranch,
|
|
185
|
+
plan,
|
|
186
|
+
logger: {
|
|
187
|
+
info: (m) => progress?.('POST-LAND', m),
|
|
188
|
+
warn: (m) => progress?.('POST-LAND', `⚠️ ${m}`),
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
if (ff.applied) {
|
|
192
|
+
progress?.(
|
|
193
|
+
'POST-LAND',
|
|
194
|
+
`⏩ fast-forwarded ${baseBranch} by ${ff.behind} commit(s).`,
|
|
195
|
+
);
|
|
196
|
+
return { ok: true, detail: null };
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
ok: false,
|
|
200
|
+
detail: `fast-forward skipped: ${ff.reason ?? plan.reason ?? 'unknown'}`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Run the whole post-land tail. Never throws.
|
|
206
|
+
*
|
|
207
|
+
* Steps run **sequentially** and in this order deliberately: follow-up
|
|
208
|
+
* capture and the status resync touch GitHub, while the ref reap and the
|
|
209
|
+
* fast-forward mutate the local checkout — and the fast-forward must run
|
|
210
|
+
* after the ref reap so `git branch -D` is not fighting a checkout that just
|
|
211
|
+
* moved HEAD.
|
|
212
|
+
*
|
|
213
|
+
* @param {object} args
|
|
214
|
+
* @param {number} args.storyId
|
|
215
|
+
* @param {string} args.storyBranch
|
|
216
|
+
* @param {string} args.baseBranch
|
|
217
|
+
* @param {string} args.cwd The MAIN checkout (never the worktree).
|
|
218
|
+
* @param {object} args.provider
|
|
219
|
+
* @param {object} [args.config]
|
|
220
|
+
* @param {(tag: string, msg: string) => void} [args.progress]
|
|
221
|
+
* @param {Function} [args.captureStoryFollowUpsFn] Test seam.
|
|
222
|
+
* @param {Function} [args.reassertStatusColumnFn] Test seam.
|
|
223
|
+
* @param {Function} [args.gitSpawnFn] Test seam.
|
|
224
|
+
* @param {Function} [args.planFastForwardFn] Test seam.
|
|
225
|
+
* @param {Function} [args.executeFastForwardFn] Test seam.
|
|
226
|
+
* @returns {Promise<{ followUps: boolean, statusResync: boolean, refCleanup: boolean, baseFastForward: boolean, details: Record<string, string|null> }>}
|
|
227
|
+
*/
|
|
228
|
+
export async function runPostLandTail({
|
|
229
|
+
storyId,
|
|
230
|
+
storyBranch,
|
|
231
|
+
baseBranch,
|
|
232
|
+
cwd,
|
|
233
|
+
provider,
|
|
234
|
+
config,
|
|
235
|
+
progress,
|
|
236
|
+
captureStoryFollowUpsFn = defaultCaptureStoryFollowUps,
|
|
237
|
+
reassertStatusColumnFn = defaultReassertStatusColumn,
|
|
238
|
+
gitSpawnFn = defaultGitSpawn,
|
|
239
|
+
planFastForwardFn = defaultPlanFastForward,
|
|
240
|
+
executeFastForwardFn = defaultExecuteFastForward,
|
|
241
|
+
}) {
|
|
242
|
+
progress?.('POST-LAND', `🧾 Running land tail for Story #${storyId}...`);
|
|
243
|
+
|
|
244
|
+
const followUps = await step(
|
|
245
|
+
() =>
|
|
246
|
+
stepFollowUps({
|
|
247
|
+
storyId,
|
|
248
|
+
provider,
|
|
249
|
+
config,
|
|
250
|
+
cwd,
|
|
251
|
+
progress,
|
|
252
|
+
captureStoryFollowUpsFn,
|
|
253
|
+
}),
|
|
254
|
+
{ name: 'follow-up capture', progress },
|
|
255
|
+
);
|
|
256
|
+
const statusResync = await step(
|
|
257
|
+
() =>
|
|
258
|
+
stepStatusResync({
|
|
259
|
+
storyId,
|
|
260
|
+
provider,
|
|
261
|
+
config,
|
|
262
|
+
progress,
|
|
263
|
+
reassertStatusColumnFn,
|
|
264
|
+
}),
|
|
265
|
+
{ name: 'status-column resync', progress },
|
|
266
|
+
);
|
|
267
|
+
const refCleanup = await step(
|
|
268
|
+
() => stepRefCleanup({ cwd, storyBranch, progress, gitSpawnFn }),
|
|
269
|
+
{ name: 'local ref cleanup', progress },
|
|
270
|
+
);
|
|
271
|
+
const baseFastForward = await step(
|
|
272
|
+
() =>
|
|
273
|
+
stepBaseFastForward({
|
|
274
|
+
cwd,
|
|
275
|
+
baseBranch,
|
|
276
|
+
progress,
|
|
277
|
+
planFastForwardFn,
|
|
278
|
+
executeFastForwardFn,
|
|
279
|
+
}),
|
|
280
|
+
{ name: 'base fast-forward', progress },
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
const tail = {
|
|
284
|
+
followUps: followUps.ok,
|
|
285
|
+
statusResync: statusResync.ok,
|
|
286
|
+
refCleanup: refCleanup.ok,
|
|
287
|
+
baseFastForward: baseFastForward.ok,
|
|
288
|
+
details: {
|
|
289
|
+
followUps: followUps.detail,
|
|
290
|
+
statusResync: statusResync.detail,
|
|
291
|
+
refCleanup: refCleanup.detail,
|
|
292
|
+
baseFastForward: baseFastForward.detail,
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
const degraded = Object.entries(tail)
|
|
296
|
+
.filter(([k, v]) => k !== 'details' && v === false)
|
|
297
|
+
.map(([k]) => k);
|
|
298
|
+
progress?.(
|
|
299
|
+
'POST-LAND',
|
|
300
|
+
degraded.length === 0
|
|
301
|
+
? `✅ Land tail complete for Story #${storyId} (all steps ok).`
|
|
302
|
+
: `✅ Land tail complete for Story #${storyId} — degraded: ${degraded.join(', ')} (the merge stands).`,
|
|
303
|
+
);
|
|
304
|
+
return tail;
|
|
305
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Logger } from '../../../Logger.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
STATE_LABELS,
|
|
4
|
+
transitionTicketState,
|
|
5
|
+
upsertStructuredComment,
|
|
6
|
+
} from '../../ticketing.js';
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Record a critical code-review halt as authoritative blocked state before
|
|
@@ -25,13 +28,14 @@ export async function handleCriticalReviewBlock({
|
|
|
25
28
|
`[single-story-close] failed to post review-block friction: ${err?.message ?? err}`,
|
|
26
29
|
);
|
|
27
30
|
}
|
|
31
|
+
// Story #4539 — route through the canonical mutator rather than writing
|
|
32
|
+
// labels directly. A bare `provider.updateTicket` skips the Projects v2
|
|
33
|
+
// column sync (Story #2548), leaving the board on the Story's prior
|
|
34
|
+
// status. That is benign today only because agent::blocked and
|
|
35
|
+
// agent::executing happen to map to the same column — a coincidence, not
|
|
36
|
+
// a design, and exactly how the next drift gets in.
|
|
28
37
|
try {
|
|
29
|
-
await provider
|
|
30
|
-
labels: {
|
|
31
|
-
add: [AGENT_LABELS.BLOCKED],
|
|
32
|
-
remove: [AGENT_LABELS.EXECUTING, AGENT_LABELS.READY],
|
|
33
|
-
},
|
|
34
|
-
});
|
|
38
|
+
await transitionTicketState(provider, storyId, STATE_LABELS.BLOCKED, {});
|
|
35
39
|
} catch (err) {
|
|
36
40
|
Logger.warn(
|
|
37
41
|
`[single-story-close] failed to block Story after critical review: ${err?.message ?? err}`,
|
|
@@ -7,6 +7,16 @@
|
|
|
7
7
|
* reports success because the operator can clean stale worktrees out of
|
|
8
8
|
* band.
|
|
9
9
|
*
|
|
10
|
+
* **Report what happened, not what was attempted** (Story #4539). This
|
|
11
|
+
* phase used to set `worktreeReaped = true` and log "🧹 Reaped worktree"
|
|
12
|
+
* on any non-throwing call — without reading the returned envelope. Since
|
|
13
|
+
* `reap` signals refusal by *returning* `{ removed: false, reason }`
|
|
14
|
+
* rather than throwing, and a v2-era precondition refused every
|
|
15
|
+
* `story-<id>` worktree outright, the result was that no close ever
|
|
16
|
+
* actually reaped and every close said it did. Best-effort means the close
|
|
17
|
+
* still succeeds on refusal; it does not mean the close may claim an
|
|
18
|
+
* outcome it never checked.
|
|
19
|
+
*
|
|
10
20
|
* Also clears the trace-hook env vars so subsequent tooling falls back to
|
|
11
21
|
* the no-op branch instead of pointing at a (now-reaped) worktree.
|
|
12
22
|
*/
|
|
@@ -26,7 +36,8 @@ import { WorktreeManager as DefaultWorktreeManager } from '../../../worktree-man
|
|
|
26
36
|
* wtIsolation: object|undefined,
|
|
27
37
|
* progress: (tag: string, msg: string) => void,
|
|
28
38
|
* }} args
|
|
29
|
-
* @returns {Promise<boolean>} true when
|
|
39
|
+
* @returns {Promise<boolean>} `true` only when the worktree was actually
|
|
40
|
+
* removed — never merely because the call did not throw.
|
|
30
41
|
*/
|
|
31
42
|
export async function reapWorktreePhase({
|
|
32
43
|
cwd,
|
|
@@ -49,9 +60,31 @@ export async function reapWorktreePhase({
|
|
|
49
60
|
error: (m) => Logger.error(`[single-story-close] ${m}`),
|
|
50
61
|
},
|
|
51
62
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
// Deliberately NO base ref. This phase runs BEFORE the merge (see
|
|
64
|
+
// `../runner.js` — reap precedes the confirm phase), so "is this work
|
|
65
|
+
// integrated into the base?" is the wrong question: the answer is
|
|
66
|
+
// always no, and supplying a ref would activate `isSafeToRemove`'s
|
|
67
|
+
// merge-reachability gate and refuse every reap with
|
|
68
|
+
// `unmerged-commits` — trading one always-refuse precondition for
|
|
69
|
+
// another.
|
|
70
|
+
//
|
|
71
|
+
// What actually makes the reap safe here is that close has already
|
|
72
|
+
// pushed `story-<id>` to origin and opened the PR, so the work is
|
|
73
|
+
// durable off-machine; and `isSafeToRemove` still refuses a dirty
|
|
74
|
+
// tree (`uncommitted-changes`), which is the check that protects
|
|
75
|
+
// unsaved work. Refusal is signalled by the returned envelope rather
|
|
76
|
+
// than by throwing — so read it.
|
|
77
|
+
const result = await wm.reap(storyId);
|
|
78
|
+
worktreeReaped = result?.removed === true;
|
|
79
|
+
if (worktreeReaped) {
|
|
80
|
+
progress('WORKTREE', `🧹 Reaped worktree for story-${storyId}.`);
|
|
81
|
+
} else {
|
|
82
|
+
progress(
|
|
83
|
+
'WORKTREE',
|
|
84
|
+
`⚠️ Worktree for story-${storyId} not reaped (${result?.reason ?? 'unknown reason'}) — ` +
|
|
85
|
+
`${worktreePath} left in place for the next sweep.`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
55
88
|
} catch (err) {
|
|
56
89
|
Logger.error(
|
|
57
90
|
`[single-story-close] ⚠️ Failed to reap worktree: ${err?.message ?? err}`,
|
|
@@ -232,7 +232,7 @@ export function formatWrongTreeFinding({ storyId, strayFiles, worktreePath }) {
|
|
|
232
232
|
`**Recovery:** relocate these edits into the worktree (re-apply them under ` +
|
|
233
233
|
`\`${worktreePath}\`), restore the main checkout ` +
|
|
234
234
|
`(\`git -C <main-repo> checkout -- <files>\`), then re-run ` +
|
|
235
|
-
`\`/
|
|
235
|
+
`\`/deliver ${storyId}\`.`
|
|
236
236
|
);
|
|
237
237
|
}
|
|
238
238
|
|
|
@@ -307,7 +307,7 @@ async function abortWrongTree({
|
|
|
307
307
|
`diff-path set while the worktree (${worktreePath}) is the active work ` +
|
|
308
308
|
`tree. Close aborted to avoid an empty-diff PR. Stray files: ` +
|
|
309
309
|
`${strayFiles.join(', ')}. Relocate the edits into the worktree, ` +
|
|
310
|
-
`restore the main checkout, then re-run /
|
|
310
|
+
`restore the main checkout, then re-run /deliver ${storyId}.`,
|
|
311
311
|
);
|
|
312
312
|
}
|
|
313
313
|
|