mandrel 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +59 -28
- package/.agents/agents/acceptance-critic.md +20 -9
- package/.agents/agents/story-worker.md +45 -48
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +60 -46
- package/.agents/docs/agentrc-reference.json +8 -13
- package/.agents/docs/configuration.md +33 -57
- package/.agents/docs/execution-reference.md +39 -10
- package/.agents/docs/quality-gates.md +17 -19
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +64 -79
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/gherkin-standards.md +10 -0
- package/.agents/rules/git-conventions-reference.md +42 -51
- package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
- package/.agents/schemas/agentrc.schema.json +35 -46
- package/.agents/schemas/audit-rules.json +59 -1
- package/.agents/schemas/audit-rules.schema.json +33 -1
- package/.agents/schemas/lifecycle/README.md +1 -2
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
- package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
- package/.agents/schemas/signal-event.schema.json +3 -3
- package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +24 -68
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/bootstrap.js +3 -3
- package/.agents/scripts/check-dead-exports.js +43 -104
- package/.agents/scripts/check-doc-links.js +2 -2
- package/.agents/scripts/check-lifecycle-lint.js +1 -1
- package/.agents/scripts/check-workflow-cli-lint.js +91 -0
- package/.agents/scripts/deliver-recover.js +122 -0
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/evidence-gate.js +20 -50
- package/.agents/scripts/generate-skills-index.js +17 -1
- package/.agents/scripts/generate-workflows-doc.js +4 -4
- package/.agents/scripts/lib/ITicketingProvider.js +1 -19
- package/.agents/scripts/lib/audit-suite/selector.js +323 -23
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
- package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
- package/.agents/scripts/lib/checks/index.js +1 -1
- package/.agents/scripts/lib/checks/loop-health.js +12 -11
- package/.agents/scripts/lib/checks/state.js +17 -248
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
- package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
- package/.agents/scripts/lib/cli-args.js +23 -2
- package/.agents/scripts/lib/close-validation/gates.js +13 -13
- package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
- package/.agents/scripts/lib/close-validation/runner.js +13 -21
- package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
- package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
- package/.agents/scripts/lib/config/delivery-routing.js +7 -6
- package/.agents/scripts/lib/config/explain.js +10 -16
- package/.agents/scripts/lib/config/github.js +7 -5
- package/.agents/scripts/lib/config/limits.js +15 -25
- package/.agents/scripts/lib/config/quality.js +11 -14
- package/.agents/scripts/lib/config/runners.js +8 -21
- package/.agents/scripts/lib/config/temp-paths.js +18 -56
- package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
- package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
- package/.agents/scripts/lib/config-settings-schema.js +48 -22
- package/.agents/scripts/lib/dead-exports-knip.js +105 -0
- package/.agents/scripts/lib/dead-exports-mode.js +51 -0
- package/.agents/scripts/lib/duplicate-search.js +38 -7
- package/.agents/scripts/lib/findings/promote-finding.js +23 -14
- package/.agents/scripts/lib/format-generated-json.js +97 -0
- package/.agents/scripts/lib/framework-version.js +19 -189
- package/.agents/scripts/lib/gh-exec.js +8 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
- package/.agents/scripts/lib/git-utils.js +0 -14
- package/.agents/scripts/lib/json-utils.js +1 -2
- package/.agents/scripts/lib/label-constants.js +0 -15
- package/.agents/scripts/lib/label-taxonomy.js +1 -12
- package/.agents/scripts/lib/observability/active-story-env.js +42 -163
- package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
- package/.agents/scripts/lib/observability/signal-validator.js +4 -4
- package/.agents/scripts/lib/observability/signals-writer.js +6 -82
- package/.agents/scripts/lib/observability/source-classifier.js +2 -2
- package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
- package/.agents/scripts/lib/orchestration/change-set.js +103 -0
- package/.agents/scripts/lib/orchestration/code-review.js +70 -191
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
- package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
- package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
- package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
- package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
- package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
- package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
- package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
- package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
- package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
- package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
- package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
- package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
- package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
- package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
- package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
- package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
- package/.agents/scripts/lib/planning-corpus.js +12 -286
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
- package/.agents/scripts/lib/signals/index.js +4 -17
- package/.agents/scripts/lib/signals/read.js +35 -35
- package/.agents/scripts/lib/signals/schema.js +8 -11
- package/.agents/scripts/lib/signals/span-tree.js +7 -7
- package/.agents/scripts/lib/signals/write.js +0 -1
- package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
- package/.agents/scripts/lib/skills/parse-skill.js +16 -3
- package/.agents/scripts/lib/story-adjacency.js +8 -7
- package/.agents/scripts/lib/story-body/story-body.js +81 -13
- package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
- package/.agents/scripts/lib/test-env.js +14 -1
- package/.agents/scripts/lib/test-tiers.js +0 -3
- package/.agents/scripts/lib/ticket-body-sections.js +0 -14
- package/.agents/scripts/lib/validation-evidence.js +31 -59
- package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
- package/.agents/scripts/plan-context.js +38 -7
- package/.agents/scripts/plan-critics.js +203 -0
- package/.agents/scripts/plan-persist.js +145 -35
- package/.agents/scripts/plan-run-epilogue.js +83 -38
- package/.agents/scripts/post-structured-comment.js +0 -38
- package/.agents/scripts/pr-watch-with-update.js +43 -22
- package/.agents/scripts/providers/github/compose.js +0 -1
- package/.agents/scripts/providers/github/errors.js +0 -19
- package/.agents/scripts/providers/github/issues.js +1 -11
- package/.agents/scripts/providers/github/mappers.js +5 -0
- package/.agents/scripts/providers/github/sub-issues.js +0 -47
- package/.agents/scripts/providers/github/tickets.js +33 -153
- package/.agents/scripts/providers/github.js +17 -6
- package/.agents/scripts/quality-preview.js +13 -6
- package/.agents/scripts/resolve-stories.js +236 -0
- package/.agents/scripts/run-coverage.js +4 -1
- package/.agents/scripts/run-lint.js +2 -2
- package/.agents/scripts/run-verify.js +31 -2
- package/.agents/scripts/signals-view.js +9 -10
- package/.agents/scripts/single-story-close.js +173 -18
- package/.agents/scripts/single-story-confirm-merge.js +288 -15
- package/.agents/scripts/single-story-init.js +6 -10
- package/.agents/scripts/stories-wave-tick.js +380 -53
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/update-ticket-state.js +8 -50
- package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
- package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
- package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
- package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
- package/.agents/skills/core/scope-triage/SKILL.md +3 -0
- package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
- package/.agents/skills/core/security-and-hardening/reference.md +375 -0
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
- package/.agents/workflows/audit-architecture.md +3 -4
- package/.agents/workflows/audit-clean-code.md +4 -4
- package/.agents/workflows/audit-documentation.md +4 -5
- package/.agents/workflows/audit-lighthouse.md +8 -0
- package/.agents/workflows/audit-navigability.md +10 -0
- package/.agents/workflows/audit-performance.md +2 -3
- package/.agents/workflows/audit-quality.md +8 -9
- package/.agents/workflows/audit-security.md +1 -2
- package/.agents/workflows/audit-seo.md +10 -0
- package/.agents/workflows/audit-ux-ui.md +7 -0
- package/.agents/workflows/deliver.md +133 -45
- package/.agents/workflows/git-cleanup.md +2 -2
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
- package/.agents/workflows/helpers/code-review.md +14 -12
- package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
- package/.agents/workflows/helpers/deliver-story.md +209 -118
- package/.agents/workflows/helpers/parallel-tooling.md +2 -2
- package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
- package/.agents/workflows/plan.md +239 -19
- package/.agents/workflows/qa-assist.md +6 -6
- package/.agents/workflows/qa-explore.md +3 -3
- package/.agents/workflows/qa-run.md +1 -5
- package/bin/mandrel.js +12 -1
- package/docs/CHANGELOG.md +62 -0
- package/lib/cli/registry.js +262 -19
- package/lib/cli/sync-agents.js +157 -0
- package/lib/cli/sync-commands.js +115 -6
- package/lib/cli/sync.js +168 -6
- package/lib/cli/update.js +105 -8
- package/lib/cli/version-helpers.js +131 -0
- package/lib/migrations/README.md +7 -5
- package/lib/migrations/index.js +17 -9
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
- package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
- package/package.json +2 -2
- package/.agents/schemas/epic-perf-report.schema.json +0 -89
- package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
- package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
- package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
- package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
- package/.agents/schemas/risk-verdict.schema.json +0 -53
- package/.agents/schemas/story-perf-summary.schema.json +0 -73
- package/.agents/scripts/analyze-execution.js +0 -444
- package/.agents/scripts/check-prepush-recovery.js +0 -90
- package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
- package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
- package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
- package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
- package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
- package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
- package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
- package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
- package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
- package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
- package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
- package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
- package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
- package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
- package/.agents/scripts/resolve-plan-run.js +0 -117
- package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
|
@@ -5,12 +5,10 @@
|
|
|
5
5
|
* `/deliver` story-list path.
|
|
6
6
|
*
|
|
7
7
|
* Thin **adapter** over the path-agnostic ready-set scheduling core
|
|
8
|
-
* (`lib/wave-runner/ready-set.js#selectReadySet`). It
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* becomes dispatchable the instant its own dependencies are done, under the
|
|
13
|
-
* same global concurrency cap and the same file-overlap co-dispatch guard
|
|
8
|
+
* (`lib/wave-runner/ready-set.js#selectReadySet`). It emits the set of
|
|
9
|
+
* Stories safe to dispatch **on this beat** — a Story becomes dispatchable
|
|
10
|
+
* the instant its own dependencies are done, under the same global
|
|
11
|
+
* concurrency cap and the same file-overlap co-dispatch guard
|
|
14
12
|
* `lib/wave-runner/ready-set.js` applies everywhere. There is no wave barrier: this no longer batches
|
|
15
13
|
* Stories into fully-draining waves; it selects continuously.
|
|
16
14
|
*
|
|
@@ -18,9 +16,27 @@
|
|
|
18
16
|
* group N+1 opens, via `Graph.js#assignLayers`) is gone. The scheduling
|
|
19
17
|
* kernel — adjacency derivation, the done-predicate classifier, the
|
|
20
18
|
* eligibility rule, and the overlap guard — lives once in `selectReadySet`;
|
|
21
|
-
* this file only
|
|
19
|
+
* this file only gathers input, resolves the cap, and renders the envelope.
|
|
20
|
+
*
|
|
21
|
+
* **Two modes, one kernel.**
|
|
22
|
+
*
|
|
23
|
+
* - **Probe mode** (`--stories <csv> --probe-live [--dispatched <csv>]`) is
|
|
24
|
+
* the canonical `/deliver` beat: the graph, the done set, and the in-flight
|
|
25
|
+
* count are resolved from **live state** via `lib/wave-runner/live-probe.js`.
|
|
26
|
+
* The caller supplies ids, so there is no accounting to hand-maintain
|
|
27
|
+
* across beats — the seed-the-first-beat's-`--done` footgun the workflow
|
|
28
|
+
* used to warn about is structurally impossible rather than merely
|
|
29
|
+
* documented. `--dispatched` is the one fact live state cannot yet report
|
|
30
|
+
* ("I spawned this id; its label has not appeared"); it is additive and
|
|
31
|
+
* live-state-filtered, never authoritative (Story #4601).
|
|
32
|
+
* - **Flag mode** (`--dag`/`--dag-file` + `--done`/`--in-flight`) keeps the
|
|
33
|
+
* caller-supplied contract byte-compatible for tests and hand-driven
|
|
34
|
+
* runs. The two are mutually exclusive: honouring a supplied `--done`
|
|
35
|
+
* under `--probe-live` would silently reintroduce exactly the
|
|
36
|
+
* hand-maintained state probe mode retires.
|
|
22
37
|
*
|
|
23
38
|
* Usage:
|
|
39
|
+
* node .agents/scripts/stories-wave-tick.js --stories 101,102 --probe-live
|
|
24
40
|
* node .agents/scripts/stories-wave-tick.js --dag '<json>'
|
|
25
41
|
* node .agents/scripts/stories-wave-tick.js --dag-file <path>
|
|
26
42
|
* node .agents/scripts/stories-wave-tick.js --dag '<json>' --concurrency 5
|
|
@@ -37,14 +53,23 @@
|
|
|
37
53
|
* totalStories: number,
|
|
38
54
|
* concurrencyCap: number,
|
|
39
55
|
* inFlight: number,
|
|
40
|
-
* cycleError: string | null
|
|
56
|
+
* cycleError: string | null,
|
|
57
|
+
* wedged: { reason, stories: [{ id, unmetBlockers }] } | null
|
|
41
58
|
* }
|
|
42
59
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
60
|
+
* Probe mode adds fields the caller can no longer compute for itself:
|
|
61
|
+
* `done: number[]` (the resolved done set, in-set ∪ satisfied foreign
|
|
62
|
+
* blockers), `epilogueDue: boolean` (true exactly when every listed Story
|
|
63
|
+
* is done — the run-end signal for `plan-run-epilogue.js`), and `blocked:
|
|
64
|
+
* number[]` + `blockedReason: string|null` (Story #4601 — the `agent::blocked`
|
|
65
|
+
* HITL pause, which ends the loop rather than being polled).
|
|
66
|
+
*
|
|
67
|
+
* The standalone loop calls this once per beat and dispatches the returned
|
|
68
|
+
* `ready` set (already capped at `concurrencyCap − inFlight` by the core).
|
|
69
|
+
* Under `--probe-live` each beat re-reads reality, so the run is complete
|
|
70
|
+
* when `epilogueDue` is true; under flag mode the caller re-supplies `--done`
|
|
71
|
+
* and `--in-flight` itself, and the run is complete when every Story is in
|
|
72
|
+
* `--done` and `ready` is empty.
|
|
48
73
|
*
|
|
49
74
|
* The per-beat concurrency cap is resolved from the same config seam
|
|
50
75
|
* `/deliver` uses — `resolveConfig` + `getRunners` reading
|
|
@@ -54,7 +79,17 @@
|
|
|
54
79
|
* deterministic config source (`delivery.deliverRunner.concurrencyCap`) and
|
|
55
80
|
* one scheduling kernel with every `/deliver` multi-Story invocation.
|
|
56
81
|
*
|
|
57
|
-
*
|
|
82
|
+
* Exit codes: 0 ok · 1 input error · 2 dependency cycle (`cycleError`) ·
|
|
83
|
+
* 3 wedged (`wedged`) — ready is empty, nothing is in flight, and undone
|
|
84
|
+
* Stories are waiting on blockers that are not done · 4 blocked (`blocked`) —
|
|
85
|
+
* a Story carries `agent::blocked`. A cycle is a self-referential DAG the
|
|
86
|
+
* operator must fix; a wedge is a well-formed DAG whose gates cannot be
|
|
87
|
+
* satisfied from the supplied `--done` set (usually a blocker outside the
|
|
88
|
+
* delivered set that has not landed); a block is the protocol's HITL pause,
|
|
89
|
+
* where a human owes a decision no beat can supply. All three are distinct
|
|
90
|
+
* from the ordinary `ready: []` that means "waiting on in-flight work" — and
|
|
91
|
+
* that distinction is the point: each of them previously presented AS that
|
|
92
|
+
* ordinary empty set, so the loop polled a state that could never improve.
|
|
58
93
|
*/
|
|
59
94
|
|
|
60
95
|
import { readFileSync } from 'node:fs';
|
|
@@ -65,16 +100,48 @@ import { getRunners, resolveConfig } from './lib/config-resolver.js';
|
|
|
65
100
|
import { detectCycle } from './lib/Graph.js';
|
|
66
101
|
import { Logger } from './lib/Logger.js';
|
|
67
102
|
import { AGENT_LABELS } from './lib/label-constants.js';
|
|
103
|
+
import { parseIds } from './lib/orchestration/resolve-stories.js';
|
|
68
104
|
import { buildStoryAdjacency } from './lib/story-adjacency.js';
|
|
105
|
+
import {
|
|
106
|
+
createProbeContext,
|
|
107
|
+
probeLiveState,
|
|
108
|
+
validateProbeFlags,
|
|
109
|
+
} from './lib/wave-runner/live-probe.js';
|
|
69
110
|
import { selectReadySet } from './lib/wave-runner/ready-set.js';
|
|
70
111
|
|
|
71
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Exit code for a wedged run — deliberately distinct from the cycle exit (2)
|
|
114
|
+
* so a caller can tell "your DAG is self-referential" from "your DAG is fine
|
|
115
|
+
* but its gates can never be satisfied from this `--done` set".
|
|
116
|
+
*/
|
|
117
|
+
export const WEDGED_EXIT_CODE = 3;
|
|
72
118
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Exit code for a run holding an `agent::blocked` Story — distinct from the
|
|
121
|
+
* cycle (2) and wedge (3) exits because the remediation is categorically
|
|
122
|
+
* different: a cycle is a malformed DAG and a wedge is an unlanded blocker,
|
|
123
|
+
* whereas this is the protocol's one runtime HITL pause. A human must decide
|
|
124
|
+
* something before any beat can help. Probe-mode only: flag-mode nodes carry
|
|
125
|
+
* no labels, so nothing there can classify blocked.
|
|
126
|
+
*/
|
|
127
|
+
export const BLOCKED_EXIT_CODE = 4;
|
|
128
|
+
|
|
129
|
+
const HELP = `Usage:
|
|
130
|
+
node .agents/scripts/stories-wave-tick.js --stories <csv> --probe-live [--dispatched <csv>] [--concurrency <n>]
|
|
131
|
+
node .agents/scripts/stories-wave-tick.js --dag '<json>' | --dag-file <path> [--concurrency <n>] [--done <csv>] [--in-flight <n>]
|
|
132
|
+
|
|
133
|
+
Continuous ready-set planner for standalone Story delivery. Emits the set of
|
|
134
|
+
Stories safe to dispatch on this beat — a Story is dispatchable the instant
|
|
135
|
+
its own dependencies are done — plus the resolved per-beat concurrency cap
|
|
136
|
+
and the same file-overlap guard as selectReadySet.
|
|
137
|
+
|
|
138
|
+
Two modes:
|
|
139
|
+
--probe-live Resolve the graph and derive done / in-flight from LIVE state
|
|
140
|
+
(the canonical /deliver beat). Nothing is hand-maintained
|
|
141
|
+
across beats. Mutually exclusive with --dag/--dag-file/--done/
|
|
142
|
+
--in-flight. Adds "done" and "epilogueDue" to the envelope.
|
|
143
|
+
--dag Legacy flag mode: the caller supplies the graph and the run
|
|
144
|
+
progress. Kept for tests and hand-driven runs.
|
|
78
145
|
|
|
79
146
|
Input DAG format (JSON array):
|
|
80
147
|
[{ "id": 101, "dependsOn": [] }, { "id": 102, "dependsOn": [101] }]
|
|
@@ -84,6 +151,19 @@ Each entry must include:
|
|
|
84
151
|
dependsOn - Array of Story IDs that must complete before this Story runs
|
|
85
152
|
|
|
86
153
|
Options:
|
|
154
|
+
--stories <csv> Story ids to deliver (probe mode). The graph, the done
|
|
155
|
+
set, and the in-flight count are resolved from live
|
|
156
|
+
state — no --done / --in-flight bookkeeping.
|
|
157
|
+
--probe-live Enable probe mode. Requires --stories.
|
|
158
|
+
--dispatched <csv> Probe mode only. Ids you have SPAWNED this run. Unioned
|
|
159
|
+
into the live-derived in-flight set, then filtered by
|
|
160
|
+
live state, so it closes the init window: a Story reads
|
|
161
|
+
agent::ready for the 3-6 minutes single-story-init.js
|
|
162
|
+
takes to flip agent::executing, and without this it is
|
|
163
|
+
dispatched a second time onto the same branch. Append
|
|
164
|
+
every id you dispatch and never remove one — a stale id
|
|
165
|
+
that has since gone done is dropped automatically, so
|
|
166
|
+
over-supplying is free and forgetting is the only error.
|
|
87
167
|
--concurrency <n> Override the per-beat concurrency cap for this run only.
|
|
88
168
|
Must be a positive integer. When omitted, the cap is
|
|
89
169
|
resolved from delivery.deliverRunner.concurrencyCap in
|
|
@@ -102,15 +182,47 @@ Output envelope:
|
|
|
102
182
|
"totalStories": 2,
|
|
103
183
|
"concurrencyCap": 3,
|
|
104
184
|
"inFlight": 0,
|
|
105
|
-
"cycleError": null
|
|
185
|
+
"cycleError": null,
|
|
186
|
+
"wedged": null
|
|
106
187
|
}
|
|
107
188
|
|
|
108
189
|
Exit codes:
|
|
109
190
|
0 - Success, ready set emitted
|
|
110
191
|
1 - Invalid input (missing/malformed DAG, invalid --concurrency/--in-flight/--done)
|
|
111
192
|
2 - Cycle detected in dependency graph
|
|
193
|
+
3 - Wedged: ready is empty, nothing is in flight, and undone Stories are
|
|
194
|
+
waiting on blockers that are not done. Distinct from an ordinary empty
|
|
195
|
+
ready set (which means "waiting on in-flight work") and from a cycle.
|
|
196
|
+
4 - Blocked: a Story carries agent::blocked (probe mode only). The HITL
|
|
197
|
+
pause — no beat can clear it. STOP the loop; do not poll.
|
|
112
198
|
`;
|
|
113
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Build the exit-1 input-error result. Shared by both modes so a malformed
|
|
202
|
+
* `--concurrency` reports identically whether it arrived alongside `--dag` or
|
|
203
|
+
* `--probe-live`.
|
|
204
|
+
*
|
|
205
|
+
* @param {string} message
|
|
206
|
+
* @param {number|null} [concurrencyCap]
|
|
207
|
+
* @param {number} [inFlightValue]
|
|
208
|
+
* @returns {{ envelope: object, exitCode: 1 }}
|
|
209
|
+
*/
|
|
210
|
+
function inputErrorResult(message, concurrencyCap = null, inFlightValue = 0) {
|
|
211
|
+
return {
|
|
212
|
+
envelope: {
|
|
213
|
+
kind: 'stories-ready-set',
|
|
214
|
+
ready: [],
|
|
215
|
+
totalStories: 0,
|
|
216
|
+
concurrencyCap,
|
|
217
|
+
inFlight: inFlightValue,
|
|
218
|
+
cycleError: null,
|
|
219
|
+
wedged: null,
|
|
220
|
+
inputError: message,
|
|
221
|
+
},
|
|
222
|
+
exitCode: 1,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
114
226
|
/**
|
|
115
227
|
* Parse and validate the raw DAG input array.
|
|
116
228
|
*
|
|
@@ -182,15 +294,16 @@ export function parseDag(raw) {
|
|
|
182
294
|
}
|
|
183
295
|
|
|
184
296
|
/**
|
|
185
|
-
* Parse a comma-separated
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
297
|
+
* Parse a comma-separated list of Story IDs into a deduped set of positive
|
|
298
|
+
* integers. Empty / absent input yields an empty set. Rejects any token that
|
|
299
|
+
* is not a positive integer so a typo never silently drops a dependency gate
|
|
300
|
+
* (`--done`) or a held dispatch slot (`--dispatched`).
|
|
189
301
|
*
|
|
190
302
|
* @param {string|undefined} raw
|
|
303
|
+
* @param {string} flag Flag name, for the error message.
|
|
191
304
|
* @returns {{ ids: Set<number>|null, error: string|null }}
|
|
192
305
|
*/
|
|
193
|
-
export function
|
|
306
|
+
export function parseIdCsv(raw, flag) {
|
|
194
307
|
if (raw == null || raw === '') {
|
|
195
308
|
return { ids: new Set(), error: null };
|
|
196
309
|
}
|
|
@@ -202,7 +315,7 @@ export function parseDoneIds(raw) {
|
|
|
202
315
|
if (!Number.isInteger(num) || num <= 0) {
|
|
203
316
|
return {
|
|
204
317
|
ids: null,
|
|
205
|
-
error:
|
|
318
|
+
error: `${flag} must be a comma-separated list of positive integers, got "${trimmed}"`,
|
|
206
319
|
};
|
|
207
320
|
}
|
|
208
321
|
ids.add(num);
|
|
@@ -210,6 +323,16 @@ export function parseDoneIds(raw) {
|
|
|
210
323
|
return { ids, error: null };
|
|
211
324
|
}
|
|
212
325
|
|
|
326
|
+
/**
|
|
327
|
+
* Parse the `--done` CSV of already-completed Story IDs (flag mode).
|
|
328
|
+
*
|
|
329
|
+
* @param {string|undefined} raw
|
|
330
|
+
* @returns {{ ids: Set<number>|null, error: string|null }}
|
|
331
|
+
*/
|
|
332
|
+
export function parseDoneIds(raw) {
|
|
333
|
+
return parseIdCsv(raw, '--done');
|
|
334
|
+
}
|
|
335
|
+
|
|
213
336
|
/**
|
|
214
337
|
* Parse the raw `--in-flight` value into a non-negative integer. Absent
|
|
215
338
|
* input defaults to 0. Rejects negatives and non-integers.
|
|
@@ -322,6 +445,7 @@ export function buildReadySetEnvelope(
|
|
|
322
445
|
concurrencyCap,
|
|
323
446
|
inFlight,
|
|
324
447
|
cycleError: null,
|
|
448
|
+
wedged: null,
|
|
325
449
|
};
|
|
326
450
|
|
|
327
451
|
if (totalStories === 0) {
|
|
@@ -352,7 +476,12 @@ export function buildReadySetEnvelope(
|
|
|
352
476
|
const rec = {
|
|
353
477
|
id: node.id,
|
|
354
478
|
dependsOn: node.dependsOn,
|
|
355
|
-
labels
|
|
479
|
+
// A node's own live labels (probe mode) are preserved so the core's
|
|
480
|
+
// classifier withholds an in-flight `agent::executing` / `agent::closing`
|
|
481
|
+
// Story rather than re-dispatching it onto a second branch. Flag-mode
|
|
482
|
+
// nodes carry none — `parseDag` accepts no labels — so this is inert
|
|
483
|
+
// there and the legacy contract is unchanged.
|
|
484
|
+
labels: doneIds.has(node.id) ? [AGENT_LABELS.DONE] : (node.labels ?? []),
|
|
356
485
|
};
|
|
357
486
|
if (node.files !== undefined) rec.files = node.files;
|
|
358
487
|
return rec;
|
|
@@ -365,7 +494,62 @@ export function buildReadySetEnvelope(
|
|
|
365
494
|
globalCap: concurrencyCap,
|
|
366
495
|
}).map((rec) => rec.id);
|
|
367
496
|
|
|
368
|
-
|
|
497
|
+
// Wedge detection (Story #4540). `ready: []` is normal while work is in
|
|
498
|
+
// flight — the loop is simply waiting. But ready-empty AND nothing in
|
|
499
|
+
// flight AND undone Stories remaining means no beat can ever make
|
|
500
|
+
// progress: the run is stuck, and the previous behaviour was to return
|
|
501
|
+
// exit 0 with an empty ready set forever, indistinguishable from
|
|
502
|
+
// "waiting". Name the stuck ids and their unmet blockers.
|
|
503
|
+
//
|
|
504
|
+
// Distinct from `cycleError`/exit 2: a cycle is a self-referential DAG,
|
|
505
|
+
// whereas this is a DAG whose gates are real but unsatisfiable from the
|
|
506
|
+
// supplied `done` set (typically a blocker outside the delivered set that
|
|
507
|
+
// has not landed).
|
|
508
|
+
const wedge = detectWedge({ nodes, doneIds, ready, inFlight });
|
|
509
|
+
if (wedge) {
|
|
510
|
+
return {
|
|
511
|
+
envelope: { ...base, ready, wedged: wedge },
|
|
512
|
+
exitCode: WEDGED_EXIT_CODE,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return { envelope: { ...base, ready, wedged: null }, exitCode: 0 };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Identify a run that cannot progress: nothing dispatchable, nothing in
|
|
521
|
+
* flight, work remaining.
|
|
522
|
+
*
|
|
523
|
+
* @param {{ nodes: object[], doneIds: Set<number>, ready: number[], inFlight: number }} args
|
|
524
|
+
* @returns {{ reason: string, stories: Array<{ id: number, unmetBlockers: number[] }> }|null}
|
|
525
|
+
*/
|
|
526
|
+
export function detectWedge({ nodes, doneIds, ready, inFlight }) {
|
|
527
|
+
if (ready.length > 0 || inFlight > 0) return null;
|
|
528
|
+
const undone = nodes.filter((n) => !doneIds.has(n.id));
|
|
529
|
+
if (undone.length === 0) return null;
|
|
530
|
+
|
|
531
|
+
const stories = undone
|
|
532
|
+
.map((n) => ({
|
|
533
|
+
id: n.id,
|
|
534
|
+
unmetBlockers: (n.dependsOn ?? []).filter((dep) => !doneIds.has(dep)),
|
|
535
|
+
}))
|
|
536
|
+
.filter((s) => s.unmetBlockers.length > 0);
|
|
537
|
+
|
|
538
|
+
// Undone work with no unmet blockers would have been dispatched; if that
|
|
539
|
+
// is the whole set, the cap or in-flight accounting explains the empty
|
|
540
|
+
// ready set rather than a wedge.
|
|
541
|
+
if (stories.length === 0) return null;
|
|
542
|
+
|
|
543
|
+
const detail = stories
|
|
544
|
+
.map((s) => `#${s.id} ← ${s.unmetBlockers.map((d) => `#${d}`).join(', ')}`)
|
|
545
|
+
.join('; ');
|
|
546
|
+
return {
|
|
547
|
+
reason:
|
|
548
|
+
`No Story can be dispatched: nothing is in flight and ${stories.length} ` +
|
|
549
|
+
`Story(ies) are waiting on blockers that are not done — ${detail}. ` +
|
|
550
|
+
`A blocker outside the delivered set must land first, or be included in --ids.`,
|
|
551
|
+
stories,
|
|
552
|
+
};
|
|
369
553
|
}
|
|
370
554
|
|
|
371
555
|
/**
|
|
@@ -397,36 +581,23 @@ export function runStoriesWaveTick({
|
|
|
397
581
|
cwd,
|
|
398
582
|
config,
|
|
399
583
|
} = {}) {
|
|
400
|
-
const inputError = (message, concurrencyCap = null, inFlightValue = 0) => ({
|
|
401
|
-
envelope: {
|
|
402
|
-
kind: 'stories-ready-set',
|
|
403
|
-
ready: [],
|
|
404
|
-
totalStories: 0,
|
|
405
|
-
concurrencyCap,
|
|
406
|
-
inFlight: inFlightValue,
|
|
407
|
-
cycleError: null,
|
|
408
|
-
inputError: message,
|
|
409
|
-
},
|
|
410
|
-
exitCode: 1,
|
|
411
|
-
});
|
|
412
|
-
|
|
413
584
|
// Validate the --concurrency override before resolving config so an invalid
|
|
414
585
|
// value fails fast with exit code 1 regardless of DAG validity.
|
|
415
586
|
const { value: override, error: concurrencyError } =
|
|
416
587
|
parseConcurrencyOverride(concurrency);
|
|
417
588
|
if (concurrencyError) {
|
|
418
|
-
return
|
|
589
|
+
return inputErrorResult(concurrencyError);
|
|
419
590
|
}
|
|
420
591
|
|
|
421
592
|
const { value: inFlightValue, error: inFlightError } =
|
|
422
593
|
parseInFlight(inFlight);
|
|
423
594
|
if (inFlightError) {
|
|
424
|
-
return
|
|
595
|
+
return inputErrorResult(inFlightError);
|
|
425
596
|
}
|
|
426
597
|
|
|
427
598
|
const { ids: doneIds, error: doneError } = parseDoneIds(done);
|
|
428
599
|
if (doneError) {
|
|
429
|
-
return
|
|
600
|
+
return inputErrorResult(doneError, null, inFlightValue);
|
|
430
601
|
}
|
|
431
602
|
|
|
432
603
|
const concurrencyCap = resolveConcurrencyCap({ cwd, config, override });
|
|
@@ -437,7 +608,7 @@ export function runStoriesWaveTick({
|
|
|
437
608
|
try {
|
|
438
609
|
rawJson = readFileSync(dagFile, 'utf8');
|
|
439
610
|
} catch (err) {
|
|
440
|
-
return
|
|
611
|
+
return inputErrorResult(
|
|
441
612
|
`Could not read DAG file "${dagFile}": ${err.message}`,
|
|
442
613
|
concurrencyCap,
|
|
443
614
|
inFlightValue,
|
|
@@ -446,7 +617,7 @@ export function runStoriesWaveTick({
|
|
|
446
617
|
} else if (dagJson) {
|
|
447
618
|
rawJson = dagJson;
|
|
448
619
|
} else {
|
|
449
|
-
return
|
|
620
|
+
return inputErrorResult(
|
|
450
621
|
'Either --dag <json> or --dag-file <path> is required',
|
|
451
622
|
concurrencyCap,
|
|
452
623
|
inFlightValue,
|
|
@@ -457,7 +628,7 @@ export function runStoriesWaveTick({
|
|
|
457
628
|
try {
|
|
458
629
|
parsed = JSON.parse(rawJson);
|
|
459
630
|
} catch (err) {
|
|
460
|
-
return
|
|
631
|
+
return inputErrorResult(
|
|
461
632
|
`Invalid JSON: ${err.message}`,
|
|
462
633
|
concurrencyCap,
|
|
463
634
|
inFlightValue,
|
|
@@ -466,7 +637,7 @@ export function runStoriesWaveTick({
|
|
|
466
637
|
|
|
467
638
|
const { nodes, error: parseError } = parseDag(parsed);
|
|
468
639
|
if (parseError) {
|
|
469
|
-
return
|
|
640
|
+
return inputErrorResult(parseError, concurrencyCap, inFlightValue);
|
|
470
641
|
}
|
|
471
642
|
|
|
472
643
|
return buildReadySetEnvelope(nodes, {
|
|
@@ -476,12 +647,144 @@ export function runStoriesWaveTick({
|
|
|
476
647
|
});
|
|
477
648
|
}
|
|
478
649
|
|
|
650
|
+
/**
|
|
651
|
+
* Probe mode: resolve the graph and the run's progress from **live state**,
|
|
652
|
+
* then run the same scheduling kernel the flag mode does.
|
|
653
|
+
*
|
|
654
|
+
* This is the flag-free beat. The caller supplies only the Story ids it was
|
|
655
|
+
* asked to deliver; `done` and `inFlight` are probed rather than transcribed,
|
|
656
|
+
* which is what makes the `/deliver` loop's old seed-the-first-beat footgun
|
|
657
|
+
* structurally impossible instead of merely documented.
|
|
658
|
+
*
|
|
659
|
+
* The envelope is the flag mode's, plus three probe-only fields the caller can
|
|
660
|
+
* no longer compute for itself:
|
|
661
|
+
* - `done` — the resolved done set (in-set ∪ satisfied foreign blockers).
|
|
662
|
+
* - `epilogueDue` — true exactly when every listed Story is done, which is
|
|
663
|
+
* the run-end signal for `plan-run-epilogue.js`.
|
|
664
|
+
* - `blocked` — ids carrying `agent::blocked` (Story #4601). Non-empty means
|
|
665
|
+
* the loop must END, not poll: see `BLOCKED_EXIT_CODE`.
|
|
666
|
+
*
|
|
667
|
+
* @param {object} args
|
|
668
|
+
* @param {string} args.stories Raw `--stories` CSV of Story ids.
|
|
669
|
+
* @param {string|number} [args.concurrency] Raw `--concurrency` override.
|
|
670
|
+
* @param {string} [args.dispatched] Raw `--dispatched` CSV of ids the host
|
|
671
|
+
* has spawned but may not yet have observed labelled.
|
|
672
|
+
* @param {string} [args.cwd] Repo root for config resolution.
|
|
673
|
+
* @param {object} [args.config] Pre-resolved config (test injection).
|
|
674
|
+
* @param {Function} [args.probe] Probe seam (test injection).
|
|
675
|
+
* @param {Function} [args.context] Provider-context seam (test injection).
|
|
676
|
+
* @returns {Promise<{ envelope: object, exitCode: number }>}
|
|
677
|
+
*/
|
|
678
|
+
export async function runProbedStoriesWaveTick({
|
|
679
|
+
stories,
|
|
680
|
+
concurrency,
|
|
681
|
+
dispatched,
|
|
682
|
+
cwd,
|
|
683
|
+
config,
|
|
684
|
+
probe = probeLiveState,
|
|
685
|
+
context = createProbeContext,
|
|
686
|
+
} = {}) {
|
|
687
|
+
const { value: override, error: concurrencyError } =
|
|
688
|
+
parseConcurrencyOverride(concurrency);
|
|
689
|
+
if (concurrencyError) {
|
|
690
|
+
return inputErrorResult(concurrencyError);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
let ids;
|
|
694
|
+
try {
|
|
695
|
+
ids = parseIds(stories);
|
|
696
|
+
} catch (err) {
|
|
697
|
+
return inputErrorResult(err.message);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const { ids: dispatchedIds, error: dispatchedError } = parseIdCsv(
|
|
701
|
+
dispatched,
|
|
702
|
+
'--dispatched',
|
|
703
|
+
);
|
|
704
|
+
if (dispatchedError) {
|
|
705
|
+
return inputErrorResult(dispatchedError);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const concurrencyCap = resolveConcurrencyCap({ cwd, config, override });
|
|
709
|
+
|
|
710
|
+
let probed;
|
|
711
|
+
try {
|
|
712
|
+
const { provider, owner, repo } = context();
|
|
713
|
+
probed = await probe({
|
|
714
|
+
ids,
|
|
715
|
+
provider,
|
|
716
|
+
owner,
|
|
717
|
+
repo,
|
|
718
|
+
dispatched: [...dispatchedIds],
|
|
719
|
+
warn: (m) => Logger.warn(m),
|
|
720
|
+
});
|
|
721
|
+
} catch (err) {
|
|
722
|
+
// A failed probe must never degrade into "nothing is ready" — that is
|
|
723
|
+
// indistinguishable from a healthy waiting beat and would silently stall
|
|
724
|
+
// the run. Fail loud with the input-error contract instead.
|
|
725
|
+
return inputErrorResult(
|
|
726
|
+
`Could not probe live state: ${err?.message ?? err}`,
|
|
727
|
+
concurrencyCap,
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const { nodes, doneIds, inFlight, blockedIds = [] } = probed;
|
|
732
|
+
const { envelope, exitCode } = buildReadySetEnvelope(nodes, {
|
|
733
|
+
concurrencyCap,
|
|
734
|
+
doneIds,
|
|
735
|
+
inFlight,
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
const done = [...doneIds].sort((a, b) => a - b);
|
|
739
|
+
const epilogueDue =
|
|
740
|
+
nodes.length > 0 && nodes.every((node) => doneIds.has(node.id));
|
|
741
|
+
return {
|
|
742
|
+
envelope: {
|
|
743
|
+
...envelope,
|
|
744
|
+
done,
|
|
745
|
+
epilogueDue,
|
|
746
|
+
blocked: blockedIds,
|
|
747
|
+
blockedReason: blockedReasonFor(blockedIds),
|
|
748
|
+
},
|
|
749
|
+
// A blocked Story outranks the scheduler's own verdict — including a
|
|
750
|
+
// wedge, whose named blockers are moot while a human owes a decision.
|
|
751
|
+
// A cycle (2) does not yield: a self-referential DAG is a planning error
|
|
752
|
+
// that must be fixed before any of this run's state means anything.
|
|
753
|
+
exitCode:
|
|
754
|
+
blockedIds.length > 0 && !envelope.cycleError
|
|
755
|
+
? BLOCKED_EXIT_CODE
|
|
756
|
+
: exitCode,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Render the operator-facing reason for a blocked run, or `null` when nothing
|
|
762
|
+
* is blocked.
|
|
763
|
+
*
|
|
764
|
+
* @param {number[]} blockedIds
|
|
765
|
+
* @returns {string|null}
|
|
766
|
+
*/
|
|
767
|
+
function blockedReasonFor(blockedIds) {
|
|
768
|
+
if (blockedIds.length === 0) return null;
|
|
769
|
+
const list = blockedIds.map((id) => `#${id}`).join(', ');
|
|
770
|
+
return (
|
|
771
|
+
`${blockedIds.length} Story(ies) carry agent::blocked — ${list}. ` +
|
|
772
|
+
`agent::blocked is the protocol's HITL pause: no beat can clear it and ` +
|
|
773
|
+
`the loop must stop rather than poll. Read each Story's friction comment ` +
|
|
774
|
+
`(gh issue view <id> --comments), resolve the blocker, then flip it back ` +
|
|
775
|
+
`with: node .agents/scripts/update-ticket-state.js --ticket <id> --state agent::ready`
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
|
|
479
779
|
async function main(argv) {
|
|
480
780
|
const { values } = parseArgs({
|
|
481
781
|
args: argv,
|
|
482
782
|
options: {
|
|
483
783
|
dag: { type: 'string' },
|
|
484
784
|
'dag-file': { type: 'string' },
|
|
785
|
+
stories: { type: 'string' },
|
|
786
|
+
'probe-live': { type: 'boolean' },
|
|
787
|
+
dispatched: { type: 'string' },
|
|
485
788
|
concurrency: { type: 'string' },
|
|
486
789
|
done: { type: 'string' },
|
|
487
790
|
'in-flight': { type: 'string' },
|
|
@@ -496,19 +799,43 @@ async function main(argv) {
|
|
|
496
799
|
return;
|
|
497
800
|
}
|
|
498
801
|
|
|
499
|
-
const
|
|
500
|
-
|
|
802
|
+
const flagError = validateProbeFlags({
|
|
803
|
+
probeLive: values['probe-live'],
|
|
804
|
+
stories: values.stories,
|
|
805
|
+
dag: values.dag,
|
|
501
806
|
dagFile: values['dag-file'],
|
|
502
|
-
concurrency: values.concurrency,
|
|
503
807
|
done: values.done,
|
|
504
808
|
inFlight: values['in-flight'],
|
|
809
|
+
dispatched: values.dispatched,
|
|
505
810
|
});
|
|
506
811
|
|
|
812
|
+
const { envelope, exitCode } = flagError
|
|
813
|
+
? inputErrorResult(flagError)
|
|
814
|
+
: values['probe-live']
|
|
815
|
+
? await runProbedStoriesWaveTick({
|
|
816
|
+
stories: values.stories,
|
|
817
|
+
concurrency: values.concurrency,
|
|
818
|
+
dispatched: values.dispatched,
|
|
819
|
+
})
|
|
820
|
+
: runStoriesWaveTick({
|
|
821
|
+
dagJson: values.dag,
|
|
822
|
+
dagFile: values['dag-file'],
|
|
823
|
+
concurrency: values.concurrency,
|
|
824
|
+
done: values.done,
|
|
825
|
+
inFlight: values['in-flight'],
|
|
826
|
+
});
|
|
827
|
+
|
|
507
828
|
process.stdout.write(`${JSON.stringify(envelope, null, 2)}\n`);
|
|
508
829
|
|
|
509
830
|
if (exitCode !== 0) {
|
|
510
831
|
Logger.error(
|
|
511
|
-
`stories-wave-tick: ${
|
|
832
|
+
`stories-wave-tick: ${
|
|
833
|
+
envelope.inputError ??
|
|
834
|
+
envelope.cycleError ??
|
|
835
|
+
envelope.blockedReason ??
|
|
836
|
+
envelope.wedged?.reason ??
|
|
837
|
+
'error'
|
|
838
|
+
}`,
|
|
512
839
|
);
|
|
513
840
|
process.exitCode = exitCode;
|
|
514
841
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Operator planning:
|
|
14
14
|
* node .agents/scripts/plan-context.js --seed "…" | --seed-file <path> | --tickets <ids>
|
|
15
|
-
* node .agents/scripts/plan-persist.js --stories …
|
|
15
|
+
* node .agents/scripts/plan-persist.js --stories …
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { readFile } from 'node:fs/promises';
|
|
@@ -146,7 +146,7 @@ async function runEmitContext({
|
|
|
146
146
|
// corpus digest reads the project's actual docs directory regardless
|
|
147
147
|
// of the directory this CLI happens to be invoked from — matching the
|
|
148
148
|
// sibling resolution pattern in
|
|
149
|
-
//
|
|
149
|
+
// planning/authoring-context.js.
|
|
150
150
|
const docsRoot = path.resolve(
|
|
151
151
|
PROJECT_ROOT,
|
|
152
152
|
config?.project?.paths?.docsRoot ?? 'docs',
|
|
@@ -157,7 +157,7 @@ async function runEmitContext({
|
|
|
157
157
|
loadBodyTemplate(projectRoot),
|
|
158
158
|
fetchOpenStories(provider),
|
|
159
159
|
readTechStackSummary(projectRoot),
|
|
160
|
-
buildCorpusContext({
|
|
160
|
+
buildCorpusContext({ docsContextFiles, docsRoot }),
|
|
161
161
|
]);
|
|
162
162
|
|
|
163
163
|
const duplicateCandidates = rankDuplicateCandidates({
|