@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,254 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const crypto = require('node:crypto');
|
|
7
|
+
const nodeFs = require('node:fs');
|
|
8
|
+
const { SAFE_TOKEN, telemetryOff, TelemetryWriter } = require('../trace/telemetry.js');
|
|
9
|
+
const { resolveRunsLocation } = require('../trace/ledger.js');
|
|
10
|
+
const { DISPATCH_RUN_ID } = require('../dispatch/store.js');
|
|
11
|
+
const { levelOf, v0Applies, v0Enabled, loadVerificationRegistry } = require('./registry.js');
|
|
12
|
+
|
|
13
|
+
let defaultRegistry = null;
|
|
14
|
+
function getDefaultRegistry() {
|
|
15
|
+
if (!defaultRegistry) {
|
|
16
|
+
try {
|
|
17
|
+
defaultRegistry = loadVerificationRegistry();
|
|
18
|
+
} catch {
|
|
19
|
+
defaultRegistry = null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return defaultRegistry;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Verify telemetry (088 C.1, IC-006, DD-4): one `verify.run` line per verify run, one `verify.tier`
|
|
28
|
+
* per report tier and one `verify.check` per check, written through `TelemetryWriter`.
|
|
29
|
+
*
|
|
30
|
+
* The report is never touched: timing comes from the runner's `metrics` WeakMap (keyed by each
|
|
31
|
+
* check result object), so `verify --json` is the same bytes with telemetry on, off or faulted
|
|
32
|
+
* (FR-002, NFR-005, NFR-007). Records are built with every design field; the writer keeps only
|
|
33
|
+
* the keys `core/registry/telemetry.json` declares for the kind, so a later phase widens the lines
|
|
34
|
+
* by declaring keys, not by changing this module. Fields whose source does not exist yet are null:
|
|
35
|
+
* `level` until G.1, `v0` until G.2, `check_key` and `shared` until E.2.
|
|
36
|
+
*
|
|
37
|
+
* Where: `DOFLOW_CONFIG_DIR` when set, even over a nearer store; else the nearest `.doflow` above
|
|
38
|
+
* `start` that holds `state/runs`, so a `.doflow` without a ledger (one left by an older runtime's
|
|
39
|
+
* cwd-rooted verification logs) is walked past. With no project store nothing is written: no global
|
|
40
|
+
* fallback, so the lines stay under the project's state (NFR-006), as the bridge's hook lines do.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
/** ISO 8601 with ms for an epoch-ms number, else undefined (the writer then stamps its own clock). */
|
|
46
|
+
function isoAt(ms ) {
|
|
47
|
+
return typeof ms === 'number' && Number.isFinite(ms) ? new Date(ms).toISOString() : undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The records for one verify run, in write order: run, then each tier followed by its checks.
|
|
52
|
+
* Pure; a value that fails its registry type is nulled by the writer, never here.
|
|
53
|
+
* @param {Object} input
|
|
54
|
+
* @param {Object} input.report a `runContract` report, or the dispatch runner's `{status, startedAt, finishedAt, tiers: []}`
|
|
55
|
+
* @param {WeakMap} [input.metrics] check result → `{ startedAt, durationMs }`
|
|
56
|
+
* @param {{verifyId: string, source: string, taskId: *, risk: *, exitCode: *}} input.ctx
|
|
57
|
+
* @returns {Array<Object>}
|
|
58
|
+
*/
|
|
59
|
+
function buildVerifyRecords({ report, metrics = null, ctx } ) {
|
|
60
|
+
const verifyId = ctx.verifyId;
|
|
61
|
+
const runStart = Date.parse(report.startedAt);
|
|
62
|
+
const runEnd = Date.parse(report.finishedAt);
|
|
63
|
+
// Timestamp of a record with no start of its own (a tier with no check, an in-run duplicate):
|
|
64
|
+
// the latest instant seen so far, so line timestamps never go backwards.
|
|
65
|
+
let cursor = Number.isFinite(runStart) ? runStart : undefined;
|
|
66
|
+
// Check stamps come from ONE wall-clock base plus the monotonic clock the durations already use: the
|
|
67
|
+
// first executed check pins `anchor`, and a later check is stamped anchor.wall + (its monotonic start -
|
|
68
|
+
// anchor.mono), so a wall-clock step mid-run neither reorders checks nor collapses them onto one instant.
|
|
69
|
+
// A check with no monotonic start (reused, or a clock that could not be read) keeps its wall start.
|
|
70
|
+
let anchor = null;
|
|
71
|
+
// The latest timestamp written so far: a record with no start of its own sits at the cursor, which
|
|
72
|
+
// advances by a rounded duration and can pass the next real start by a millisecond; no stamp goes back.
|
|
73
|
+
let latest = cursor;
|
|
74
|
+
const stamp = (ms ) => {
|
|
75
|
+
if (ms === undefined) return latest;
|
|
76
|
+
latest = latest === undefined ? ms : Math.max(latest, ms);
|
|
77
|
+
return latest;
|
|
78
|
+
};
|
|
79
|
+
let firstStamp ;
|
|
80
|
+
let checksRun = 0;
|
|
81
|
+
const body = [];
|
|
82
|
+
const commandKeys = new Map ();
|
|
83
|
+
|
|
84
|
+
for (const tier of Array.isArray(report.tiers) ? report.tiers : []) {
|
|
85
|
+
const checks = [];
|
|
86
|
+
let tierMs = null;
|
|
87
|
+
let tierStart ;
|
|
88
|
+
for (const check of Array.isArray(tier.checks) ? tier.checks : []) {
|
|
89
|
+
const timing = check && typeof check === 'object' && metrics ? metrics.get(check) : undefined;
|
|
90
|
+
let durationMs = null;
|
|
91
|
+
let at = cursor;
|
|
92
|
+
let checkKey = null;
|
|
93
|
+
let shared = null;
|
|
94
|
+
let sharedRef = null;
|
|
95
|
+
let tests = null;
|
|
96
|
+
let testsFailed = null;
|
|
97
|
+
let cpuMs = null;
|
|
98
|
+
let resourceBasis = null;
|
|
99
|
+
let failedFirst = null;
|
|
100
|
+
|
|
101
|
+
if (check && check.status === 'NOT_RUN') {
|
|
102
|
+
durationMs = null;
|
|
103
|
+
failedFirst = null;
|
|
104
|
+
} else if (timing) {
|
|
105
|
+
// A follower waited on another run and a reused check (089 IC-007) read a record: neither executed.
|
|
106
|
+
if (timing.shared !== 'follower' && timing.shared !== 'reused') checksRun += 1;
|
|
107
|
+
if (Number.isFinite(timing.startedAt)) {
|
|
108
|
+
at = timing.startedAt;
|
|
109
|
+
if (Number.isFinite(timing.startedMonoMs)) {
|
|
110
|
+
anchor = anchor ?? { wall: timing.startedAt, mono: timing.startedMonoMs };
|
|
111
|
+
at = Math.round(anchor.wall + (timing.startedMonoMs - anchor.mono));
|
|
112
|
+
// Nothing but the run line is written yet when the first check lands before it: lower the floor.
|
|
113
|
+
if (firstStamp === undefined) { firstStamp = at; if (latest !== undefined && at < latest) latest = at; }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (Number.isFinite(timing.durationMs)) durationMs = Math.round(timing.durationMs);
|
|
117
|
+
if (at !== undefined) cursor = Math.max(cursor ?? at, at + (durationMs ?? 0));
|
|
118
|
+
checkKey = timing.checkKey ?? null;
|
|
119
|
+
shared = timing.shared ?? 'none';
|
|
120
|
+
sharedRef = timing.sharedRef ?? null;
|
|
121
|
+
tests = typeof timing.tests === 'number' ? timing.tests : null;
|
|
122
|
+
testsFailed = typeof timing.testsFailed === 'number' ? timing.testsFailed : null;
|
|
123
|
+
cpuMs = typeof timing.cpuMs === 'number' ? timing.cpuMs : null;
|
|
124
|
+
resourceBasis = timing.resourceBasis ?? null;
|
|
125
|
+
failedFirst = timing.failedFirst ?? (check?.failedFirst ? (check.failedFirst.status === 'FAIL' ? 'fail' : 'pass') : 'none');
|
|
126
|
+
if (check && check.command && checkKey) {
|
|
127
|
+
commandKeys.set(check.command, checkKey);
|
|
128
|
+
}
|
|
129
|
+
} else if (check && check.deduplicated) {
|
|
130
|
+
durationMs = 0; // reused the result of an identical check earlier in this run (S-10)
|
|
131
|
+
shared = 'in-run';
|
|
132
|
+
failedFirst = 'none';
|
|
133
|
+
checkKey = (check.command && commandKeys.get(check.command)) ?? null;
|
|
134
|
+
} else if (check) {
|
|
135
|
+
failedFirst = check.failedFirst ? (check.failedFirst.status === 'FAIL' ? 'fail' : 'pass') : 'none';
|
|
136
|
+
}
|
|
137
|
+
if (durationMs !== null) tierMs = (tierMs ?? 0) + durationMs;
|
|
138
|
+
at = stamp(at);
|
|
139
|
+
if (tierStart === undefined) tierStart = at;
|
|
140
|
+
checks.push({
|
|
141
|
+
kind: 'verify.check',
|
|
142
|
+
timestamp: isoAt(at),
|
|
143
|
+
verify_id: verifyId,
|
|
144
|
+
tier: tier.id,
|
|
145
|
+
status: check ? check.status : null,
|
|
146
|
+
exit_code: check ? (check.exitCode ?? null) : null,
|
|
147
|
+
duration_ms: durationMs,
|
|
148
|
+
tests,
|
|
149
|
+
tests_failed: testsFailed,
|
|
150
|
+
cpu_ms: cpuMs,
|
|
151
|
+
resource_basis: resourceBasis,
|
|
152
|
+
check_key: checkKey,
|
|
153
|
+
shared,
|
|
154
|
+
shared_ref: sharedRef,
|
|
155
|
+
failed_first: failedFirst,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const selection = tier.selection || {};
|
|
159
|
+
body.push({
|
|
160
|
+
kind: 'verify.tier',
|
|
161
|
+
timestamp: isoAt(tierStart ?? stamp(cursor)),
|
|
162
|
+
verify_id: verifyId,
|
|
163
|
+
tier: tier.id,
|
|
164
|
+
status: tier.status,
|
|
165
|
+
duration_ms: tierMs, // derived: the sum of its checks (IC-006 basis derived-sum)
|
|
166
|
+
selection_basis: selection.basis ?? null,
|
|
167
|
+
selected: selection.selectedCount ?? null,
|
|
168
|
+
total: selection.totalCount ?? null,
|
|
169
|
+
fallback: selection.fallback ?? null,
|
|
170
|
+
}, ...checks);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const dispatchRun = ctx.dispatchRun ?? ctx.dispatch_run;
|
|
174
|
+
const reg = ctx.registry || getDefaultRegistry();
|
|
175
|
+
const risk = ctx.risk ?? null;
|
|
176
|
+
const level = ctx.level ?? (risk ? levelOf(reg, risk) : null);
|
|
177
|
+
const applies = ctx.v0Applies !== undefined ? Boolean(ctx.v0Applies) : (risk ? v0Applies(reg, risk) : false);
|
|
178
|
+
|
|
179
|
+
let v0 = null;
|
|
180
|
+
if (ctx.source === 'dispatch') {
|
|
181
|
+
v0 = null;
|
|
182
|
+
} else if (ctx.v0 === 'off' || ctx.v0 === false) {
|
|
183
|
+
v0 = 'off';
|
|
184
|
+
} else if (ctx.v0 === 'on' || ctx.v0 === true) {
|
|
185
|
+
v0 = applies ? 'on' : null;
|
|
186
|
+
} else if (ctx.v0 === null) {
|
|
187
|
+
v0 = null;
|
|
188
|
+
} else {
|
|
189
|
+
const isEnabled = v0Enabled(ctx.env || process.env, ctx.configDir);
|
|
190
|
+
if (!isEnabled) {
|
|
191
|
+
v0 = 'off';
|
|
192
|
+
} else {
|
|
193
|
+
v0 = applies ? 'on' : null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const run = {
|
|
198
|
+
kind: 'verify.run',
|
|
199
|
+
timestamp: isoAt(firstStamp !== undefined && Number.isFinite(runStart) ? Math.min(runStart, firstStamp) : runStart),
|
|
200
|
+
verify_id: verifyId,
|
|
201
|
+
source: ctx.source,
|
|
202
|
+
task: typeof ctx.taskId === 'string' && SAFE_TOKEN.test(ctx.taskId) ? ctx.taskId : null,
|
|
203
|
+
dispatch_run: ctx.source === 'dispatch' && typeof dispatchRun === 'string' && DISPATCH_RUN_ID.test(dispatchRun)
|
|
204
|
+
? dispatchRun
|
|
205
|
+
: null,
|
|
206
|
+
risk,
|
|
207
|
+
level,
|
|
208
|
+
status: report.status,
|
|
209
|
+
exit_code: ctx.exitCode ?? null,
|
|
210
|
+
// Wall time between the report's own stamps: the engine clock, not a monotonic one.
|
|
211
|
+
duration_ms: Number.isFinite(runStart) && Number.isFinite(runEnd) ? Math.max(0, runEnd - runStart) : null,
|
|
212
|
+
checks_run: checksRun,
|
|
213
|
+
v0,
|
|
214
|
+
};
|
|
215
|
+
return [run, ...body];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* The directory the writer should walk up from, or null when no project store exists.
|
|
220
|
+
* @param {string} [start]
|
|
221
|
+
* @param {Object} env
|
|
222
|
+
* @param {Object} fsImpl
|
|
223
|
+
* @returns {string|null}
|
|
224
|
+
*/
|
|
225
|
+
function storeStart(start , env , fsImpl ) {
|
|
226
|
+
const at = resolveRunsLocation({ start, env, fsImpl, containing: path.join('state', 'runs') });
|
|
227
|
+
if (at.origin === 'DOFLOW_CONFIG_DIR') return start || process.cwd(); // the writer reads the same variable
|
|
228
|
+
return at.origin === 'project' ? path.dirname(at.configDir) : null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Writes the verify lines for one run. Never throws; any fault writes nothing (NFR-005).
|
|
233
|
+
* @param {Object} input
|
|
234
|
+
* @param {Object} input.report
|
|
235
|
+
* @param {WeakMap} [input.metrics]
|
|
236
|
+
* @param {{source: 'verb'|'dispatch', taskId: *, risk: *, exitCode: *}} input.ctx
|
|
237
|
+
* @param {Object} [input.env=process.env]
|
|
238
|
+
* @param {string} [input.start] the verify's working directory (walk-up origin)
|
|
239
|
+
* @param {Function} [input.now] writer clock; test seam
|
|
240
|
+
* @param {Object} [input.fsImpl] test seam
|
|
241
|
+
*/
|
|
242
|
+
function emitVerifyTelemetry({ report, metrics = null, ctx, env = process.env, start, now, fsImpl = nodeFs } ) {
|
|
243
|
+
try {
|
|
244
|
+
if (telemetryOff(env)) return;
|
|
245
|
+
const from = storeStart(start, env, fsImpl);
|
|
246
|
+
if (from === null) return;
|
|
247
|
+
const records = buildVerifyRecords({ report, metrics, ctx: { env: ctx?.env || env, ...ctx, verifyId: crypto.randomBytes(8).toString('hex') } });
|
|
248
|
+
new TelemetryWriter({ env, fsImpl, start: from, ...(now ? { now } : {}) }).write(records);
|
|
249
|
+
} catch {
|
|
250
|
+
// Telemetry changes no outcome (FR-002, NFR-005).
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = { buildVerifyRecords, emitVerifyTelemetry };
|
|
@@ -20,7 +20,7 @@ const nodeFs = require('node:fs');
|
|
|
20
20
|
/** The guard lane of a `node --test` repository: structural checks that run with every selection. */
|
|
21
21
|
const GUARD_DIR = 'test/guards';
|
|
22
22
|
|
|
23
|
-
const NODE_TEST_FILE = /\.test\.(?:js|mjs|cjs)$/;
|
|
23
|
+
const NODE_TEST_FILE = /\.test\.(?:js|mjs|cjs|ts)$/;
|
|
24
24
|
const PYTEST_FILE = /(?:^|\/)(?:test_[^/]*|[^/]*_test)\.py$/;
|
|
25
25
|
|
|
26
26
|
const SKIPPED_DIRS = new Set(['node_modules', '.git', '.venv', 'venv', '__pycache__']);
|
|
@@ -97,14 +97,37 @@ function conventionFor(found ) {
|
|
|
97
97
|
roots,
|
|
98
98
|
isTest: (file ) => NODE_TEST_FILE.test(file) && underRoot(file),
|
|
99
99
|
guardDir: roots.some((r ) => GUARD_DIR === r || GUARD_DIR.startsWith(`${r}/`)) ? GUARD_DIR : null,
|
|
100
|
+
found,
|
|
100
101
|
};
|
|
101
102
|
}
|
|
102
103
|
if (found && found.source === 'pyproject.toml') {
|
|
103
|
-
return { basis: 'changed-test-files', roots: ['.'], isTest: (file ) => PYTEST_FILE.test(file), guardDir: null };
|
|
104
|
+
return { basis: 'changed-test-files', roots: ['.'], isTest: (file ) => PYTEST_FILE.test(file), guardDir: null, found };
|
|
104
105
|
}
|
|
105
106
|
return null;
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Walks the test files for the toolchain described by `found`.
|
|
111
|
+
* Returns sorted unique repo-relative paths of regular files that satisfy `convention.isTest`.
|
|
112
|
+
* @param {Object} found the detected `testTargeted` entry
|
|
113
|
+
* @param {string} root project root
|
|
114
|
+
* @param {Object} [fsImpl]
|
|
115
|
+
* @returns {Array<string>}
|
|
116
|
+
*/
|
|
117
|
+
function testFilesFor(found , root , fsImpl = nodeFs) {
|
|
118
|
+
const convention = conventionFor(found);
|
|
119
|
+
if (!convention) return [];
|
|
120
|
+
const files = new Set ();
|
|
121
|
+
for (const r of convention.roots) {
|
|
122
|
+
for (const file of walkFiles(fsImpl, root, r)) {
|
|
123
|
+
if (convention.isTest(file) && statOf(fsImpl, path.join(root, file))?.isFile()) {
|
|
124
|
+
files.add(file);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return [...files].sort();
|
|
129
|
+
}
|
|
130
|
+
|
|
108
131
|
/**
|
|
109
132
|
* @param {Object} input
|
|
110
133
|
* @param {Object} input.found the detected `testTargeted` entry (`source`, `testDirs`)
|
|
@@ -126,10 +149,7 @@ function selectChangedTests({ found, observation, root, fsImpl = nodeFs, platfor
|
|
|
126
149
|
const convention = conventionFor(found);
|
|
127
150
|
if (!convention) return null;
|
|
128
151
|
|
|
129
|
-
const total = new Set ();
|
|
130
|
-
for (const r of convention.roots) {
|
|
131
|
-
for (const file of walkFiles(fsImpl, root, r)) if (convention.isTest(file)) total.add(file);
|
|
132
|
-
}
|
|
152
|
+
const total = new Set (testFilesFor(found, root, fsImpl));
|
|
133
153
|
const totalCount = total.size;
|
|
134
154
|
const fallback = (reason , selectedCount = 0) => ({
|
|
135
155
|
pattern: null,
|
|
@@ -169,4 +189,44 @@ function selectChangedTests({ found, observation, root, fsImpl = nodeFs, platfor
|
|
|
169
189
|
};
|
|
170
190
|
}
|
|
171
191
|
|
|
172
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Formats a sorted list of selected test files into a shell-quoted command argument pattern (IC-003).
|
|
194
|
+
* When guardDir is present and files under guardDir are included in selectedTests, they are replaced
|
|
195
|
+
* by a single quoted `${guardDir}/` token.
|
|
196
|
+
*
|
|
197
|
+
* @param {Object} input
|
|
198
|
+
* @param {Array<string>} input.selectedTests repo-relative test paths
|
|
199
|
+
* @param {string|null} [input.guardDir] optional guard directory (e.g. 'test/guards')
|
|
200
|
+
* @param {string} [input.platform] platform for path quoting
|
|
201
|
+
* @returns {string} shell-quoted argument string
|
|
202
|
+
*/
|
|
203
|
+
function formatSelectedTestPattern({
|
|
204
|
+
selectedTests = [],
|
|
205
|
+
guardDir = null,
|
|
206
|
+
platform = process.platform,
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
) {
|
|
212
|
+
const underGuard = (file ) => Boolean(guardDir && (file === guardDir || file.startsWith(`${guardDir}/`)));
|
|
213
|
+
const nonGuard = [...new Set(selectedTests.map(normalize))]
|
|
214
|
+
.filter((f ) => !underGuard(f))
|
|
215
|
+
.sort();
|
|
216
|
+
const asArgument = (file ) => (file.startsWith('-') ? `./${file}` : file);
|
|
217
|
+
const words = nonGuard.map((file ) => quotePath(asArgument(file), platform));
|
|
218
|
+
const hasGuard = Boolean(guardDir && (selectedTests.some(underGuard) || selectedTests.length === 0));
|
|
219
|
+
if (hasGuard) {
|
|
220
|
+
words.push(quotePath(`${guardDir}/`, platform));
|
|
221
|
+
}
|
|
222
|
+
return words.join(' ');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = {
|
|
226
|
+
selectChangedTests,
|
|
227
|
+
formatSelectedTestPattern,
|
|
228
|
+
quotePath,
|
|
229
|
+
GUARD_DIR,
|
|
230
|
+
testFilesFor,
|
|
231
|
+
conventionFor,
|
|
232
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { shippedWorkerIds, knownWorkerIds } = require('../../registry/worker-ids.js');
|
|
4
|
+
const { loadRegistry } = require('../../registry/index.js');
|
|
5
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/** IC-009: resolve the usable workers and in-session permission from workers.enabled. */
|
|
18
|
+
function activeWorkers({ registry, view, env = process.env, projectRoot = null } = {}) {
|
|
19
|
+
const reg = registry || loadRegistry({ repoRoot: REPO_ROOT });
|
|
20
|
+
const known = knownWorkerIds({ registry: reg, view }).ids;
|
|
21
|
+
let res ;
|
|
22
|
+
try {
|
|
23
|
+
const { loadSettings } = require('../settings/resolve.js');
|
|
24
|
+
res = loadSettings({ projectRoot, env: { ...env } }).get('workers.enabled', 'project');
|
|
25
|
+
} catch {
|
|
26
|
+
return {
|
|
27
|
+
state: 'invalid', workers: [], inSession: false, dangling: [],
|
|
28
|
+
origin: { layer: 'default', source: null }, reason: 'unreadable', narrowedFrom: null,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const origin = { layer: res.origin.layer, source: res.origin.source };
|
|
33
|
+
if (res.state === 'invalid') {
|
|
34
|
+
const candidate = res.candidates.find((item ) =>
|
|
35
|
+
item.status === 'invalid' && item.layer === origin.layer && item.source === origin.source);
|
|
36
|
+
return {
|
|
37
|
+
state: 'invalid', workers: [], inSession: false, dangling: [],
|
|
38
|
+
origin, reason: candidate?.reason || 'invalid', narrowedFrom: null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (origin.layer === 'default') {
|
|
42
|
+
return {
|
|
43
|
+
state: 'unset', workers: shippedWorkerIds(reg), inSession: true, dangling: [],
|
|
44
|
+
origin, reason: null, narrowedFrom: null,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const value = res.value ;
|
|
49
|
+
const ids = value.filter((id) => id !== 'primary');
|
|
50
|
+
const user = origin.layer === 'project' || origin.layer === 'local'
|
|
51
|
+
? res.candidates.find((item ) => item.layer === 'user' && item.value !== null && item.status === 'overridden')
|
|
52
|
+
: null;
|
|
53
|
+
return {
|
|
54
|
+
state: 'set',
|
|
55
|
+
workers: ids.filter((id) => known.includes(id)),
|
|
56
|
+
inSession: value.at(-1) === 'primary',
|
|
57
|
+
dangling: ids.filter((id) => !known.includes(id)),
|
|
58
|
+
origin,
|
|
59
|
+
reason: null,
|
|
60
|
+
narrowedFrom: user ? [...user.value] : null,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** IC-009: refuse a worker outside the resolved usable set. */
|
|
65
|
+
function workerGate(active , id ) {
|
|
66
|
+
if (active.workers.includes(id)) return null;
|
|
67
|
+
return {
|
|
68
|
+
reason: active.state === 'invalid' ? 'workers-enabled-invalid' : 'worker-disabled',
|
|
69
|
+
harness: id,
|
|
70
|
+
enabled: [...active.workers],
|
|
71
|
+
origin: active.origin,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** IC-009: refuse in-session work when primary is absent or the list is invalid. */
|
|
76
|
+
function inSessionGate(active ) {
|
|
77
|
+
if (active.inSession) return null;
|
|
78
|
+
if (active.state === 'invalid') {
|
|
79
|
+
return {
|
|
80
|
+
reason: 'workers-enabled-invalid',
|
|
81
|
+
text: 'workers-enabled-invalid: workers.enabled is invalid, do not run in-session',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
reason: 'no-enabled-worker',
|
|
86
|
+
text: 'no-enabled-worker: primary not enabled, do not run in-session',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { activeWorkers, workerGate, inSessionGate };
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Feature 081 C.1 (IC-021, IC-020 end state; DEC-005, DEC-038, DEC-040, DEC-046, DEC-047, DEC-050):
|
|
4
|
+
// the cancel sequence, shared by the `worker --action cancel` command and the supervisor's timeout.
|
|
5
|
+
//
|
|
6
|
+
// In order: (1) refusals before any write or signal, against the calling process's own identity;
|
|
7
|
+
// (2) the `cancelling` transition; (3) the tree escalation, outside every lock; (4) 080's attempt
|
|
8
|
+
// cancel, in process, with the caller's own `env` and `cwd` and no rebuilt identity, a refusal
|
|
9
|
+
// recorded and a throw stored as `disposition: 'error'`; (5) the end, always written once step 2
|
|
10
|
+
// applied. The escalation runs before the 080 cancel, so an 080 failure never leaves the tree alive.
|
|
11
|
+
// A child run (082 IC-015, DEC-028, DEC-041) shares its granter's 080 attempt, so step 4 is skipped
|
|
12
|
+
// for it on the user and timeout paths alike: `attemptCancel: { disposition: 'skipped', reason:
|
|
13
|
+
// 'child-run' }`, and ending a child never cancels its granter's attempt.
|
|
14
|
+
//
|
|
15
|
+
// This module makes no fs write call: every write goes through the run store's `transition`, and
|
|
16
|
+
// the 080 ledger is written only by 080's own `cancel` (G26 (b), IC-027).
|
|
17
|
+
|
|
18
|
+
const fs = require('node:fs');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
const { readRuns, transition, RunStoreError } = require('./run-store.js');
|
|
21
|
+
const processTree = require('./process-tree.js');
|
|
22
|
+
const { runKeyFor, identityRefusal } = require('../task-run.js');
|
|
23
|
+
const attempts = require('../task-attempts.js');
|
|
24
|
+
|
|
25
|
+
/** 080's `SAFE_NAME`, which `--by` must match. */
|
|
26
|
+
const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9._ -]*$/;
|
|
27
|
+
/** A `cancelling` run whose request is younger than this belongs to the canceller that holds it. */
|
|
28
|
+
const CANCEL_TAKEOVER_MS = 30000;
|
|
29
|
+
/** How long a cancel of a launching run waits for its supervisor: its own escalation of a
|
|
30
|
+
* SIGTERM-ignoring worker, both waits and both `ps` bounds (IC-021 step 3, DEC-040, DEC-064). */
|
|
31
|
+
const LAUNCH_WAIT_MS = processTree.KILL_GRACE_MS + processTree.KILL_FINAL_MS + 2 * processTree.PS_TIMEOUT_MS;
|
|
32
|
+
const SOURCES = Object.freeze(['user', 'timeout']);
|
|
33
|
+
|
|
34
|
+
/** Thrown inside a locked transition to abandon it with a refusal and no write. */
|
|
35
|
+
class Refused extends Error {
|
|
36
|
+
|
|
37
|
+
constructor(result ) {
|
|
38
|
+
super(result.reason);
|
|
39
|
+
this.result = result;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const defaultSleep = (ms ) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
44
|
+
|
|
45
|
+
function refused(reason , runId , extra = {}) {
|
|
46
|
+
return { disposition: 'refused', reason, runId, ...extra };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** `<root>/.doflow/state/worker-runs/<slug>.json` gives the lifecycle root and the slug: the 080
|
|
50
|
+
* ledger lives under the same root (data-model.md §1). */
|
|
51
|
+
function ledgerFor(file ) {
|
|
52
|
+
const root = path.resolve(path.dirname(file), '..', '..', '..');
|
|
53
|
+
const slug = path.basename(file, '.json');
|
|
54
|
+
return { root, slug, ledger: attempts.storeFile(root, slug) };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A `cancelling` run whose request is under 30 s old, or null. Pure. */
|
|
58
|
+
function inProgress(run , at ) {
|
|
59
|
+
if (run.phase !== 'cancelling') return null;
|
|
60
|
+
const requested = Date.parse(run.cancel?.requestedAt);
|
|
61
|
+
if (Number.isFinite(requested) && at.getTime() - requested < CANCEL_TAKEOVER_MS) {
|
|
62
|
+
return refused('cancel-in-progress', run.runId, { requestedAt: run.cancel.requestedAt });
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const pidOf = (entry ) => (entry && typeof entry === 'object' ? entry.pid : entry);
|
|
68
|
+
|
|
69
|
+
function union(a = [], b = []) {
|
|
70
|
+
const seen = new Map ();
|
|
71
|
+
for (const entry of [...a, ...b]) if (!seen.has(pidOf(entry))) seen.set(pidOf(entry), entry);
|
|
72
|
+
return [...seen.values()];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* IC-014's add-only rule (DEC-059): a recorded `supervisorCleanup` may only add to `cleanup`. An
|
|
77
|
+
* `unknown` status becomes `verified` only when `supervisorCleanup.status` is `verified`, becomes
|
|
78
|
+
* `survivors` when the supervisor found any, and otherwise stays `unknown` with its own reason; a
|
|
79
|
+
* `verified` one becomes `survivors` when the supervisor found any; the lists are unions.
|
|
80
|
+
*/
|
|
81
|
+
function mergeCleanup(cleanup , supervisorCleanup ) {
|
|
82
|
+
if (!supervisorCleanup) return cleanup;
|
|
83
|
+
const survivors = union(cleanup?.survivors, supervisorCleanup.survivors);
|
|
84
|
+
const zombies = union(cleanup?.zombies, supervisorCleanup.zombies);
|
|
85
|
+
let status ;
|
|
86
|
+
if (survivors.length || cleanup?.status === 'survivors') status = 'survivors';
|
|
87
|
+
else if (cleanup?.status === 'verified' || supervisorCleanup.status === 'verified') status = 'verified';
|
|
88
|
+
else status = 'unknown';
|
|
89
|
+
const reason = status === 'unknown' ? cleanup?.reason ?? supervisorCleanup.reason ?? null : null;
|
|
90
|
+
return { status, reason, survivors, zombies };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Polls the store until the supervisor has exited or recorded `supervisorCleanup`, up to `budgetMs`. */
|
|
94
|
+
async function waitForSupervisor({ file, runId, supervisor, platform, budgetMs, sleep, clock } ) {
|
|
95
|
+
const until = clock().getTime() + budgetMs;
|
|
96
|
+
for (;;) {
|
|
97
|
+
const run = readRuns(file)[runId];
|
|
98
|
+
if (run?.supervisorCleanup) return { run, supervisorAlive: false };
|
|
99
|
+
const live = supervisor ? processTree.alive(supervisor, { platform }) : false;
|
|
100
|
+
if (live === false || clock().getTime() >= until) return { run, supervisorAlive: live };
|
|
101
|
+
await sleep(processTree.POLL_MS);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** IC-021 step 3 for the record step 2 returned. */
|
|
106
|
+
async function endTree({ file, runId, run, platform, sleep, clock, escalate } ) {
|
|
107
|
+
if (platform === 'win32') {
|
|
108
|
+
if (run.worker) await escalate({ proc: run.worker, platform });
|
|
109
|
+
return { status: 'unknown', reason: 'win32', survivors: [], zombies: [] };
|
|
110
|
+
}
|
|
111
|
+
if (run.worker) return escalate({ proc: run.worker, platform });
|
|
112
|
+
// A starting run. With no `launchingAt` the supervisor's launch transition is refused and it
|
|
113
|
+
// spawns nothing (DEC-040).
|
|
114
|
+
if (!run.launchingAt) return { status: 'verified', reason: 'not-launched', survivors: [], zombies: [] };
|
|
115
|
+
// `launchingAt` and no `worker`: the supervisor may already have spawned. Its own `running`
|
|
116
|
+
// transition is refused, so it kills its worker and records `supervisorCleanup`; wait for that,
|
|
117
|
+
// then end the supervisor's tree when it still runs. The verdict stays `unknown` here: only a
|
|
118
|
+
// recorded `supervisorCleanup` resolves it, at step 5.
|
|
119
|
+
const waited = await waitForSupervisor({ file, runId, supervisor: run.supervisor, platform, budgetMs: LAUNCH_WAIT_MS, sleep, clock });
|
|
120
|
+
let survivors = [];
|
|
121
|
+
let zombies = [];
|
|
122
|
+
if (waited.supervisorAlive === true) {
|
|
123
|
+
const outcome = await escalate({ proc: run.supervisor, platform });
|
|
124
|
+
survivors = outcome.survivors;
|
|
125
|
+
zombies = outcome.zombies;
|
|
126
|
+
}
|
|
127
|
+
return { status: 'unknown', reason: 'worker-unrecorded', survivors, zombies };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** 082 IC-015: what step 4 records for a child run, which has no 080 attempt of its own. */
|
|
131
|
+
const CHILD_SKIP = Object.freeze({ disposition: 'skipped', reason: 'child-run' });
|
|
132
|
+
|
|
133
|
+
/** IC-021 step 4: 080's cancel, its refusal recorded and its throw stored (DEC-046). */
|
|
134
|
+
function attemptCancelStep({ file, run, by, reason, env, cwd, clock, cancelImpl } ) {
|
|
135
|
+
const { root, slug, ledger } = ledgerFor(file);
|
|
136
|
+
try {
|
|
137
|
+
const answer = cancelImpl({
|
|
138
|
+
file: ledger, fsImpl: fs, roots: [...new Set([path.resolve(cwd), root])], slug,
|
|
139
|
+
task: run.task, token: run.token, by, reason, env, cwd, now: clock(),
|
|
140
|
+
});
|
|
141
|
+
return { disposition: answer?.disposition ?? 'error', reason: answer?.reason ?? null };
|
|
142
|
+
} catch (error ) {
|
|
143
|
+
return { disposition: 'error', reason: typeof error?.code === 'string' && error.code ? error.code : 'cancel-threw' };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* IC-021 steps 1 to 5. `now` is a clock function (default `() => new Date()`) or a fixed Date.
|
|
149
|
+
* Refusals return `{ disposition: 'refused', reason, runId, ... }` with nothing written or
|
|
150
|
+
* signalled; success returns `{ disposition: 'cancelled', runId, endState, cleanup, attemptCancel,
|
|
151
|
+
* attemptCancelled }`, survivors included. `cancelImpl`, `escalate` and `sleep` default to the real
|
|
152
|
+
* 080 cancel, A.3's escalation and a timer, and exist for in-process tests.
|
|
153
|
+
*/
|
|
154
|
+
async function cancelRun({
|
|
155
|
+
file, runId, by, reason, source = 'user', env = process.env, cwd = process.cwd(), platform = process.platform,
|
|
156
|
+
now = () => new Date(), cancelImpl = attempts.cancel, escalate = processTree.escalate, sleep = defaultSleep,
|
|
157
|
+
} ) {
|
|
158
|
+
if (!by || !SAFE_NAME.test(String(by))) throw new RunStoreError('usage', '--by is required: the name of the agent or person cancelling the run');
|
|
159
|
+
const why = typeof reason === 'string' ? reason.trim() : '';
|
|
160
|
+
if (!why) throw new RunStoreError('usage', '--reason is required: why the run is being cancelled');
|
|
161
|
+
if (!SOURCES.includes(source)) throw new RunStoreError('usage', `source '${source}' is not one of ${SOURCES.join(', ')}`);
|
|
162
|
+
const clock = typeof now === 'function' ? now : () => now;
|
|
163
|
+
|
|
164
|
+
// Step 1: the calling process's own identity, before anything is read for writing.
|
|
165
|
+
const key = runKeyFor(env, cwd);
|
|
166
|
+
const who = identityRefusal(key.identity);
|
|
167
|
+
if (who) return refused(who.reason, runId, { runKey: key.runKey });
|
|
168
|
+
const seen = readRuns(file)[runId];
|
|
169
|
+
if (!seen) return refused('unknown-run', runId);
|
|
170
|
+
if (seen.caller?.runKey !== key.runKey) return refused('foreign-run', runId, { runKey: seen.caller?.runKey ?? null });
|
|
171
|
+
if (seen.phase === 'ended') return refused('run-ended', runId, { endState: seen.end?.state ?? null });
|
|
172
|
+
const early = inProgress(seen, clock());
|
|
173
|
+
if (early) return early;
|
|
174
|
+
|
|
175
|
+
// Step 2: the same checks again under the lock, then `cancelling`.
|
|
176
|
+
const requestedAt = clock();
|
|
177
|
+
let marked ;
|
|
178
|
+
try {
|
|
179
|
+
marked = transition(file, runId, ['starting', 'running', 'cancelling'], (run ) => {
|
|
180
|
+
if (run.caller?.runKey !== key.runKey) throw new Refused(refused('foreign-run', runId, { runKey: run.caller?.runKey ?? null }));
|
|
181
|
+
const busy = inProgress(run, requestedAt);
|
|
182
|
+
if (busy) throw new Refused(busy);
|
|
183
|
+
run.phase = 'cancelling';
|
|
184
|
+
run.cancel = { requestedAt: requestedAt.toISOString(), by: String(by), reason: why, source };
|
|
185
|
+
});
|
|
186
|
+
} catch (error ) {
|
|
187
|
+
if (error instanceof Refused) return error.result;
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
if (!marked.applied) {
|
|
191
|
+
if (marked.reason === 'unknown-run') return refused('unknown-run', runId);
|
|
192
|
+
return refused('run-ended', runId, { endState: marked.run?.end?.state ?? null });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Step 3, outside every lock.
|
|
196
|
+
const cleanup = await endTree({ file, runId, run: marked.run, platform, sleep, clock, escalate });
|
|
197
|
+
|
|
198
|
+
// Step 4, after the tree is ended.
|
|
199
|
+
const attemptCancel = marked.run.childOf
|
|
200
|
+
? { ...CHILD_SKIP }
|
|
201
|
+
: attemptCancelStep({ file, run: marked.run, by: String(by), reason: why, env, cwd, clock, cancelImpl });
|
|
202
|
+
|
|
203
|
+
// Step 5: always written.
|
|
204
|
+
const endState = source === 'timeout' ? 'timed-out' : 'cancelled';
|
|
205
|
+
const ended = transition(file, runId, ['cancelling'], (run ) => {
|
|
206
|
+
run.attemptCancel = attemptCancel;
|
|
207
|
+
run.cleanup = mergeCleanup(cleanup, run.supervisorCleanup);
|
|
208
|
+
run.end = { state: endState, at: clock().toISOString() };
|
|
209
|
+
run.phase = 'ended';
|
|
210
|
+
});
|
|
211
|
+
if (!ended.applied) return refused('run-ended', runId, { endState: ended.run?.end?.state ?? null, cleanup, attemptCancel });
|
|
212
|
+
return {
|
|
213
|
+
disposition: 'cancelled',
|
|
214
|
+
runId,
|
|
215
|
+
endState,
|
|
216
|
+
cleanup: ended.run.cleanup,
|
|
217
|
+
attemptCancel,
|
|
218
|
+
attemptCancelled: attemptCancel.disposition === 'cancelled',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
module.exports = { cancelRun, mergeCleanup, CANCEL_TAKEOVER_MS, LAUNCH_WAIT_MS };
|