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
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* phases/notification.js — post-merge story-complete notification phase.
|
|
3
|
-
*
|
|
4
|
-
* Fires a single consolidated story-merged webhook, then a rolled-up
|
|
5
|
-
* `epic-progress` webhook (comment-suppressed) so operators see the
|
|
6
|
-
* Epic's stories-done count tick up at each story-close without
|
|
7
|
-
* subscribing to the per-story `story-merged` channel. The rolled-up
|
|
8
|
-
* dispatch is best-effort — a flaky webhook MUST NOT block story-close.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { notify } from '../../../../notify.js';
|
|
12
|
-
import { Logger } from '../../../Logger.js';
|
|
13
|
-
|
|
14
|
-
function reapPhaseLogger(progress) {
|
|
15
|
-
return progress ?? (() => {});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function notificationPhase(ctx, state) {
|
|
19
|
-
const {
|
|
20
|
-
epicId,
|
|
21
|
-
storyId,
|
|
22
|
-
story,
|
|
23
|
-
epicBranch,
|
|
24
|
-
config,
|
|
25
|
-
progress,
|
|
26
|
-
provider,
|
|
27
|
-
notifyFn = notify,
|
|
28
|
-
logger = Logger,
|
|
29
|
-
} = ctx;
|
|
30
|
-
const closedTickets = state.ticketClosure?.closedTickets ?? [];
|
|
31
|
-
const log = reapPhaseLogger(progress);
|
|
32
|
-
log('NOTIFY', `Sending story-complete notification for Story #${storyId}...`);
|
|
33
|
-
await notifyFn(
|
|
34
|
-
epicId,
|
|
35
|
-
{
|
|
36
|
-
severity: 'medium',
|
|
37
|
-
message: `✅ Story #${storyId} — *${story.title}* — has been completed and merged into \`${epicBranch}\`. ${closedTickets.length} ticket(s) closed.`,
|
|
38
|
-
event: 'story-merged',
|
|
39
|
-
level: 'story',
|
|
40
|
-
epicId,
|
|
41
|
-
},
|
|
42
|
-
{ config },
|
|
43
|
-
);
|
|
44
|
-
// Fire a rolled-up `epic-progress` webhook so operators see the Epic's
|
|
45
|
-
// overall stories-done count tick up at each story-close, without
|
|
46
|
-
// subscribing to the per-story `story-merged` channel. Comment is
|
|
47
|
-
// suppressed (skipComment: true) — the operator-facing GitHub
|
|
48
|
-
// comment is owned by the wave-record path; this fire is webhook-only.
|
|
49
|
-
// Failures are swallowed by design (warn-then-continue) so a flaky
|
|
50
|
-
// webhook never blocks story-close.
|
|
51
|
-
if (provider && epicId) {
|
|
52
|
-
try {
|
|
53
|
-
const subs = (await provider.getSubTickets?.(epicId)) ?? [];
|
|
54
|
-
const stories = subs.filter(
|
|
55
|
-
(t) => Array.isArray(t.labels) && t.labels.includes('type::story'),
|
|
56
|
-
);
|
|
57
|
-
const total = stories.length;
|
|
58
|
-
const done = stories.filter((s) => s.state === 'closed').length;
|
|
59
|
-
const pct = total === 0 ? 0 : Math.round((done / total) * 100);
|
|
60
|
-
await notifyFn(
|
|
61
|
-
epicId,
|
|
62
|
-
{
|
|
63
|
-
severity: 'medium',
|
|
64
|
-
message: `Epic #${epicId} progress · ${done}/${total} stories done (${pct}%) · Story #${storyId} merged`,
|
|
65
|
-
event: 'epic-progress',
|
|
66
|
-
level: 'epic',
|
|
67
|
-
epicId,
|
|
68
|
-
},
|
|
69
|
-
{ config, skipComment: true },
|
|
70
|
-
);
|
|
71
|
-
} catch (err) {
|
|
72
|
-
logger?.warn?.(
|
|
73
|
-
`[notificationPhase] rolled-up epic-progress dispatch failed (swallowed): ${err?.message ?? err}`,
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
log('NOTIFY', '✅ Notification sent');
|
|
78
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* phases/temp-cleanup.js — remove the per-Story manifest pair under
|
|
3
|
-
* `temp/epic-<eid>/stories/story-<sid>/manifest.{md,json}` (Epic #1030
|
|
4
|
-
* Story #1040). Falls back to the legacy flat
|
|
5
|
-
* `temp/story-manifest-<id>.{md,json}` layout when `epicId` is unknown
|
|
6
|
-
* — both paths are tried so partial migrations don't leak files in
|
|
7
|
-
* either layout. All deletes are idempotent; ENOENT is silently
|
|
8
|
-
* absorbed.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import path from 'node:path';
|
|
12
|
-
import { storyArtifactPath } from '../../../config/temp-paths.js';
|
|
13
|
-
|
|
14
|
-
function reapPhaseLogger(progress) {
|
|
15
|
-
return progress ?? (() => {});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function tempCleanupPhase(ctx) {
|
|
19
|
-
const { storyId, epicId, projectRoot, progress, unlinkFn, config } = ctx;
|
|
20
|
-
const log = reapPhaseLogger(progress);
|
|
21
|
-
const unlink = unlinkFn ?? (await import('node:fs/promises')).unlink;
|
|
22
|
-
|
|
23
|
-
// Per-Epic layout (Epic #1030 Story #1040): `temp/epic-<eid>/stories/story-<sid>/manifest.{md,json}`.
|
|
24
|
-
// Legacy flat layout: `temp/story-manifest-<sid>.{md,json}`. The migration
|
|
25
|
-
// tolerates both — try the per-Epic path first when `epicId` is known,
|
|
26
|
-
// and always sweep the legacy path so a half-migrated cohort doesn't
|
|
27
|
-
// leave residue.
|
|
28
|
-
const targets = [];
|
|
29
|
-
if (epicId) {
|
|
30
|
-
const eid = Number(epicId);
|
|
31
|
-
const sid = Number(storyId);
|
|
32
|
-
targets.push(
|
|
33
|
-
{
|
|
34
|
-
path: storyArtifactPath(eid, sid, 'manifest.md', config),
|
|
35
|
-
label: `temp/epic-${epicId}/stories/story-${storyId}/manifest.md`,
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
path: storyArtifactPath(eid, sid, 'manifest.json', config),
|
|
39
|
-
label: `temp/epic-${epicId}/stories/story-${storyId}/manifest.json`,
|
|
40
|
-
},
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
// Legacy flat layout is rooted at the framework's projectRoot — this is
|
|
44
|
-
// a half-migrated-cohort sweep, not a configured-tempRoot target. Once
|
|
45
|
-
// the legacy paths can no longer exist on any live install, this block
|
|
46
|
-
// can be deleted entirely.
|
|
47
|
-
const legacyBase = path.join(
|
|
48
|
-
projectRoot,
|
|
49
|
-
'temp',
|
|
50
|
-
`story-manifest-${storyId}`,
|
|
51
|
-
);
|
|
52
|
-
targets.push(
|
|
53
|
-
{ path: `${legacyBase}.md`, label: `temp/story-manifest-${storyId}.md` },
|
|
54
|
-
{
|
|
55
|
-
path: `${legacyBase}.json`,
|
|
56
|
-
label: `temp/story-manifest-${storyId}.json`,
|
|
57
|
-
},
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
for (const target of targets) {
|
|
61
|
-
try {
|
|
62
|
-
await unlink(target.path);
|
|
63
|
-
log('CLEANUP', `🗑️ Deleted ${target.label}`);
|
|
64
|
-
} catch {
|
|
65
|
-
// File may not exist — deletion is idempotent.
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* phases/ticket-closure.js — post-merge ticket transition + cascade phase.
|
|
3
|
-
*
|
|
4
|
-
* Transitions the Story to `agent::done`, then runs cascade completion
|
|
5
|
-
* so any parent Epic-side rollup that is now fully resolved closes too.
|
|
6
|
-
*
|
|
7
|
-
* **2-tier closure (Story #3127).** Under the 2-tier hierarchy a Story
|
|
8
|
-
* is the leaf unit of execution and has no child tickets — `tasks`
|
|
9
|
-
* arrives as an empty array. `batchTransitionTickets` handles the empty
|
|
10
|
-
* input cleanly (the loop trivially completes), the Story is
|
|
11
|
-
* transitioned alone, and cascade completion walks upward to
|
|
12
|
-
* parent. No branch on hierarchy mode is required here.
|
|
13
|
-
*
|
|
14
|
-
* Notifications are intentionally
|
|
15
|
-
* NOT routed through the per-ticket transitions here — `notificationPhase`
|
|
16
|
-
* fires a single rolled-up story-complete message immediately after this
|
|
17
|
-
* phase, so threading `notify` through would double-emit events on every
|
|
18
|
-
* close (Story #2534 / Task #2539).
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { batchTransitionTickets } from '../../../story-lifecycle.js';
|
|
22
|
-
import { toDone } from '../../label-transitions.js';
|
|
23
|
-
import { cascadeCompletion, STATE_LABELS } from '../../ticketing.js';
|
|
24
|
-
|
|
25
|
-
function reapPhaseLogger(progress) {
|
|
26
|
-
return progress ?? (() => {});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function ticketClosurePhase(ctx) {
|
|
30
|
-
const { provider, tasks, storyId, progress, logger } = ctx;
|
|
31
|
-
const log = reapPhaseLogger(progress);
|
|
32
|
-
|
|
33
|
-
// The `notify` function is intentionally NOT forwarded to per-ticket
|
|
34
|
-
// transitions here. `notificationPhase` fires a single consolidated
|
|
35
|
-
// story-complete message immediately after this phase; passing notify
|
|
36
|
-
// through would emit redundant state-transition events (one from the
|
|
37
|
-
// cascade-up triggered by any child ticket, one from the explicit
|
|
38
|
-
// Story toDone below) that show up as duplicate Slack/webhook lines per
|
|
39
|
-
// story close.
|
|
40
|
-
log(
|
|
41
|
-
'TICKETS',
|
|
42
|
-
`Transitioning ${tasks.length} child ticket(s) to agent::done...`,
|
|
43
|
-
);
|
|
44
|
-
const batch = await batchTransitionTickets(
|
|
45
|
-
provider,
|
|
46
|
-
tasks,
|
|
47
|
-
STATE_LABELS.DONE,
|
|
48
|
-
{ progress: log },
|
|
49
|
-
);
|
|
50
|
-
const closedTickets = [...batch.transitioned, ...batch.skipped];
|
|
51
|
-
|
|
52
|
-
// Story #2534 / Task #2539 — auto-transition `agent::closing →
|
|
53
|
-
// agent::done` deterministically and idempotently on every successful
|
|
54
|
-
// merge. The pre-Story #2534 path silently swallowed transport errors
|
|
55
|
-
// here, which made an already-merged-but-not-labelled Story look closed
|
|
56
|
-
// in the close-result envelope. We now:
|
|
57
|
-
// - read the Story snapshot first to detect an "already done" state
|
|
58
|
-
// (label `agent::done` AND issue state `closed`) and treat re-runs
|
|
59
|
-
// as a no-op record, satisfying the idempotency contract;
|
|
60
|
-
// - otherwise call `toDone` unconditionally — there is no other
|
|
61
|
-
// conditional skip in any normal exit path;
|
|
62
|
-
// - rethrow transport errors so the surrounding
|
|
63
|
-
// `runPhase('ticket-closure', ...)` logs the failure loudly via
|
|
64
|
-
// `[phase=ticket-closure] <message>` instead of letting the close
|
|
65
|
-
// report success on a half-failed transition.
|
|
66
|
-
log('TICKETS', `Transitioning Story #${storyId} to agent::done...`);
|
|
67
|
-
let storySnapshot = null;
|
|
68
|
-
try {
|
|
69
|
-
storySnapshot = await provider.getTicket(storyId);
|
|
70
|
-
} catch (err) {
|
|
71
|
-
logger?.warn?.(
|
|
72
|
-
`[phase=tickets] Story #${storyId} snapshot read failed (continuing with transition): ${err?.message ?? err}`,
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
const alreadyDone =
|
|
76
|
-
storySnapshot &&
|
|
77
|
-
Array.isArray(storySnapshot.labels) &&
|
|
78
|
-
storySnapshot.labels.includes(STATE_LABELS.DONE) &&
|
|
79
|
-
storySnapshot.state === 'closed';
|
|
80
|
-
if (alreadyDone) {
|
|
81
|
-
log(
|
|
82
|
-
'TICKETS',
|
|
83
|
-
` #${storyId} already agent::done — no-op (idempotent re-run)`,
|
|
84
|
-
);
|
|
85
|
-
closedTickets.push(storyId);
|
|
86
|
-
} else {
|
|
87
|
-
await toDone(provider, [storyId]);
|
|
88
|
-
closedTickets.push(storyId);
|
|
89
|
-
log('TICKETS', ` #${storyId} → agent::done ✅`);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
log('TICKETS', 'Running cascade completion...');
|
|
93
|
-
let cascadedTo = [];
|
|
94
|
-
let cascadeFailed = [];
|
|
95
|
-
try {
|
|
96
|
-
const cascade = (await cascadeCompletion(provider, storyId)) ?? {
|
|
97
|
-
cascadedTo: [],
|
|
98
|
-
failed: [],
|
|
99
|
-
};
|
|
100
|
-
cascadedTo = cascade.cascadedTo ?? [];
|
|
101
|
-
cascadeFailed = cascade.failed ?? [];
|
|
102
|
-
if (cascadedTo.length > 0) {
|
|
103
|
-
log(
|
|
104
|
-
'TICKETS',
|
|
105
|
-
` Cascaded to: ${cascadedTo.map((id) => `#${id}`).join(', ')}`,
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
for (const { parentId, error } of cascadeFailed) {
|
|
109
|
-
logger.error(
|
|
110
|
-
` Cascade partial-failure on parent #${parentId}: ${error}`,
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
} catch (err) {
|
|
114
|
-
logger.error(` Cascade fully failed (non-fatal): ${err.message}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return { closedTickets, cascadedTo, cascadeFailed };
|
|
118
|
-
}
|
|
@@ -1,397 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* phases/worktree-reap.js — post-merge worktree reap phase.
|
|
3
|
-
*
|
|
4
|
-
* After a Story branch is merged (pre-v2: into the Epic branch; v2:
|
|
5
|
-
* squash-merged to `main` via `single-story-close.js`), this phase removes
|
|
6
|
-
* the per-Story worktree at `.worktrees/story-<id>/`. The reap is split
|
|
7
|
-
* into three concerns:
|
|
8
|
-
*
|
|
9
|
-
* 1. `worktreeReapPhase` — the phase entry point. Honors the
|
|
10
|
-
* `worktreeIsolation.{enabled,reapOnSuccess}` config, delegates the
|
|
11
|
-
* actual remove to `WorktreeManager.reap`, then runs a re-prune
|
|
12
|
-
* retry loop to clear stale `.git/worktrees/<name>/` registry
|
|
13
|
-
* entries that Windows file-locks occasionally leave behind.
|
|
14
|
-
* 2. `applyStillRegisteredState` — classifies a still-registered entry
|
|
15
|
-
* after retries as either `stale-registry-entry` (operationally
|
|
16
|
-
* complete; schedule pending-cleanup drain) or `still-registered`
|
|
17
|
-
* (genuine failure; OPERATOR ACTION REQUIRED).
|
|
18
|
-
* 3. Friction-signal emission via `signals-writer` so retrospectives
|
|
19
|
-
* surface lock failures and stale-registry escalations.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import fs from 'node:fs';
|
|
23
|
-
import path from 'node:path';
|
|
24
|
-
import { setTimeout as sleepPromise } from 'node:timers/promises';
|
|
25
|
-
import { getStoryBranch } from '../../../git-utils.js';
|
|
26
|
-
import { appendSignal } from '../../../observability/signals-writer.js';
|
|
27
|
-
import { recordPendingCleanup } from '../../../worktree/lifecycle/pending-cleanup.js';
|
|
28
|
-
import { WorktreeManager } from '../../../worktree-manager.js';
|
|
29
|
-
|
|
30
|
-
const WINDOWS_LOCK_RE =
|
|
31
|
-
/(permission denied|access is denied|directory not empty|resource busy|device or resource busy|sharing violation|EACCES|EBUSY|ENOTEMPTY)/i;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Backoff delays (ms) between `git worktree prune` + re-list passes when a
|
|
35
|
-
* worktree entry is still registered after a successful reap. On Windows the
|
|
36
|
-
* registry-cleanup half of `git worktree remove` often loses a race with a
|
|
37
|
-
* background file handle (Defender / Search indexer / the editor) — the
|
|
38
|
-
* directory and branch are gone, but `.git/worktrees/<name>/` is locked
|
|
39
|
-
* for a beat. Three retries (250ms, 1s, 4s) cover the common case without
|
|
40
|
-
* adding noticeable latency to clean POSIX runs.
|
|
41
|
-
*/
|
|
42
|
-
const STALE_REGISTRY_REPRUNE_DELAYS_MS = [250, 1000, 4000];
|
|
43
|
-
|
|
44
|
-
function isWindowsReapLockFailure(reason) {
|
|
45
|
-
return typeof reason === 'string' && WINDOWS_LOCK_RE.test(reason);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function findStillRegisteredEntry(entries, storyId) {
|
|
49
|
-
if (!Array.isArray(entries)) return undefined;
|
|
50
|
-
const want = Number(storyId);
|
|
51
|
-
return entries.find((r) => {
|
|
52
|
-
if (!r || typeof r.path !== 'string') return false;
|
|
53
|
-
const match = r.path.match(/[/\\]story-(\d+)$/);
|
|
54
|
-
return match ? Number(match[1]) === want : false;
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function resolveWorktreeRoot(repoRoot, delivery) {
|
|
59
|
-
if (!repoRoot) return null;
|
|
60
|
-
const configuredRoot = delivery?.worktreeIsolation?.root ?? '.worktrees';
|
|
61
|
-
return path.join(repoRoot, configuredRoot);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function retryPruneUntilCleared(
|
|
65
|
-
wm,
|
|
66
|
-
storyId,
|
|
67
|
-
{ sleep, delays = STALE_REGISTRY_REPRUNE_DELAYS_MS } = {},
|
|
68
|
-
) {
|
|
69
|
-
const sleepFn = typeof sleep === 'function' ? sleep : sleepPromise;
|
|
70
|
-
let attempts = 0;
|
|
71
|
-
let lastEntry;
|
|
72
|
-
for (const delay of delays) {
|
|
73
|
-
await sleepFn(delay);
|
|
74
|
-
attempts += 1;
|
|
75
|
-
if (typeof wm.prune === 'function') {
|
|
76
|
-
try {
|
|
77
|
-
await wm.prune();
|
|
78
|
-
} catch {
|
|
79
|
-
// best-effort — prune failure is non-fatal; fall through to list check
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const refreshed = (await wm.list()) ?? [];
|
|
83
|
-
lastEntry = findStillRegisteredEntry(refreshed, storyId);
|
|
84
|
-
if (!lastEntry) return { cleared: true, attempts };
|
|
85
|
-
}
|
|
86
|
-
return { cleared: false, attempts, stillRegistered: lastEntry };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function reapPhaseLogger(progress) {
|
|
90
|
-
return progress ?? (() => {});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function createWorktreeReapState(overrides = {}) {
|
|
94
|
-
return {
|
|
95
|
-
status: 'not-run',
|
|
96
|
-
path: null,
|
|
97
|
-
reason: null,
|
|
98
|
-
method: null,
|
|
99
|
-
pendingCleanup: null,
|
|
100
|
-
branchDeleted: null,
|
|
101
|
-
remoteBranchDeleted: null,
|
|
102
|
-
...overrides,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function emitReapFailureFriction({
|
|
107
|
-
storyId,
|
|
108
|
-
epicId,
|
|
109
|
-
reapResult,
|
|
110
|
-
epicBranch,
|
|
111
|
-
logger,
|
|
112
|
-
config,
|
|
113
|
-
}) {
|
|
114
|
-
if (!epicId || !storyId) return;
|
|
115
|
-
const reason = String(reapResult?.reason ?? 'unknown');
|
|
116
|
-
const wtPath = reapResult?.path ?? '(unknown path)';
|
|
117
|
-
try {
|
|
118
|
-
await appendSignal({
|
|
119
|
-
epicId: Number(epicId),
|
|
120
|
-
storyId: Number(storyId),
|
|
121
|
-
signal: {
|
|
122
|
-
kind: 'friction',
|
|
123
|
-
ts: new Date().toISOString(),
|
|
124
|
-
epicId: Number(epicId),
|
|
125
|
-
storyId: Number(storyId),
|
|
126
|
-
category: 'reap-failure',
|
|
127
|
-
emitter: { tool: 'story-close.js' },
|
|
128
|
-
details: { message: `Worktree reap failed: ${reason}` },
|
|
129
|
-
epicBranch,
|
|
130
|
-
worktreePath: wtPath,
|
|
131
|
-
reason,
|
|
132
|
-
},
|
|
133
|
-
config,
|
|
134
|
-
});
|
|
135
|
-
} catch (err) {
|
|
136
|
-
logger?.warn?.(
|
|
137
|
-
`[post-merge-pipeline] friction signal append failed: ${err?.message ?? err}`,
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function resolveSkipState(wtConfig, log) {
|
|
143
|
-
if (!wtConfig?.enabled) {
|
|
144
|
-
log('WORKTREE', '⏭️ Skipping worktree reap (worktree isolation disabled)');
|
|
145
|
-
return createWorktreeReapState({ status: 'skipped-disabled' });
|
|
146
|
-
}
|
|
147
|
-
if (!(wtConfig.reapOnSuccess ?? true)) {
|
|
148
|
-
log('WORKTREE', '⏭️ Skipping worktree reap (reapOnSuccess=false)');
|
|
149
|
-
return createWorktreeReapState({ status: 'skipped-config' });
|
|
150
|
-
}
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function resolveInitialReapStatus(reapResult) {
|
|
155
|
-
if (reapResult.removed) return 'removed';
|
|
156
|
-
if (reapResult.method === 'deferred-to-sweep') return 'deferred-to-sweep';
|
|
157
|
-
return 'failed';
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function initialReapState(reapResult) {
|
|
161
|
-
return createWorktreeReapState({
|
|
162
|
-
status: resolveInitialReapStatus(reapResult),
|
|
163
|
-
path: reapResult.path ?? null,
|
|
164
|
-
reason: reapResult.reason ?? null,
|
|
165
|
-
method: reapResult.method ?? null,
|
|
166
|
-
pendingCleanup: reapResult.pendingCleanup ?? null,
|
|
167
|
-
branchDeleted:
|
|
168
|
-
reapResult.branchDeleted !== undefined ? reapResult.branchDeleted : null,
|
|
169
|
-
remoteBranchDeleted:
|
|
170
|
-
reapResult.remoteBranchDeleted !== undefined
|
|
171
|
-
? reapResult.remoteBranchDeleted
|
|
172
|
-
: null,
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async function logReapOutcome({
|
|
177
|
-
reapResult,
|
|
178
|
-
log,
|
|
179
|
-
logger,
|
|
180
|
-
storyId,
|
|
181
|
-
epicId,
|
|
182
|
-
epicBranch,
|
|
183
|
-
config,
|
|
184
|
-
}) {
|
|
185
|
-
if (reapResult.removed) {
|
|
186
|
-
log('WORKTREE', `🗑️ Reaped worktree: ${reapResult.path}`);
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
if (!reapResult.reason) return;
|
|
190
|
-
await emitReapFailureFriction({
|
|
191
|
-
storyId,
|
|
192
|
-
epicId,
|
|
193
|
-
reapResult,
|
|
194
|
-
epicBranch,
|
|
195
|
-
logger,
|
|
196
|
-
config,
|
|
197
|
-
});
|
|
198
|
-
log(
|
|
199
|
-
'WORKTREE',
|
|
200
|
-
`⚠️ Worktree not reaped (${reapResult.reason}): ${reapResult.path}`,
|
|
201
|
-
);
|
|
202
|
-
if (isWindowsReapLockFailure(reapResult.reason)) {
|
|
203
|
-
logger.error(
|
|
204
|
-
`[story-close] OPERATOR ACTION REQUIRED: Worktree at ${reapResult.path} ` +
|
|
205
|
-
`could not be removed (Windows lock/permission error: ${reapResult.reason}). ` +
|
|
206
|
-
'Close any editor/terminal holding the path, then run ' +
|
|
207
|
-
'`git worktree remove <path> --force && git worktree prune` to clean up.',
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async function detectStillRegistered({ wm, storyId, log, sleep }) {
|
|
213
|
-
const leftover = (await wm.list()) ?? [];
|
|
214
|
-
const initial = findStillRegisteredEntry(leftover, storyId);
|
|
215
|
-
if (!initial) return null;
|
|
216
|
-
const retry = await retryPruneUntilCleared(wm, storyId, { sleep });
|
|
217
|
-
if (retry.cleared) {
|
|
218
|
-
log(
|
|
219
|
-
'WORKTREE',
|
|
220
|
-
`🧹 Stale worktree registry entry cleared after ${retry.attempts} re-prune attempt(s)`,
|
|
221
|
-
);
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
return retry.stillRegistered ?? initial;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
async function escalateStillRegistered({
|
|
228
|
-
state,
|
|
229
|
-
stillRegistered,
|
|
230
|
-
storyId,
|
|
231
|
-
epicId,
|
|
232
|
-
epicBranch,
|
|
233
|
-
logger,
|
|
234
|
-
config,
|
|
235
|
-
}) {
|
|
236
|
-
logger.error(
|
|
237
|
-
`[story-close] OPERATOR ACTION REQUIRED: Worktree still registered after reap: ` +
|
|
238
|
-
`${stillRegistered.path}. Run ` +
|
|
239
|
-
'`git worktree remove <path> --force && git worktree prune` to clean up.',
|
|
240
|
-
);
|
|
241
|
-
await emitReapFailureFriction({
|
|
242
|
-
storyId,
|
|
243
|
-
epicId,
|
|
244
|
-
reapResult: {
|
|
245
|
-
path: stillRegistered.path,
|
|
246
|
-
reason: 'still-registered-after-reap',
|
|
247
|
-
},
|
|
248
|
-
epicBranch,
|
|
249
|
-
logger,
|
|
250
|
-
config,
|
|
251
|
-
});
|
|
252
|
-
return state;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function logStaleRegistryEntry({ state, stillRegistered, logger }) {
|
|
256
|
-
logger.warn(
|
|
257
|
-
`[story-close] Worktree directory removed and branch deleted, but ` +
|
|
258
|
-
`\`git worktree list\` still shows ${stillRegistered.path}. ` +
|
|
259
|
-
'Scheduled for background prune via pending-cleanup; ' +
|
|
260
|
-
`branchDeleted=${state.branchDeleted}.`,
|
|
261
|
-
);
|
|
262
|
-
return state;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export async function worktreeReapPhase(ctx) {
|
|
266
|
-
const {
|
|
267
|
-
delivery,
|
|
268
|
-
storyId,
|
|
269
|
-
epicId,
|
|
270
|
-
epicBranch,
|
|
271
|
-
repoRoot,
|
|
272
|
-
progress,
|
|
273
|
-
logger,
|
|
274
|
-
worktreeManagerFactory,
|
|
275
|
-
config,
|
|
276
|
-
sleep,
|
|
277
|
-
recordPendingCleanupFn = recordPendingCleanup,
|
|
278
|
-
pathExistsFn = fs.existsSync,
|
|
279
|
-
} = ctx;
|
|
280
|
-
const wtConfig = delivery?.worktreeIsolation;
|
|
281
|
-
const log = reapPhaseLogger(progress);
|
|
282
|
-
const skipState = resolveSkipState(wtConfig, log);
|
|
283
|
-
if (skipState) return skipState;
|
|
284
|
-
|
|
285
|
-
const wm = worktreeManagerFactory
|
|
286
|
-
? worktreeManagerFactory({ repoRoot, config: wtConfig })
|
|
287
|
-
: new WorktreeManager({ repoRoot, config: wtConfig });
|
|
288
|
-
const reapResult = await wm.reap(storyId, { epicBranch });
|
|
289
|
-
let state = initialReapState(reapResult);
|
|
290
|
-
await logReapOutcome({
|
|
291
|
-
reapResult,
|
|
292
|
-
log,
|
|
293
|
-
logger,
|
|
294
|
-
storyId,
|
|
295
|
-
epicId,
|
|
296
|
-
epicBranch,
|
|
297
|
-
config,
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
const stillRegistered = await detectStillRegistered({
|
|
301
|
-
wm,
|
|
302
|
-
storyId,
|
|
303
|
-
log,
|
|
304
|
-
sleep,
|
|
305
|
-
});
|
|
306
|
-
if (!stillRegistered) return state;
|
|
307
|
-
|
|
308
|
-
state = applyStillRegisteredState({
|
|
309
|
-
state,
|
|
310
|
-
stillRegistered,
|
|
311
|
-
reapResult,
|
|
312
|
-
storyId,
|
|
313
|
-
delivery,
|
|
314
|
-
repoRoot,
|
|
315
|
-
logger,
|
|
316
|
-
recordPendingCleanupFn,
|
|
317
|
-
pathExistsFn,
|
|
318
|
-
});
|
|
319
|
-
if (state.status === 'still-registered') {
|
|
320
|
-
return escalateStillRegistered({
|
|
321
|
-
state,
|
|
322
|
-
stillRegistered,
|
|
323
|
-
storyId,
|
|
324
|
-
epicId,
|
|
325
|
-
epicBranch,
|
|
326
|
-
logger,
|
|
327
|
-
config,
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
if (state.status === 'stale-registry-entry') {
|
|
331
|
-
return logStaleRegistryEntry({ state, stillRegistered, logger });
|
|
332
|
-
}
|
|
333
|
-
return state;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Decide how to treat a worktree entry that is still registered after reap +
|
|
338
|
-
* re-prune retries. Two outcomes:
|
|
339
|
-
*
|
|
340
|
-
* - `stale-registry-entry` (operationally complete): the reap succeeded,
|
|
341
|
-
* the worktree directory is gone, and the local branch was deleted by
|
|
342
|
-
* reap. The only artifact left is the `.git/worktrees/<name>/` registry
|
|
343
|
-
* entry — a Windows file-lock artifact, not a genuine cleanup failure.
|
|
344
|
-
* Record a pending-cleanup entry so the post-close drain (or the next
|
|
345
|
-
* plan-time sweep) re-runs `git worktree prune`, and let the close
|
|
346
|
-
* pipeline report `branchDeleted: true` honestly.
|
|
347
|
-
* - `still-registered` (genuine failure): the directory is still on disk
|
|
348
|
-
* OR the branch was not deleted. The pre-existing OPERATOR ACTION
|
|
349
|
-
* escalation still fires.
|
|
350
|
-
*/
|
|
351
|
-
function applyStillRegisteredState({
|
|
352
|
-
state,
|
|
353
|
-
stillRegistered,
|
|
354
|
-
reapResult,
|
|
355
|
-
storyId,
|
|
356
|
-
delivery,
|
|
357
|
-
repoRoot,
|
|
358
|
-
logger,
|
|
359
|
-
recordPendingCleanupFn,
|
|
360
|
-
pathExistsFn,
|
|
361
|
-
}) {
|
|
362
|
-
const pathGone = !pathExistsFn(stillRegistered.path);
|
|
363
|
-
const branchDeleted = reapResult.branchDeleted === true;
|
|
364
|
-
const operationallyComplete =
|
|
365
|
-
reapResult.removed === true && pathGone && branchDeleted;
|
|
366
|
-
if (!operationallyComplete) {
|
|
367
|
-
return {
|
|
368
|
-
...state,
|
|
369
|
-
status: 'still-registered',
|
|
370
|
-
path: stillRegistered.path,
|
|
371
|
-
reason: 'still-registered-after-reap',
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
const worktreeRoot = resolveWorktreeRoot(repoRoot, delivery);
|
|
375
|
-
let manifestEntry = null;
|
|
376
|
-
if (worktreeRoot) {
|
|
377
|
-
try {
|
|
378
|
-
manifestEntry = recordPendingCleanupFn(worktreeRoot, {
|
|
379
|
-
storyId: Number(storyId),
|
|
380
|
-
branch: getStoryBranch(storyId),
|
|
381
|
-
path: stillRegistered.path,
|
|
382
|
-
push: false,
|
|
383
|
-
});
|
|
384
|
-
} catch (err) {
|
|
385
|
-
logger?.warn?.(
|
|
386
|
-
`[post-merge-pipeline] pending-cleanup record failed (continuing): ${err?.message ?? err}`,
|
|
387
|
-
);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
return {
|
|
391
|
-
...state,
|
|
392
|
-
status: 'stale-registry-entry',
|
|
393
|
-
path: stillRegistered.path,
|
|
394
|
-
reason: 'stale-registry-entry',
|
|
395
|
-
pendingCleanup: manifestEntry ?? state.pendingCleanup,
|
|
396
|
-
};
|
|
397
|
-
}
|