@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,541 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Feature 081 C.2 (IC-014, IC-017 recorded half, IC-018, IC-020 timer; DEC-010, DEC-022, DEC-028,
|
|
4
|
+
// DEC-033, DEC-037, DEC-040): the detached supervisor of one worker run.
|
|
5
|
+
//
|
|
6
|
+
// The run command spawns this file detached as
|
|
7
|
+
// `[...process.execArgv, SUPERVISOR_FILE, storeFile, runId]` with the caller's environment and cwd,
|
|
8
|
+
// so on the timeout path the supervisor hands 080's cancel its own inherited `env` and `cwd` and
|
|
9
|
+
// rebuilds no identity (DEC-038). It runs IC-014 steps 1 to 7: re-check the attempt and the prompt,
|
|
10
|
+
// take the before snapshot, mark `launchingAt`, launch, mark `running` with one timer armed, pipe
|
|
11
|
+
// both streams to masked, bounded output files and stdout to the parser, then record the process,
|
|
12
|
+
// read-only check, report and `launchObserved` and, while still `running`, the end. It never calls
|
|
13
|
+
// 080 finish (DEC-010): a `success` report changes no task state.
|
|
14
|
+
//
|
|
15
|
+
// The run record carries the full worker block as `descriptor`, beside data-model §1's reduced
|
|
16
|
+
// `recipe`: a detached process cannot receive the in-process descriptors tests inject (DEC-036), and
|
|
17
|
+
// the reduced recipe's `argvShape` has the prompt replaced, so it cannot be launched from.
|
|
18
|
+
//
|
|
19
|
+
// An edit run (082 IC-014, DEC-021, DEC-046, DEC-048) takes the edit check in place of the
|
|
20
|
+
// read-only check of steps 2 and 6: before the launch a snapshot of the caller checkout
|
|
21
|
+
// (`edit.callerTop`) and the attempt worktree's HEAD; after the exit `isolation` from the caller
|
|
22
|
+
// comparison, the attempt branch and `merge-base --is-ancestor <edit.base> HEAD`, with `readOnly`
|
|
23
|
+
// left null. The attempt worktree's files are not compared: they are the change.
|
|
24
|
+
//
|
|
25
|
+
// This is the one module under `src/runtime/worker/` that holds a direct fs write call: the output
|
|
26
|
+
// sink's `mkdirSync` and `createWriteStream` (IC-018, IC-027, RK3). Every record write goes through
|
|
27
|
+
// the run store's `transition`.
|
|
28
|
+
|
|
29
|
+
const fs = require('node:fs');
|
|
30
|
+
const path = require('node:path');
|
|
31
|
+
const crypto = require('node:crypto');
|
|
32
|
+
const { spawnSync } = require('node:child_process');
|
|
33
|
+
const { readRuns, transition, RUN_ID } = require('./run-store.js');
|
|
34
|
+
const { readTaskState } = require('../task-state.js');
|
|
35
|
+
const attempts = require('../task-attempts.js');
|
|
36
|
+
const { launchWorker } = require('./launch.js');
|
|
37
|
+
const { parserFor } = require('./parsers.js');
|
|
38
|
+
const worktreeCheck = require('./worktree-check.js');
|
|
39
|
+
const processTree = require('./process-tree.js');
|
|
40
|
+
const { cancelRun, mergeCleanup } = require('./cancel.js');
|
|
41
|
+
const { bindingObserved } = require('./readiness.js');
|
|
42
|
+
const { maskBody } = require('../mask.js');
|
|
43
|
+
const { quotaOf } = require('./quota.js');
|
|
44
|
+
|
|
45
|
+
/** This module's own file, which the run command spawns (G16). */
|
|
46
|
+
const SUPERVISOR_FILE = __filename;
|
|
47
|
+
/** IC-018: the most bytes one stream's output file holds, and the most one buffered line holds. */
|
|
48
|
+
const OUTPUT_MAX_BYTES = 4194304;
|
|
49
|
+
/** The former split point; since DEC-057 no longer a parse or mask boundary. */
|
|
50
|
+
const LINE_MAX_BYTES = 65536;
|
|
51
|
+
/**
|
|
52
|
+
* IC-018 (DEC-057): bytes dropped from the masked prefix of an oversize line, so a token cut at the
|
|
53
|
+
* buffer edge is never written unmasked. `mask.ts` exports no longest-match length (its token shapes
|
|
54
|
+
* are open-ended), so this is a constant above the bounded shapes: a key name of at most about 130
|
|
55
|
+
* characters plus its value, a 40-character key, a JWT or a `gh*_` token of ordinary length.
|
|
56
|
+
* Known ceiling: a single token longer than this that straddles the 4 MiB cut leaves its head.
|
|
57
|
+
*/
|
|
58
|
+
const MASK_TAIL = 4096;
|
|
59
|
+
/** IC-011 step 0's default, for a record that carries none. */
|
|
60
|
+
const DEFAULT_TIMEOUT_SECONDS = 1800;
|
|
61
|
+
/** How long the streams may stay open after the worker's exit (a grandchild may hold them). */
|
|
62
|
+
const DRAIN_MS = 500;
|
|
63
|
+
const STREAMS = Object.freeze({ stdout: 'out', stderr: 'err' });
|
|
64
|
+
/** 082 IC-014: the bound on each attempt-worktree git read, and the variables it drops (081 REPO_ENV). */
|
|
65
|
+
const EDIT_GIT_TIMEOUT_MS = 10000;
|
|
66
|
+
const REPO_ENV = Object.freeze(['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_COMMON_DIR', 'GIT_OBJECT_DIRECTORY']);
|
|
67
|
+
|
|
68
|
+
const iso = (clock ) => clock().toISOString();
|
|
69
|
+
|
|
70
|
+
/** `<root>/.doflow/state/worker-runs/<slug>.json` gives the lifecycle root, the state root and the slug. */
|
|
71
|
+
function rootsFor(file ) {
|
|
72
|
+
const stateRoot = path.resolve(path.dirname(file), '..');
|
|
73
|
+
const root = path.resolve(stateRoot, '..', '..');
|
|
74
|
+
const slug = path.basename(file, '.json');
|
|
75
|
+
return { root, stateRoot, slug, ledger: attempts.storeFile(root, slug) };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Splits a byte stream into logical lines, each buffered whole up to `OUTPUT_MAX_BYTES` (DEC-057).
|
|
80
|
+
* A line over that is handed to `onOversize` once with its first `OUTPUT_MAX_BYTES` bytes; the rest
|
|
81
|
+
* of it is dropped up to its newline, which `onOversizeEnd` reports.
|
|
82
|
+
*/
|
|
83
|
+
function lineSplitter({ onLine, onOversize, onOversizeEnd }
|
|
84
|
+
|
|
85
|
+
) {
|
|
86
|
+
let parts = [];
|
|
87
|
+
let size = 0;
|
|
88
|
+
let skipping = false;
|
|
89
|
+
const take = () => {
|
|
90
|
+
const line = parts.length === 1 ? parts[0] : Buffer.concat(parts, size);
|
|
91
|
+
parts = [];
|
|
92
|
+
size = 0;
|
|
93
|
+
return line;
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
push(chunk ) {
|
|
97
|
+
let start = 0;
|
|
98
|
+
while (start < chunk.length) {
|
|
99
|
+
const nl = chunk.indexOf(0x0a, start);
|
|
100
|
+
const piece = chunk.subarray(start, nl === -1 ? chunk.length : nl);
|
|
101
|
+
if (!skipping && size + piece.length > OUTPUT_MAX_BYTES) {
|
|
102
|
+
parts.push(piece.subarray(0, OUTPUT_MAX_BYTES - size));
|
|
103
|
+
size = OUTPUT_MAX_BYTES;
|
|
104
|
+
onOversize(take());
|
|
105
|
+
skipping = true;
|
|
106
|
+
} else if (!skipping && piece.length) {
|
|
107
|
+
parts.push(piece);
|
|
108
|
+
size += piece.length;
|
|
109
|
+
}
|
|
110
|
+
if (nl === -1) break;
|
|
111
|
+
if (skipping) onOversizeEnd(true);
|
|
112
|
+
else onLine(take(), true);
|
|
113
|
+
skipping = false;
|
|
114
|
+
start = nl + 1;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
end() {
|
|
118
|
+
if (skipping) onOversizeEnd(false);
|
|
119
|
+
else if (size) onLine(take(), false);
|
|
120
|
+
parts = [];
|
|
121
|
+
size = 0;
|
|
122
|
+
skipping = false;
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* IC-018: the masked, bounded output file of one stream, `worker-output/<runId>.<out|err>.log` under
|
|
129
|
+
* `stateRoot` (the project's `.doflow/state`). Each whole line is masked with `maskBody` and written
|
|
130
|
+
* until the file reaches `OUTPUT_MAX_BYTES`, the cut made after masking; later bytes are counted and
|
|
131
|
+
* dropped. `onLine`, when given, receives every line's unmasked text, those past the cap included,
|
|
132
|
+
* for the parser; a line over `OUTPUT_MAX_BYTES` goes to `onOversize(looksJson)` instead, and its
|
|
133
|
+
* masked prefix is written less its last `MASK_TAIL` bytes (DEC-057). A write stream `'error'` marks
|
|
134
|
+
* the sink `{ truncated: true, failed: <code> }` and stops writing; lines still reach `onLine`
|
|
135
|
+
* (DEC-065). `end()` resolves `{ path, bytes, written, truncated, failed? }`, `path` relative to
|
|
136
|
+
* `stateRoot`.
|
|
137
|
+
*/
|
|
138
|
+
function createOutputSink({ stateRoot, runId, stream, onLine, onOversize }
|
|
139
|
+
|
|
140
|
+
) {
|
|
141
|
+
if (typeof runId !== 'string' || !RUN_ID.test(runId)) throw new TypeError(`createOutputSink: run id '${runId}' is not 'w-' plus 12 hex characters`);
|
|
142
|
+
if (!Object.hasOwn(STREAMS, stream)) throw new TypeError(`createOutputSink: stream must be stdout or stderr: ${stream}`);
|
|
143
|
+
const rel = `worker-output/${runId}.${STREAMS[stream]}.log`;
|
|
144
|
+
const abs = path.join(stateRoot, rel);
|
|
145
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
146
|
+
const file = fs.createWriteStream(abs, { flags: 'w' });
|
|
147
|
+
let bytes = 0;
|
|
148
|
+
let written = 0;
|
|
149
|
+
let truncated = false;
|
|
150
|
+
let full = false;
|
|
151
|
+
let failed = null;
|
|
152
|
+
let closed = false;
|
|
153
|
+
file.on('error', (error ) => {
|
|
154
|
+
failed = typeof error?.code === 'string' && error.code ? error.code : 'write-failed';
|
|
155
|
+
truncated = true;
|
|
156
|
+
});
|
|
157
|
+
file.on('close', () => { closed = true; });
|
|
158
|
+
/** Writes `out` up to the file cap. */
|
|
159
|
+
const emit = (out ) => {
|
|
160
|
+
if (failed || full || !out.length) return;
|
|
161
|
+
const room = OUTPUT_MAX_BYTES - written;
|
|
162
|
+
if (out.length >= room) {
|
|
163
|
+
if (out.length > room) truncated = true;
|
|
164
|
+
full = true;
|
|
165
|
+
out = out.subarray(0, room);
|
|
166
|
+
}
|
|
167
|
+
if (out.length) {
|
|
168
|
+
file.write(out);
|
|
169
|
+
written += out.length;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const splitter = lineSplitter({
|
|
173
|
+
onLine(line, newline) {
|
|
174
|
+
const text = line.toString('utf8');
|
|
175
|
+
if (onLine) onLine(text);
|
|
176
|
+
if (full) {
|
|
177
|
+
truncated = true;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
emit(Buffer.from(maskBody(text).text + (newline ? '\n' : ''), 'utf8'));
|
|
181
|
+
},
|
|
182
|
+
onOversize(prefix) {
|
|
183
|
+
if (onOversize) onOversize(prefix[0] === 0x7b);
|
|
184
|
+
truncated = true;
|
|
185
|
+
const masked = Buffer.from(maskBody(prefix.toString('utf8')).text, 'utf8');
|
|
186
|
+
emit(masked.subarray(0, Math.max(0, masked.length - MASK_TAIL)));
|
|
187
|
+
},
|
|
188
|
+
onOversizeEnd(newline) {
|
|
189
|
+
if (newline) emit(Buffer.from('\n'));
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
const summary = () => ({ path: rel, bytes, written, truncated, ...(failed ? { failed } : {}) });
|
|
193
|
+
let ended = null;
|
|
194
|
+
return {
|
|
195
|
+
path: rel,
|
|
196
|
+
write(chunk ) {
|
|
197
|
+
const data = typeof chunk === 'string' ? Buffer.from(chunk, 'utf8') : chunk;
|
|
198
|
+
bytes += data.length;
|
|
199
|
+
splitter.push(data);
|
|
200
|
+
},
|
|
201
|
+
end() {
|
|
202
|
+
if (!ended) {
|
|
203
|
+
splitter.end();
|
|
204
|
+
ended = new Promise((resolve) => {
|
|
205
|
+
if (closed) {
|
|
206
|
+
resolve(summary());
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
file.once('close', () => resolve(summary()));
|
|
210
|
+
if (!failed) file.end();
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return ended;
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** One bounded, read-only git call in `cwd`: `{ status, stdout }`, `status` null when it did not exit. */
|
|
219
|
+
function gitRead(cwd , args , env ) {
|
|
220
|
+
const clean = { ...env, GIT_OPTIONAL_LOCKS: '0' };
|
|
221
|
+
for (const key of REPO_ENV) delete clean[key];
|
|
222
|
+
const r = spawnSync('git', ['-C', cwd, ...args], { env: clean, timeout: EDIT_GIT_TIMEOUT_MS, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
223
|
+
return { status: r.error ? null : r.status, stdout: r.stdout || '' };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** The attempt worktree's HEAD ref (`''` when detached) and object, or null when git failed. */
|
|
227
|
+
function attemptHead(worktree , env ) {
|
|
228
|
+
const ref = gitRead(worktree, ['symbolic-ref', '-q', 'HEAD'], env);
|
|
229
|
+
if (ref.status !== 0 && ref.status !== 1) return null;
|
|
230
|
+
const object = gitRead(worktree, ['rev-parse', '--verify', '-q', 'HEAD'], env);
|
|
231
|
+
if (object.status !== 0) return null;
|
|
232
|
+
return { ref: ref.status === 0 ? ref.stdout.trim() : '', object: object.stdout.trim() };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* 082 IC-014's after-exit reading of an edit run: `violated` `outside-write` when the caller
|
|
237
|
+
* checkout changed, `violated` `branch-moved` when the attempt branch moved off `edit.branch` or
|
|
238
|
+
* off a descendant of `edit.base`, `unverifiable` `isolation-unverifiable` when a snapshot was
|
|
239
|
+
* incomplete or a git call failed, else `clean`. `refsChanged` stays advisory (DEC-046).
|
|
240
|
+
*/
|
|
241
|
+
function isolationOf(run , before , attemptBefore , env ) {
|
|
242
|
+
let caller ;
|
|
243
|
+
try {
|
|
244
|
+
caller = worktreeCheck.compare(before, worktreeCheck.snapshot(run.edit.callerTop));
|
|
245
|
+
} catch {
|
|
246
|
+
caller = { status: 'unverifiable', changes: [], head: { before: before.head, after: null }, refsChanged: null };
|
|
247
|
+
}
|
|
248
|
+
const after = attemptHead(run.execWorktree, env);
|
|
249
|
+
const ancestor = after ? gitRead(run.execWorktree, ['merge-base', '--is-ancestor', run.edit.base, 'HEAD'], env).status : null;
|
|
250
|
+
let status ;
|
|
251
|
+
let reason ;
|
|
252
|
+
if (caller.status === 'violated') [status, reason] = ['violated', 'outside-write'];
|
|
253
|
+
else if ((after && after.ref !== run.edit.branch) || ancestor === 1) [status, reason] = ['violated', 'branch-moved'];
|
|
254
|
+
else if (caller.status !== 'clean' || !after || ancestor !== 0) [status, reason] = ['unverifiable', 'isolation-unverifiable'];
|
|
255
|
+
else [status, reason] = ['clean', null];
|
|
256
|
+
return {
|
|
257
|
+
status, reason, changes: caller.changes, callerHead: caller.head,
|
|
258
|
+
attemptHead: { before: attemptBefore, after }, refsChanged: caller.refsChanged,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* IC-017, computed from a record, never stored: `resultOffered` is true only for a run that ended
|
|
264
|
+
* `exited` with `process.kind: 'exited'`, a `clean` read-only check and a `success` report, and
|
|
265
|
+
* whose group left no survivors after the exit (`group-survived`, DEC-058). For an edit run
|
|
266
|
+
* (082 IC-014) `isolation.status` stands in for `readOnly.status`, and a withheld one names
|
|
267
|
+
* `outside-write`, `branch-moved` or `isolation-unverifiable`.
|
|
268
|
+
*/
|
|
269
|
+
function resultOfRun(run ) {
|
|
270
|
+
const withheld = [];
|
|
271
|
+
if (run?.end?.state !== 'exited' || run?.process?.kind !== 'exited') withheld.push('not-exited');
|
|
272
|
+
if (run?.edit) {
|
|
273
|
+
const isolation = run?.isolation;
|
|
274
|
+
if (isolation?.status === 'violated') withheld.push(isolation.reason === 'branch-moved' ? 'branch-moved' : 'outside-write');
|
|
275
|
+
else if (isolation?.status !== 'clean') withheld.push('isolation-unverifiable');
|
|
276
|
+
} else {
|
|
277
|
+
const readOnly = run?.readOnly?.status;
|
|
278
|
+
if (readOnly === 'violated') withheld.push('read-only-violation');
|
|
279
|
+
else if (readOnly !== 'clean') withheld.push('read-only-unverifiable');
|
|
280
|
+
}
|
|
281
|
+
const report = run?.report?.status;
|
|
282
|
+
if (report === 'denied') withheld.push('report-denied');
|
|
283
|
+
else if (report === 'failure') withheld.push(run?.report?.quota ? 'report-quota' : 'report-failure');
|
|
284
|
+
else if (report !== 'success') withheld.push('report-unparsed');
|
|
285
|
+
if (run?.process?.leftover?.status === 'survivors') withheld.push('group-survived');
|
|
286
|
+
return { resultOffered: withheld.length === 0, withheld };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** The 080 attempt holding the run's token is still active. */
|
|
290
|
+
function attemptActive(ledger , run ) {
|
|
291
|
+
try {
|
|
292
|
+
const data = readTaskState(fs, ledger);
|
|
293
|
+
const entry = data?.tasks?.[run.task];
|
|
294
|
+
return Array.isArray(entry?.attempts) && entry.attempts.some((a ) => a.token === run.token && a.state === 'active');
|
|
295
|
+
} catch {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function promptUnchanged(run ) {
|
|
301
|
+
try {
|
|
302
|
+
const bytes = fs.readFileSync(run.prompt.path);
|
|
303
|
+
return crypto.createHash('sha256').update(bytes).digest('hex') === run.prompt.sha256 ? bytes : null;
|
|
304
|
+
} catch {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Resolves once `stream` has ended or closed. */
|
|
310
|
+
function drained(stream ) {
|
|
311
|
+
if (!stream || stream.readableEnded || stream.destroyed) return Promise.resolve();
|
|
312
|
+
return new Promise((resolve) => {
|
|
313
|
+
stream.once('end', resolve);
|
|
314
|
+
stream.once('close', resolve);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function boundedWait(promise , ms ) {
|
|
319
|
+
let timer ;
|
|
320
|
+
const bound = new Promise((resolve) => { timer = setTimeout(resolve, ms); });
|
|
321
|
+
return Promise.race([promise, bound]).finally(() => clearTimeout(timer));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Reads only the bounded Codex output-last-message prefix; absent or unreadable files are optional. */
|
|
325
|
+
function readLastMessage(parser , file ) {
|
|
326
|
+
if (typeof parser.lastMessage !== 'function') return;
|
|
327
|
+
let fd ;
|
|
328
|
+
try {
|
|
329
|
+
fd = fs.openSync(file, 'r');
|
|
330
|
+
const bytes = Buffer.alloc(65536);
|
|
331
|
+
const length = fs.readSync(fd, bytes, 0, bytes.length, 0);
|
|
332
|
+
const masked = maskBody(bytes.subarray(0, length).toString('utf8')).text;
|
|
333
|
+
parser.lastMessage(masked);
|
|
334
|
+
// NFR-004: the retained copy holds only the masked, bounded text, never the raw worker output.
|
|
335
|
+
fs.closeSync(fd);
|
|
336
|
+
fd = undefined;
|
|
337
|
+
try { fs.writeFileSync(file, masked, 'utf8'); } catch { fs.rmSync(file, { force: true }); }
|
|
338
|
+
} catch {
|
|
339
|
+
// Codex may not have produced this optional file; that does not change the run outcome.
|
|
340
|
+
} finally {
|
|
341
|
+
if (fd !== undefined) {
|
|
342
|
+
try { fs.closeSync(fd); } catch { /* unreadable/closed files are ignored */ }
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* IC-014 steps 1 to 7 for `runId` in the run store `file`. Resolves the run's record as it stands
|
|
349
|
+
* when the supervisor is done. `env`, `cwd` and `platform` default to this process's; `launch`,
|
|
350
|
+
* `escalate`, `cancel` and `now` exist for in-process tests.
|
|
351
|
+
*/
|
|
352
|
+
async function superviseRun({
|
|
353
|
+
file, runId, env = process.env, cwd = process.cwd(), platform = process.platform, now = () => new Date(),
|
|
354
|
+
launch = launchWorker, escalate = processTree.escalate, cancel = cancelRun,
|
|
355
|
+
} ) {
|
|
356
|
+
const clock = typeof now === 'function' ? now : () => now;
|
|
357
|
+
const { stateRoot, ledger } = rootsFor(file);
|
|
358
|
+
const current = () => readRuns(file)[runId];
|
|
359
|
+
const notStarted = (reason ) => transition(file, runId, ['starting'], (run ) => {
|
|
360
|
+
const at = iso(clock);
|
|
361
|
+
run.process = { kind: 'not-started', reason, at };
|
|
362
|
+
run.end = { state: 'exited', at };
|
|
363
|
+
run.phase = 'ended';
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Step 1: the run, the attempt and the prompt.
|
|
367
|
+
const run = current();
|
|
368
|
+
if (!run || run.phase !== 'starting') return run ?? null;
|
|
369
|
+
if (!attemptActive(ledger, run)) {
|
|
370
|
+
notStarted('attempt-not-active');
|
|
371
|
+
return current();
|
|
372
|
+
}
|
|
373
|
+
const promptBytes = promptUnchanged(run);
|
|
374
|
+
if (!promptBytes) {
|
|
375
|
+
notStarted('prompt-changed');
|
|
376
|
+
return current();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Step 2: the before snapshot; for an edit run the caller checkout and the attempt HEAD (082 IC-014).
|
|
380
|
+
let before ;
|
|
381
|
+
let attemptBefore = null;
|
|
382
|
+
try {
|
|
383
|
+
before = worktreeCheck.snapshot(run.edit ? run.edit.callerTop : run.execWorktree);
|
|
384
|
+
if (run.edit) attemptBefore = attemptHead(run.execWorktree, env);
|
|
385
|
+
} catch {
|
|
386
|
+
before = null;
|
|
387
|
+
}
|
|
388
|
+
if (!before || before.incomplete || (run.edit && !attemptBefore)) {
|
|
389
|
+
notStarted('snapshot-failed');
|
|
390
|
+
return current();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Step 3: `launchingAt`, then the launch. A refused transition means a cancel took the run.
|
|
394
|
+
const marked = transition(file, runId, ['starting'], (r ) => { r.launchingAt = iso(clock); });
|
|
395
|
+
if (!marked.applied) return current();
|
|
396
|
+
const usesLastMessageFile = run.descriptor?.structuredOutput?.argv?.includes('{lastMessageFile}') === true;
|
|
397
|
+
const lastMessageFile = usesLastMessageFile ? path.join(stateRoot, 'worker-output', `${runId}.last.log`) : undefined;
|
|
398
|
+
let launched ;
|
|
399
|
+
try {
|
|
400
|
+
if (lastMessageFile) {
|
|
401
|
+
fs.mkdirSync(path.dirname(lastMessageFile), { recursive: true });
|
|
402
|
+
fs.rmSync(lastMessageFile, { force: true });
|
|
403
|
+
}
|
|
404
|
+
launched = await launch({
|
|
405
|
+
recipe: run.descriptor, prompt: promptBytes.toString('utf8'), identity: run.identity, runId,
|
|
406
|
+
execWorktree: run.execWorktree, env, taskId: run.task, ...(run.edit ? { edit: true } : {}),
|
|
407
|
+
...(lastMessageFile ? { lastMessageFile } : {}),
|
|
408
|
+
...(run.binding ? { binding: run.binding } : {}),
|
|
409
|
+
});
|
|
410
|
+
} catch (error ) {
|
|
411
|
+
notStarted(typeof error?.code === 'string' && error.code ? error.code : 'launch-failed');
|
|
412
|
+
return current();
|
|
413
|
+
}
|
|
414
|
+
const { child } = launched;
|
|
415
|
+
const exited = new Promise((resolve) => {
|
|
416
|
+
if (child.exitCode !== null || child.signalCode !== null) resolve({ code: child.exitCode, signal: child.signalCode });
|
|
417
|
+
else child.once('exit', (code , signal ) => resolve({ code, signal }));
|
|
418
|
+
});
|
|
419
|
+
const streamsDone = Promise.all([drained(child.stdout), drained(child.stderr)]);
|
|
420
|
+
|
|
421
|
+
// Step 4: `running`, with the worker's identity and the deadline.
|
|
422
|
+
const worker = { pid: child.pid, pgid: child.pid, start: platform === 'win32' ? null : processTree.procStart(child.pid) };
|
|
423
|
+
const timeoutSeconds = Number.isInteger(run.timeoutSeconds) && run.timeoutSeconds > 0 ? run.timeoutSeconds : DEFAULT_TIMEOUT_SECONDS;
|
|
424
|
+
const startedAt = clock();
|
|
425
|
+
const deadline = new Date(startedAt.getTime() + timeoutSeconds * 1000);
|
|
426
|
+
const running = transition(file, runId, ['starting'], (r ) => {
|
|
427
|
+
r.phase = 'running';
|
|
428
|
+
r.worker = worker;
|
|
429
|
+
r.startedAt = startedAt.toISOString();
|
|
430
|
+
r.deadline = deadline.toISOString();
|
|
431
|
+
r.launched = launched.launched;
|
|
432
|
+
});
|
|
433
|
+
if (!running.applied) {
|
|
434
|
+
// A cancel took the run after the spawn: end this supervisor's own worker and record it (DEC-040).
|
|
435
|
+
child.stdout?.resume();
|
|
436
|
+
child.stderr?.resume();
|
|
437
|
+
const outcome = await escalate({ proc: worker, platform });
|
|
438
|
+
transition(file, runId, ['cancelling', 'ended'], (r ) => {
|
|
439
|
+
r.supervisorCleanup = {
|
|
440
|
+
at: iso(clock), worker, status: outcome.status ?? 'unknown', reason: outcome.reason ?? null,
|
|
441
|
+
survivors: outcome.survivors ?? [], zombies: outcome.zombies ?? [],
|
|
442
|
+
};
|
|
443
|
+
if (r.cleanup) r.cleanup = mergeCleanup(r.cleanup, r.supervisorCleanup);
|
|
444
|
+
});
|
|
445
|
+
child.stdout?.destroy();
|
|
446
|
+
child.stderr?.destroy();
|
|
447
|
+
return current();
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Step 5: the parser, the output files and the one timer.
|
|
451
|
+
const parser = parserFor(run.descriptor?.structuredOutput?.parser ?? run.recipe?.parser ?? null).create();
|
|
452
|
+
const out = createOutputSink({
|
|
453
|
+
stateRoot, runId, stream: 'stdout', onLine: (text) => parser.line(text), onOversize: (looksJson) => parser.oversize(looksJson),
|
|
454
|
+
});
|
|
455
|
+
const err = createOutputSink({ stateRoot, runId, stream: 'stderr' });
|
|
456
|
+
child.stdout?.on('data', (chunk ) => out.write(chunk));
|
|
457
|
+
child.stderr?.on('data', (chunk ) => err.write(chunk));
|
|
458
|
+
let timedOut = null;
|
|
459
|
+
const timer = setTimeout(() => {
|
|
460
|
+
let phase ;
|
|
461
|
+
try {
|
|
462
|
+
phase = current()?.phase;
|
|
463
|
+
} catch {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (phase !== 'running') return;
|
|
467
|
+
timedOut = Promise.resolve()
|
|
468
|
+
.then(() => cancel({ file, runId, by: 'doflow-worker', reason: `timeout after ${timeoutSeconds}s`, source: 'timeout', env, cwd, platform }))
|
|
469
|
+
.catch((error ) => ({ disposition: 'error', reason: error?.code ?? 'cancel-threw' }));
|
|
470
|
+
}, Math.max(0, deadline.getTime() - clock().getTime()));
|
|
471
|
+
|
|
472
|
+
// Step 6: the exit and the recorded outcomes. These writes also reach a run a timeout or a cancel
|
|
473
|
+
// already ended, where each field is added only when absent and the end is never touched (DEC-056).
|
|
474
|
+
const STEP6 = ['running', 'cancelling', 'ended'];
|
|
475
|
+
const put = (r , key , value ) => {
|
|
476
|
+
if (r.phase !== 'ended' || r[key] === undefined) r[key] = value;
|
|
477
|
+
};
|
|
478
|
+
const { code, signal } = await exited;
|
|
479
|
+
clearTimeout(timer);
|
|
480
|
+
transition(file, runId, STEP6, (r ) => put(r, 'process', { kind: 'exited', code, signal, at: iso(clock) }));
|
|
481
|
+
// The group may outlive its leader: end what remains before the after snapshot (DEC-058). The
|
|
482
|
+
// reading is recorded as `process.groupAlive` (false, true or 'unknown'; absent on win32, where no
|
|
483
|
+
// group is read), so a reader infers a cleaned-up tree only from an observed `false` (DEC-047).
|
|
484
|
+
const groupAlive = platform === 'win32' ? null : processTree.groupAlive(worker.pgid, worker.start);
|
|
485
|
+
if (groupAlive !== null) {
|
|
486
|
+
transition(file, runId, STEP6, (r ) => {
|
|
487
|
+
if (r.process?.kind === 'exited' && r.process.groupAlive === undefined) r.process.groupAlive = groupAlive;
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
if (groupAlive === true) {
|
|
491
|
+
const outcome = await escalate({ proc: worker, platform });
|
|
492
|
+
const leftover = { status: outcome.status ?? 'unknown', reason: outcome.reason ?? null, survivors: outcome.survivors ?? [], zombies: outcome.zombies ?? [] };
|
|
493
|
+
transition(file, runId, STEP6, (r ) => {
|
|
494
|
+
if (r.process?.kind === 'exited' && r.process.leftover === undefined) r.process.leftover = leftover;
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
await boundedWait(streamsDone, DRAIN_MS);
|
|
498
|
+
child.stdout?.destroy();
|
|
499
|
+
child.stderr?.destroy();
|
|
500
|
+
const output = { stdout: await out.end(), stderr: await err.end() };
|
|
501
|
+
let readOnly = null;
|
|
502
|
+
let isolation = null;
|
|
503
|
+
if (run.edit) {
|
|
504
|
+
isolation = isolationOf(run, before, attemptBefore, env);
|
|
505
|
+
} else {
|
|
506
|
+
try {
|
|
507
|
+
readOnly = worktreeCheck.compare(before, worktreeCheck.snapshot(run.execWorktree));
|
|
508
|
+
} catch {
|
|
509
|
+
readOnly = { status: 'unverifiable', changes: [], head: { before: before.head, after: null }, refsChanged: null };
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (lastMessageFile) readLastMessage(parser, lastMessageFile);
|
|
513
|
+
const report = parser.end();
|
|
514
|
+
// 106 IC-002: a failure whose masked summary holds a declared quota signal is marked quota.
|
|
515
|
+
const quota = quotaOf(report, run.descriptor);
|
|
516
|
+
if (quota) report.quota = quota;
|
|
517
|
+
const unit = `L.${run.identity?.run}.${run.identity?.lineage}.${run.token}`;
|
|
518
|
+
const launchObserved = run.identity ? (bindingObserved(launched.env ?? env, unit) ? 'yes' : 'no') : 'unknown';
|
|
519
|
+
transition(file, runId, STEP6, (r ) => {
|
|
520
|
+
put(r, 'readOnly', readOnly);
|
|
521
|
+
if (run.edit) put(r, 'isolation', isolation);
|
|
522
|
+
put(r, 'report', report);
|
|
523
|
+
put(r, 'launchObserved', launchObserved);
|
|
524
|
+
put(r, 'output', output);
|
|
525
|
+
if (r.phase === 'running') {
|
|
526
|
+
r.end = { state: 'exited', at: iso(clock) };
|
|
527
|
+
r.phase = 'ended';
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// Step 7: a timeout's cancel finishes before the supervisor exits; the canceller writes that end.
|
|
532
|
+
if (timedOut) await timedOut;
|
|
533
|
+
return current();
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
module.exports = { SUPERVISOR_FILE, OUTPUT_MAX_BYTES, LINE_MAX_BYTES, MASK_TAIL, superviseRun, createOutputSink, resultOfRun };
|
|
537
|
+
|
|
538
|
+
if (require.main === module) {
|
|
539
|
+
const [file, runId] = process.argv.slice(2);
|
|
540
|
+
superviseRun({ file, runId }).then(() => process.exit(0), () => process.exit(1));
|
|
541
|
+
}
|