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,754 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `tick({ epic, collaborators })` — single callable entry point for
|
|
3
|
-
* "advance this Epic one beat." Stateless adapter over the continuous
|
|
4
|
-
* ready-set scheduling core (`lib/wave-runner/ready-set.js`).
|
|
5
|
-
*
|
|
6
|
-
* Story #4155 (Epic #4151) — the Epic `/deliver` runtime cut over from
|
|
7
|
-
* the wave-batch scheduler to the ready-set core. Each tick:
|
|
8
|
-
*
|
|
9
|
-
* 1. reads the shrunk `epic-run-state` checkpoint (per-Story status map
|
|
10
|
-
* + the GLOBAL in-flight `concurrencyCap`),
|
|
11
|
-
* 2. re-fetches the **live** Story records (body + labels + issue
|
|
12
|
-
* state) for every Story in scope,
|
|
13
|
-
* 3. classifies each by live label (`classifyStory`), re-derives
|
|
14
|
-
* adjacency from the live bodies (`buildStoryAdjacency`, inside
|
|
15
|
-
* `selectReadySet`) and selects the ready set under a global
|
|
16
|
-
* in-flight cap with the file-overlap co-dispatch guard
|
|
17
|
-
* (`storiesOverlap`),
|
|
18
|
-
* 4. returns a `WaveTickResult` describing the next action.
|
|
19
|
-
*
|
|
20
|
-
* There is **no wave barrier**: a Story whose own dependencies are all
|
|
21
|
-
* done is dispatched the instant a slot is free, even while an unrelated
|
|
22
|
-
* sibling Story is still `agent::executing`. The selector neither reads
|
|
23
|
-
* GitHub nor a checkpoint nor the ledger — this adapter supplies the live
|
|
24
|
-
* records, the `inFlight` count (from the lifecycle ledger), and the
|
|
25
|
-
* `globalCap`, then maps its return into the `WaveTickResult` envelope.
|
|
26
|
-
*
|
|
27
|
-
* Contract (Story #1430, refined by #4155): stateless; caller owns
|
|
28
|
-
* concurrency, worktrees, and the checkpoint. Expected failures (blocked
|
|
29
|
-
* stories) flow back through result fields; unexpected failures (GH 5xx,
|
|
30
|
-
* malformed / old-shape checkpoint) throw `WaveRunnerError`.
|
|
31
|
-
*
|
|
32
|
-
* Story #4183 — the `tick(args)` orchestrator was a 252-line SRP /
|
|
33
|
-
* cognitive-load hotspot carrying six distinct responsibilities in one
|
|
34
|
-
* body. It is now a thin coordinator (Coordinator-plus-Phases pattern,
|
|
35
|
-
* `docs/patterns.md`) that wires four extracted stages:
|
|
36
|
-
* `resolveTickCollaborators` (collaborator/fallback resolution),
|
|
37
|
-
* `readAndValidateCheckpoint` (checkpoint read + shape validation, folding
|
|
38
|
-
* in `assertNotOldShape`), `refetchStoryRecords` (force-fresh re-fetch),
|
|
39
|
-
* and the **pure** `planTick` (classification → cycle detection → ready-set
|
|
40
|
-
* selection → dispatch decision, returning the signals to emit rather than
|
|
41
|
-
* emitting them, so it carries no I/O). The exported `tick(args)`
|
|
42
|
-
* signature, the `tickResult` / `withInFlight` envelope shapes, and every
|
|
43
|
-
* `WaveRunnerError` code are preserved verbatim — callers and tests are
|
|
44
|
-
* unchanged.
|
|
45
|
-
*
|
|
46
|
-
* @module lib/wave-runner/tick
|
|
47
|
-
*/
|
|
48
|
-
|
|
49
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
50
|
-
|
|
51
|
-
import { epicLedgerPath } from '../config/temp-paths.js';
|
|
52
|
-
import { detectCycle } from '../Graph.js';
|
|
53
|
-
import { AGENT_LABELS } from '../label-constants.js';
|
|
54
|
-
import { appendEpicSignal } from '../observability/signals-writer.js';
|
|
55
|
-
import * as epicRunStateStoreModule from '../orchestration/epic-run-state-store.js';
|
|
56
|
-
import { detectRecurringFailures } from '../orchestration/recurring-failure-detector.js';
|
|
57
|
-
import { upsertStructuredComment as defaultUpsertStructuredComment } from '../orchestration/ticketing.js';
|
|
58
|
-
import { buildStoryAdjacency } from '../story-adjacency.js';
|
|
59
|
-
|
|
60
|
-
import { classifyStory, selectReadySet, storyIdOf } from './ready-set.js';
|
|
61
|
-
import { WaveRunnerError } from './wave-runner-error.js';
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* The checkpoint fields whose presence marks an **old-shape** (wave-batch)
|
|
65
|
-
* `epic-run-state` comment. The ready-set runtime cannot mis-schedule
|
|
66
|
-
* against a wave-indexed plan — indexing the old wave grouping would silently
|
|
67
|
-
* dispatch the wrong stories — so the tick fails closed on any of these
|
|
68
|
-
* fields with an explicit operator message rather than guessing.
|
|
69
|
-
*/
|
|
70
|
-
const OLD_SHAPE_FIELDS = Object.freeze(['plan', 'currentWave', 'totalWaves']);
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Advance the Epic one beat. Returns a `WaveTickResult`:
|
|
74
|
-
*
|
|
75
|
-
* nextAction: { kind: 'dispatch', stories: [{ id, title? }, ...] }
|
|
76
|
-
* | { kind: 'observe', waitingOn: number[] }
|
|
77
|
-
* | { kind: 'halt', reason: string, stuckStories: number[],
|
|
78
|
-
* cycle?: number[] }
|
|
79
|
-
* | { kind: 'epic-complete' }
|
|
80
|
-
* blockedStories: [{ storyId, reason, detail? }, ...]
|
|
81
|
-
* gateFailures: [{ storyId, gate, detail? }, ...]
|
|
82
|
-
* readyCount: number // size of the ready set this beat
|
|
83
|
-
* inFlight: number[] // ledger-derived dispatched-not-yet-ended ids
|
|
84
|
-
*
|
|
85
|
-
* Readiness comes entirely from the **live** Story bodies + labels — the
|
|
86
|
-
* checkpoint contributes only the Story set in scope and the global cap.
|
|
87
|
-
*
|
|
88
|
-
* `epic-complete` is returned **only** when every in-scope Story is done.
|
|
89
|
-
* If the ready set is empty and nothing is in flight but at least one Story
|
|
90
|
-
* is still not done — a Story gated on an unsatisfiable dependency
|
|
91
|
-
* (a dependency cycle, or a `blocked by #N` that survived adjacency closure)
|
|
92
|
-
* — the tick returns a non-terminal `halt` naming the stuck Story ids rather
|
|
93
|
-
* than silently reporting the Epic complete and stranding the Story. A
|
|
94
|
-
* dependency cycle among the in-scope Stories is likewise surfaced as a
|
|
95
|
-
* `halt` (with the offending `cycle`), never collapsed to `epic-complete`.
|
|
96
|
-
*
|
|
97
|
-
* Coordinator (Story #4183): this function is a thin dispatcher. It resolves
|
|
98
|
-
* collaborators, reads + validates the checkpoint, re-fetches the live Story
|
|
99
|
-
* records, runs the best-effort recurring-failure scan, delegates the pure
|
|
100
|
-
* dispatch decision to `planTick`, then drains the `signals` `planTick`
|
|
101
|
-
* returned through the configured emitter. Each stage is an independently
|
|
102
|
-
* testable helper below.
|
|
103
|
-
*
|
|
104
|
-
* @typedef {object} WaveTickArgs
|
|
105
|
-
* @property {number | { id: number }} epic
|
|
106
|
-
* @property {{
|
|
107
|
-
* provider?: object,
|
|
108
|
-
* epicRunStateStore?: { read: () => Promise<object|null> },
|
|
109
|
-
* signalEmit?: (signal: object) => Promise<unknown>,
|
|
110
|
-
* inFlightReader?: () => Promise<number[]>,
|
|
111
|
-
* recurringFailureReporter?: () => Promise<void>,
|
|
112
|
-
* }} [collaborators]
|
|
113
|
-
* @property {{ provider?: object, config?: object }} [ctx]
|
|
114
|
-
*
|
|
115
|
-
* @param {WaveTickArgs} args
|
|
116
|
-
*/
|
|
117
|
-
export async function tick(args = {}) {
|
|
118
|
-
const { epicId, provider, epicRunStateStore, emit, inFlightReader, ctx } =
|
|
119
|
-
resolveTickCollaborators(args);
|
|
120
|
-
|
|
121
|
-
const state = await readAndValidateCheckpoint(epicRunStateStore, epicId);
|
|
122
|
-
|
|
123
|
-
const storyIds = checkpointStoryIds(state);
|
|
124
|
-
|
|
125
|
-
if (storyIds.length === 0) {
|
|
126
|
-
// No Stories in scope — the Epic has nothing to dispatch.
|
|
127
|
-
return tickResult({
|
|
128
|
-
nextAction: withInFlight({ kind: 'epic-complete' }, []),
|
|
129
|
-
readyCount: 0,
|
|
130
|
-
inFlight: [],
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Re-fetch the live Story records (body + labels + issue state) for every
|
|
135
|
-
// Story in scope. In-flight Stories are force-fresh-fetched so a label that
|
|
136
|
-
// flipped since the last tick is observed; every other Story serves from
|
|
137
|
-
// the provider's in-process cache.
|
|
138
|
-
const inFlight = await safeReadInFlight(inFlightReader);
|
|
139
|
-
const inFlightSet = new Set(inFlight);
|
|
140
|
-
const records = await refetchStoryRecords(provider, storyIds, inFlightSet);
|
|
141
|
-
|
|
142
|
-
// Best-effort recurring-failure scan (≥2 distinct Stories sharing the same
|
|
143
|
-
// `close-validate.end` failedGate). Idempotent across re-ticks; a reporter
|
|
144
|
-
// throw must not crash the planner.
|
|
145
|
-
const recurringFailureReporter =
|
|
146
|
-
args.collaborators?.recurringFailureReporter ??
|
|
147
|
-
defaultRecurringFailureReporter({ provider, epicId, config: ctx?.config });
|
|
148
|
-
await safeReportRecurringFailures(recurringFailureReporter);
|
|
149
|
-
|
|
150
|
-
// Decide the next action from the live records + ledger in-flight set. The
|
|
151
|
-
// decision is pure (no I/O); the signals it wants emitted come back in
|
|
152
|
-
// `plan.signals` and are drained by the coordinator below.
|
|
153
|
-
const plan = planTick(state, records, inFlight);
|
|
154
|
-
for (const signal of plan.signals) {
|
|
155
|
-
await emit(signal);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return tickResult({
|
|
159
|
-
nextAction: withInFlight(plan.nextAction, inFlight),
|
|
160
|
-
blockedStories: plan.blockedStories,
|
|
161
|
-
gateFailures: plan.gateFailures,
|
|
162
|
-
readyCount: plan.readyCount,
|
|
163
|
-
inFlight,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Resolve the Epic id and the five injectable collaborators (with their
|
|
169
|
-
* production-default fallbacks) from the `tick` args. The single home for the
|
|
170
|
-
* collaborator/fallback wiring so the coordinator stays declarative.
|
|
171
|
-
*
|
|
172
|
-
* Throws `WaveRunnerError('invalid-input')` when the epic id is not a
|
|
173
|
-
* positive integer (or `{ id: positiveInt }`) or when no provider is supplied
|
|
174
|
-
* via either `collaborators.provider` or `ctx.provider`.
|
|
175
|
-
*
|
|
176
|
-
* @param {WaveTickArgs} args
|
|
177
|
-
* @returns {{
|
|
178
|
-
* epicId: number,
|
|
179
|
-
* provider: object,
|
|
180
|
-
* epicRunStateStore: { read: () => Promise<object|null> },
|
|
181
|
-
* emit: (signal: object) => Promise<unknown>,
|
|
182
|
-
* inFlightReader: () => Promise<number[]>,
|
|
183
|
-
* ctx: object,
|
|
184
|
-
* }}
|
|
185
|
-
*/
|
|
186
|
-
function resolveTickCollaborators(args) {
|
|
187
|
-
const epicId = resolveEpicId(args.epic);
|
|
188
|
-
const {
|
|
189
|
-
provider: collabProvider,
|
|
190
|
-
epicRunStateStore: collabStore,
|
|
191
|
-
signalEmit,
|
|
192
|
-
inFlightReader: collabInFlightReader,
|
|
193
|
-
} = args.collaborators ?? {};
|
|
194
|
-
const ctx = args.ctx ?? {};
|
|
195
|
-
const provider = collabProvider ?? ctx.provider;
|
|
196
|
-
if (!provider) {
|
|
197
|
-
throw new WaveRunnerError('invalid-input', 'provider is required');
|
|
198
|
-
}
|
|
199
|
-
// The ready-set tick is stateless. When the caller does not supply a
|
|
200
|
-
// collaborator shim, read the `epic-run-state` structured comment directly
|
|
201
|
-
// via the function-based store.
|
|
202
|
-
const epicRunStateStore = collabStore ?? {
|
|
203
|
-
read: () => epicRunStateStoreModule.read({ provider, epicId }),
|
|
204
|
-
};
|
|
205
|
-
const emit = signalEmit ?? defaultSignalEmit(epicId, ctx);
|
|
206
|
-
const inFlightReader =
|
|
207
|
-
collabInFlightReader ?? (() => defaultInFlightReader(epicId, ctx?.config));
|
|
208
|
-
return { epicId, provider, epicRunStateStore, emit, inFlightReader, ctx };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Read the `epic-run-state` checkpoint via the store, validate its shape, and
|
|
213
|
-
* fail closed on a pre-ready-set (wave-batch) checkpoint.
|
|
214
|
-
*
|
|
215
|
-
* Throws:
|
|
216
|
-
* - `WaveRunnerError('checkpoint-read')` when the store read rejects,
|
|
217
|
-
* - `WaveRunnerError('checkpoint-missing')` when the read resolves to a
|
|
218
|
-
* non-object (no comment),
|
|
219
|
-
* - `WaveRunnerError('old-shape-checkpoint')` when the checkpoint still
|
|
220
|
-
* carries a `plan` / `currentWave` / `totalWaves` field (via
|
|
221
|
-
* `assertNotOldShape`). A `plan` / `currentWave` / `totalWaves` comment
|
|
222
|
-
* predates the ready-set cutover (Story #4155); the ready-set runtime
|
|
223
|
-
* would otherwise ignore those fields and re-derive readiness from live
|
|
224
|
-
* labels — silently discarding an in-progress wave-batch run's resume
|
|
225
|
-
* pointer. Refuse with an explicit operator remediation instead.
|
|
226
|
-
*
|
|
227
|
-
* @param {{ read: () => Promise<object|null> }} store
|
|
228
|
-
* @param {number} epicId
|
|
229
|
-
* @returns {Promise<object>} the validated checkpoint state.
|
|
230
|
-
*/
|
|
231
|
-
async function readAndValidateCheckpoint(store, epicId) {
|
|
232
|
-
let state;
|
|
233
|
-
try {
|
|
234
|
-
state = await store.read();
|
|
235
|
-
} catch (err) {
|
|
236
|
-
throw new WaveRunnerError('checkpoint-read', err);
|
|
237
|
-
}
|
|
238
|
-
if (!state || typeof state !== 'object') {
|
|
239
|
-
throw new WaveRunnerError(
|
|
240
|
-
'checkpoint-missing',
|
|
241
|
-
`no epic-run-state comment on Epic #${epicId}`,
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
assertNotOldShape(state, epicId);
|
|
245
|
-
return state;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Re-fetch the live Story records (body + labels + issue state) for every
|
|
250
|
-
* Story in scope. The body feeds `buildStoryAdjacency` (inside
|
|
251
|
-
* `selectReadySet`) so the dependency edges are always read from the current
|
|
252
|
-
* ticket text, never a stale checkpoint snapshot. Stories in `inFlightSet`
|
|
253
|
-
* are force-fresh-fetched (`{ fresh: true }`) so a label that flipped since
|
|
254
|
-
* the last tick is observed; every other Story serves from the provider's
|
|
255
|
-
* in-process cache.
|
|
256
|
-
*
|
|
257
|
-
* Throws `WaveRunnerError('story-fetch')` when any `provider.getTicket`
|
|
258
|
-
* rejects.
|
|
259
|
-
*
|
|
260
|
-
* @param {{ getTicket: (id: number, opts?: object) => Promise<object> }} provider
|
|
261
|
-
* @param {number[]} storyIds Ascending, deduped in-scope Story ids.
|
|
262
|
-
* @param {Set<number>} inFlightSet Ledger-derived in-flight Story ids.
|
|
263
|
-
* @returns {Promise<Array<object>>} normalized Story records.
|
|
264
|
-
*/
|
|
265
|
-
async function refetchStoryRecords(provider, storyIds, inFlightSet) {
|
|
266
|
-
try {
|
|
267
|
-
return await Promise.all(
|
|
268
|
-
storyIds.map(async (id) => {
|
|
269
|
-
const opts = inFlightSet.has(id) ? { fresh: true } : {};
|
|
270
|
-
const ticket = await provider.getTicket(id, opts);
|
|
271
|
-
return {
|
|
272
|
-
id,
|
|
273
|
-
title: ticket?.title,
|
|
274
|
-
body: ticket?.body ?? '',
|
|
275
|
-
labels: Array.isArray(ticket?.labels) ? ticket.labels : [],
|
|
276
|
-
state: ticket?.state,
|
|
277
|
-
// Forward every declared file-footprint shape so the selector's
|
|
278
|
-
// overlap co-dispatch guard (`storiesOverlap`) can withhold two
|
|
279
|
-
// Stories that would race the same path on parallel branches.
|
|
280
|
-
files: Array.isArray(ticket?.files) ? ticket.files : undefined,
|
|
281
|
-
changes: Array.isArray(ticket?.changes) ? ticket.changes : undefined,
|
|
282
|
-
changeset: Array.isArray(ticket?.changeset)
|
|
283
|
-
? ticket.changeset
|
|
284
|
-
: undefined,
|
|
285
|
-
};
|
|
286
|
-
}),
|
|
287
|
-
);
|
|
288
|
-
} catch (err) {
|
|
289
|
-
throw new WaveRunnerError('story-fetch', err);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Pure dispatch planner — the scheduler tick's decision core with **no I/O**.
|
|
295
|
-
* Given the parsed checkpoint, the live Story records, and the ledger-derived
|
|
296
|
-
* in-flight id list, it classifies every Story, detects a sibling dependency
|
|
297
|
-
* cycle, selects the ready set under the global in-flight cap, and decides the
|
|
298
|
-
* `nextAction`. It performs no fetching, no signal emission, and no ledger
|
|
299
|
-
* read: the two wave-window forensics signals are returned in the `signals`
|
|
300
|
-
* array for the coordinator to drain, so this function stays independently
|
|
301
|
-
* unit-testable against fixture records without a provider stub or an emitter.
|
|
302
|
-
*
|
|
303
|
-
* @param {object} state Parsed `epic-run-state` checkpoint (for the global
|
|
304
|
-
* cap and the per-Story `failed` rows surfaced as gate failures).
|
|
305
|
-
* @param {Array<object>} records Live Story records (id, title, body, labels,
|
|
306
|
-
* state, file-footprint shapes).
|
|
307
|
-
* @param {number[]} inFlight Ledger-derived dispatched-not-yet-ended ids.
|
|
308
|
-
* @returns {{
|
|
309
|
-
* nextAction: object,
|
|
310
|
-
* blockedStories: Array<{ storyId: number, reason: string, detail?: string }>,
|
|
311
|
-
* gateFailures: Array<{ storyId: number, gate: string, detail?: string }>,
|
|
312
|
-
* readyCount: number,
|
|
313
|
-
* signals: Array<object>,
|
|
314
|
-
* }}
|
|
315
|
-
*/
|
|
316
|
-
export function planTick(state, records, inFlight) {
|
|
317
|
-
const globalCap = positiveIntOrZero(state.concurrencyCap);
|
|
318
|
-
const inFlightSet = new Set(inFlight);
|
|
319
|
-
|
|
320
|
-
// 1. Classify by live label. `done` / `blocked` / `executing` / `ready`.
|
|
321
|
-
const byClass = { done: [], blocked: [], executing: [], ready: [] };
|
|
322
|
-
for (const rec of records) {
|
|
323
|
-
byClass[classifyStory(rec)].push(rec);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// 1a. Detect a dependency cycle among the in-scope Stories BEFORE selecting.
|
|
327
|
-
// A cycle makes every Story on it permanently un-eligible (no member's
|
|
328
|
-
// deps can all be done), so `selectReadySet` would return an empty set
|
|
329
|
-
// and the terminal decision could otherwise mistake the stall for
|
|
330
|
-
// completion. Surface it as a `halt` so the workflow parks the Epic on
|
|
331
|
-
// a diagnosable condition instead of silently dropping the cycle. Build
|
|
332
|
-
// adjacency with `dropForeign: true` to match the Epic-scoped semantics
|
|
333
|
-
// (a cycle is only meaningful over the scheduled sibling set). Mirrors
|
|
334
|
-
// the cycle handling in `stories-wave-tick.js`.
|
|
335
|
-
const epicAdjacency = buildStoryAdjacency(records, { dropForeign: true });
|
|
336
|
-
const cycle = detectCycle(epicAdjacency);
|
|
337
|
-
|
|
338
|
-
// 2. Select the ready set under the GLOBAL in-flight cap. The selector
|
|
339
|
-
// re-derives adjacency from the live bodies (with `dropForeign: true` so
|
|
340
|
-
// a `blocked by #N` whose target is outside this Epic's Story set — a
|
|
341
|
-
// foreign id or a typo — is pruned rather than treated as a permanent
|
|
342
|
-
// unsatisfiable gate that strands the dependent), and applies the
|
|
343
|
-
// file-overlap co-dispatch guard, returning the deterministic,
|
|
344
|
-
// overlap-free, dependency-satisfied subset capped at the remaining
|
|
345
|
-
// slots.
|
|
346
|
-
//
|
|
347
|
-
// A Story recorded in-flight on the ledger (`story.dispatch.start`
|
|
348
|
-
// without a matching `.end`) but whose label has not yet flipped to
|
|
349
|
-
// `agent::executing` (the child is mid-`story-init`, or the host crashed
|
|
350
|
-
// after the dispatch-ledger write but before the label flip) still reads
|
|
351
|
-
// as `ready` by label alone. Re-dispatching it would put a second agent
|
|
352
|
-
// on the same `story-<id>` branch — the worst failure mode in the
|
|
353
|
-
// system. So the candidate set passed to the selector marks those
|
|
354
|
-
// Stories `executing`: they keep occupying a slot (and gate any
|
|
355
|
-
// dependent, since they are not done) but are never re-selected.
|
|
356
|
-
//
|
|
357
|
-
// The slot denominator is the size of the UNION of (a) ledger-in-flight
|
|
358
|
-
// ids and (b) Stories carrying `agent::executing` by label. A Story that
|
|
359
|
-
// flipped to `agent::executing` but whose `story.dispatch.start` never
|
|
360
|
-
// landed in the ledger (e.g. the label flip raced ahead of the ledger
|
|
361
|
-
// write) occupies a real slot the ledger count alone misses; counting
|
|
362
|
-
// only the ledger would let the global cap be exceeded. The union is the
|
|
363
|
-
// authoritative occupied-slot count.
|
|
364
|
-
const candidates = records.map((rec) =>
|
|
365
|
-
inFlightSet.has(rec.id) && classifyStory(rec) === 'ready'
|
|
366
|
-
? { ...rec, labels: [...rec.labels, AGENT_LABELS.EXECUTING] }
|
|
367
|
-
: rec,
|
|
368
|
-
);
|
|
369
|
-
const doneIds = byClass.done.map((s) => s.id);
|
|
370
|
-
const occupiedSlotIds = new Set([
|
|
371
|
-
...inFlight,
|
|
372
|
-
...byClass.executing.map((s) => s.id),
|
|
373
|
-
]);
|
|
374
|
-
const readySet = selectReadySet({
|
|
375
|
-
stories: candidates,
|
|
376
|
-
doneIds,
|
|
377
|
-
inFlight: occupiedSlotIds.size,
|
|
378
|
-
globalCap,
|
|
379
|
-
dropForeign: true,
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
const blockedStories = byClass.blocked.map((s) => ({
|
|
383
|
-
storyId: s.id,
|
|
384
|
-
reason: 'agent::blocked',
|
|
385
|
-
detail: s.title,
|
|
386
|
-
}));
|
|
387
|
-
const gateFailures = readGateFailures(state);
|
|
388
|
-
|
|
389
|
-
// 3. Decide nextAction.
|
|
390
|
-
// - A blocked Story halts the Epic → observe (the workflow flips the
|
|
391
|
-
// Epic to agent::blocked and parks).
|
|
392
|
-
// - A dependency cycle among the in-scope Stories halts the Epic → halt
|
|
393
|
-
// (the cycle is an unsatisfiable gate; never collapse it to complete).
|
|
394
|
-
// - A non-empty ready set → dispatch it. Fire `wave-start` on the very
|
|
395
|
-
// first dispatch of the run (nothing executing / in-flight / done
|
|
396
|
-
// yet) so the perf-aggregator can bracket the run's wall-clock.
|
|
397
|
-
// - Otherwise, if any Story is still executing or in-flight → observe.
|
|
398
|
-
// - Otherwise, if EVERY in-scope Story is done → epic-complete.
|
|
399
|
-
// - Otherwise the ready set is empty, nothing is in flight, yet not all
|
|
400
|
-
// Stories are done: at least one Story is permanently gated (an
|
|
401
|
-
// unsatisfiable dependency that survived adjacency closure). Halt and
|
|
402
|
-
// name the stuck Story ids — never silently report the Epic complete.
|
|
403
|
-
const allDone = byClass.done.length === records.length;
|
|
404
|
-
const signals = [];
|
|
405
|
-
let nextAction;
|
|
406
|
-
if (blockedStories.length) {
|
|
407
|
-
nextAction = {
|
|
408
|
-
kind: 'observe',
|
|
409
|
-
waitingOn: byClass.blocked.map((s) => s.id).sort((a, b) => a - b),
|
|
410
|
-
};
|
|
411
|
-
} else if (cycle) {
|
|
412
|
-
const cycleIds = cycle
|
|
413
|
-
.filter((id) => Number.isInteger(id))
|
|
414
|
-
.sort((a, b) => a - b);
|
|
415
|
-
nextAction = {
|
|
416
|
-
kind: 'halt',
|
|
417
|
-
reason: 'dependency-cycle',
|
|
418
|
-
stuckStories: cycleIds,
|
|
419
|
-
cycle,
|
|
420
|
-
};
|
|
421
|
-
} else if (readySet.length) {
|
|
422
|
-
if (
|
|
423
|
-
byClass.executing.length === 0 &&
|
|
424
|
-
byClass.done.length === 0 &&
|
|
425
|
-
inFlight.length === 0
|
|
426
|
-
) {
|
|
427
|
-
signals.push({
|
|
428
|
-
// `index` is what the perf-aggregator's `bucketWaves` keys on to
|
|
429
|
-
// bracket a wave's wall-clock (Epic #4406 / Story #4413). Without
|
|
430
|
-
// it every wave-start collapsed to NaN and the waveParallelism
|
|
431
|
-
// table rendered structurally empty. The continuous ready-set
|
|
432
|
-
// scheduler runs a single logical wave, so `index: 0`.
|
|
433
|
-
kind: 'wave-start',
|
|
434
|
-
index: 0,
|
|
435
|
-
stories: records.map((s) => ({ id: s.id, title: s.title })),
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
nextAction = {
|
|
439
|
-
kind: 'dispatch',
|
|
440
|
-
stories: readySet.map((s) => ({
|
|
441
|
-
id: storyIdOf(s),
|
|
442
|
-
title: s.title,
|
|
443
|
-
})),
|
|
444
|
-
};
|
|
445
|
-
} else if (byClass.executing.length || inFlight.length) {
|
|
446
|
-
const waitingOn = [
|
|
447
|
-
...new Set([...byClass.executing.map((s) => s.id), ...inFlight]),
|
|
448
|
-
].sort((a, b) => a - b);
|
|
449
|
-
nextAction = { kind: 'observe', waitingOn };
|
|
450
|
-
} else if (allDone) {
|
|
451
|
-
// Every Story is done and nothing is in flight: the run is complete.
|
|
452
|
-
signals.push({ kind: 'wave-complete', index: 0 });
|
|
453
|
-
nextAction = { kind: 'epic-complete' };
|
|
454
|
-
} else {
|
|
455
|
-
// Ready set empty, nothing in flight, but not all Stories are done — a
|
|
456
|
-
// Story is gated on an unsatisfiable dependency. Halt with the stuck ids
|
|
457
|
-
// (every not-done, not-in-flight Story) so the operator can see exactly
|
|
458
|
-
// which Story stranded the run instead of a false epic-complete.
|
|
459
|
-
const stuckStories = records
|
|
460
|
-
.filter((rec) => classifyStory(rec) !== 'done')
|
|
461
|
-
.map((rec) => rec.id)
|
|
462
|
-
.filter((id) => Number.isInteger(id))
|
|
463
|
-
.sort((a, b) => a - b);
|
|
464
|
-
nextAction = {
|
|
465
|
-
kind: 'halt',
|
|
466
|
-
reason: 'unsatisfiable-dependency',
|
|
467
|
-
stuckStories,
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
return {
|
|
472
|
-
nextAction,
|
|
473
|
-
blockedStories,
|
|
474
|
-
gateFailures,
|
|
475
|
-
readyCount: readySet.length,
|
|
476
|
-
signals,
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
/**
|
|
481
|
-
* Throw `WaveRunnerError('old-shape-checkpoint')` when the checkpoint still
|
|
482
|
-
* carries any wave-batch field. The message names the offending field(s) and
|
|
483
|
-
* the operator remediation so a stuck delivery is diagnosable from the
|
|
484
|
-
* thrown error alone.
|
|
485
|
-
*
|
|
486
|
-
* @param {object} state Parsed checkpoint.
|
|
487
|
-
* @param {number} epicId
|
|
488
|
-
*/
|
|
489
|
-
function assertNotOldShape(state, epicId) {
|
|
490
|
-
const present = OLD_SHAPE_FIELDS.filter((f) => Object.hasOwn(state, f));
|
|
491
|
-
if (present.length === 0) return;
|
|
492
|
-
throw new WaveRunnerError(
|
|
493
|
-
'old-shape-checkpoint',
|
|
494
|
-
`Epic #${epicId} carries a pre-ready-set (wave-batch) epic-run-state ` +
|
|
495
|
-
`checkpoint (fields: ${present.join(', ')}). The ready-set /deliver ` +
|
|
496
|
-
`runtime cannot resume a wave-batch run. Re-run ` +
|
|
497
|
-
`\`node .agents/scripts/epic-deliver-prepare.js --epic ${epicId}\` to ` +
|
|
498
|
-
`re-seed the checkpoint in the per-Story-status shape, then re-run ` +
|
|
499
|
-
`/deliver.`,
|
|
500
|
-
);
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Extract the in-scope Story ids from the shrunk checkpoint's per-Story
|
|
505
|
-
* `stories` status map (`{ [storyId]: { status, ... } }`). Returns an
|
|
506
|
-
* ascending-sorted, deduped array of positive integers; tolerates an absent
|
|
507
|
-
* / malformed map by returning `[]`.
|
|
508
|
-
*
|
|
509
|
-
* @param {object} state
|
|
510
|
-
* @returns {number[]}
|
|
511
|
-
*/
|
|
512
|
-
function checkpointStoryIds(state) {
|
|
513
|
-
const stories = state?.stories;
|
|
514
|
-
if (!stories || typeof stories !== 'object') return [];
|
|
515
|
-
const ids = new Set();
|
|
516
|
-
for (const key of Object.keys(stories)) {
|
|
517
|
-
const id = Number(key);
|
|
518
|
-
if (Number.isInteger(id) && id > 0) ids.add(id);
|
|
519
|
-
}
|
|
520
|
-
return [...ids].sort((a, b) => a - b);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Attach the ledger-derived in-flight Story-id list to a `nextAction`
|
|
525
|
-
* envelope under the `in-flight` key. Always present (empty array when the
|
|
526
|
-
* ledger is silent) so downstream consumers pattern-match on presence
|
|
527
|
-
* without an existence check.
|
|
528
|
-
*
|
|
529
|
-
* @param {object} nextAction
|
|
530
|
-
* @param {number[]} inFlight
|
|
531
|
-
* @returns {object} the same nextAction (mutated) for call-site convenience
|
|
532
|
-
*/
|
|
533
|
-
function withInFlight(nextAction, inFlight) {
|
|
534
|
-
nextAction['in-flight'] = inFlight;
|
|
535
|
-
return nextAction;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
/**
|
|
539
|
-
* Wrap the configured `inFlightReader` with a defensive guard so an
|
|
540
|
-
* unreadable ledger never crashes the tick. The default reader already
|
|
541
|
-
* returns `[]` on missing files; this catches any other shape of
|
|
542
|
-
* accidental throw and degrades to an empty list so the planner can
|
|
543
|
-
* still make a decision.
|
|
544
|
-
*
|
|
545
|
-
* @param {() => Promise<number[]>} reader
|
|
546
|
-
* @returns {Promise<number[]>}
|
|
547
|
-
*/
|
|
548
|
-
async function safeReadInFlight(reader) {
|
|
549
|
-
try {
|
|
550
|
-
const raw = await reader();
|
|
551
|
-
return Array.isArray(raw) ? raw.filter((n) => Number.isInteger(n)) : [];
|
|
552
|
-
} catch {
|
|
553
|
-
return [];
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
/**
|
|
558
|
-
* Default `recurringFailureReporter` collaborator — reads the per-Epic
|
|
559
|
-
* lifecycle ledger via `detectRecurringFailures`, and when at least one
|
|
560
|
-
* recurring-failure-class finding is returned, upserts a
|
|
561
|
-
* `recurring-failure-class` structured comment on the Epic ticket.
|
|
562
|
-
*
|
|
563
|
-
* The body carries the findings array verbatim in a JSON fence plus a
|
|
564
|
-
* compact human-readable bullet list keyed by gate. Idempotent across
|
|
565
|
-
* re-ticks: `upsertStructuredComment` diffs body bytes, so a tick that
|
|
566
|
-
* produces the same findings does not generate a new comment.
|
|
567
|
-
*
|
|
568
|
-
* Story #3062 (Epic #3051).
|
|
569
|
-
*
|
|
570
|
-
* @param {object} args
|
|
571
|
-
* @param {object} args.provider Ticketing provider passed to upsert.
|
|
572
|
-
* @param {number} args.epicId
|
|
573
|
-
* @param {object} [args.config]
|
|
574
|
-
* @returns {() => Promise<void>}
|
|
575
|
-
*/
|
|
576
|
-
function defaultRecurringFailureReporter({ provider, epicId, config }) {
|
|
577
|
-
return async () => {
|
|
578
|
-
const ledgerPath = epicLedgerPath(epicId, config);
|
|
579
|
-
const findings = detectRecurringFailures(epicId, { ledgerPath });
|
|
580
|
-
if (findings.length === 0) return;
|
|
581
|
-
const body = renderRecurringFailureBody(findings);
|
|
582
|
-
await defaultUpsertStructuredComment(
|
|
583
|
-
provider,
|
|
584
|
-
epicId,
|
|
585
|
-
'recurring-failure-class',
|
|
586
|
-
body,
|
|
587
|
-
);
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/**
|
|
592
|
-
* Render the comment body the recurring-failure-class reporter upserts.
|
|
593
|
-
* The body is deterministic given a deterministic findings array (the
|
|
594
|
-
* detector sorts findings by gate and storyIds ascending), which is what
|
|
595
|
-
* makes the upsert idempotent across re-ticks.
|
|
596
|
-
*
|
|
597
|
-
* @param {Array<{gate: string, storyIds: number[], firstSeenAt: string, lastSeenAt: string}>} findings
|
|
598
|
-
* @returns {string}
|
|
599
|
-
*/
|
|
600
|
-
export function renderRecurringFailureBody(findings) {
|
|
601
|
-
const lines = ['### 🔁 Recurring failure classes detected', ''];
|
|
602
|
-
for (const f of findings) {
|
|
603
|
-
const storiesList = f.storyIds.map((id) => `#${id}`).join(', ');
|
|
604
|
-
lines.push(
|
|
605
|
-
`- **\`${f.gate}\`** — ${f.storyIds.length} stories (${storiesList}); first \`${f.firstSeenAt}\`, last \`${f.lastSeenAt}\``,
|
|
606
|
-
);
|
|
607
|
-
}
|
|
608
|
-
lines.push('');
|
|
609
|
-
lines.push('```json');
|
|
610
|
-
lines.push(
|
|
611
|
-
JSON.stringify({ kind: 'recurring-failure-class', findings }, null, 2),
|
|
612
|
-
);
|
|
613
|
-
lines.push('```');
|
|
614
|
-
return lines.join('\n');
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
/**
|
|
618
|
-
* Wrap the reporter so a throw (e.g. transient provider error, malformed
|
|
619
|
-
* ledger) never crashes the stateless tick. Best-effort — the next tick
|
|
620
|
-
* will retry.
|
|
621
|
-
*
|
|
622
|
-
* @param {() => Promise<void>} reporter
|
|
623
|
-
*/
|
|
624
|
-
async function safeReportRecurringFailures(reporter) {
|
|
625
|
-
try {
|
|
626
|
-
await reporter();
|
|
627
|
-
} catch {
|
|
628
|
-
// best-effort
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
/**
|
|
633
|
-
* Default `inFlightReader` — parses `temp/epic-<id>/lifecycle.ndjson`
|
|
634
|
-
* and returns the Story IDs that have a `story.dispatch.start`
|
|
635
|
-
* `emitted` record without a matching `story.dispatch.end` `emitted`
|
|
636
|
-
* record. The check is order-insensitive (the wave-runner records the
|
|
637
|
-
* pair on the same Bus, so the start always lands first, but we don't
|
|
638
|
-
* depend on that here).
|
|
639
|
-
*
|
|
640
|
-
* Returns `[]` when the ledger file does not yet exist or is empty —
|
|
641
|
-
* the tick is stateless and must not throw when nothing has been
|
|
642
|
-
* dispatched on this Epic yet.
|
|
643
|
-
*
|
|
644
|
-
* @param {number} epicId
|
|
645
|
-
* @param {object|undefined} config Resolved config (forwarded to
|
|
646
|
-
* `epicLedgerPath` so `project.paths.tempRoot` overrides apply).
|
|
647
|
-
* @returns {Promise<number[]>}
|
|
648
|
-
*/
|
|
649
|
-
async function defaultInFlightReader(epicId, config) {
|
|
650
|
-
const ledgerPath = epicLedgerPath(epicId, config);
|
|
651
|
-
if (!existsSync(ledgerPath)) return [];
|
|
652
|
-
let raw;
|
|
653
|
-
try {
|
|
654
|
-
raw = readFileSync(ledgerPath, 'utf8');
|
|
655
|
-
} catch {
|
|
656
|
-
return [];
|
|
657
|
-
}
|
|
658
|
-
if (!raw) return [];
|
|
659
|
-
const started = new Set();
|
|
660
|
-
const ended = new Set();
|
|
661
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
662
|
-
if (!line) continue;
|
|
663
|
-
let record;
|
|
664
|
-
try {
|
|
665
|
-
record = JSON.parse(line);
|
|
666
|
-
} catch {
|
|
667
|
-
continue;
|
|
668
|
-
}
|
|
669
|
-
if (!record || record.kind !== 'emitted') continue;
|
|
670
|
-
const storyId = record.payload?.storyId;
|
|
671
|
-
if (!Number.isInteger(storyId) || storyId <= 0) continue;
|
|
672
|
-
if (record.event === 'story.dispatch.start') started.add(storyId);
|
|
673
|
-
else if (record.event === 'story.dispatch.end') ended.add(storyId);
|
|
674
|
-
}
|
|
675
|
-
const inFlight = [];
|
|
676
|
-
for (const id of started) {
|
|
677
|
-
if (!ended.has(id)) inFlight.push(id);
|
|
678
|
-
}
|
|
679
|
-
return inFlight.sort((a, b) => a - b);
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
function tickResult({
|
|
683
|
-
nextAction,
|
|
684
|
-
blockedStories = [],
|
|
685
|
-
gateFailures = [],
|
|
686
|
-
readyCount = 0,
|
|
687
|
-
inFlight = [],
|
|
688
|
-
}) {
|
|
689
|
-
return { nextAction, blockedStories, gateFailures, readyCount, inFlight };
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
function resolveEpicId(epic) {
|
|
693
|
-
const id = typeof epic === 'number' ? epic : epic?.id;
|
|
694
|
-
if (Number.isInteger(id) && id > 0) return id;
|
|
695
|
-
throw new WaveRunnerError(
|
|
696
|
-
'invalid-input',
|
|
697
|
-
`epic must be a positive integer or { id: positiveInt }; got ${
|
|
698
|
-
epic === null ? 'null' : typeof epic
|
|
699
|
-
}`,
|
|
700
|
-
);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
function positiveIntOrZero(v) {
|
|
704
|
-
return Number.isInteger(v) && v >= 0 ? v : 0;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
/**
|
|
708
|
-
* Derive gate-failure rows from the checkpoint's per-Story `stories` status
|
|
709
|
-
* map: every Story recorded as `failed` surfaces as a gate failure so the
|
|
710
|
-
* operator workflow can act on it. The shrunk checkpoint no longer carries a
|
|
711
|
-
* per-wave history with explicit gate names, so the gate is reported as
|
|
712
|
-
* `unspecified` and the recorded `title` (when present) is the detail.
|
|
713
|
-
*
|
|
714
|
-
* @param {object} state Parsed checkpoint.
|
|
715
|
-
* @returns {Array<{ storyId: number, gate: string, detail?: string }>}
|
|
716
|
-
*/
|
|
717
|
-
function readGateFailures(state) {
|
|
718
|
-
const stories = state?.stories;
|
|
719
|
-
if (!stories || typeof stories !== 'object') return [];
|
|
720
|
-
const out = [];
|
|
721
|
-
for (const [key, rec] of Object.entries(stories)) {
|
|
722
|
-
const id = Number(key);
|
|
723
|
-
if (!Number.isInteger(id) || id <= 0) continue;
|
|
724
|
-
if (rec?.status !== 'failed') continue;
|
|
725
|
-
const row = { storyId: id, gate: 'unspecified' };
|
|
726
|
-
if (typeof rec.title === 'string' && rec.title) row.detail = rec.title;
|
|
727
|
-
out.push(row);
|
|
728
|
-
}
|
|
729
|
-
return out.sort((a, b) => a.storyId - b.storyId);
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/**
|
|
733
|
-
* Default emitter — appends to per-Epic `signals.ndjson`. Best-effort;
|
|
734
|
-
* never throws. Tests override via `collaborators.signalEmit`.
|
|
735
|
-
*
|
|
736
|
-
* Story #3909 / #4155 — the planner emits only the two wave-window
|
|
737
|
-
* forensics events with a live consumer: `wave-start` (fired on the run's
|
|
738
|
-
* first dispatch) and `wave-complete` (fired when the run finishes), which
|
|
739
|
-
* the perf-aggregator (`waveParallelism` report) brackets into the run's
|
|
740
|
-
* wall-clock. The write-only per-call telemetry and `epic-complete` emits
|
|
741
|
-
* were dropped — they duplicated the `epic-run-state` checkpoint and the
|
|
742
|
-
* `epic-run-progress` rollup and nothing consumed them.
|
|
743
|
-
*/
|
|
744
|
-
function defaultSignalEmit(epicId, ctx) {
|
|
745
|
-
return async (signal) => {
|
|
746
|
-
await appendEpicSignal({
|
|
747
|
-
epicId,
|
|
748
|
-
// Canonical envelope (Epic #4406 / Story #4413): the single
|
|
749
|
-
// `epicId` key — the legacy `epic` alias is gone.
|
|
750
|
-
signal: { ts: new Date().toISOString(), epicId, ...signal },
|
|
751
|
-
config: ctx?.config,
|
|
752
|
-
});
|
|
753
|
-
};
|
|
754
|
-
}
|