mandrel 1.94.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +116 -99
- package/.agents/agents/acceptance-critic.md +9 -7
- package/.agents/agents/story-worker.md +45 -51
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +498 -1287
- package/.agents/docs/agentrc-reference.json +185 -80
- package/.agents/docs/configuration.md +132 -190
- package/.agents/docs/execution-reference.md +51 -25
- package/.agents/docs/quality-gates.md +25 -33
- package/.agents/docs/workflows.md +8 -8
- package/.agents/instructions.md +113 -139
- package/.agents/rules/ci-remediation.md +11 -15
- package/.agents/rules/git-conventions-reference.md +48 -58
- package/.agents/rules/git-conventions.md +16 -22
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +83 -254
- package/.agents/schemas/audit-rules.json +59 -1
- package/.agents/schemas/audit-rules.schema.json +33 -1
- package/.agents/schemas/lifecycle/README.md +1 -2
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
- package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
- package/.agents/schemas/signal-event.schema.json +3 -3
- package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +22 -66
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/audit-to-stories.js +7 -7
- package/.agents/scripts/boot-sweep.js +1 -1
- package/.agents/scripts/bootstrap.js +3 -3
- package/.agents/scripts/check-dead-exports.js +43 -104
- package/.agents/scripts/check-doc-links.js +2 -2
- package/.agents/scripts/check-lifecycle-lint.js +7 -10
- package/.agents/scripts/check-workflow-cli-lint.js +91 -0
- package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
- package/.agents/scripts/deliver-recover.js +122 -0
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/evidence-gate.js +20 -50
- package/.agents/scripts/generate-skills-index.js +17 -1
- package/.agents/scripts/generate-workflows-doc.js +4 -4
- package/.agents/scripts/lib/ITicketingProvider.js +1 -19
- package/.agents/scripts/lib/Logger.js +6 -10
- package/.agents/scripts/lib/audit-suite/runner.js +2 -2
- package/.agents/scripts/lib/audit-suite/selector.js +328 -28
- package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
- package/.agents/scripts/lib/baselines/kernel.js +206 -18
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -15
- package/.agents/scripts/lib/baselines/reader.js +1 -6
- package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
- package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +6 -3
- package/.agents/scripts/lib/checks/index.js +3 -2
- package/.agents/scripts/lib/checks/loop-health.js +12 -11
- package/.agents/scripts/lib/checks/state.js +17 -248
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +26 -24
- package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
- package/.agents/scripts/lib/cli/standard-args.js +13 -22
- package/.agents/scripts/lib/cli-args.js +39 -9
- package/.agents/scripts/lib/close-validation/gates.js +15 -15
- package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
- package/.agents/scripts/lib/close-validation/runner.js +13 -21
- package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
- package/.agents/scripts/lib/config/ci.js +6 -31
- package/.agents/scripts/lib/config/delivery-routing.js +52 -35
- package/.agents/scripts/lib/config/explain.js +61 -48
- package/.agents/scripts/lib/config/github.js +7 -5
- package/.agents/scripts/lib/config/limits.js +29 -80
- package/.agents/scripts/lib/config/paths.js +0 -2
- package/.agents/scripts/lib/config/quality.js +12 -15
- package/.agents/scripts/lib/config/runners.js +20 -66
- package/.agents/scripts/lib/config/temp-paths.js +30 -63
- package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
- package/.agents/scripts/lib/config-resolver.js +2 -7
- package/.agents/scripts/lib/config-settings-schema-delivery.js +55 -161
- package/.agents/scripts/lib/config-settings-schema-quality.js +17 -16
- package/.agents/scripts/lib/config-settings-schema.js +100 -60
- package/.agents/scripts/lib/dead-exports-knip.js +105 -0
- package/.agents/scripts/lib/dead-exports-mode.js +51 -0
- package/.agents/scripts/lib/dependency-parser.js +3 -2
- package/.agents/scripts/lib/doc-tiers.js +2 -2
- package/.agents/scripts/lib/duplicate-search.js +242 -41
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +23 -14
- package/.agents/scripts/lib/format-generated-json.js +97 -0
- package/.agents/scripts/lib/framework-version.js +19 -190
- package/.agents/scripts/lib/gh-exec.js +8 -0
- package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
- package/.agents/scripts/lib/git-branch-lifecycle.js +17 -180
- package/.agents/scripts/lib/git-utils.js +32 -20
- package/.agents/scripts/lib/github/framework-repo.js +6 -0
- package/.agents/scripts/lib/json-utils.js +1 -2
- package/.agents/scripts/lib/label-constants.js +10 -38
- package/.agents/scripts/lib/label-taxonomy.js +10 -55
- package/.agents/scripts/lib/observability/active-story-env.js +44 -165
- package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
- package/.agents/scripts/lib/observability/signal-validator.js +4 -4
- package/.agents/scripts/lib/observability/signals-writer.js +6 -82
- package/.agents/scripts/lib/observability/source-classifier.js +5 -5
- package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
- package/.agents/scripts/lib/onboard/init-tail.js +1 -3
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +105 -44
- package/.agents/scripts/lib/orchestration/code-review.js +78 -436
- package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +4 -4
- package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
- package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
- package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
- package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
- package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
- package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +17 -19
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +21 -43
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
- package/.agents/scripts/lib/orchestration/merge-block-class.js +77 -21
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
- package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
- package/.agents/scripts/lib/orchestration/plan-context.js +354 -282
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +4 -8
- package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
- package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +71 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +245 -0
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +432 -858
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +778 -0
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +47 -115
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
- package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +52 -51
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
- package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
- package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
- package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
- package/.agents/scripts/lib/orchestration/review-depth.js +126 -47
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
- package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
- package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
- package/.agents/scripts/lib/orchestration/run-epilogue.js +784 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +25 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +86 -41
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +77 -32
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +44 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +275 -46
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
- package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
- package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +28 -42
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +298 -0
- package/.agents/scripts/lib/orchestration/story-init-remote.js +51 -0
- package/.agents/scripts/lib/orchestration/story-plan-state.js +33 -0
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +60 -25
- package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +130 -75
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +192 -377
- package/.agents/scripts/lib/orchestration/ticket-validator.js +123 -25
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -72
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +45 -55
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +66 -6
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
- package/.agents/scripts/lib/planning-corpus.js +12 -281
- package/.agents/scripts/lib/preflight-runner.js +4 -4
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
- package/.agents/scripts/lib/signals/index.js +4 -17
- package/.agents/scripts/lib/signals/read.js +35 -35
- package/.agents/scripts/lib/signals/schema.js +8 -11
- package/.agents/scripts/lib/signals/span-tree.js +7 -7
- package/.agents/scripts/lib/signals/write.js +0 -1
- package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
- package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
- package/.agents/scripts/lib/skills/parse-skill.js +16 -3
- package/.agents/scripts/lib/story-adjacency.js +17 -19
- package/.agents/scripts/lib/story-body/story-body.js +130 -75
- package/.agents/scripts/lib/story-plan.js +2 -4
- package/.agents/scripts/lib/templates/decomposer-prompts.js +51 -46
- package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
- package/.agents/scripts/lib/test-env.js +14 -1
- package/.agents/scripts/lib/test-tiers.js +0 -3
- package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +25 -39
- package/.agents/scripts/lib/validation-evidence.js +32 -60
- package/.agents/scripts/lib/wave-runner/ready-set.js +38 -12
- package/.agents/scripts/lib/workspace-provisioner.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +72 -25
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
- package/.agents/scripts/lint-issue-body.js +71 -40
- package/.agents/scripts/mandrel-update-preflight.js +1 -1
- package/.agents/scripts/notify.js +4 -3
- package/.agents/scripts/plan-context.js +102 -80
- package/.agents/scripts/plan-persist.js +230 -279
- package/.agents/scripts/plan-run-epilogue.js +142 -0
- package/.agents/scripts/post-structured-comment.js +0 -38
- package/.agents/scripts/pr-watch-with-update.js +43 -22
- package/.agents/scripts/providers/github/compose.js +0 -1
- package/.agents/scripts/providers/github/errors.js +0 -19
- package/.agents/scripts/providers/github/issues.js +13 -39
- package/.agents/scripts/providers/github/mappers.js +5 -12
- package/.agents/scripts/providers/github/sub-issues.js +0 -47
- package/.agents/scripts/providers/github/tickets.js +33 -156
- package/.agents/scripts/providers/github.js +17 -6
- package/.agents/scripts/resolve-stories.js +236 -0
- package/.agents/scripts/run-coverage.js +4 -1
- package/.agents/scripts/run-lint.js +2 -2
- package/.agents/scripts/run-verify.js +31 -2
- package/.agents/scripts/signals-view.js +25 -21
- package/.agents/scripts/single-story-close.js +178 -26
- package/.agents/scripts/single-story-confirm-merge.js +313 -24
- package/.agents/scripts/single-story-init.js +35 -30
- package/.agents/scripts/stories-wave-tick.js +85 -10
- package/.agents/scripts/story-plan.js +28 -49
- package/.agents/scripts/update-ticket-state.js +14 -65
- package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
- package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
- package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
- package/.agents/skills/core/idea-refinement/SKILL.md +9 -9
- package/.agents/skills/core/scope-triage/SKILL.md +31 -172
- package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
- package/.agents/skills/core/security-and-hardening/reference.md +375 -0
- package/.agents/skills/skills.index.json +3 -53
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
- package/.agents/starter-agentrc.json +0 -5
- package/.agents/templates/agent-protocol.md +9 -10
- package/.agents/workflows/audit-architecture.md +6 -7
- package/.agents/workflows/audit-clean-code.md +7 -7
- package/.agents/workflows/audit-dependencies.md +3 -3
- package/.agents/workflows/audit-devops.md +3 -3
- package/.agents/workflows/audit-documentation.md +9 -10
- package/.agents/workflows/audit-lighthouse.md +11 -3
- package/.agents/workflows/audit-navigability.md +13 -2
- package/.agents/workflows/audit-performance.md +5 -6
- package/.agents/workflows/audit-privacy.md +3 -3
- package/.agents/workflows/audit-quality.md +11 -12
- package/.agents/workflows/audit-security.md +4 -5
- package/.agents/workflows/audit-seo.md +13 -3
- package/.agents/workflows/audit-sre.md +3 -3
- package/.agents/workflows/audit-to-stories.md +20 -20
- package/.agents/workflows/audit-ux-ui.md +10 -3
- package/.agents/workflows/deliver.md +177 -176
- package/.agents/workflows/git-cleanup.md +5 -6
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +35 -40
- package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
- package/.agents/workflows/helpers/code-review.md +75 -196
- package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +83 -44
- package/.agents/workflows/helpers/deliver-story.md +606 -0
- package/.agents/workflows/helpers/diagnose.md +10 -10
- package/.agents/workflows/helpers/parallel-tooling.md +3 -3
- package/.agents/workflows/helpers/signals.md +16 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +66 -86
- package/.agents/workflows/mandrel-update.md +2 -1
- package/.agents/workflows/plan.md +277 -145
- package/.agents/workflows/qa-assist.md +27 -33
- package/.agents/workflows/qa-explore.md +29 -38
- package/.agents/workflows/qa-run.md +2 -6
- package/README.md +9 -8
- package/bin/mandrel.js +12 -1
- package/docs/CHANGELOG.md +70 -0
- package/lib/cli/registry.js +262 -19
- package/lib/cli/sync-agents.js +157 -0
- package/lib/cli/sync-commands.js +115 -6
- package/lib/cli/sync.js +168 -6
- package/lib/cli/update.js +105 -8
- package/lib/cli/version-helpers.js +131 -0
- package/lib/migrations/README.md +7 -5
- package/lib/migrations/index.js +12 -8
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
- package/package.json +2 -2
- package/.agents/agents/retro.md +0 -42
- package/.agents/personas/architect.md +0 -113
- package/.agents/personas/devops-engineer.md +0 -38
- package/.agents/personas/engineer.md +0 -33
- package/.agents/personas/project-manager.md +0 -114
- package/.agents/personas/qa-engineer.md +0 -95
- package/.agents/personas/security-engineer.md +0 -111
- package/.agents/personas/technical-writer.md +0 -101
- package/.agents/schemas/dispatch-manifest.json +0 -232
- package/.agents/schemas/epic-perf-report.schema.json +0 -89
- package/.agents/schemas/epic-spec.schema.json +0 -153
- package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
- package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
- package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
- package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
- package/.agents/schemas/lifecycle/slice.end.schema.json +0 -21
- package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +0 -20
- package/.agents/schemas/lifecycle/slice.start.schema.json +0 -17
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
- package/.agents/schemas/risk-verdict.schema.json +0 -66
- package/.agents/schemas/story-perf-summary.schema.json +0 -73
- package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
- package/.agents/scripts/analyze-execution.js +0 -444
- package/.agents/scripts/bookkeeping-reconcile.js +0 -117
- package/.agents/scripts/check-prepush-recovery.js +0 -90
- package/.agents/scripts/dispatcher.js +0 -295
- package/.agents/scripts/epic-audit-prepare.js +0 -497
- package/.agents/scripts/epic-audit-recheck.js +0 -274
- package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
- package/.agents/scripts/epic-deliver-preflight.js +0 -462
- package/.agents/scripts/epic-deliver-prepare.js +0 -852
- package/.agents/scripts/epic-execute-record-wave.js +0 -449
- package/.agents/scripts/epic-plan-clarity.js +0 -211
- package/.agents/scripts/epic-plan-healthcheck.js +0 -581
- package/.agents/scripts/epic-reconcile.js +0 -625
- package/.agents/scripts/lib/baseline-snapshot.js +0 -979
- package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
- package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
- package/.agents/scripts/lib/config/lifecycle.js +0 -40
- package/.agents/scripts/lib/config/preflight.js +0 -58
- package/.agents/scripts/lib/config/retro.js +0 -77
- package/.agents/scripts/lib/epic-merge-lock.js +0 -322
- package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
- package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
- package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
- package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
- package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -219
- package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
- package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
- package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -270
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -539
- package/.agents/scripts/lib/orchestration/deliver-route.js +0 -173
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/risk-verdict.js +0 -105
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -621
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
- package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +0 -400
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
- package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
- package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
- package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
- package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +0 -270
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -465
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -284
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
- package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
- package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
- package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
- package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
- package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
- package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -33
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
- package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -396
- package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
- package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
- package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
- package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
- package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
- package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
- package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
- package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
- package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
- package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
- package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
- package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
- package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
- package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
- package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
- package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
- package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
- package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -302
- package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
- package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
- package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
- package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
- package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
- package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
- package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
- package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
- package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
- package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
- package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
- package/.agents/scripts/lib/push-epic-retry.js +0 -209
- package/.agents/scripts/lib/spec/index.js +0 -36
- package/.agents/scripts/lib/spec/loader.js +0 -425
- package/.agents/scripts/lib/spec/state.js +0 -208
- package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
- package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
- package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
- package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
- package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
- package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
- package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
- package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
- package/.agents/scripts/lib/wave-runner/tick.js +0 -754
- package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
- package/.agents/scripts/lifecycle-emit.js +0 -510
- package/.agents/scripts/retro-run.js +0 -218
- package/.agents/scripts/slice-phase.js +0 -361
- package/.agents/scripts/standalone-feedback-rollup.js +0 -188
- package/.agents/scripts/story-close.js +0 -294
- package/.agents/scripts/story-init.js +0 -599
- package/.agents/scripts/story-phase.js +0 -369
- package/.agents/scripts/wave-tick.js +0 -464
- package/.agents/skills/core/analyze-execution/SKILL.md +0 -98
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
- package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
- package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
- package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
- package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -383
- package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
- package/.agents/workflows/helpers/deliver-epic-reference.md +0 -547
- package/.agents/workflows/helpers/deliver-epic-single.md +0 -331
- package/.agents/workflows/helpers/deliver-epic.md +0 -998
- package/.agents/workflows/helpers/deliver-stories.md +0 -450
- package/.agents/workflows/helpers/epic-audit.md +0 -189
- package/.agents/workflows/helpers/epic-deliver-story.md +0 -436
- package/.agents/workflows/helpers/epic-testing.md +0 -125
- package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
- package/.agents/workflows/helpers/plan-epic.md +0 -353
- package/.agents/workflows/helpers/plan-story.md +0 -251
- package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
- package/.agents/workflows/helpers/single-story-deliver.md +0 -557
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* epic-merge-lock-stale — refuse-and-print warning check.
|
|
3
|
-
*
|
|
4
|
-
* Detects orphan `epic-<id>.merge.lock` files left behind by a crashed
|
|
5
|
-
* or hung `withEpicMergeLock()` holder. The live serialization path is
|
|
6
|
-
* owned by `lib/epic-merge-lock.js` itself; this check intentionally
|
|
7
|
-
* does NOT serialize against it. Instead, at preflight time it probes
|
|
8
|
-
* the lock file for each known epic and surfaces a warning when:
|
|
9
|
-
*
|
|
10
|
-
* - the lock file exists, AND
|
|
11
|
-
* - the recorded holder PID is not alive (per `process.kill(pid, 0)`).
|
|
12
|
-
*
|
|
13
|
-
* Severity is `warning` rather than `blocker` because the live lock
|
|
14
|
-
* implementation already steals stale locks on its own (per
|
|
15
|
-
* `tryStealStale()` in epic-merge-lock.js). Surfacing the residue
|
|
16
|
-
* earlier just helps operators diagnose hung CI runs before they
|
|
17
|
-
* snowball into wave-aggregation timeouts. The fix is a literal
|
|
18
|
-
* `rm <lockPath>` — refuse-and-print so the operator runs it
|
|
19
|
-
* deliberately.
|
|
20
|
-
*
|
|
21
|
-
* Reads `state.fs.epicMergeLocks`, a map of `epicId → { exists, pid,
|
|
22
|
-
* holderAlive, path, ... }` populated by state.js.
|
|
23
|
-
*/
|
|
24
|
-
export default {
|
|
25
|
-
id: 'epic-merge-lock-stale',
|
|
26
|
-
severity: 'warning',
|
|
27
|
-
scope: ['story-close', 'retro'],
|
|
28
|
-
autoCorrect: 'refuse-and-print',
|
|
29
|
-
detect(state) {
|
|
30
|
-
const locks = state?.fs?.epicMergeLocks ?? {};
|
|
31
|
-
for (const [epicId, info] of Object.entries(locks)) {
|
|
32
|
-
if (!info?.exists) continue;
|
|
33
|
-
if (info.holderAlive) continue;
|
|
34
|
-
// Lock exists and the holder PID is not alive — orphan.
|
|
35
|
-
return {
|
|
36
|
-
id: 'epic-merge-lock-stale',
|
|
37
|
-
severity: 'warning',
|
|
38
|
-
scope: state?.scope ?? 'story-close',
|
|
39
|
-
summary: `Orphan epic merge lock for epic/${epicId} (holder PID ${info.pid ?? 'unknown'} not alive)`,
|
|
40
|
-
detail: [
|
|
41
|
-
`lock path: ${info.path}`,
|
|
42
|
-
`holder pid: ${info.pid ?? 'unknown'}`,
|
|
43
|
-
info.acquiredAt
|
|
44
|
-
? `acquired at: ${new Date(info.acquiredAt).toISOString()}`
|
|
45
|
-
: 'acquired at: unknown',
|
|
46
|
-
'The live lock will steal this on next contention, but the residue can be cleared manually.',
|
|
47
|
-
].join('\n'),
|
|
48
|
-
fixCommand: `rm "${info.path}"`,
|
|
49
|
-
autoCorrectable: false,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
},
|
|
54
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* stale-origin-epic — refuse-and-print blocker check.
|
|
3
|
-
*
|
|
4
|
-
* Detects the failure mode where a manual merge into `epic/<id>` landed
|
|
5
|
-
* locally but the operator forgot to `git push` the epic branch before
|
|
6
|
-
* re-running `story-close.js`. The close script's rebase then fights a
|
|
7
|
-
* stale `origin/epic/<id>` and re-introduces a different conflict on
|
|
8
|
-
* every retry.
|
|
9
|
-
*
|
|
10
|
-
* The check surfaces this at story-close preflight (and at retro time
|
|
11
|
-
* read-only) so the operator is told to fetch + push the epic branch
|
|
12
|
-
* before the close re-runs. It is `refuse-and-print` because the fix
|
|
13
|
-
* is `git fetch origin` followed by `git push origin epic/<id>`; both
|
|
14
|
-
* touch remote state and therefore violate the autoCorrect rules.
|
|
15
|
-
*
|
|
16
|
-
* Detection reads `state.git.epicBranchSync` (assembled in state.js).
|
|
17
|
-
* Each entry reports `{ local, remote, ahead }` for an `epic/<id>` ref;
|
|
18
|
-
* we surface the first ref whose local SHA differs from its remote SHA.
|
|
19
|
-
* If the remote ref is missing entirely we return null (this is a
|
|
20
|
-
* pre-push epic, not a stale one — story-close.js handles that path).
|
|
21
|
-
*/
|
|
22
|
-
export default {
|
|
23
|
-
id: 'stale-origin-epic',
|
|
24
|
-
severity: 'blocker',
|
|
25
|
-
scope: ['story-close', 'retro'],
|
|
26
|
-
autoCorrect: 'refuse-and-print',
|
|
27
|
-
detect(state) {
|
|
28
|
-
const sync = state?.git?.epicBranchSync ?? {};
|
|
29
|
-
const entries = Object.entries(sync);
|
|
30
|
-
for (const [branch, info] of entries) {
|
|
31
|
-
if (!info?.local || !info.remote) continue;
|
|
32
|
-
if (!info.ahead) continue;
|
|
33
|
-
return {
|
|
34
|
-
id: 'stale-origin-epic',
|
|
35
|
-
severity: 'blocker',
|
|
36
|
-
scope: state?.scope ?? 'story-close',
|
|
37
|
-
summary: `Local ${branch} differs from origin/${branch}; close will rebase against a stale base.`,
|
|
38
|
-
detail: [
|
|
39
|
-
`local ${branch} = ${info.local}`,
|
|
40
|
-
`remote origin/${branch} = ${info.remote}`,
|
|
41
|
-
'Push the epic branch (or fetch origin) before re-running story-close.',
|
|
42
|
-
].join('\n'),
|
|
43
|
-
fixCommand: `git fetch origin && git push origin ${branch}`,
|
|
44
|
-
autoCorrectable: false,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
},
|
|
49
|
-
};
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `delivery.lifecycle` accessor + framework defaults.
|
|
3
|
-
*
|
|
4
|
-
* `TimeoutWatchdog` returns null (no budget) for any timeout key absent from
|
|
5
|
-
* the operator config, meaning phases can hang indefinitely on projects that
|
|
6
|
-
* omit the block. These defaults ensure every Epic run has a watchdog out of
|
|
7
|
-
* the box; projects override individual keys by supplying their own values,
|
|
8
|
-
* which are merged over the defaults.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export const LIFECYCLE_DEFAULTS = Object.freeze({
|
|
12
|
-
timeouts: Object.freeze({
|
|
13
|
-
'acceptance.reconcile': 600,
|
|
14
|
-
'epic.finalize': 600,
|
|
15
|
-
'epic.watch': 1800,
|
|
16
|
-
}),
|
|
17
|
-
heartbeatWarnSeconds: 60,
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Read the merged `delivery.lifecycle` block, applying framework defaults
|
|
22
|
-
* for any field the operator omitted. Accepts the full resolved config or
|
|
23
|
-
* a bare `{ delivery: { lifecycle: ... } }` / `{ lifecycle: ... }` shape.
|
|
24
|
-
*
|
|
25
|
-
* User-supplied timeout keys win over defaults; unknown keys are preserved
|
|
26
|
-
* so projects can define custom phase budgets.
|
|
27
|
-
*
|
|
28
|
-
* @param {object | null | undefined} config
|
|
29
|
-
* @returns {{ timeouts: Record<string, number>, heartbeatWarnSeconds: number }}
|
|
30
|
-
*/
|
|
31
|
-
export function getLifecycle(config) {
|
|
32
|
-
const lc = config?.delivery?.lifecycle ?? config?.lifecycle ?? config ?? {};
|
|
33
|
-
return {
|
|
34
|
-
timeouts: { ...LIFECYCLE_DEFAULTS.timeouts, ...(lc.timeouts ?? {}) },
|
|
35
|
-
heartbeatWarnSeconds:
|
|
36
|
-
Number.isInteger(lc.heartbeatWarnSeconds) && lc.heartbeatWarnSeconds >= 1
|
|
37
|
-
? lc.heartbeatWarnSeconds
|
|
38
|
-
: LIFECYCLE_DEFAULTS.heartbeatWarnSeconds,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `delivery.preflight` accessor + framework defaults — Story #2899
|
|
3
|
-
* (Epic #2880, F13).
|
|
4
|
-
*
|
|
5
|
-
* Thresholds consumed by `.agents/scripts/epic-deliver-preflight.js`.
|
|
6
|
-
* The defaults are intentionally generous so that a brand-new Epic on a
|
|
7
|
-
* zero-config project does not trip a breach. Operators tighten thresholds
|
|
8
|
-
* per project in `.agentrc.json`. A `null` floor means "no cap" — used by
|
|
9
|
-
* the preflight runner to skip the corresponding breach check.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export const PREFLIGHT_DEFAULTS = Object.freeze({
|
|
13
|
-
maxStories: null,
|
|
14
|
-
maxWaves: null,
|
|
15
|
-
maxInstallCostSeconds: null,
|
|
16
|
-
maxGithubApiRequests: null,
|
|
17
|
-
maxClaudeQuotaTokens: null,
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Read the merged `delivery.preflight` block, applying framework defaults
|
|
22
|
-
* for any field the operator omitted. Accepts the full resolved config,
|
|
23
|
-
* the bare delivery bag, or the bare preflight bag.
|
|
24
|
-
*
|
|
25
|
-
* @param {object | null | undefined} config
|
|
26
|
-
* @returns {{
|
|
27
|
-
* maxStories: number | null,
|
|
28
|
-
* maxWaves: number | null,
|
|
29
|
-
* maxInstallCostSeconds: number | null,
|
|
30
|
-
* maxGithubApiRequests: number | null,
|
|
31
|
-
* maxClaudeQuotaTokens: number | null,
|
|
32
|
-
* }}
|
|
33
|
-
*/
|
|
34
|
-
export function getPreflight(config) {
|
|
35
|
-
const pf = config?.delivery?.preflight ?? config?.preflight ?? config ?? {};
|
|
36
|
-
return {
|
|
37
|
-
maxStories: normaliseFloor(pf.maxStories, PREFLIGHT_DEFAULTS.maxStories),
|
|
38
|
-
maxWaves: normaliseFloor(pf.maxWaves, PREFLIGHT_DEFAULTS.maxWaves),
|
|
39
|
-
maxInstallCostSeconds: normaliseFloor(
|
|
40
|
-
pf.maxInstallCostSeconds,
|
|
41
|
-
PREFLIGHT_DEFAULTS.maxInstallCostSeconds,
|
|
42
|
-
),
|
|
43
|
-
maxGithubApiRequests: normaliseFloor(
|
|
44
|
-
pf.maxGithubApiRequests,
|
|
45
|
-
PREFLIGHT_DEFAULTS.maxGithubApiRequests,
|
|
46
|
-
),
|
|
47
|
-
maxClaudeQuotaTokens: normaliseFloor(
|
|
48
|
-
pf.maxClaudeQuotaTokens,
|
|
49
|
-
PREFLIGHT_DEFAULTS.maxClaudeQuotaTokens,
|
|
50
|
-
),
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function normaliseFloor(value, fallback) {
|
|
55
|
-
if (value === null) return null;
|
|
56
|
-
if (Number.isInteger(value) && value >= 1) return value;
|
|
57
|
-
return fallback;
|
|
58
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Retro accessor (Story #3042 / Task #3043 — Epic #3019).
|
|
3
|
-
*
|
|
4
|
-
* Resolves `.agentrc.json → delivery.retro` into the canonical shape the
|
|
5
|
-
* retro-runner consumes. Currently exposes only `perfThresholds`, the
|
|
6
|
-
* operator-tunable gates for the perf-signals classifier in
|
|
7
|
-
* `lib/orchestration/retro-perf-heuristics.js`.
|
|
8
|
-
*
|
|
9
|
-
* Defaults mirror `DEFAULT_RETRO_PERF_THRESHOLDS` in the heuristics module
|
|
10
|
-
* (single source of behavioural truth; this resolver is a thin merge layer).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Default perf-threshold trio applied when `.agentrc.json` omits
|
|
15
|
-
* `delivery.retro.perfThresholds` (or any of its sub-keys). Frozen so
|
|
16
|
-
* downstream callers cannot accidentally mutate the resolver's defaults
|
|
17
|
-
* across processes.
|
|
18
|
-
*
|
|
19
|
-
* Keep in lockstep with `DEFAULT_RETRO_PERF_THRESHOLDS` in
|
|
20
|
-
* `lib/orchestration/retro-perf-heuristics.js` and the schema mirror in
|
|
21
|
-
* `agentrc.schema.json → $defs.retro.properties.perfThresholds`.
|
|
22
|
-
*/
|
|
23
|
-
export const DEFAULT_RETRO = Object.freeze({
|
|
24
|
-
perfThresholds: Object.freeze({
|
|
25
|
-
utilisation: 0.6,
|
|
26
|
-
bootstrapShare: 0.4,
|
|
27
|
-
capBindingRunLength: 2,
|
|
28
|
-
}),
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Read the merged retro block. Returns the canonical shape:
|
|
33
|
-
*
|
|
34
|
-
* {
|
|
35
|
-
* perfThresholds: {
|
|
36
|
-
* utilisation: number,
|
|
37
|
-
* bootstrapShare: number,
|
|
38
|
-
* capBindingRunLength: number,
|
|
39
|
-
* },
|
|
40
|
-
* }
|
|
41
|
-
*
|
|
42
|
-
* Each sub-key falls back to its documented default when the resolved
|
|
43
|
-
* `.agentrc.json` omits it. Out-of-range values fall back to defaults too —
|
|
44
|
-
* the AJV schema rejects them before they reach this accessor, but the
|
|
45
|
-
* resolver stays defensive so unit-test fixtures and degraded configs
|
|
46
|
-
* never produce nonsensical thresholds.
|
|
47
|
-
*
|
|
48
|
-
* @param {object | null | undefined} config
|
|
49
|
-
* @returns {{ perfThresholds: { utilisation: number, bootstrapShare: number, capBindingRunLength: number } }}
|
|
50
|
-
*/
|
|
51
|
-
export function getRetro(config) {
|
|
52
|
-
const user = config?.delivery?.retro?.perfThresholds ?? {};
|
|
53
|
-
const defaults = DEFAULT_RETRO.perfThresholds;
|
|
54
|
-
return {
|
|
55
|
-
perfThresholds: {
|
|
56
|
-
utilisation: resolveUnit(user.utilisation, defaults.utilisation),
|
|
57
|
-
bootstrapShare: resolveUnit(user.bootstrapShare, defaults.bootstrapShare),
|
|
58
|
-
capBindingRunLength: resolvePositiveInt(
|
|
59
|
-
user.capBindingRunLength,
|
|
60
|
-
defaults.capBindingRunLength,
|
|
61
|
-
),
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function resolveUnit(value, fallback) {
|
|
67
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) return fallback;
|
|
68
|
-
if (value < 0 || value > 1) return fallback;
|
|
69
|
-
return value;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function resolvePositiveInt(value, fallback) {
|
|
73
|
-
if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {
|
|
74
|
-
return fallback;
|
|
75
|
-
}
|
|
76
|
-
return value;
|
|
77
|
-
}
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/epic-merge-lock.js — Filesystem mutex for Epic-branch merges.
|
|
3
|
-
*
|
|
4
|
-
* Parallel-wave story closures can race on the Epic branch: two
|
|
5
|
-
* `story-close.js` invocations both `git checkout <epic>`, both
|
|
6
|
-
* `git pull --rebase`, and both attempt to merge — the second push
|
|
7
|
-
* often ends up rejected or, worse, races past the first and produces
|
|
8
|
-
* an incorrect history.
|
|
9
|
-
*
|
|
10
|
-
* This module provides a best-effort cooperative lock keyed per Epic.
|
|
11
|
-
* The lock file lives at `<repoRoot>/.git/epic-<epicId>.merge.lock`
|
|
12
|
-
* (inside `.git/` so it never lands in a commit). Acquisition uses
|
|
13
|
-
* `fs.openSync(..., 'wx')` for atomicity; on contention we poll every
|
|
14
|
-
* 250ms until `timeoutMs` elapses.
|
|
15
|
-
*
|
|
16
|
-
* Stale-lock stealing:
|
|
17
|
-
* - If the PID recorded in the lock is not running (per
|
|
18
|
-
* `process.kill(pid, 0)`), or
|
|
19
|
-
* - if the lock file is older than `timeoutMs * 2`,
|
|
20
|
-
* the lock is stolen (unlinked) and re-acquired.
|
|
21
|
-
*
|
|
22
|
-
* Test seams (mirroring `single-story-sweep/sweep-lock.js`):
|
|
23
|
-
* - `nowFn` — `() => number` (ms epoch); replaces `Date.now`.
|
|
24
|
-
* - `fsImpl` — Node `fs` shim; replaces the imported `fs`.
|
|
25
|
-
* - `killFn` — `(pid, signal) => void`; replaces `process.kill`,
|
|
26
|
-
* so a test can make `pidDead` deterministic without
|
|
27
|
-
* fabricating a real dead PID.
|
|
28
|
-
* - `sleepFn` — `(ms) => Promise<void>`; replaces the real
|
|
29
|
-
* `setTimeout`-backed sleep, so the poll loop can spin
|
|
30
|
-
* with no wall-clock waits.
|
|
31
|
-
* All four default to the real implementations, so production callers
|
|
32
|
-
* (`acquire(epicId, { repoRoot, timeoutMs })`) see no behavior change.
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
import { execFileSync } from 'node:child_process';
|
|
36
|
-
import fs from 'node:fs';
|
|
37
|
-
import path from 'node:path';
|
|
38
|
-
|
|
39
|
-
const POLL_INTERVAL_MS = 250;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Resolve the *common* gitdir for a given working directory.
|
|
43
|
-
*
|
|
44
|
-
* In a linked worktree (`git worktree add ...`), `<repoRoot>/.git` is a
|
|
45
|
-
* one-line gitlink **file**, not a directory. `path.join(repoRoot, '.git')`
|
|
46
|
-
* therefore points at the gitlink file and any `mkdir`/`openSync` against
|
|
47
|
-
* it fails with `EEXIST: file already exists`.
|
|
48
|
-
*
|
|
49
|
-
* Resolution order:
|
|
50
|
-
* 1. If `<repoRoot>/.git` is already a directory, return it. Covers the
|
|
51
|
-
* main-checkout case and the test fixtures, which create a bare
|
|
52
|
-
* `.git/` under a temp root — no need to spawn git for those.
|
|
53
|
-
* 2. Otherwise (gitlink file, or `.git` absent), shell out to
|
|
54
|
-
* `git rev-parse --git-common-dir`. In a worktree this returns the
|
|
55
|
-
* parent repo's `.git/`, so lock files placed there are shared
|
|
56
|
-
* across every worktree racing on the same Epic — which is the
|
|
57
|
-
* correct semantics for an epic-merge mutex.
|
|
58
|
-
* 3. If neither succeeds, fall back to `<repoRoot>/.git`. Lock
|
|
59
|
-
* acquisition will then surface the underlying error to the
|
|
60
|
-
* operator with the literal path that failed.
|
|
61
|
-
*/
|
|
62
|
-
export function resolveGitCommonDir(repoRoot, fsImpl = fs) {
|
|
63
|
-
const local = path.join(repoRoot, '.git');
|
|
64
|
-
try {
|
|
65
|
-
if (fsImpl.statSync(local).isDirectory()) return local;
|
|
66
|
-
} catch {
|
|
67
|
-
// .git does not exist — fall through to git rev-parse.
|
|
68
|
-
}
|
|
69
|
-
try {
|
|
70
|
-
const out = execFileSync('git', ['rev-parse', '--git-common-dir'], {
|
|
71
|
-
cwd: repoRoot,
|
|
72
|
-
encoding: 'utf8',
|
|
73
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
74
|
-
}).trim();
|
|
75
|
-
if (out) return path.isAbsolute(out) ? out : path.resolve(repoRoot, out);
|
|
76
|
-
} catch {
|
|
77
|
-
// not a git repo, or git is unavailable — fall through.
|
|
78
|
-
}
|
|
79
|
-
return local;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function lockPathFor(epicId, repoRoot, fsImpl = fs) {
|
|
83
|
-
return path.join(
|
|
84
|
-
resolveGitCommonDir(repoRoot, fsImpl),
|
|
85
|
-
`epic-${epicId}.merge.lock`,
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function isProcessRunning(pid, killFn = process.kill) {
|
|
90
|
-
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
91
|
-
try {
|
|
92
|
-
// Signal 0 does not deliver a signal; it just checks existence.
|
|
93
|
-
killFn(pid, 0);
|
|
94
|
-
return true;
|
|
95
|
-
} catch (err) {
|
|
96
|
-
// ESRCH = no such process. EPERM = exists but we can't signal — still alive.
|
|
97
|
-
return err.code === 'EPERM';
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function readLockMeta(filePath, fsImpl = fs) {
|
|
102
|
-
try {
|
|
103
|
-
const raw = fsImpl.readFileSync(filePath, 'utf8');
|
|
104
|
-
const parsed = JSON.parse(raw);
|
|
105
|
-
return {
|
|
106
|
-
pid: Number(parsed.pid),
|
|
107
|
-
acquiredAt: Number(parsed.acquiredAt),
|
|
108
|
-
};
|
|
109
|
-
} catch {
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function tryStealStale(filePath, timeoutMs, seams) {
|
|
115
|
-
const { fsImpl, nowFn, killFn } = seams;
|
|
116
|
-
let stats;
|
|
117
|
-
try {
|
|
118
|
-
stats = fsImpl.statSync(filePath);
|
|
119
|
-
} catch {
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const meta = readLockMeta(filePath, fsImpl);
|
|
124
|
-
// Corrupted lock file (null meta): we can't verify the writer's PID and
|
|
125
|
-
// the age comparison is unsafe on Windows where NTFS mtime vs Date.now()
|
|
126
|
-
// can disagree by hundreds of milliseconds, falsely flipping `ancient`
|
|
127
|
-
// true at short timeouts. Treat the file as held; the caller times out.
|
|
128
|
-
// A truly stuck corrupted lock has to be cleared manually — that's the
|
|
129
|
-
// safer failure mode than wrongly stealing a lock another process owns.
|
|
130
|
-
if (!meta) return false;
|
|
131
|
-
|
|
132
|
-
const ageMs = nowFn() - stats.mtimeMs;
|
|
133
|
-
const pidDead = !isProcessRunning(meta.pid, killFn);
|
|
134
|
-
const ancient = ageMs > timeoutMs * 2;
|
|
135
|
-
|
|
136
|
-
if (pidDead || ancient) {
|
|
137
|
-
try {
|
|
138
|
-
fsImpl.unlinkSync(filePath);
|
|
139
|
-
return true;
|
|
140
|
-
} catch {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function defaultSleep(ms) {
|
|
148
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Inner polling loop. Acquires the lock file with `wx` (atomic
|
|
152
|
-
// create-or-error); on EEXIST it tries to steal a stale lock, then
|
|
153
|
-
// either times out or sleeps and retries. Kept separate from
|
|
154
|
-
// `acquireEpicMergeLock` so the public function's cyclomatic complexity
|
|
155
|
-
// stays flat under CRAP.
|
|
156
|
-
async function pollForLock(epicId, filePath, timeoutMs, seams) {
|
|
157
|
-
const { fsImpl, nowFn, sleepFn } = seams;
|
|
158
|
-
const started = nowFn();
|
|
159
|
-
while (true) {
|
|
160
|
-
try {
|
|
161
|
-
const fd = fsImpl.openSync(filePath, 'wx');
|
|
162
|
-
const acquiredAt = nowFn();
|
|
163
|
-
fsImpl.writeSync(
|
|
164
|
-
fd,
|
|
165
|
-
JSON.stringify({ pid: process.pid, acquiredAt }, null, 2),
|
|
166
|
-
);
|
|
167
|
-
fsImpl.closeSync(fd);
|
|
168
|
-
return { epicId, filePath, acquiredAt };
|
|
169
|
-
} catch (err) {
|
|
170
|
-
if (err.code !== 'EEXIST') throw err;
|
|
171
|
-
if (tryStealStale(filePath, timeoutMs, seams)) continue;
|
|
172
|
-
if (nowFn() - started >= timeoutMs) {
|
|
173
|
-
const meta = readLockMeta(filePath, fsImpl);
|
|
174
|
-
const detail = meta
|
|
175
|
-
? ` (held by pid ${meta.pid} since ${new Date(meta.acquiredAt).toISOString()})`
|
|
176
|
-
: '';
|
|
177
|
-
throw new Error(
|
|
178
|
-
`acquireEpicMergeLock timed out after ${timeoutMs}ms for epic ${epicId}${detail}`,
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
await sleepFn(POLL_INTERVAL_MS);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Acquire an exclusive Epic merge lock.
|
|
188
|
-
*
|
|
189
|
-
* @param {number|string} epicId
|
|
190
|
-
* @param {object} opts
|
|
191
|
-
* @param {string} opts.repoRoot Repo working dir; the lock lands
|
|
192
|
-
* in its common `.git/`.
|
|
193
|
-
* @param {number} [opts.timeoutMs=60000] Poll-until deadline.
|
|
194
|
-
* @param {() => number} [opts.nowFn] Clock seam (ms epoch).
|
|
195
|
-
* @param {object} [opts.fsImpl] Node `fs` shim.
|
|
196
|
-
* @param {(pid:number, signal:number)=>void} [opts.killFn]
|
|
197
|
-
* `process.kill` shim for the
|
|
198
|
-
* pid-liveness probe.
|
|
199
|
-
* @param {(ms:number)=>Promise<void>} [opts.sleepFn]
|
|
200
|
-
* Poll-interval sleep shim.
|
|
201
|
-
* @returns {Promise<{ epicId: number|string, filePath: string, acquiredAt: number }>}
|
|
202
|
-
* @throws {Error} on timeout.
|
|
203
|
-
*/
|
|
204
|
-
export async function acquireEpicMergeLock(
|
|
205
|
-
epicId,
|
|
206
|
-
{
|
|
207
|
-
repoRoot,
|
|
208
|
-
timeoutMs = 60_000,
|
|
209
|
-
nowFn = Date.now,
|
|
210
|
-
fsImpl = fs,
|
|
211
|
-
killFn = process.kill.bind(process),
|
|
212
|
-
sleepFn = defaultSleep,
|
|
213
|
-
} = {},
|
|
214
|
-
) {
|
|
215
|
-
if (!repoRoot) throw new Error('acquireEpicMergeLock: repoRoot is required');
|
|
216
|
-
|
|
217
|
-
const filePath = lockPathFor(epicId, repoRoot, fsImpl);
|
|
218
|
-
// Ensure the .git directory exists (it will, in a real repo, but the
|
|
219
|
-
// tests use a temp dir and need us to be forgiving).
|
|
220
|
-
fsImpl.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
221
|
-
|
|
222
|
-
const seams = { fsImpl, nowFn, killFn, sleepFn };
|
|
223
|
-
return pollForLock(epicId, filePath, timeoutMs, seams);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Release a previously-acquired Epic merge lock.
|
|
228
|
-
*
|
|
229
|
-
* @param {{ filePath: string }} handle
|
|
230
|
-
* @param {object} [fsImpl] Node `fs` shim for tests.
|
|
231
|
-
*/
|
|
232
|
-
export function releaseEpicMergeLock(handle, fsImpl = fs) {
|
|
233
|
-
if (!handle?.filePath) return;
|
|
234
|
-
try {
|
|
235
|
-
fsImpl.unlinkSync(handle.filePath);
|
|
236
|
-
} catch (err) {
|
|
237
|
-
if (err.code !== 'ENOENT') throw err;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const LOCK_FILE_PREFIX = 'epic-';
|
|
242
|
-
const LOCK_FILE_SUFFIX = '.merge.lock';
|
|
243
|
-
|
|
244
|
-
// Extract the epic id from a lock filename, or `null` when `entry` isn't a
|
|
245
|
-
// `epic-*.merge.lock` file. Split out of `findForeignActiveEpicLock` so
|
|
246
|
-
// that function stays a flat filter+map instead of a nested-conditional
|
|
247
|
-
// loop body (Story #4460).
|
|
248
|
-
function parseLockFileEpicId(entry) {
|
|
249
|
-
if (
|
|
250
|
-
!entry.startsWith(LOCK_FILE_PREFIX) ||
|
|
251
|
-
!entry.endsWith(LOCK_FILE_SUFFIX)
|
|
252
|
-
) {
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
return entry.slice(
|
|
256
|
-
LOCK_FILE_PREFIX.length,
|
|
257
|
-
entry.length - LOCK_FILE_SUFFIX.length,
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Read `<dir>/<entry>`'s lock meta and report it only when the recorded
|
|
262
|
-
// pid is still alive — a foreign lock whose pid is dead (or whose meta is
|
|
263
|
-
// unreadable/corrupt) is stale debris, not an active holder, mirroring the
|
|
264
|
-
// pid-liveness half of `tryStealStale`'s heuristic.
|
|
265
|
-
function readLiveLock(dir, entry, otherEpicId, fsImpl, killFn) {
|
|
266
|
-
const filePath = path.join(dir, entry);
|
|
267
|
-
const meta = readLockMeta(filePath, fsImpl);
|
|
268
|
-
if (!meta || !isProcessRunning(meta.pid, killFn)) return null;
|
|
269
|
-
return {
|
|
270
|
-
epicId: otherEpicId,
|
|
271
|
-
filePath,
|
|
272
|
-
pid: meta.pid,
|
|
273
|
-
acquiredAt: meta.acquiredAt,
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Scan the shared common `.git/` dir for a *different* epic's live
|
|
279
|
-
* merge lock (Story #4460 — cross-epic shared-checkout guard).
|
|
280
|
-
*
|
|
281
|
-
* `acquireEpicMergeLock` only ever contends against locks for the *same*
|
|
282
|
-
* `epicId` (its own lock filename). This helper is the cross-epic
|
|
283
|
-
* counterpart: it lists every `epic-*.merge.lock` file in the common
|
|
284
|
-
* gitdir, skips the caller's own `epicId` namespace, and returns the
|
|
285
|
-
* first foreign lock whose recorded `pid` is still alive.
|
|
286
|
-
*
|
|
287
|
-
* @param {number|string} epicId Caller's own epic id (excluded from the scan).
|
|
288
|
-
* @param {{
|
|
289
|
-
* repoRoot: string,
|
|
290
|
-
* fsImpl?: object,
|
|
291
|
-
* killFn?: (pid:number, signal:number)=>void,
|
|
292
|
-
* }} opts
|
|
293
|
-
* @returns {{ epicId: string, filePath: string, pid: number, acquiredAt: number }|null}
|
|
294
|
-
*/
|
|
295
|
-
export function findForeignActiveEpicLock(
|
|
296
|
-
epicId,
|
|
297
|
-
{ repoRoot, fsImpl = fs, killFn = process.kill.bind(process) } = {},
|
|
298
|
-
) {
|
|
299
|
-
if (!repoRoot) {
|
|
300
|
-
throw new Error('findForeignActiveEpicLock: repoRoot is required');
|
|
301
|
-
}
|
|
302
|
-
const dir = resolveGitCommonDir(repoRoot, fsImpl);
|
|
303
|
-
let entries;
|
|
304
|
-
try {
|
|
305
|
-
entries = fsImpl.readdirSync(dir);
|
|
306
|
-
} catch {
|
|
307
|
-
return null;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const foreignEntries = entries
|
|
311
|
-
.map((entry) => ({ entry, otherEpicId: parseLockFileEpicId(entry) }))
|
|
312
|
-
.filter(
|
|
313
|
-
({ otherEpicId }) =>
|
|
314
|
-
otherEpicId !== null && String(otherEpicId) !== String(epicId),
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
for (const { entry, otherEpicId } of foreignEntries) {
|
|
318
|
-
const live = readLiveLock(dir, entry, otherEpicId, fsImpl, killFn);
|
|
319
|
-
if (live) return live;
|
|
320
|
-
}
|
|
321
|
-
return null;
|
|
322
|
-
}
|