@khoavu882/doflow 2.0.0-beta.5 → 2.0.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/README.md +12 -11
- package/bin/doflow.js +21 -11
- package/core/.antigravity-plugin/plugin.json +1 -2
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/lib.sh +43 -24
- package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
- package/core/harnesses/claude/hooks/post-compact.sh +35 -32
- package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
- package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/settings/settings.json +18 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
- package/core/harnesses/codex/agents/core-implementer.toml +1 -0
- package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
- package/core/harnesses/codex/agents/research-writer.toml +1 -1
- package/core/harnesses/codex/agents/reviewer.toml +10 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
- package/core/harnesses/codex/agents/system-architect.toml +1 -1
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
- package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow.json +57 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -1
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +1 -0
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +1 -0
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +1 -0
- package/core/harnesses/kiro/hooks/session-start.sh +1 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
- package/core/harnesses/pi/extensions/doflow-guard.js +8 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +0 -1
- package/core/harnesses/shared/hooks/bridge/bridge.sh +102 -9
- package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +10 -2
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +48 -15
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +2 -11
- package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
- package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
- package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
- package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +3 -2
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +213 -12
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +114 -38
- package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
- package/core/harnesses/shared/hooks/policies/stop-check.sh +20 -9
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +5 -2
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
- package/core/harnesses/shared/hooks/stream-hook-runner.js +47 -8
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +55 -82
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +58 -31
- package/core/registry/contribution-points.json +32 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1442 -363
- package/core/registry/lifecycle.json +485 -304
- package/core/registry/mcp.json +5 -1
- package/core/registry/models.json +7 -1
- package/core/registry/package-templates/README.md +19 -0
- package/core/registry/package-templates/agent/files/agent.md +9 -0
- package/core/registry/package-templates/agent/template.json +4 -0
- package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
- package/core/registry/package-templates/capability-provider/template.json +11 -0
- package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
- package/core/registry/package-templates/debate-preset/template.json +4 -0
- package/core/registry/package-templates/review-preset/files/preset.json +30 -0
- package/core/registry/package-templates/review-preset/template.json +4 -0
- package/core/registry/package-templates/skill/files/SKILL.md +8 -0
- package/core/registry/package-templates/skill/template.json +4 -0
- package/core/registry/package-templates/worker/files/worker.json +92 -0
- package/core/registry/package-templates/worker/template.json +11 -0
- package/core/registry/packages/doflow/doflow-package.json +15 -0
- package/core/registry/packages/doflow/presets/debate-3.json +26 -0
- package/core/registry/protocols.json +61 -0
- package/core/registry/routes.json +17 -0
- package/core/registry/rules.json +431 -0
- package/core/registry/settings.json +276 -0
- package/core/registry/storage.json +679 -31
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -1
- package/core/registry/workflows.json +55 -0
- package/core/shared/agent-specs/core-implementer.md +3 -0
- package/core/shared/agent-specs/delivery-lead.md +68 -0
- package/core/shared/agent-specs/quality-guardian.md +5 -1
- package/core/shared/agent-specs/research-writer.md +10 -3
- package/core/shared/agent-specs/reviewer.md +53 -0
- package/core/shared/agent-specs/spec-analyst.md +5 -6
- package/core/shared/agent-specs/system-architect.md +5 -1
- package/core/shared/guidance/DOFLOW_CORE.md +1 -5
- package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
- package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
- package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
- package/core/shared/guidance/pointers/codex.md +1 -4
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
- package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
- package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
- package/core/shared/guidance/rules/RULES.md +45 -0
- package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
- package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
- package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
- package/core/shared/scripts/doflow/bin/doflow-run +149 -34
- package/core/shared/skills/do/SKILL.md +12 -10
- package/core/shared/skills/do/references/advice_calls.md +26 -0
- package/core/shared/skills/do/references/pm_routing.md +1 -2
- package/core/shared/skills/do/references/program_mode.md +31 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
- package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
- package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
- package/core/shared/skills/do-code-review/SKILL.md +41 -144
- package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
- package/core/shared/skills/do-code-review/references/tools.md +90 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
- package/core/shared/skills/do-design/SKILL.md +18 -17
- package/core/shared/skills/do-diagnose/SKILL.md +14 -4
- package/core/shared/skills/do-document/SKILL.md +11 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +18 -6
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +15 -58
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
- package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
- package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
- package/core/shared/skills/do-flow/SKILL.md +8 -9
- package/core/shared/skills/do-flow/references/task_classes.md +2 -2
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +4 -7
- package/core/shared/skills/do-plan/SKILL.md +26 -15
- package/core/shared/skills/do-test/SKILL.md +7 -4
- package/core/shared/templates/doflow/constitution-template.md +9 -3
- package/core/shared/templates/doflow/data-model-template.md +1 -1
- package/core/shared/templates/doflow/design-template.md +113 -16
- package/core/shared/templates/doflow/plan-template.md +11 -10
- package/core/shared/templates/doflow/question-log-template.md +14 -2
- package/core/shared/templates/doflow/requirement-template.md +1 -1
- package/core/worker-evidence/antigravity/checks.json +14 -0
- package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
- package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
- package/core/worker-evidence/codex/checks.json +38 -0
- package/package.json +10 -6
- package/src/adapters/antigravity/index.js +143 -66
- package/src/adapters/claude/index.js +24 -4
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/index.js +48 -13
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +290 -44
- package/src/adapters/guidance-manifest.js +71 -0
- package/src/adapters/guidance-pointer.js +19 -0
- package/src/adapters/hook-commands.js +6 -10
- package/src/adapters/index.js +1 -0
- package/src/adapters/kiro/index.js +23 -12
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +2 -1
- package/src/adapters/pi/index.js +14 -7
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +2 -1
- package/src/cli/commands/reconcile.js +53 -9
- package/src/cli/commands/remove.js +2 -2
- package/src/cli/commands/rollback.js +6 -3
- package/src/cli/commands/self-update.js +18 -9
- package/src/cli/commands/status.js +21 -2
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +2 -1
- package/src/cli/index.js +322 -29
- package/src/cli/runtime-commands.js +209 -57
- package/src/cli/shared.js +0 -2
- package/src/helper/git.js +1 -1
- package/src/helper/guardrails.js +1 -1
- package/src/helper/marker-merge.js +31 -13
- package/src/helper/package-ids.js +37 -0
- package/src/helper/semver.js +94 -0
- package/src/install/backup.js +105 -15
- package/src/install/context.js +1 -1
- package/src/install/targets.js +31 -16
- package/src/lifecycle/coverage.js +20 -3
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +412 -78
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +551 -26
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +492 -0
- package/src/release/preflight.js +425 -0
- package/src/release/rehearse.js +401 -0
- package/src/release/release-workflow.js +733 -0
- package/src/release/sync-plugin-versions.js +43 -7
- package/src/runtime/advice/bench-gate.js +25 -0
- package/src/runtime/advice/cli.js +59 -16
- package/src/runtime/advice/client.js +32 -9
- package/src/runtime/advice/config.js +98 -133
- package/src/runtime/advice/doctor.js +1 -1
- package/src/runtime/advice/elevation.js +97 -0
- package/src/runtime/advice/endpoint-guard.js +9 -3
- package/src/runtime/advice/mode.js +37 -0
- package/src/runtime/advice/record.js +18 -5
- package/src/runtime/advice/registry.js +4 -1
- package/src/runtime/advice/shadow-config.js +229 -0
- package/src/runtime/advice/shadow-dispatcher.js +276 -0
- package/src/runtime/brief-rules.js +81 -0
- package/src/runtime/brief-tools.js +111 -34
- package/src/runtime/candidate.js +20 -11
- package/src/runtime/capabilities/executor.js +450 -0
- package/src/runtime/capabilities/loopback.js +41 -0
- package/src/runtime/capabilities/sanitizer.js +88 -0
- package/src/runtime/capability-router.js +135 -3
- package/src/runtime/checkouts.js +73 -11
- package/src/runtime/claims.js +13 -7
- package/src/runtime/cli.js +196 -19
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/delivery-store.js +4 -6
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/primary-command.js +117 -0
- package/src/runtime/context/primary.js +344 -0
- package/src/runtime/context/summary-store.js +655 -0
- package/src/runtime/context-pack.js +154 -78
- package/src/runtime/decision-register.js +13 -4
- package/src/runtime/dispatch/admission.js +392 -0
- package/src/runtime/dispatch/attempt-end.js +274 -0
- package/src/runtime/dispatch/command.js +628 -0
- package/src/runtime/dispatch/controls.js +904 -0
- package/src/runtime/dispatch/envelope.js +276 -0
- package/src/runtime/dispatch/grants.js +310 -0
- package/src/runtime/dispatch/integrate.js +617 -0
- package/src/runtime/dispatch/loop.js +1835 -0
- package/src/runtime/dispatch/store.js +352 -0
- package/src/runtime/dispatch/verify-run.js +201 -0
- package/src/runtime/evidence-ledger.js +34 -7
- package/src/runtime/failure/capture.js +3 -1
- package/src/runtime/failure/cli.js +25 -9
- package/src/runtime/failure/home.js +57 -30
- package/src/runtime/failure/store.js +7 -2
- package/src/runtime/feature-pointer.js +174 -0
- package/src/runtime/feature-resolve.js +13 -2
- package/src/runtime/fleet/command.js +77 -0
- package/src/runtime/fleet/projects.js +178 -0
- package/src/runtime/fleet/tree.js +383 -0
- package/src/runtime/freshness.js +33 -3
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +565 -17
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +277 -22
- package/src/runtime/inventory/coverage.js +1 -1
- package/src/runtime/inventory/index.js +1 -1
- package/src/runtime/inventory/precedence.js +2 -2
- package/src/runtime/inventory/siblings.js +6 -4
- package/src/runtime/knowledge/code-corpus.js +277 -0
- package/src/runtime/knowledge/code-lexical.js +130 -0
- package/src/runtime/knowledge/code-retrieval.js +307 -0
- package/src/runtime/knowledge/code-validation.js +106 -0
- package/src/runtime/knowledge/gp3-gate.js +74 -0
- package/src/runtime/knowledge/graph-adapter.js +288 -0
- package/src/runtime/knowledge/graph-fallback.js +394 -0
- package/src/runtime/knowledge/hybrid.js +136 -8
- package/src/runtime/knowledge/retrieval.js +34 -1
- package/src/runtime/knowledge/rtk-compress.js +77 -0
- package/src/runtime/knowledge/semble-bridge.py +106 -0
- package/src/runtime/knowledge/semble-lifecycle.js +114 -0
- package/src/runtime/knowledge/semble-provider.js +365 -0
- package/src/runtime/lifecycle/cli.js +16 -6
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +6 -1
- package/src/runtime/lifecycle/overview.js +24 -1
- package/src/runtime/lifecycle/report-store.js +8 -10
- package/src/runtime/lifecycle/retention.js +95 -31
- package/src/runtime/lifecycle/root.js +9 -1
- package/src/runtime/lifecycle/status.js +34 -17
- package/src/runtime/lifecycle/store-upkeep.js +56 -7
- package/src/runtime/mask.js +134 -7
- package/src/runtime/mcp-consultations.js +40 -6
- package/src/runtime/model-router.js +79 -7
- package/src/runtime/models/catalogue.js +191 -0
- package/src/runtime/models/command.js +153 -0
- package/src/runtime/off-words.js +12 -0
- package/src/runtime/outcome.js +2 -2
- package/src/runtime/packages/command.js +1009 -0
- package/src/runtime/packages/contributions.js +116 -0
- package/src/runtime/packages/digest.js +132 -0
- package/src/runtime/packages/fetch.js +182 -0
- package/src/runtime/packages/grants.js +192 -0
- package/src/runtime/packages/index-file.js +134 -0
- package/src/runtime/packages/instruction.js +90 -0
- package/src/runtime/packages/manifest.js +331 -0
- package/src/runtime/packages/points.js +477 -0
- package/src/runtime/packages/programs.js +216 -0
- package/src/runtime/packages/scaffold.js +277 -0
- package/src/runtime/packages/source.js +90 -0
- package/src/runtime/packages/state.js +164 -0
- package/src/runtime/packages/store.js +449 -0
- package/src/runtime/packages/tar.js +106 -0
- package/src/runtime/packages/view.js +406 -0
- package/src/runtime/plan-check.js +59 -0
- package/src/runtime/plan-content.js +324 -0
- package/src/runtime/policy-init.js +118 -0
- package/src/runtime/profiles/doctor.js +96 -0
- package/src/runtime/profiles/resolve.js +244 -0
- package/src/runtime/profiles/rollback.js +254 -0
- package/src/runtime/profiles/sampler.js +41 -0
- package/src/runtime/readiness-record.js +16 -11
- package/src/runtime/readiness.js +46 -5
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +10 -30
- package/src/runtime/retrieval-record.js +75 -5
- package/src/runtime/review/command.js +246 -0
- package/src/runtime/review/dominance.js +68 -0
- package/src/runtime/review/findings.js +434 -0
- package/src/runtime/review/identity.js +255 -0
- package/src/runtime/review/protocol-command.js +109 -0
- package/src/runtime/review/protocols.js +405 -0
- package/src/runtime/review/resolve-protocol.js +302 -0
- package/src/runtime/review/start.js +530 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +270 -0
- package/src/runtime/roadmap/loop.js +1636 -0
- package/src/runtime/roadmap/merge.js +381 -0
- package/src/runtime/roadmap/rows.js +1113 -0
- package/src/runtime/roadmap/seed.js +951 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +60 -9
- package/src/runtime/scaffold/generate.js +21 -9
- package/src/runtime/settings/autopilot.js +106 -0
- package/src/runtime/settings/cli.js +280 -0
- package/src/runtime/settings/config-write.js +314 -0
- package/src/runtime/settings/files.js +271 -0
- package/src/runtime/settings/legacy.js +154 -0
- package/src/runtime/settings/lexical.js +56 -0
- package/src/runtime/settings/prepared-run.js +269 -0
- package/src/runtime/settings/project-layers.js +118 -0
- package/src/runtime/settings/refs.js +288 -0
- package/src/runtime/settings/registry.js +553 -0
- package/src/runtime/settings/resolve.js +713 -0
- package/src/runtime/settings/shared.js +37 -0
- package/src/runtime/settings/worker-billing.js +107 -0
- package/src/runtime/settings/write.js +269 -0
- package/src/runtime/slot-command.js +78 -0
- package/src/runtime/slots.js +671 -0
- package/src/runtime/source-version.js +1 -0
- package/src/runtime/state-root.js +217 -0
- package/src/runtime/storage/cli.js +12 -5
- package/src/runtime/storage/engine.js +9 -2
- package/src/runtime/storage/registry.js +14 -1
- package/src/runtime/storage/roots.js +66 -16
- package/src/runtime/storage/rules.js +82 -10
- package/src/runtime/storage/upkeep.js +10 -2
- package/src/runtime/storage/work-status.js +245 -26
- package/src/runtime/task-attempts.js +589 -69
- package/src/runtime/task-classifier.js +47 -11
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +176 -55
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +374 -64
- package/src/runtime/team/admission.js +301 -0
- package/src/runtime/team/envelope.js +324 -0
- package/src/runtime/team/review-guard.js +156 -0
- package/src/runtime/trace/analyze.js +427 -0
- package/src/runtime/trace/baseline.js +770 -0
- package/src/runtime/trace/ledger.js +63 -140
- package/src/runtime/trace/otlp-export.js +568 -0
- package/src/runtime/trace/otlp-map.js +481 -0
- package/src/runtime/trace/spans.js +745 -0
- package/src/runtime/trace/telemetry.js +225 -0
- package/src/runtime/trace/views.js +1 -0
- package/src/runtime/transcripts/command.js +98 -0
- package/src/runtime/transcripts/cursor.js +326 -0
- package/src/runtime/transcripts/read.js +126 -0
- package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
- package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
- package/src/runtime/transcripts/readers/copilot-events.js +90 -0
- package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
- package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
- package/src/runtime/transcripts/registry.js +161 -0
- package/src/runtime/transcripts/stats.js +161 -0
- package/src/runtime/verification/check-key.js +628 -0
- package/src/runtime/verification/contract-runner.js +546 -11
- package/src/runtime/verification/engine.js +499 -9
- package/src/runtime/verification/impact.js +617 -0
- package/src/runtime/verification/inflight.js +205 -0
- package/src/runtime/verification/registry.js +176 -0
- package/src/runtime/verification/reuse.js +184 -0
- package/src/runtime/verification/telemetry-emit.js +254 -0
- package/src/runtime/verification/test-selection.js +67 -7
- package/src/runtime/worker/active.js +90 -0
- package/src/runtime/worker/cancel.js +222 -0
- package/src/runtime/worker/catalogue.js +122 -0
- package/src/runtime/worker/command.js +677 -0
- package/src/runtime/worker/evidence.js +288 -0
- package/src/runtime/worker/launch.js +288 -0
- package/src/runtime/worker/models.js +137 -0
- package/src/runtime/worker/parsers.js +218 -0
- package/src/runtime/worker/probes/index.js +706 -0
- package/src/runtime/worker/process-tree.js +415 -0
- package/src/runtime/worker/quota.js +43 -0
- package/src/runtime/worker/readiness.js +713 -0
- package/src/runtime/worker/run-store.js +166 -0
- package/src/runtime/worker/supervisor.js +541 -0
- package/src/runtime/worker/worktree-check.js +252 -0
- package/src/runtime/workflow-engine.js +5 -1
- package/src/runtime/workflow-orchestrator.js +578 -63
- package/src/runtime/worktree.js +149 -22
- package/src/state/index.js +1 -1
- package/src/state/lockfile.js +64 -10
- package/src/state/run-lock.js +1 -1
- package/types/node-commonjs.d.ts +106 -7
- package/core/harnesses/gemini/hooks/hooks.json +0 -77
- package/core/harnesses/gemini/hooks/lib.sh +0 -302
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
- package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
- package/core/harnesses/gemini/hooks/session-end.sh +0 -5
- package/core/harnesses/gemini/hooks/session-start.sh +0 -7
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/shared/guidance/FLAGS.md +0 -25
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
- package/src/adapters/gemini/hooks.js +0 -251
- package/src/adapters/gemini/index.js +0 -429
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Feature 082 C.5 (IC-001, IC-002; DEC-002, DEC-017, DEC-024, DEC-029, DEC-034, DEC-035, DEC-037,
|
|
4
|
+
// DEC-039, DEC-044, DEC-051): the `dispatch` verb's handler.
|
|
5
|
+
//
|
|
6
|
+
// `executeDispatchCommand(options)` prints nothing and resolves `{ exit, body }`, so tests and the
|
|
7
|
+
// loop's own callers use it in process (the pattern of 081's `executeWorkerCommand`).
|
|
8
|
+
// `handleDispatchCommand(options)` wraps it, prints the JSON with `json` or one summary line per
|
|
9
|
+
// action (inspect: IC-029's lines), and sets the process exit code. No text line carries envelope,
|
|
10
|
+
// output or verification argv content.
|
|
11
|
+
//
|
|
12
|
+
// Order for every action: `--action`, then IC-001's flag rules (a flag on an action that does not
|
|
13
|
+
// take it is exit 2 `usage`) and every value, before any read; then the slug; then, for every
|
|
14
|
+
// action but `spawn`, IC-001's identity checks before any read for writing, git call, spawn or
|
|
15
|
+
// signal (DEC-044): `identity-solo` and `worker-session` for all of them, and for every action but
|
|
16
|
+
// `start` the dispatch run's existence and its stored `runKey` (`no-dispatch-run`, `foreign-run`).
|
|
17
|
+
// The module each action calls repeats the check under its own lock, so it cannot be raced.
|
|
18
|
+
//
|
|
19
|
+
// `start` is IC-002's steps 0 to 8: steps 1 to 6 hold no lock, the integration worktree is made
|
|
20
|
+
// outside every lock, and steps 7 and 8 (the store check again and the in-process 080 `start`) run
|
|
21
|
+
// inside one dispatch-store write (lock order dispatch then ledger, IC-006). A refusal at step 7 or
|
|
22
|
+
// 8 removes the integration worktree by `removeVerified` and carries its `cleanup`.
|
|
23
|
+
//
|
|
24
|
+
// Exits (IC-001): 0 for the applied and read dispositions; 1 with the JSON for `refused`, `stale`
|
|
25
|
+
// and `ack-required`; 2 with `{ ok: false, error, message }` for `usage`, `store-unreadable`,
|
|
26
|
+
// `plan-unreadable`, `plan-malformed` and `registry-invalid`; `run` interrupted by SIGINT or SIGTERM
|
|
27
|
+
// exits 130 or 143 as the loop reports. `step` and `run` exit 0 whenever the pass ran, whatever its
|
|
28
|
+
// outcome.
|
|
29
|
+
//
|
|
30
|
+
// This module makes no fs write call: the store is written through `store.ts`, the ledger by 080's
|
|
31
|
+
// `start`, the worktree by `integrate.ts`.
|
|
32
|
+
//
|
|
33
|
+
// Feature 106 A.2 (IC-005): `start` takes `--fallback off|ask|auto` and `--ladder <id[,id...]>`
|
|
34
|
+
// (registry harness ids, each once, at most 8, never with `--harness`) and stores `run.fallback`;
|
|
35
|
+
// without `--fallback` the `dispatch.fallback` setting applies (fail-safe `off`). `resume` takes
|
|
36
|
+
// `--fallback`: once resumed, `off` or `ask` sets the run's mode in one write, and `auto` accepts the
|
|
37
|
+
// pending proposals once (review C-14): it clears the `quota` holds and records each task in
|
|
38
|
+
// `run.fallback.accepted`, the run's mode unchanged; run-wide `auto` comes only from `start`.
|
|
39
|
+
// Feature 120 C.4 (IC-010): start gates enabled rungs and stores the ladder and in-session permission.
|
|
40
|
+
|
|
41
|
+
const fs = require('node:fs');
|
|
42
|
+
const path = require('node:path');
|
|
43
|
+
const attempts = require('../task-attempts.js');
|
|
44
|
+
const { withGitMemo } = require('../git-memo.js');
|
|
45
|
+
const { isGraphPlan } = require('../task-graph.js');
|
|
46
|
+
const { runKeyFor, identityRefusal } = require('../task-run.js');
|
|
47
|
+
const { isSafeSlug } = require('../task-scope.js');
|
|
48
|
+
const { projectRoot } = require('../lifecycle/root.js');
|
|
49
|
+
const { loadRegistry } = require('../../registry/index.js');
|
|
50
|
+
const { knownWorkerIds, LADDER_MAX } = require('../../registry/worker-ids.js');
|
|
51
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
52
|
+
const readiness = require('../worker/readiness.js');
|
|
53
|
+
const { activeWorkers, workerGate, inSessionGate } = require('../worker/active.js');
|
|
54
|
+
const { storeFileFor, newDispatchRunId, readDispatch, mutateDispatch, fallbackOf, DispatchStoreError, DISPATCH_ID } = require('./store.js');
|
|
55
|
+
const { concurrencyOf } = require('./admission.js');
|
|
56
|
+
const { validateGrant, requestSpawn, spawnStatus } = require('./grants.js');
|
|
57
|
+
const { createIntegrationWorktree, managerFor } = require('./integrate.js');
|
|
58
|
+
const { argvDigest } = require('./verify-run.js');
|
|
59
|
+
const loop = require('./loop.js');
|
|
60
|
+
const controls = require('./controls.js');
|
|
61
|
+
|
|
62
|
+
const ACTIONS = Object.freeze(['start', 'step', 'run', 'submit', 'spawn', 'pause', 'inspect', 'cancel', 'takeover', 'resume']);
|
|
63
|
+
/** IC-001: the dispositions that exit 0; every other disposition exits 1 with the JSON. */
|
|
64
|
+
const EXIT_ZERO = Object.freeze([
|
|
65
|
+
'started', 'stepped', 'ran', 'submitted', 'spawn-requested', 'spawn-status', 'paused', 'inspected', 'cancelled', 'taken-over', 'resumed',
|
|
66
|
+
]);
|
|
67
|
+
const ERROR_CODES = Object.freeze(['usage', 'store-unreadable', 'plan-unreadable', 'plan-malformed', 'registry-invalid']);
|
|
68
|
+
const MODES = Object.freeze(['auto', 'sequential', 'manual']);
|
|
69
|
+
const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._ -]*$/;
|
|
70
|
+
const TASK_ID = /^[A-Za-z]+\.\d+$/;
|
|
71
|
+
const ACK = /^sha256:[0-9a-f]{64}$/;
|
|
72
|
+
const DIGITS = /^[0-9]+$/;
|
|
73
|
+
/** IC-001's `--request-id`: a child of one dispatch, `c1` to `c4` (DEC-051). */
|
|
74
|
+
const REQUEST_ID_FLAG = /^d-[0-9a-f]{12}\.c[1-4]$/;
|
|
75
|
+
const VERIFY_ARGV_MAX = 64;
|
|
76
|
+
const VERIFY_ARG_CHARS = 4096;
|
|
77
|
+
const VERIFY_SECONDS_DEFAULT = 600;
|
|
78
|
+
const VERIFY_SECONDS_MAX = 14400;
|
|
79
|
+
/** DEC-035: 081's backstop of `n + 120` stays within 081's 14400. */
|
|
80
|
+
const ATTEMPT_SECONDS_MAX = 14400 - loop.BACKSTOP_SECONDS;
|
|
81
|
+
const RUN_MAX_SECONDS_MAX = 14400;
|
|
82
|
+
/** IC-005's submit bounds, the same values `loop.submit` checks. */
|
|
83
|
+
const SUMMARY_BYTES = 8192;
|
|
84
|
+
const BLOCKERS_MAX = 20;
|
|
85
|
+
const BLOCKER_CHARS = 200;
|
|
86
|
+
const TOKEN_BASES = Object.freeze(['measured', 'estimated']);
|
|
87
|
+
|
|
88
|
+
/** IC-001: the actions each flag may be given to; any other action is exit 2 `usage`. */
|
|
89
|
+
const FLAG_ACTIONS = Object.freeze({
|
|
90
|
+
by: { flag: '--by', actions: ['start', 'step', 'run', 'pause', 'cancel', 'takeover', 'resume'] },
|
|
91
|
+
reason: { flag: '--reason', actions: ['pause', 'cancel', 'takeover', 'resume'] },
|
|
92
|
+
concurrency: { flag: '--concurrency', actions: ['start'] },
|
|
93
|
+
mode: { flag: '--mode', actions: ['start'] },
|
|
94
|
+
billingCap: { flag: '--billing-cap', actions: ['start'] },
|
|
95
|
+
verifyArgv: { flag: '--verify-argv', actions: ['start'] },
|
|
96
|
+
verifySeconds: { flag: '--verify-seconds', actions: ['start'] },
|
|
97
|
+
attemptSeconds: { flag: '--attempt-seconds', actions: ['start'] },
|
|
98
|
+
reserveTokens: { flag: '--reserve-tokens', actions: ['start', 'spawn'] },
|
|
99
|
+
budgetAttempts: { flag: '--budget-attempts', actions: ['start'] },
|
|
100
|
+
budgetTokens: { flag: '--budget-tokens', actions: ['start'] },
|
|
101
|
+
spawnGrant: { flag: '--spawn-grant', actions: ['start'] },
|
|
102
|
+
harness: { flag: '--harness', actions: ['start'] },
|
|
103
|
+
need: { flag: '--need', actions: ['start'] },
|
|
104
|
+
maxSeconds: { flag: '--max-seconds', actions: ['run'] },
|
|
105
|
+
dispatch: { flag: '--dispatch', actions: ['submit'] },
|
|
106
|
+
status: { flag: '--status', actions: ['submit'] },
|
|
107
|
+
summary: { flag: '--summary', actions: ['submit'] },
|
|
108
|
+
blocker: { flag: '--blocker', actions: ['submit'] },
|
|
109
|
+
tokens: { flag: '--tokens', actions: ['submit'] },
|
|
110
|
+
tokensBasis: { flag: '--tokens-basis', actions: ['submit'] },
|
|
111
|
+
promptFile: { flag: '--prompt-file', actions: ['spawn'] },
|
|
112
|
+
requestId: { flag: '--request-id', actions: ['spawn'] },
|
|
113
|
+
task: { flag: '--task', actions: ['takeover'] },
|
|
114
|
+
ack: { flag: '--ack', actions: ['resume'] },
|
|
115
|
+
fallback: { flag: '--fallback', actions: ['start', 'resume'] },
|
|
116
|
+
ladder: { flag: '--ladder', actions: ['start'] },
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
const usage = (message ) => new DispatchStoreError('usage', message);
|
|
122
|
+
const given = (value ) => value !== undefined && value !== null && value !== false;
|
|
123
|
+
const iso = (at ) => at.toISOString();
|
|
124
|
+
|
|
125
|
+
function refused(reason , extra = {}) {
|
|
126
|
+
return { disposition: 'refused', reason, ...extra };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** A whole number from digits only (or an integer), within `[min, max]`; undefined when not given. */
|
|
130
|
+
function digits(value , flag , min , max = Number.MAX_SAFE_INTEGER) {
|
|
131
|
+
if (!given(value)) return undefined;
|
|
132
|
+
const text = String(value);
|
|
133
|
+
if (!DIGITS.test(text) || Number(text) < min || Number(text) > max) throw usage(`${flag} expects a whole number from ${min} to ${max}, got '${value}'`);
|
|
134
|
+
return Number(text);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function requireBy(by , action ) {
|
|
138
|
+
if (!given(by) || !SAFE_NAME.test(String(by))) throw usage(`--by is required for --action ${action}: the name of the agent or person acting`);
|
|
139
|
+
return String(by);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function requireReason(reason , action ) {
|
|
143
|
+
const text = typeof reason === 'string' ? reason.trim() : '';
|
|
144
|
+
if (!text) throw usage(`--reason is required for --action ${action}`);
|
|
145
|
+
return text;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function refuseMisplacedFlags(action , flags ) {
|
|
149
|
+
for (const [key, { flag, actions }] of Object.entries(FLAG_ACTIONS)) {
|
|
150
|
+
if (given(flags[key]) && !actions.includes(action)) throw usage(`${flag} does not apply to --action ${action}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function needList(need ) {
|
|
155
|
+
if (!given(need)) return [];
|
|
156
|
+
const items = Array.isArray(need) ? need : [need];
|
|
157
|
+
return [...new Set(items.flatMap((item ) => String(item).split(',')).map((item ) => item.trim()).filter(Boolean))];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** DEC-024: `--verify-argv` as 1 to 64 strings, each 1 to 4096 characters without NUL; null when absent. */
|
|
161
|
+
function verifyArgvOf(value ) {
|
|
162
|
+
if (!given(value)) return null;
|
|
163
|
+
let argv = value;
|
|
164
|
+
if (typeof value === 'string') {
|
|
165
|
+
try { argv = JSON.parse(value); } catch { throw usage('--verify-argv is not valid JSON: it expects a JSON array of strings'); }
|
|
166
|
+
}
|
|
167
|
+
if (!Array.isArray(argv) || argv.length < 1 || argv.length > VERIFY_ARGV_MAX) throw usage(`--verify-argv expects a JSON array of 1 to ${VERIFY_ARGV_MAX} strings`);
|
|
168
|
+
for (const arg of argv) {
|
|
169
|
+
if (typeof arg !== 'string' || arg.length < 1 || Array.from(arg).length > VERIFY_ARG_CHARS || arg.includes('\u0000')) {
|
|
170
|
+
throw usage(`--verify-argv elements are strings of 1 to ${VERIFY_ARG_CHARS} characters without NUL`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return [...argv];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The registry, loaded once; a validation failure is `registry-invalid`. */
|
|
177
|
+
function registryOf(registry ) {
|
|
178
|
+
if (registry) return registry;
|
|
179
|
+
try {
|
|
180
|
+
return loadRegistry({ repoRoot: REPO_ROOT });
|
|
181
|
+
} catch (error ) {
|
|
182
|
+
throw new DispatchStoreError('registry-invalid', error?.message || String(error));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** 106 IC-005: `--fallback` as a mode, or null when absent. */
|
|
187
|
+
function fallbackFlag(value ) {
|
|
188
|
+
if (!given(value)) return null;
|
|
189
|
+
if (!loop.FALLBACK_MODES.includes(String(value))) throw usage(`--fallback expects ${loop.FALLBACK_MODES.join(', ')}, got '${value}'`);
|
|
190
|
+
return String(value);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** 106 IC-005: `--ladder` as 1 to 8 distinct registry harness ids in order, or null when absent. */
|
|
194
|
+
function ladderOf(o ) {
|
|
195
|
+
if (!given(o.ladder)) return null;
|
|
196
|
+
if (given(o.harness)) throw usage('--ladder does not go with --harness: a --harness run is a one-rung ladder');
|
|
197
|
+
const ids = String(o.ladder).split(',').map((id ) => id.trim()).filter(Boolean);
|
|
198
|
+
if (ids.length < 1 || ids.length > LADDER_MAX) throw usage(`--ladder expects 1 to ${LADDER_MAX} comma-separated harness ids, got ${ids.length}`);
|
|
199
|
+
const known = knownWorkerIds({ registry: registryOf(o.registry) }).ids;
|
|
200
|
+
const unknown = ids.find((id ) => !known.includes(id));
|
|
201
|
+
if (unknown !== undefined) throw usage(`--ladder names unknown harness '${unknown}'; known: ${known.join(', ')}`);
|
|
202
|
+
if (new Set(ids).size !== ids.length) throw usage('--ladder names a harness more than once');
|
|
203
|
+
return ids;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 106 IC-005: the `dispatch.fallback` setting for the project at `root`, read the way `verify.reuse`
|
|
208
|
+
* is (089 IC-005). Only a valid resolved `off` or `ask` applies; an invalid value, a load error or any
|
|
209
|
+
* fault is the key's fail-safe `off`.
|
|
210
|
+
*/
|
|
211
|
+
function fallbackSetting(root , env ) {
|
|
212
|
+
try {
|
|
213
|
+
const { loadSettings } = require('../settings/resolve.js');
|
|
214
|
+
const resolution = loadSettings({ projectRoot: root, env: { ...env } }).get('dispatch.fallback', 'project');
|
|
215
|
+
return resolution?.state === 'ok' && ['off', 'ask'].includes(resolution.value) ? resolution.value : 'off';
|
|
216
|
+
} catch {
|
|
217
|
+
return 'off';
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** IC-010: gate start flags and resolve the run's enabled ladder and in-session permission. */
|
|
222
|
+
function enabledStart(o , s , root , env ) {
|
|
223
|
+
const active = activeWorkers({ registry: registryOf(o.registry), env, projectRoot: root });
|
|
224
|
+
if (s.harness !== null) {
|
|
225
|
+
const gate = workerGate(active, s.harness);
|
|
226
|
+
if (gate) return { refusal: refused(gate.reason, { harness: gate.harness, enabled: gate.enabled, origin: gate.origin }) };
|
|
227
|
+
}
|
|
228
|
+
if (s.ladder !== null) {
|
|
229
|
+
for (const id of s.ladder) {
|
|
230
|
+
const gate = workerGate(active, id);
|
|
231
|
+
if (gate) return { refusal: refused(gate.reason, { harness: gate.harness, enabled: gate.enabled, origin: gate.origin }) };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (s.mode === 'sequential') {
|
|
235
|
+
const gate = inSessionGate(active);
|
|
236
|
+
if (gate) return { refusal: refused(gate.reason, { text: gate.text, enabled: [...active.workers], origin: active.origin }) };
|
|
237
|
+
}
|
|
238
|
+
const stored = active.state !== 'unset';
|
|
239
|
+
let ladder = s.ladder;
|
|
240
|
+
if (ladder === null) {
|
|
241
|
+
if (s.harness !== null) ladder = stored ? [s.harness] : null;
|
|
242
|
+
else ladder = stored ? [...active.workers] : null;
|
|
243
|
+
}
|
|
244
|
+
return { ladder, inSession: active.inSession, stored };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** IC-002 step 0: every start flag, validated and normalized. */
|
|
248
|
+
function startSettings(o ) {
|
|
249
|
+
const by = requireBy(o.by, 'start');
|
|
250
|
+
const concurrency = concurrencyOf(given(o.concurrency) ? (typeof o.concurrency === 'number' ? o.concurrency : String(o.concurrency)) : null);
|
|
251
|
+
const mode = given(o.mode) ? String(o.mode) : 'auto';
|
|
252
|
+
if (!MODES.includes(mode)) throw usage(`--mode expects ${MODES.join(', ')}, got '${o.mode}'`);
|
|
253
|
+
const billingCap = given(o.billingCap) ? String(o.billingCap) : null;
|
|
254
|
+
if (billingCap !== null && billingCap !== 'free') throw usage(`--billing-cap expects free, got '${billingCap}'`);
|
|
255
|
+
const argv = verifyArgvOf(o.verifyArgv);
|
|
256
|
+
const verifySeconds = digits(o.verifySeconds, '--verify-seconds', 1, VERIFY_SECONDS_MAX) ?? VERIFY_SECONDS_DEFAULT;
|
|
257
|
+
const attemptSeconds = digits(o.attemptSeconds, '--attempt-seconds', 1, ATTEMPT_SECONDS_MAX) ?? loop.ATTEMPT_SECONDS_DEFAULT;
|
|
258
|
+
const reserveTokens = digits(o.reserveTokens, '--reserve-tokens', 0) ?? null;
|
|
259
|
+
const budgetAttempts = digits(o.budgetAttempts, '--budget-attempts', 1);
|
|
260
|
+
const budgetTokens = digits(o.budgetTokens, '--budget-tokens', 1);
|
|
261
|
+
if (budgetTokens !== undefined && (reserveTokens === null || reserveTokens < 1)) {
|
|
262
|
+
throw usage('--budget-tokens needs --reserve-tokens of at least 1: every claim reserves tokens against the budget');
|
|
263
|
+
}
|
|
264
|
+
const spawnGrant = given(o.spawnGrant) ? validateGrant(o.spawnGrant, reserveTokens) : null;
|
|
265
|
+
const needs = needList(o.need);
|
|
266
|
+
let harness = null;
|
|
267
|
+
if (given(o.harness) || needs.length) {
|
|
268
|
+
const reg = registryOf(o.registry);
|
|
269
|
+
if (given(o.harness)) {
|
|
270
|
+
harness = String(o.harness);
|
|
271
|
+
const ids = knownWorkerIds({ registry: reg }).ids;
|
|
272
|
+
if (!ids.includes(harness)) throw usage(`unknown harness '${harness}'; known: ${ids.join(', ')}`);
|
|
273
|
+
}
|
|
274
|
+
if (needs.length) readiness.validateNeeds(needs, o.coverage === undefined ? reg.coverage : o.coverage);
|
|
275
|
+
}
|
|
276
|
+
const fallback = fallbackFlag(o.fallback);
|
|
277
|
+
const ladder = ladderOf(o);
|
|
278
|
+
return { by, concurrency, mode, billingCap, argv, verifySeconds, attemptSeconds, reserveTokens, budgetAttempts, budgetTokens, spawnGrant, needs, harness, fallback, ladder };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* IC-005 step 0's `--summary`, `--blocker` and `--tokens`/`--tokens-basis` values, checked here so
|
|
283
|
+
* a malformed submit is `usage` before any store read (IC-001). `loop.submit` repeats them for
|
|
284
|
+
* in-process callers.
|
|
285
|
+
*/
|
|
286
|
+
function submitValues(o ) {
|
|
287
|
+
const summary = o.summary;
|
|
288
|
+
if (typeof summary !== 'string' || !summary.length || Buffer.byteLength(summary, 'utf8') > SUMMARY_BYTES) throw usage(`--summary expects 1 to ${SUMMARY_BYTES} bytes`);
|
|
289
|
+
const blockers = given(o.blocker) ? [].concat(o.blocker).map(String) : [];
|
|
290
|
+
if (blockers.length > BLOCKERS_MAX || blockers.some((b) => !b.length || Array.from(b).length > BLOCKER_CHARS)) throw usage(`--blocker is repeatable up to ${BLOCKERS_MAX}, each 1 to ${BLOCKER_CHARS} characters`);
|
|
291
|
+
if (given(o.tokens) !== given(o.tokensBasis)) throw usage('--tokens and --tokens-basis go together or not at all');
|
|
292
|
+
if (given(o.tokens) && (!DIGITS.test(String(o.tokens)) || !TOKEN_BASES.includes(String(o.tokensBasis)))) throw usage('--tokens expects a whole number and --tokens-basis measured or estimated');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** IC-001 step 0 for every action but start: required flags and their values. */
|
|
296
|
+
function actionFlags(action , o ) {
|
|
297
|
+
switch (action) {
|
|
298
|
+
case 'step': if (given(o.by)) requireBy(o.by, action); break;
|
|
299
|
+
case 'run':
|
|
300
|
+
if (given(o.by)) requireBy(o.by, action);
|
|
301
|
+
digits(o.maxSeconds, '--max-seconds', 1, RUN_MAX_SECONDS_MAX);
|
|
302
|
+
break;
|
|
303
|
+
case 'submit':
|
|
304
|
+
if (!given(o.dispatch) || !DISPATCH_ID.test(String(o.dispatch))) throw usage(`--dispatch expects 'd-' plus 12 hex characters, got '${o.dispatch ?? ''}'`);
|
|
305
|
+
if (o.status !== 'success' && o.status !== 'failure') throw usage('--status expects success or failure');
|
|
306
|
+
submitValues(o);
|
|
307
|
+
break;
|
|
308
|
+
case 'spawn': {
|
|
309
|
+
const request = given(o.promptFile) || given(o.reserveTokens);
|
|
310
|
+
if (request && given(o.requestId)) throw usage('--request-id reads one request; it does not go with --prompt-file or --reserve-tokens');
|
|
311
|
+
if (given(o.requestId)) {
|
|
312
|
+
if (!REQUEST_ID_FLAG.test(String(o.requestId))) throw usage(`--request-id expects '<d-id>.c<n>', got '${o.requestId}'`);
|
|
313
|
+
} else {
|
|
314
|
+
if (!given(o.promptFile)) throw usage('--action spawn needs --prompt-file and --reserve-tokens, or --request-id');
|
|
315
|
+
if (digits(o.reserveTokens, '--reserve-tokens', 0) === undefined) throw usage('--action spawn needs --reserve-tokens with --prompt-file');
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
case 'pause': case 'cancel':
|
|
320
|
+
requireBy(o.by, action);
|
|
321
|
+
requireReason(o.reason, action);
|
|
322
|
+
break;
|
|
323
|
+
case 'takeover':
|
|
324
|
+
requireBy(o.by, action);
|
|
325
|
+
requireReason(o.reason, action);
|
|
326
|
+
if (!given(o.task) || !TASK_ID.test(String(o.task))) throw usage(`--task expects a plan task id such as A.1, got '${o.task ?? ''}'`);
|
|
327
|
+
break;
|
|
328
|
+
case 'resume':
|
|
329
|
+
if (given(o.ack) && !ACK.test(String(o.ack))) throw usage(`--ack expects sha256:<64 hex>, got '${o.ack}'`);
|
|
330
|
+
if (given(o.ack)) requireBy(o.by, action);
|
|
331
|
+
else if (given(o.by)) requireBy(o.by, action);
|
|
332
|
+
if (given(o.reason)) requireReason(o.reason, action);
|
|
333
|
+
fallbackFlag(o.fallback);
|
|
334
|
+
break;
|
|
335
|
+
default: break;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function featureOf(slug , resolveSlug , cwd ) {
|
|
340
|
+
const feature = given(slug) ? String(slug) : (resolveSlug ? resolveSlug(cwd) : null);
|
|
341
|
+
if (!feature) throw usage('--slug is required: no feature could be resolved from the current branch');
|
|
342
|
+
if (!isSafeSlug(feature)) throw usage(`'${feature}' is not a valid feature slug`);
|
|
343
|
+
return feature;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** One bounded git call through the integration manager's helper; `{ ok, stdout, code }`. */
|
|
347
|
+
function gitAt(root , slug , env , cwd , args ) {
|
|
348
|
+
return managerFor(root, slug, 'integration', env).git(args, cwd, { allowFailure: true });
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** IC-002: steps 1 to 8. `s` is startSettings' result. */
|
|
352
|
+
function start(o , s ) {
|
|
353
|
+
const { env, cwd, slug, clock } = o;
|
|
354
|
+
// Step 1.
|
|
355
|
+
const key = runKeyFor(env, cwd);
|
|
356
|
+
const who = identityRefusal(key.identity);
|
|
357
|
+
if (who) return refused(who.reason, { runKey: key.runKey });
|
|
358
|
+
const root = projectRoot(cwd);
|
|
359
|
+
const enabled = enabledStart(o, s, root, env);
|
|
360
|
+
if (enabled.refusal) return enabled.refusal;
|
|
361
|
+
// Step 2.
|
|
362
|
+
const roots = [...new Set([cwd, root])];
|
|
363
|
+
const plan = attempts.loadPlan({ roots, slug, fsImpl: fs });
|
|
364
|
+
// Step 3 (DEC-034).
|
|
365
|
+
if (!isGraphPlan(plan.graph)) return refused('not-graph-plan', { slug });
|
|
366
|
+
// Step 4.
|
|
367
|
+
const top = key.top;
|
|
368
|
+
const inside = fs.existsSync(top) ? gitAt(root, slug, env, top, ['rev-parse', '--is-inside-work-tree']) : { ok: false, stdout: '' };
|
|
369
|
+
if (!inside.ok || inside.stdout.trim() !== 'true') return refused('not-a-git-worktree', { top });
|
|
370
|
+
const head = gitAt(root, slug, env, top, ['rev-parse', '--verify', '-q', 'HEAD^{commit}']);
|
|
371
|
+
if (!head.ok || !head.stdout.trim()) return refused('no-head', { top });
|
|
372
|
+
const base = head.stdout.trim();
|
|
373
|
+
const status = gitAt(root, slug, env, top, ['status', '--porcelain=v1', '-z', '--untracked-files=all']);
|
|
374
|
+
const baseDirty = !status.ok || status.stdout.length > 0;
|
|
375
|
+
// Step 5.
|
|
376
|
+
const file = storeFileFor(root, slug);
|
|
377
|
+
const seen = readDispatch(file);
|
|
378
|
+
if (seen.run) return refused('dispatch-run-exists', { dispatchRunId: seen.run.dispatchRunId, state: seen.run.state });
|
|
379
|
+
// The cap names the user's billing record it judges by, so the owner sees what counts as free.
|
|
380
|
+
const billingRecord = s.billingCap ? billingRecordOf(env, registryOf(o.registry)) : null;
|
|
381
|
+
// Step 6: outside every lock.
|
|
382
|
+
const dispatchRunId = newDispatchRunId();
|
|
383
|
+
let integration ;
|
|
384
|
+
try {
|
|
385
|
+
integration = createIntegrationWorktree({ root, slug, dispatchRunId, base, env });
|
|
386
|
+
} catch (error ) {
|
|
387
|
+
const code = String(error?.exitCode ?? error?.status ?? error?.signal ?? error?.code ?? 'unknown');
|
|
388
|
+
return refused('integration-worktree-failed', { code });
|
|
389
|
+
}
|
|
390
|
+
const verify = s.argv
|
|
391
|
+
? { argv: s.argv, argvSha256: argvDigest(s.argv), argvCount: s.argv.length, timeoutSeconds: s.verifySeconds }
|
|
392
|
+
: { argv: null, argvSha256: null, argvCount: 0, timeoutSeconds: s.verifySeconds };
|
|
393
|
+
const remove = () => managerFor(root, slug, 'integration', env).removeVerified(dispatchRunId);
|
|
394
|
+
// 106 IC-005: `--fallback` wins for the run, else the setting.
|
|
395
|
+
const { ladder, inSession, stored } = enabled;
|
|
396
|
+
const fallback = { mode: s.fallback ?? fallbackSetting(root, env), ladder, ...(stored ? { inSession } : {}), exhausted: [], holds: {}, events: [], eventsDropped: 0 };
|
|
397
|
+
// Steps 7 and 8: one dispatch-store write, the 080 start inside it.
|
|
398
|
+
let outcome ;
|
|
399
|
+
try {
|
|
400
|
+
outcome = mutateDispatch(file, (store ) => {
|
|
401
|
+
if (store.run) return { write: false, result: refused('dispatch-run-exists', { dispatchRunId: store.run.dispatchRunId, state: store.run.state }) };
|
|
402
|
+
const now = clock();
|
|
403
|
+
const res = attempts.start({
|
|
404
|
+
file: attempts.storeFile(root, slug), fsImpl: fs, roots, slug, by: s.by,
|
|
405
|
+
budgetAttempts: s.budgetAttempts, budgetTokens: s.budgetTokens, env, cwd, now,
|
|
406
|
+
});
|
|
407
|
+
if (res.disposition !== 'started') return { write: false, result: res };
|
|
408
|
+
store.run = {
|
|
409
|
+
dispatchRunId, slug, state: 'running', runKey: key.runKey,
|
|
410
|
+
run: key.identity.run, lineage: key.identity.lineage, lineageKind: key.identity.lineageKind,
|
|
411
|
+
epoch: res.epoch, by: s.by, concurrency: s.concurrency, mode: s.mode,
|
|
412
|
+
...(s.billingCap ? { billingCap: s.billingCap } : {}),
|
|
413
|
+
reserveTokens: s.reserveTokens, attemptSeconds: s.attemptSeconds, harness: s.harness, needs: s.needs,
|
|
414
|
+
spawnGrant: s.spawnGrant, baseDirty, createdAt: iso(now), endedAt: null,
|
|
415
|
+
integration: { worktree: integration.worktree, branch: integration.branch, base: integration.base, head: integration.head },
|
|
416
|
+
verify, fallback,
|
|
417
|
+
};
|
|
418
|
+
return { write: true, result: { started: true, epoch: res.epoch, runKey: res.runKey } };
|
|
419
|
+
});
|
|
420
|
+
} catch (error) {
|
|
421
|
+
remove();
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
424
|
+
if (!outcome.started) return { ...outcome, cleanup: remove() };
|
|
425
|
+
return {
|
|
426
|
+
disposition: 'started', dispatchRunId, slug, runKey: key.runKey, epoch: outcome.epoch,
|
|
427
|
+
integration: { worktree: integration.worktree, branch: integration.branch, base: integration.base },
|
|
428
|
+
baseDirty, concurrency: s.concurrency, mode: s.mode,
|
|
429
|
+
verification: { declared: verify.argv !== null, argc: verify.argvCount, sha256: verify.argvSha256, seconds: verify.timeoutSeconds },
|
|
430
|
+
reserveTokens: s.reserveTokens, attemptSeconds: s.attemptSeconds, workerTimeoutSeconds: s.attemptSeconds + loop.BACKSTOP_SECONDS,
|
|
431
|
+
spawnGrant: s.spawnGrant,
|
|
432
|
+
...(billingRecord ? { billingRecord } : {}),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** The `start` body's `billingRecord`: the record's state and reason, and one row per recorded worker. */
|
|
437
|
+
function billingRecordOf(env , reg ) {
|
|
438
|
+
const { readWorkerBilling } = require('../settings/worker-billing.js'); // lazy: see loop.ts evaluateHarnesses
|
|
439
|
+
const record = readWorkerBilling(env, { workerIds: knownWorkerIds({ registry: reg }).ids });
|
|
440
|
+
return {
|
|
441
|
+
state: record.state, reason: record.reason,
|
|
442
|
+
workers: Object.entries(record.workers).map(([harness, w] ) => ({
|
|
443
|
+
harness, billing: w.billing, asserted: w.asserted, ...(w.basis !== undefined ? { basis: w.basis } : {}),
|
|
444
|
+
})),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** The loop's and controls' shared options: identity inputs, clock and the in-process test seams. */
|
|
449
|
+
function passOptions(o ) {
|
|
450
|
+
const out = { slug: o.slug, env: o.env, cwd: o.cwd, now: o.clock };
|
|
451
|
+
for (const seam of ['platform', 'descriptors', 'registry', 'coverage', 'evaluateImpl', 'verify', 'sleep', 'fallbackDefault']) {
|
|
452
|
+
if (o[seam] !== undefined) out[seam] = o[seam];
|
|
453
|
+
}
|
|
454
|
+
return out;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* 106 IC-005: after an applied resume with `--fallback`, one locked write (creating `run.fallback`
|
|
459
|
+
* for a run started before 106, with the setting's mode). `off` or `ask` sets `run.fallback.mode`
|
|
460
|
+
* and clears `run.fallback.accepted` (review R-2).
|
|
461
|
+
* `auto` accepts once (review C-14): every hold whose reason is `quota` is deleted and its task
|
|
462
|
+
* recorded in `run.fallback.accepted` with its proposal; the mode is unchanged, and the task's next
|
|
463
|
+
* selection steps once. `frontier-no-step` and `ladder-exhausted` holds stay. Returns
|
|
464
|
+
* `{ mode, cleared }`, `mode` the run's mode after the write.
|
|
465
|
+
*/
|
|
466
|
+
function setFallbackMode(o , mode ) {
|
|
467
|
+
const initial = mode === 'auto' ? fallbackSetting(projectRoot(o.cwd), o.env) : mode;
|
|
468
|
+
return mutateDispatch(storeFileFor(projectRoot(o.cwd), o.slug), (s ) => {
|
|
469
|
+
if (!s.run) return { write: false, result: { mode: initial, cleared: [] } };
|
|
470
|
+
const f = fallbackOf(s.run, initial);
|
|
471
|
+
const cleared = [];
|
|
472
|
+
if (mode !== 'auto') {
|
|
473
|
+
f.mode = mode;
|
|
474
|
+
// Review R-2: `off` or `ask` revokes every pending acceptance.
|
|
475
|
+
if (f.accepted && typeof f.accepted === 'object') f.accepted = {};
|
|
476
|
+
}
|
|
477
|
+
else if (f.holds && typeof f.holds === 'object') {
|
|
478
|
+
if (!f.accepted || typeof f.accepted !== 'object') f.accepted = {};
|
|
479
|
+
for (const [task, hold] of Object.entries(f.holds) ) {
|
|
480
|
+
if (!hold || hold.reason !== 'quota') continue;
|
|
481
|
+
f.accepted[task] = hold.proposed ?? null;
|
|
482
|
+
delete f.holds[task];
|
|
483
|
+
cleared.push(task);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return { write: true, result: { mode: f.mode, cleared } };
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async function perform(action , o ) {
|
|
491
|
+
const base = passOptions(o);
|
|
492
|
+
switch (action) {
|
|
493
|
+
case 'step': return loop.step({ ...base, by: given(o.by) ? String(o.by) : undefined });
|
|
494
|
+
case 'run': return loop.run({ ...base, by: given(o.by) ? String(o.by) : undefined, maxSeconds: digits(o.maxSeconds, '--max-seconds', 1, RUN_MAX_SECONDS_MAX) });
|
|
495
|
+
case 'submit': return loop.submit({
|
|
496
|
+
...base, dispatchId: String(o.dispatch), status: o.status, summary: o.summary,
|
|
497
|
+
blockers: given(o.blocker) ? [].concat(o.blocker) : undefined,
|
|
498
|
+
tokens: given(o.tokens) ? String(o.tokens) : undefined, tokensBasis: given(o.tokensBasis) ? String(o.tokensBasis) : undefined,
|
|
499
|
+
});
|
|
500
|
+
case 'pause': return controls.pause({ ...base, by: o.by, reason: o.reason });
|
|
501
|
+
case 'inspect': {
|
|
502
|
+
const body = controls.inspect(base);
|
|
503
|
+
// 106 IC-005: the run's fallback state, as stored.
|
|
504
|
+
if (body && body.disposition === 'inspected') body.fallback = readDispatch(storeFileFor(projectRoot(o.cwd), o.slug)).run?.fallback ?? null;
|
|
505
|
+
return body;
|
|
506
|
+
}
|
|
507
|
+
case 'cancel': return controls.cancel({ ...base, by: o.by, reason: o.reason });
|
|
508
|
+
case 'takeover': return controls.takeOver({ ...base, by: o.by, reason: o.reason, task: String(o.task) });
|
|
509
|
+
case 'resume': {
|
|
510
|
+
const body = await controls.resume({ ...base, by: given(o.by) ? o.by : undefined, reason: given(o.reason) ? o.reason : undefined, ack: given(o.ack) ? String(o.ack) : undefined });
|
|
511
|
+
const mode = fallbackFlag(o.fallback);
|
|
512
|
+
if (mode !== null && body && body.disposition === 'resumed') body.fallback = setFallbackMode(o, mode);
|
|
513
|
+
return body;
|
|
514
|
+
}
|
|
515
|
+
default: throw usage(`unknown action '${action}'`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* IC-001: the `dispatch` verb. Options mirror the flags (camelCase: `action`, `slug`, `by`,
|
|
521
|
+
* `reason`, `concurrency`, `mode`, `billingCap`, `verifyArgv`, `verifySeconds`, `attemptSeconds`, `reserveTokens`,
|
|
522
|
+
* `budgetAttempts`, `budgetTokens`, `spawnGrant`, `harness`, `need`, `maxSeconds`, `dispatch`,
|
|
523
|
+
* `status`, `summary`, `blocker`, `tokens`, `tokensBasis`, `promptFile`, `requestId`, `task`,
|
|
524
|
+
* `ack`, `fallback`, `ladder`), plus `env`, `cwd`, `resolveSlug(cwd)`, `now`, and the in-process seams `platform`,
|
|
525
|
+
* `descriptors`, `registry`, `coverage`, `evaluateImpl`, `verify`, `sleep`. Prints nothing.
|
|
526
|
+
*/
|
|
527
|
+
async function executeDispatchCommand(options = {}) {
|
|
528
|
+
// One call, one git memo scope (git-memo.ts): the project root and common directory are asked once.
|
|
529
|
+
return withGitMemo(() => runDispatchCommand(options));
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async function runDispatchCommand(options ) {
|
|
533
|
+
const env = options.env || process.env;
|
|
534
|
+
const cwd = path.resolve(options.cwd || process.cwd());
|
|
535
|
+
const now = options.now;
|
|
536
|
+
const clock = typeof now === 'function'
|
|
537
|
+
? () => { const at = now(); return at instanceof Date ? at : new Date(at); }
|
|
538
|
+
: (now !== undefined && now !== null ? () => new Date(now) : () => new Date());
|
|
539
|
+
try {
|
|
540
|
+
const { action } = options;
|
|
541
|
+
if (!given(action)) throw usage(`--action is required; valid: ${ACTIONS.join(' | ')}`);
|
|
542
|
+
if (!ACTIONS.includes(action)) throw usage(`unknown action '${action}'; valid: ${ACTIONS.join(' | ')}`);
|
|
543
|
+
refuseMisplacedFlags(action, options);
|
|
544
|
+
const settings = action === 'start' ? startSettings(options) : (actionFlags(action, options), null);
|
|
545
|
+
const slug = featureOf(options.slug, options.resolveSlug, cwd);
|
|
546
|
+
const o = { ...options, env, cwd, slug, clock };
|
|
547
|
+
|
|
548
|
+
let body ;
|
|
549
|
+
if (action === 'start') body = start(o, settings);
|
|
550
|
+
else if (action === 'spawn') {
|
|
551
|
+
body = given(options.requestId)
|
|
552
|
+
? spawnStatus({ slug, requestId: String(options.requestId), env, cwd })
|
|
553
|
+
: requestSpawn({ slug, promptFile: String(options.promptFile), reserveTokens: String(options.reserveTokens), env, cwd, now: clock() });
|
|
554
|
+
} else {
|
|
555
|
+
// IC-001 identity checks (DEC-044), before anything the action reads for writing, runs or signals.
|
|
556
|
+
const caller = loop.checkCaller({ env, cwd, file: storeFileFor(projectRoot(cwd), slug) });
|
|
557
|
+
// 106 IC-004 (D3): a run started before 106 has no `run.fallback`; the loop reads it with the setting.
|
|
558
|
+
if (!caller.refusal && (action === 'step' || action === 'run') && !caller.store.run.fallback && o.fallbackDefault === undefined) {
|
|
559
|
+
o.fallbackDefault = fallbackSetting(projectRoot(cwd), env);
|
|
560
|
+
}
|
|
561
|
+
body = caller.refusal ? caller.refusal : await perform(action, o);
|
|
562
|
+
}
|
|
563
|
+
if (body && body.disposition === 'ran' && Number.isInteger(body.exit)) return { exit: body.exit, body };
|
|
564
|
+
return { exit: body && EXIT_ZERO.includes(body.disposition) ? 0 : 1, body };
|
|
565
|
+
} catch (error ) {
|
|
566
|
+
const code = typeof error?.code === 'string' ? error.code : null;
|
|
567
|
+
if (code && ERROR_CODES.includes(code)) return { exit: 2, body: { ok: false, error: code, message: error.message } };
|
|
568
|
+
throw error;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** 106 IC-004 / FR-005: one `fallback <kind> <task> <from> -> <to|none> (...)` line per fallback StepAction. */
|
|
573
|
+
function fallbackLines(actions ) {
|
|
574
|
+
return (Array.isArray(actions) ? actions : []).filter((a ) => a && a.action === 'fallback')
|
|
575
|
+
.map((a ) => `fallback ${String(a.reason).replace(/^([a-z]+):/, '$1 ')}`);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** One summary line per result (plus one line per fallback event for step and run); inspect uses
|
|
579
|
+
* IC-029's lines. No envelope, output or argv content. */
|
|
580
|
+
function summaryLines(body ) {
|
|
581
|
+
const d = body.disposition;
|
|
582
|
+
switch (d) {
|
|
583
|
+
case 'started': {
|
|
584
|
+
const v = body.verification;
|
|
585
|
+
const record = body.billingRecord;
|
|
586
|
+
const recorded = !record ? [] : record.state === 'invalid'
|
|
587
|
+
? [`billing: user record invalid (${record.reason}): no worker counts as free`]
|
|
588
|
+
: record.workers.map((w ) => `billing: ${w.harness} ${w.billing} (user record, asserted ${w.asserted})`);
|
|
589
|
+
return [`started ${body.dispatchRunId} slug ${body.slug} branch ${body.integration.branch} base ${body.integration.base}${body.baseDirty ? ' (caller checkout dirty)' : ''} concurrency ${body.concurrency} mode ${body.mode} verification ${v.declared ? `argc ${v.argc} sha256 ${v.sha256}` : 'undeclared'}`, ...recorded];
|
|
590
|
+
}
|
|
591
|
+
case 'stepped': {
|
|
592
|
+
const extra = body.perform ? ` perform ${body.perform.dispatchId} ${body.perform.task} token ${body.perform.token}` : '';
|
|
593
|
+
return [`stepped ${body.dispatchRunId} step ${body.step ?? '-'} outcome ${body.outcome} actions ${body.actions.length}${extra}`, ...fallbackLines(body.actions)];
|
|
594
|
+
}
|
|
595
|
+
case 'ran': return [`ran ${body.dispatchRunId} steps ${body.steps} outcome ${body.outcome}${body.timeUp ? ' time-up' : ''}${body.signal ? ` signal ${body.signal}` : ''}`, ...fallbackLines(body.fallback)];
|
|
596
|
+
case 'submitted': return [`submitted ${body.dispatchId} task ${body.task} token ${body.token} phase ${body.phase} changes ${body.changes.length}`];
|
|
597
|
+
case 'spawn-requested': return [`spawn-requested ${body.requestId} ${body.state}`];
|
|
598
|
+
case 'spawn-status': return [`spawn-status ${body.requestId} ${body.state}${body.childRunId ? ` child ${body.childRunId}` : ''}`];
|
|
599
|
+
case 'paused': return [`paused ${body.dispatchRunId} active ${Array.isArray(body.active) ? body.active.length : body.active ?? 0}`];
|
|
600
|
+
case 'inspected': {
|
|
601
|
+
// Review C-10: one line per fallback hold after IC-029's lines.
|
|
602
|
+
const holds = body.fallback && body.fallback.holds && typeof body.fallback.holds === 'object' ? body.fallback.holds : {};
|
|
603
|
+
return [...controls.inspectLines(body), ...Object.entries(holds).map(([task, h] ) => `hold ${task} ${h.reason} from ${h.from} proposed ${h.proposed ?? 'none'}`)];
|
|
604
|
+
}
|
|
605
|
+
case 'cancelled': return [`cancelled ${body.dispatchRunId} cleanupVerified ${body.cleanupVerified}`];
|
|
606
|
+
case 'taken-over': return [`taken-over ${body.task} token ${body.token} dispatch ${body.dispatchId}`];
|
|
607
|
+
case 'resumed': return [`resumed epoch ${body.epoch} settled ${body.settled.length} orphans ${body.orphans.length}${body.fallback ? ` fallback ${body.fallback.mode} cleared ${body.fallback.cleared.length}` : ''}`];
|
|
608
|
+
case 'ack-required': return [`ack-required ${body.digest} settled ${Array.isArray(body.settled) ? body.settled.length : 0}`];
|
|
609
|
+
default: return [`${d ?? 'refused'} ${body.reason ?? ''}`.trim()];
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* `doflow-run dispatch --action <action> ...` (IC-001). Takes `executeDispatchCommand`'s options
|
|
615
|
+
* plus `json`. Prints the JSON with `json`, else one summary line (inspect: one per dispatch, then
|
|
616
|
+
* the run lines); a usage or environment error goes to stderr. Sets `process.exitCode` and resolves
|
|
617
|
+
* the exit code.
|
|
618
|
+
*/
|
|
619
|
+
async function handleDispatchCommand(options = {}) {
|
|
620
|
+
const { finishRuntime } = require('../cli-result.js');
|
|
621
|
+
const { exit, body } = await executeDispatchCommand(options);
|
|
622
|
+
if (options.json) console.log(JSON.stringify(body, null, 2));
|
|
623
|
+
else if (exit === 2) console.error(`doflow dispatch: ${body.message}`);
|
|
624
|
+
else for (const line of summaryLines(body)) (exit === 0 ? console.log : console.error)(line);
|
|
625
|
+
return finishRuntime(exit);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
module.exports = { handleDispatchCommand, executeDispatchCommand, summaryLines, fallbackSetting, ACTIONS, FLAG_ACTIONS, EXIT_ZERO, ERROR_CODES };
|