@khoavu882/doflow 0.0.0-stage → 2.0.0-beta.3
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/LICENSE +21 -0
- package/README.md +132 -2
- package/bin/doflow.js +44 -0
- package/core/.antigravity-plugin/hooks.json +39 -0
- package/core/.antigravity-plugin/mcp_config.json +18 -0
- package/core/.antigravity-plugin/plugin.json +31 -0
- package/core/.claude-plugin/marketplace.json +20 -0
- package/core/.claude-plugin/plugin.json +21 -0
- package/core/.codex-plugin/marketplace.json +20 -0
- package/core/.codex-plugin/plugin.json +35 -0
- package/core/.plugin/plugin.json +19 -0
- package/core/harnesses/antigravity/hooks/pre-implementation-gate.sh +9 -0
- package/core/harnesses/antigravity/hooks/stop-check.sh +10 -0
- package/core/harnesses/claude/hooks/lib.sh +304 -0
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +20 -0
- package/core/harnesses/claude/hooks/permission-audit.sh +47 -0
- package/core/harnesses/claude/hooks/post-compact.sh +74 -0
- package/core/harnesses/claude/hooks/post-edit-lint.sh +36 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +47 -0
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +20 -0
- package/core/harnesses/claude/hooks/pre-compact.sh +43 -0
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +21 -0
- package/core/harnesses/claude/hooks/session-end.sh +89 -0
- package/core/harnesses/claude/hooks/session-start.sh +5 -0
- package/core/harnesses/claude/hooks/skill-config-audit.sh +39 -0
- package/core/harnesses/claude/hooks/stop-check.sh +5 -0
- package/core/harnesses/claude/hooks/subagent-audit.sh +7 -0
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -0
- package/core/harnesses/claude/settings/keybindings.json +20 -0
- package/core/harnesses/claude/settings/settings.json +201 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +49 -0
- package/core/harnesses/codex/agents/core-implementer.toml +7 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +8 -0
- package/core/harnesses/codex/agents/research-writer.toml +8 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +8 -0
- package/core/harnesses/codex/agents/system-architect.toml +9 -0
- package/core/harnesses/codex/config/config.toml +10 -0
- package/core/harnesses/codex/hooks/hooks.json +122 -0
- package/core/harnesses/codex/hooks/lib.sh +335 -0
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +18 -0
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +52 -0
- package/core/harnesses/codex/hooks/post-edit-lint.sh +6 -0
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +18 -0
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +46 -0
- package/core/harnesses/codex/hooks/pre-compact.sh +9 -0
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +19 -0
- package/core/harnesses/codex/hooks/session-end.impl.sh +92 -0
- package/core/harnesses/codex/hooks/session-end.sh +5 -0
- package/core/harnesses/codex/hooks/session-start.sh +5 -0
- package/core/harnesses/codex/hooks/stop-check.sh +8 -0
- package/core/harnesses/codex/hooks/subagent-audit.sh +8 -0
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +6 -0
- package/core/harnesses/gemini/hooks/hooks.json +77 -0
- package/core/harnesses/gemini/hooks/lib.sh +302 -0
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +39 -0
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +21 -0
- package/core/harnesses/gemini/hooks/pre-compact.sh +38 -0
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +22 -0
- package/core/harnesses/gemini/hooks/session-end.impl.sh +89 -0
- package/core/harnesses/gemini/hooks/session-end.sh +5 -0
- package/core/harnesses/gemini/hooks/session-start.sh +5 -0
- package/core/harnesses/gemini/settings/adapter-defaults.json +4 -0
- package/core/harnesses/kiro/agents/doflow.json +58 -0
- package/core/harnesses/kiro/hooks/doflow.json +53 -0
- package/core/harnesses/kiro/hooks/lib.sh +324 -0
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +9 -0
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +9 -0
- package/core/harnesses/kiro/hooks/session-start.sh +9 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +43 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +39 -0
- package/core/harnesses/shared/hooks/adapters/antigravity.js +111 -0
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +19 -0
- package/core/harnesses/shared/hooks/adapters/gemini.js +114 -0
- package/core/harnesses/shared/hooks/hooks.json +47 -0
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +91 -0
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +97 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +29 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +501 -0
- package/core/harnesses/shared/hooks/policies/mcp-policy.conf +21 -0
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +91 -0
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +328 -0
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +496 -0
- package/core/harnesses/shared/hooks/policies/prompt-nudge.jq +131 -0
- package/core/harnesses/shared/hooks/policies/session-context.sh +141 -0
- package/core/harnesses/shared/hooks/policies/stop-check.sh +264 -0
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +46 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +235 -0
- package/core/harnesses/shared/hooks/stream-hook-runner.js +590 -0
- package/core/harnesses/shared/locator/doflow-run +87 -0
- package/core/registry/advice.json +101 -0
- package/core/registry/assets.json +792 -0
- package/core/registry/capabilities.json +120 -0
- package/core/registry/contracts.json +189 -0
- package/core/registry/external-tools.json +191 -0
- package/core/registry/harnesses.json +2062 -0
- package/core/registry/lifecycle.json +231 -0
- package/core/registry/mcp.json +18 -0
- package/core/registry/models.json +79 -0
- package/core/registry/readiness-templates.json +118 -0
- package/core/registry/routes.json +47 -0
- package/core/registry/storage.json +1057 -0
- package/core/registry/verification.json +258 -0
- package/core/registry/workflows.json +790 -0
- package/core/shared/agent-specs/core-implementer.md +73 -0
- package/core/shared/agent-specs/quality-guardian.md +28 -0
- package/core/shared/agent-specs/research-writer.md +31 -0
- package/core/shared/agent-specs/spec-analyst.md +33 -0
- package/core/shared/agent-specs/system-architect.md +29 -0
- package/core/shared/guidance/DOFLOW_CORE.md +8 -0
- package/core/shared/guidance/FLAGS.md +25 -0
- package/core/shared/guidance/PRINCIPLES.md +61 -0
- package/core/shared/guidance/VERSION +1 -0
- package/core/shared/guidance/mcp/MCP_Context7.md +18 -0
- package/core/shared/guidance/mcp/MCP_Sequential.md +17 -0
- package/core/shared/guidance/modes/MODE_Brainstorming.md +46 -0
- package/core/shared/guidance/modes/MODE_DeepResearch.md +69 -0
- package/core/shared/guidance/modes/MODE_Introspection.md +33 -0
- package/core/shared/guidance/modes/MODE_Orchestration.md +57 -0
- package/core/shared/guidance/modes/MODE_Task_Management.md +74 -0
- package/core/shared/guidance/pointers/claude-gemini.md +2 -0
- package/core/shared/guidance/pointers/codex.md +6 -0
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +373 -0
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +104 -0
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +47 -0
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +161 -0
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +87 -0
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +93 -0
- package/core/shared/guidance/references/MODEL_SELECTION.md +74 -0
- package/core/shared/guidance/references/ORCHESTRATION.md +36 -0
- package/core/shared/guidance/references/RESEARCH_CONFIG.md +21 -0
- package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +81 -0
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +25 -0
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +23 -0
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +14 -0
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +46 -0
- package/core/shared/scripts/doflow/assets/mermaid-theme.json +46 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +167 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +546 -0
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +208 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +347 -0
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +66 -0
- package/core/shared/scripts/doflow/bash/do-review-package.sh +127 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +698 -0
- package/core/shared/scripts/doflow/bash/render-audit.sh +224 -0
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +177 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +223 -0
- package/core/shared/scripts/doflow/bash/sync-context.sh +66 -0
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +526 -0
- package/core/shared/scripts/doflow/bin/doflow-run +592 -0
- package/core/shared/skills/do/SKILL.md +128 -0
- package/core/shared/skills/do/references/estimation.md +13 -0
- package/core/shared/skills/do/references/pm_routing.md +25 -0
- package/core/shared/skills/do/references/tool_matrix.md +52 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +252 -0
- package/core/shared/skills/do-code-review/README.md +49 -0
- package/core/shared/skills/do-code-review/SKILL.md +333 -0
- package/core/shared/skills/do-code-review/assets/sample_c_clean.c +71 -0
- package/core/shared/skills/do-code-review/assets/sample_c_smells.c +66 -0
- package/core/shared/skills/do-code-review/assets/sample_cpp_shift.cpp +19 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_clean.cs +100 -0
- package/core/shared/skills/do-code-review/assets/sample_csharp_smells.cs +78 -0
- package/core/shared/skills/do-code-review/assets/sample_java_clean.java +55 -0
- package/core/shared/skills/do-code-review/assets/sample_java_smells.java +56 -0
- package/core/shared/skills/do-code-review/assets/sample_json_clean.json +7 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_clean.md +30 -0
- package/core/shared/skills/do-code-review/assets/sample_markdown_smells.md +29 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_heredoc.sh +38 -0
- package/core/shared/skills/do-code-review/assets/sample_shell_smells.sh +15 -0
- package/core/shared/skills/do-code-review/assets/sample_typescript_strings.ts +36 -0
- package/core/shared/skills/do-code-review/assets/sample_yaml_smells.yaml +6 -0
- package/core/shared/skills/do-code-review/content-types/config.md +83 -0
- package/core/shared/skills/do-code-review/content-types/markdown.md +132 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_clean_quality.json +79 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_c_smells_quality.json +169 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_cpp_shift_quality.json +49 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_clean_quality.json +149 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_smells_quality.json +157 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_clean_quality.json +67 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_java_smells_quality.json +97 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_json_clean_quality.json +33 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_clean_quality.json +1 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_smells_quality.json +16 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_heredoc_quality.json +54 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_shell_smells_quality.json +91 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_typescript_strings_quality.json +60 -0
- package/core/shared/skills/do-code-review/expected_outputs/sample_yaml_smells_quality.json +46 -0
- package/core/shared/skills/do-code-review/languages/c.md +103 -0
- package/core/shared/skills/do-code-review/languages/cpp.md +103 -0
- package/core/shared/skills/do-code-review/languages/csharp.md +97 -0
- package/core/shared/skills/do-code-review/languages/dart.md +108 -0
- package/core/shared/skills/do-code-review/languages/go.md +92 -0
- package/core/shared/skills/do-code-review/languages/java.md +103 -0
- package/core/shared/skills/do-code-review/languages/kotlin.md +88 -0
- package/core/shared/skills/do-code-review/languages/php.md +109 -0
- package/core/shared/skills/do-code-review/languages/python.md +94 -0
- package/core/shared/skills/do-code-review/languages/ruby.md +105 -0
- package/core/shared/skills/do-code-review/languages/rust.md +99 -0
- package/core/shared/skills/do-code-review/languages/shell.md +90 -0
- package/core/shared/skills/do-code-review/languages/swift.md +88 -0
- package/core/shared/skills/do-code-review/languages/typescript.md +97 -0
- package/core/shared/skills/do-code-review/review-policy.json +47 -0
- package/core/shared/skills/do-code-review/rules/universal.md +49 -0
- package/core/shared/skills/do-code-review/scripts/code_quality_checker.py +1696 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +532 -0
- package/core/shared/skills/do-code-review/scripts/pr_analyzer.py +620 -0
- package/core/shared/skills/do-code-review/scripts/review_report_generator.py +505 -0
- package/core/shared/skills/do-constitution/SKILL.md +72 -0
- package/core/shared/skills/do-design/SKILL.md +212 -0
- package/core/shared/skills/do-diagnose/SKILL.md +156 -0
- package/core/shared/skills/do-diagnose/references/code_audit.md +15 -0
- package/core/shared/skills/do-diagnose/references/refactoring.md +12 -0
- package/core/shared/skills/do-diagnose/references/root_cause.md +17 -0
- package/core/shared/skills/do-document/SKILL.md +81 -0
- package/core/shared/skills/do-document/references/api-reference.md +42 -0
- package/core/shared/skills/do-document/references/deep_research.md +18 -0
- package/core/shared/skills/do-document/references/embedded-research.md +11 -0
- package/core/shared/skills/do-document/references/implementation-flow.md +105 -0
- package/core/shared/skills/do-document/references/index_generator.md +9 -0
- package/core/shared/skills/do-document/references/user-guide.md +22 -0
- package/core/shared/skills/do-execute-plan/SKILL.md +261 -0
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +57 -0
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +92 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +155 -0
- package/core/shared/skills/do-execute-plan/references/run_position.md +63 -0
- package/core/shared/skills/do-execute-plan/references/scaffold.md +441 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +41 -0
- package/core/shared/skills/do-flow/SKILL.md +186 -0
- package/core/shared/skills/do-flow/references/task_classes.md +134 -0
- package/core/shared/skills/do-git/SKILL.md +115 -0
- package/core/shared/skills/do-git/references/hotfix-ritual.md +44 -0
- package/core/shared/skills/do-git/references/lifecycle-policy.md +54 -0
- package/core/shared/skills/do-git/references/release-ritual.md +64 -0
- package/core/shared/skills/do-implement/SKILL.md +222 -0
- package/core/shared/skills/do-plan/SKILL.md +219 -0
- package/core/shared/skills/do-test/SKILL.md +113 -0
- package/core/shared/templates/doflow/constitution-template.md +28 -0
- package/core/shared/templates/doflow/data-model-template.md +169 -0
- package/core/shared/templates/doflow/design-template.md +193 -0
- package/core/shared/templates/doflow/external-contract-template.md +35 -0
- package/core/shared/templates/doflow/intent-template.md +45 -0
- package/core/shared/templates/doflow/plan-template.md +151 -0
- package/core/shared/templates/doflow/question-log-template.md +51 -0
- package/core/shared/templates/doflow/requirement-template.md +126 -0
- package/core/shared/templates/doflow/specs-template.md +55 -0
- package/core/shared/templates/doflow/state-template.md +59 -0
- package/core/shared/workflows/do-flow-chain.md +17 -0
- package/package.json +46 -4
- package/src/adapters/antigravity/index.ts +631 -0
- package/src/adapters/claude/index.ts +548 -0
- package/src/adapters/claude/mcp.ts +94 -0
- package/src/adapters/codex/agents.ts +208 -0
- package/src/adapters/codex/config.ts +272 -0
- package/src/adapters/codex/hooks.ts +203 -0
- package/src/adapters/codex/index.ts +707 -0
- package/src/adapters/codex/mcp.ts +271 -0
- package/src/adapters/codex/native-projection.ts +34 -0
- package/src/adapters/copilot/index.ts +413 -0
- package/src/adapters/copy-tree.ts +620 -0
- package/src/adapters/gemini/hooks.ts +210 -0
- package/src/adapters/gemini/index.ts +390 -0
- package/src/adapters/hook-commands.ts +60 -0
- package/src/adapters/index.ts +106 -0
- package/src/adapters/kiro/index.ts +310 -0
- package/src/adapters/mcp-entries.ts +331 -0
- package/src/adapters/opencode/index.ts +466 -0
- package/src/adapters/pi/index.ts +279 -0
- package/src/adapters/pi/json-members.ts +185 -0
- package/src/adapters/pi/mcp.ts +373 -0
- package/src/adapters/resolve-source.ts +19 -0
- package/src/cli/commands/install.ts +218 -0
- package/src/cli/commands/list-backups.ts +21 -0
- package/src/cli/commands/reconcile.ts +120 -0
- package/src/cli/commands/remove.ts +82 -0
- package/src/cli/commands/rollback.ts +172 -0
- package/src/cli/commands/self-update.ts +33 -0
- package/src/cli/commands/status.ts +138 -0
- package/src/cli/commands/tools.ts +92 -0
- package/src/cli/commands/update.ts +162 -0
- package/src/cli/index.ts +568 -0
- package/src/cli/runtime-commands.ts +281 -0
- package/src/cli/shared.ts +218 -0
- package/src/helper/bash-helper.ts +34 -0
- package/src/helper/git.ts +24 -0
- package/src/helper/guardrails.ts +49 -0
- package/src/helper/harness-paths.ts +140 -0
- package/src/helper/marker-merge.ts +116 -0
- package/src/helper/prompt.ts +61 -0
- package/src/helper/repo-root.ts +29 -0
- package/src/helper/settings-merge.ts +151 -0
- package/src/helper/settings-scope.ts +33 -0
- package/src/helper/toml.ts +244 -0
- package/src/install/backup-set.ts +149 -0
- package/src/install/backup.ts +724 -0
- package/src/install/context.ts +58 -0
- package/src/install/manifest.ts +145 -0
- package/src/install/mcp.ts +254 -0
- package/src/install/paths.ts +84 -0
- package/src/install/targets.ts +90 -0
- package/src/install/tool-lifecycle.ts +151 -0
- package/src/lifecycle/bash-availability.ts +25 -0
- package/src/lifecycle/index.ts +812 -0
- package/src/lifecycle/mcp-index.ts +28 -0
- package/src/lifecycle/policies.ts +73 -0
- package/src/lifecycle/view.ts +175 -0
- package/src/registry/index.ts +611 -0
- package/src/release/sync-plugin-versions.ts +66 -0
- package/src/runtime/advice/cli.ts +283 -0
- package/src/runtime/advice/client.ts +324 -0
- package/src/runtime/advice/config.ts +189 -0
- package/src/runtime/advice/doctor.ts +169 -0
- package/src/runtime/advice/endpoint-guard.ts +134 -0
- package/src/runtime/advice/kinds.ts +154 -0
- package/src/runtime/advice/providers/ollama-systemone.ts +209 -0
- package/src/runtime/advice/record.ts +166 -0
- package/src/runtime/advice/registry.ts +170 -0
- package/src/runtime/advice/strength.ts +66 -0
- package/src/runtime/brief-tools.ts +342 -0
- package/src/runtime/c4-project.ts +371 -0
- package/src/runtime/capability-router.ts +484 -0
- package/src/runtime/checkouts.ts +170 -0
- package/src/runtime/claims.ts +468 -0
- package/src/runtime/cli-result.ts +55 -0
- package/src/runtime/cli.ts +678 -0
- package/src/runtime/command-detect.ts +516 -0
- package/src/runtime/context-pack.ts +492 -0
- package/src/runtime/decision-register.ts +493 -0
- package/src/runtime/evidence-ledger.ts +338 -0
- package/src/runtime/failure/capture.ts +272 -0
- package/src/runtime/failure/classifier.ts +68 -0
- package/src/runtime/failure/cli.ts +178 -0
- package/src/runtime/failure/home.ts +64 -0
- package/src/runtime/failure/store.ts +226 -0
- package/src/runtime/feature-resolve.ts +79 -0
- package/src/runtime/freshness.ts +177 -0
- package/src/runtime/health.ts +863 -0
- package/src/runtime/history-compactor.ts +237 -0
- package/src/runtime/implementation-gate.ts +321 -0
- package/src/runtime/inventory/coverage.ts +218 -0
- package/src/runtime/inventory/identity.ts +65 -0
- package/src/runtime/inventory/index.ts +705 -0
- package/src/runtime/inventory/precedence.ts +134 -0
- package/src/runtime/inventory/read-scopes.ts +131 -0
- package/src/runtime/inventory/siblings.ts +192 -0
- package/src/runtime/knowledge/chunker.ts +124 -0
- package/src/runtime/knowledge/hybrid.ts +149 -0
- package/src/runtime/knowledge/index-store.ts +145 -0
- package/src/runtime/knowledge/retrieval.ts +194 -0
- package/src/runtime/leak-scan.ts +185 -0
- package/src/runtime/lifecycle/cli.ts +276 -0
- package/src/runtime/lifecycle/event-store.ts +340 -0
- package/src/runtime/lifecycle/fold.ts +353 -0
- package/src/runtime/lifecycle/followup.ts +432 -0
- package/src/runtime/lifecycle/goal.ts +249 -0
- package/src/runtime/lifecycle/intent-writer.ts +86 -0
- package/src/runtime/lifecycle/overview.ts +292 -0
- package/src/runtime/lifecycle/release.ts +241 -0
- package/src/runtime/lifecycle/report-store.ts +221 -0
- package/src/runtime/lifecycle/retention.ts +212 -0
- package/src/runtime/lifecycle/root.ts +71 -0
- package/src/runtime/lifecycle/status.ts +353 -0
- package/src/runtime/lifecycle/store-upkeep.ts +393 -0
- package/src/runtime/locator-resolve.ts +158 -0
- package/src/runtime/mask.ts +210 -0
- package/src/runtime/mcp-consultations.ts +412 -0
- package/src/runtime/mcp-reach.ts +157 -0
- package/src/runtime/model-router.ts +153 -0
- package/src/runtime/outcome.ts +467 -0
- package/src/runtime/reach.ts +148 -0
- package/src/runtime/readiness-record.ts +150 -0
- package/src/runtime/readiness.ts +435 -0
- package/src/runtime/recovery.ts +282 -0
- package/src/runtime/research-request.ts +231 -0
- package/src/runtime/retrieval-plan.ts +578 -0
- package/src/runtime/scaffold/artifacts.ts +217 -0
- package/src/runtime/scaffold/fingerprint.ts +56 -0
- package/src/runtime/scaffold/generate.ts +911 -0
- package/src/runtime/scaffold/languages.ts +245 -0
- package/src/runtime/storage/cli.ts +64 -0
- package/src/runtime/storage/engine.ts +249 -0
- package/src/runtime/storage/registry.ts +130 -0
- package/src/runtime/storage/roots.ts +63 -0
- package/src/runtime/storage/rules.ts +282 -0
- package/src/runtime/storage/upkeep.ts +243 -0
- package/src/runtime/storage/work-status.ts +326 -0
- package/src/runtime/task-attempts.ts +415 -0
- package/src/runtime/task-classifier.ts +550 -0
- package/src/runtime/task-scope.ts +130 -0
- package/src/runtime/task-state.ts +176 -0
- package/src/runtime/trace/indicators.ts +235 -0
- package/src/runtime/trace/ledger.ts +626 -0
- package/src/runtime/trace/render.ts +76 -0
- package/src/runtime/trace/views.ts +459 -0
- package/src/runtime/verification/contract-runner.ts +341 -0
- package/src/runtime/verification/engine.ts +1461 -0
- package/src/runtime/verification/registry.ts +130 -0
- package/src/runtime/verification/scope-bound.ts +418 -0
- package/src/runtime/verification/test-selection.ts +172 -0
- package/src/runtime/workflow-engine.ts +588 -0
- package/src/runtime/workflow-orchestrator.ts +953 -0
- package/src/runtime/worktree.ts +340 -0
- package/src/state/index.ts +193 -0
- package/src/state/lockfile.ts +136 -0
- package/src/state/run-lock.ts +408 -0
- package/types/node-commonjs.d.ts +68 -0
- package/types/runtime.d.ts +91 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* DoFlow Cross-Harness Hook Runner (design.md C2) — front door for every in-scope lifecycle
|
|
6
|
+
* event (SessionStart, PreToolUse, PostToolUse, Stop, PreInvocation) for the two harnesses whose
|
|
7
|
+
* native hook contract speaks JSON stdin/stdout rather than a bare exit code: Gemini CLI and
|
|
8
|
+
* Antigravity. It owns event dispatch and per-harness payload/decision translation (delegated to
|
|
9
|
+
* adapters/{gemini,antigravity}.js, D2) and delegates every policy decision to the Canonical
|
|
10
|
+
* Policy Library (core/harnesses/shared/hooks/policies/*.sh, Phase A) — it implements no guard
|
|
11
|
+
* policy logic itself.
|
|
12
|
+
*
|
|
13
|
+
* Invocation: `node stream-hook-runner.js [EventName]`, JSON payload on stdin, JSON decision on
|
|
14
|
+
* stdout. EventName is the preferred way to select which canonical policy applies; when omitted
|
|
15
|
+
* (older hooks.json wiring that doesn't yet pass it), the event is inferred from the payload's
|
|
16
|
+
* own shape as a fallback, matching this runner's behavior before event names were threaded
|
|
17
|
+
* through argv.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const { execFileSync } = require('node:child_process');
|
|
21
|
+
const fs = require('node:fs');
|
|
22
|
+
const os = require('node:os');
|
|
23
|
+
const path = require('node:path');
|
|
24
|
+
|
|
25
|
+
const geminiAdapter = require('./adapters/gemini');
|
|
26
|
+
const antigravityAdapter = require('./adapters/antigravity');
|
|
27
|
+
|
|
28
|
+
// Tool-name classification for PreToolUse, generalized to the union every Phase A canonical
|
|
29
|
+
// script's own case statement already recognizes (session-context.sh/pre-implementation-gate.sh/
|
|
30
|
+
// mcp-tool-guard.sh/pre-bash-guard.sh headers) rather than only the two harnesses this runner
|
|
31
|
+
// drives — kept in sync with those scripts, not redefined independently of them.
|
|
32
|
+
const MCP_TOOL_PATTERN = /^mcp_/i;
|
|
33
|
+
const EDIT_TOOL_PATTERN = /^(replace_file_content|write_to_file|multi_replace_file_content|edit_file|write_file|create_file|replace_file|replace|Edit|Write|MultiEdit|apply_patch)$/i;
|
|
34
|
+
const COMMAND_TOOL_PATTERN = /^(run_command|run_shell_command|Bash|bash)$/i;
|
|
35
|
+
|
|
36
|
+
// ── Failure capture (feature 046: IC-010, IC-011, IC-014, IC-015, IC-018) ─────────────────────────
|
|
37
|
+
//
|
|
38
|
+
// Hook files are installed without DoFlow's runtime library, so this runner carries its own copy of
|
|
39
|
+
// the IC-010 classifier and of the Node failure writer (src/runtime/failure/{classifier,home,capture}.js);
|
|
40
|
+
// a test runs both classifier copies over one fixture set and requires equal answers. Capture is a
|
|
41
|
+
// side channel at exactly two points, never changes the runner's stdout, stderr or exit status,
|
|
42
|
+
// never throws, and is a no-op while capture is off or when no failure home can be resolved. A line
|
|
43
|
+
// holds only names from DoFlow's own code and the working directory, never the payload.
|
|
44
|
+
|
|
45
|
+
const CAPTURE_MAX_LINE_BYTES = 2048;
|
|
46
|
+
const CAPTURE_ROTATE_AT_BYTES = 1048576;
|
|
47
|
+
const CAPTURE_KEEP_ROTATED = 4;
|
|
48
|
+
const CAPTURE_OFF_VALUES = ['off', '0', 'false', 'no'];
|
|
49
|
+
const ROTATED_FILE = /^events-\d{8}T\d{6}Z-\d+\.jsonl$/;
|
|
50
|
+
const PROGRAMMING_ERROR_NAMES = ['TypeError', 'RangeError', 'ReferenceError'];
|
|
51
|
+
|
|
52
|
+
/** Reads one property without ever throwing (a throwing getter reads as undefined). */
|
|
53
|
+
function readProp(error, key) {
|
|
54
|
+
try { return error[key]; } catch { return undefined; }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isInstance(error, Class) {
|
|
58
|
+
try { return error instanceof Class; } catch { return false; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** IC-010. Pure and total: never throws whatever it is handed. Kept equal to src/runtime/failure/classifier.js. */
|
|
62
|
+
function isProgrammingError(error) {
|
|
63
|
+
try {
|
|
64
|
+
if (error === null || typeof error !== 'object') return false;
|
|
65
|
+
const code = readProp(error, 'code');
|
|
66
|
+
const name = readProp(error, 'name');
|
|
67
|
+
if (code === 'EPIPE') return false;
|
|
68
|
+
if (isInstance(error, SyntaxError) || name === 'SyntaxError') return false;
|
|
69
|
+
if (isInstance(error, TypeError) || isInstance(error, RangeError) || isInstance(error, ReferenceError)) return true;
|
|
70
|
+
if (PROGRAMMING_ERROR_NAMES.includes(name)) return true;
|
|
71
|
+
if (name === 'AssertionError' || code === 'ERR_ASSERTION') return true;
|
|
72
|
+
if (code === 'MODULE_NOT_FOUND') return true;
|
|
73
|
+
return typeof code === 'string' && (typeof readProp(error, 'syscall') === 'string' || typeof readProp(error, 'errno') === 'number');
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** IC-011: `$XDG_CONFIG_HOME/doflow/failures` when absolute, else `$HOME/.config/doflow/failures` when HOME is absolute, else null. */
|
|
80
|
+
function failureHome(env) {
|
|
81
|
+
const xdg = env.XDG_CONFIG_HOME;
|
|
82
|
+
if (typeof xdg === 'string' && xdg !== '') return path.isAbsolute(xdg) ? path.join(xdg, 'doflow', 'failures') : null;
|
|
83
|
+
const home = env.HOME;
|
|
84
|
+
return typeof home === 'string' && path.isAbsolute(home) ? path.join(home, '.config', 'doflow', 'failures') : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** IC-014: the environment first, then one stat of the sentinel file. */
|
|
88
|
+
function captureIsOff(home, env) {
|
|
89
|
+
const setting = env.DOFLOW_FAILURE_CAPTURE;
|
|
90
|
+
if (typeof setting === 'string' && CAPTURE_OFF_VALUES.includes(setting.trim().toLowerCase())) return true;
|
|
91
|
+
try { return fs.statSync(path.join(home, 'off')).isFile(); } catch { return false; }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The first stack frame inside this hooks folder as `<path relative to it>:<function>`, no line number. */
|
|
95
|
+
function runnerFrame(error) {
|
|
96
|
+
try {
|
|
97
|
+
const root = __dirname + path.sep;
|
|
98
|
+
for (const line of String(error.stack || '').split('\n')) {
|
|
99
|
+
const match = /^\s*at (?:async )?(?:(.+?) \()?(.+?):\d+:\d+\)?$/.exec(line);
|
|
100
|
+
if (!match) continue;
|
|
101
|
+
const file = match[2].replace(/^file:\/\//, '');
|
|
102
|
+
if (!file.startsWith(root) || file.includes(`${path.sep}node_modules${path.sep}`)) continue;
|
|
103
|
+
const fn = (match[1] || '').replace(/^new /, '').split('.').pop().replace(/\s.*$/, '') || '<anonymous>';
|
|
104
|
+
return `${path.relative(__dirname, file).split(path.sep).join('/')}:${fn}`;
|
|
105
|
+
}
|
|
106
|
+
} catch { /* no frame */ }
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The package version in a checkout, else `script_version` of the nearest install manifest, else `unknown`. */
|
|
111
|
+
function hookVersion(env) {
|
|
112
|
+
const valid = (v) => (typeof v === 'string' && /^[A-Za-z0-9._+-]{1,40}$/.test(v) ? v : null);
|
|
113
|
+
try {
|
|
114
|
+
const pkg = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', '..', '..', '..', 'package.json'), 'utf8'));
|
|
115
|
+
if (/doflow$/.test(pkg.name) && valid(pkg.version)) return pkg.version;
|
|
116
|
+
} catch { /* not a checkout */ }
|
|
117
|
+
const starts = [__dirname];
|
|
118
|
+
if (typeof env.HOME === 'string' && path.isAbsolute(env.HOME)) starts.push(env.HOME);
|
|
119
|
+
for (const start of starts) {
|
|
120
|
+
for (let dir = start; ; dir = path.dirname(dir)) {
|
|
121
|
+
try {
|
|
122
|
+
const version = valid(JSON.parse(fs.readFileSync(path.join(dir, '.doflow', '.install-manifest.json'), 'utf8')).script_version);
|
|
123
|
+
if (version) return version;
|
|
124
|
+
} catch { /* none here */ }
|
|
125
|
+
if (path.dirname(dir) === dir || start === env.HOME) break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return 'unknown';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* IC-015: at 1 MiB the live file is renamed and only the newest four full rotated files are kept. A
|
|
133
|
+
* rename that took a not-full file (a lost race) is linked back as the live file, or kept as a small
|
|
134
|
+
* rotated file that never counts toward the four and is removed after an hour. Same rules as
|
|
135
|
+
* src/runtime/failure/capture.js.
|
|
136
|
+
*/
|
|
137
|
+
function rotateFailures(home) {
|
|
138
|
+
const live = path.join(home, 'events.jsonl');
|
|
139
|
+
try {
|
|
140
|
+
if (fs.statSync(live).size < CAPTURE_ROTATE_AT_BYTES) return;
|
|
141
|
+
} catch { return; }
|
|
142
|
+
// Never rename onto an earlier rotated file of this process (same second, same pid): take the next free second.
|
|
143
|
+
const stampAt = (ms) => new Date(ms).toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
|
|
144
|
+
let rotatedPath = path.join(home, `events-${stampAt(Date.now())}-${process.pid}.jsonl`);
|
|
145
|
+
for (let i = 1; i < 120 && fs.existsSync(rotatedPath); i++) {
|
|
146
|
+
rotatedPath = path.join(home, `events-${stampAt(Date.now() + i * 1000)}-${process.pid}.jsonl`);
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
fs.renameSync(live, rotatedPath);
|
|
150
|
+
} catch { return; }
|
|
151
|
+
try {
|
|
152
|
+
if (fs.statSync(rotatedPath).size < CAPTURE_ROTATE_AT_BYTES) {
|
|
153
|
+
try { fs.linkSync(rotatedPath, live); fs.unlinkSync(rotatedPath); } catch { /* the live file exists again: the small file stays */ }
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
} catch { return; }
|
|
157
|
+
try {
|
|
158
|
+
const files = fs.readdirSync(home).filter((name) => ROTATED_FILE.test(name)).map((name) => {
|
|
159
|
+
let size = 0;
|
|
160
|
+
let mtime = 0;
|
|
161
|
+
try { const st = fs.statSync(path.join(home, name)); size = st.size; mtime = st.mtimeMs; } catch { /* gone */ }
|
|
162
|
+
return { name, size, mtime };
|
|
163
|
+
}).sort((x, y) => x.mtime - y.mtime || (x.name < y.name ? -1 : x.name > y.name ? 1 : 0));
|
|
164
|
+
const full = files.filter((f) => f.size >= CAPTURE_ROTATE_AT_BYTES);
|
|
165
|
+
const doomed = [
|
|
166
|
+
...full.slice(0, Math.max(0, full.length - CAPTURE_KEEP_ROTATED)),
|
|
167
|
+
...files.filter((f) => f.size < CAPTURE_ROTATE_AT_BYTES && Date.now() - f.mtime > 60 * 60 * 1000),
|
|
168
|
+
];
|
|
169
|
+
for (const f of doomed) {
|
|
170
|
+
try { fs.unlinkSync(path.join(home, f.name)); } catch { /* ignored */ }
|
|
171
|
+
}
|
|
172
|
+
} catch { /* ignored */ }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The working directory with the home prefix as `~`, matching HOME as given, its real path and
|
|
177
|
+
* `os.homedir()`; under no known home, when HOME is not usable, only the folder name. Same rule as
|
|
178
|
+
* projectName in src/runtime/failure/capture.js (this file ships without src/).
|
|
179
|
+
*/
|
|
180
|
+
function hookProject(cwd, env) {
|
|
181
|
+
const homes = [];
|
|
182
|
+
const add = (value) => {
|
|
183
|
+
if (typeof value !== 'string' || !path.isAbsolute(value)) return;
|
|
184
|
+
const home = value.replace(/\/+$/, '');
|
|
185
|
+
if (home.length > 1 && !homes.includes(home)) homes.push(home);
|
|
186
|
+
};
|
|
187
|
+
add(env.HOME);
|
|
188
|
+
if (typeof env.HOME === 'string' && path.isAbsolute(env.HOME)) { try { add(fs.realpathSync(env.HOME)); } catch { /* HOME does not exist */ } }
|
|
189
|
+
try { add(os.homedir()); add(fs.realpathSync(os.homedir())); } catch { /* no home known to the system */ }
|
|
190
|
+
const home = homes.find((h) => cwd === h || cwd.startsWith(`${h}/`));
|
|
191
|
+
if (home) return `~${cwd.slice(home.length)}`;
|
|
192
|
+
return typeof env.HOME === 'string' && path.isAbsolute(env.HOME) ? cwd : path.basename(cwd);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Appends one line only when `file` is absent or a regular file: a FIFO there would block the open
|
|
197
|
+
* until something reads it and stall a guarded tool call. Same rule as appendRegular in
|
|
198
|
+
* src/runtime/failure/capture.js (this file ships without src/).
|
|
199
|
+
*/
|
|
200
|
+
function appendRegularLine(file, line) {
|
|
201
|
+
const c = fs.constants;
|
|
202
|
+
let fd;
|
|
203
|
+
try {
|
|
204
|
+
fd = fs.openSync(file, c.O_WRONLY | c.O_APPEND | c.O_CREAT | (c.O_NOFOLLOW || 0) | (c.O_NONBLOCK || 0), 0o600);
|
|
205
|
+
if (!fs.fstatSync(fd).isFile()) return false;
|
|
206
|
+
fs.writeSync(fd, line);
|
|
207
|
+
return true;
|
|
208
|
+
} catch { return false; } finally {
|
|
209
|
+
if (fd !== undefined) { try { fs.closeSync(fd); } catch { /* nothing more to do */ } }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Appends one `source: "hook"` line (IC-011) with an empty `message` and `exit: null`. Silent and
|
|
215
|
+
* best-effort: every failure is swallowed.
|
|
216
|
+
* @param {{command: string, kind: string, frame?: string|null}} fields
|
|
217
|
+
* @param {Object} [env]
|
|
218
|
+
* @returns {boolean} whether a line was written
|
|
219
|
+
*/
|
|
220
|
+
function captureHookFailure({ command, kind, frame = null }, env = process.env) {
|
|
221
|
+
try {
|
|
222
|
+
const home = failureHome(env);
|
|
223
|
+
if (!home || captureIsOff(home, env)) return false;
|
|
224
|
+
let project = '';
|
|
225
|
+
try { project = hookProject(process.cwd(), env); } catch { project = ''; }
|
|
226
|
+
const record = {
|
|
227
|
+
v: 1,
|
|
228
|
+
at: new Date().toISOString(),
|
|
229
|
+
source: 'hook',
|
|
230
|
+
command: /^[a-z][a-z0-9-]{0,39}$/.test(command) ? command : 'unknown',
|
|
231
|
+
harness: /^[A-Za-z0-9._-]{1,40}$/.test(env.DOFLOW_AGENT || '') ? env.DOFLOW_AGENT : 'none',
|
|
232
|
+
version: hookVersion(env),
|
|
233
|
+
project: project.slice(0, 200),
|
|
234
|
+
kind: String(kind).slice(0, 80),
|
|
235
|
+
message: '',
|
|
236
|
+
frame: typeof frame === 'string' ? frame : null,
|
|
237
|
+
exit: null,
|
|
238
|
+
};
|
|
239
|
+
const bytes = (r) => Buffer.byteLength(JSON.stringify(r), 'utf8') + 1;
|
|
240
|
+
while (bytes(record) > CAPTURE_MAX_LINE_BYTES && record.project.length > 0) {
|
|
241
|
+
record.project = record.project.slice(0, Math.max(0, record.project.length - Math.max(8, bytes(record) - CAPTURE_MAX_LINE_BYTES)));
|
|
242
|
+
}
|
|
243
|
+
if (bytes(record) > CAPTURE_MAX_LINE_BYTES) return false;
|
|
244
|
+
fs.mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
245
|
+
rotateFailures(home);
|
|
246
|
+
return appendRegularLine(path.join(home, 'events.jsonl'), `${JSON.stringify(record)}\n`);
|
|
247
|
+
} catch {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Canonical script name -> pre-normalization per-harness file name, for the fallback search only.
|
|
253
|
+
const LEGACY_SCRIPT_NAMES = {
|
|
254
|
+
'session-context.sh': 'session-start.sh',
|
|
255
|
+
'pre-implementation-gate.sh': 'pre-implement-gate.sh',
|
|
256
|
+
'mcp-tool-guard.sh': 'mcp-tool-guard.sh',
|
|
257
|
+
'stop-check.sh': 'stop-check.sh',
|
|
258
|
+
'pre-bash-guard.sh': 'pre-bash-guard.sh',
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// A hook payload is one tool call's JSON — a few KB in the overwhelming majority of cases, a few
|
|
262
|
+
// hundred KB for a large file edit at most. These bounds exist only to stop a misbehaving harness
|
|
263
|
+
// process (stdin that never closes, or streams unboundedly) from hanging this front door forever;
|
|
264
|
+
// they are far above anything a real payload should ever reach.
|
|
265
|
+
const MAX_STDIN_BYTES = 10 * 1024 * 1024;
|
|
266
|
+
const STDIN_TIMEOUT_MS = 5000;
|
|
267
|
+
|
|
268
|
+
function readStdin() {
|
|
269
|
+
return new Promise((resolve) => {
|
|
270
|
+
let data = '';
|
|
271
|
+
let settled = false;
|
|
272
|
+
const settle = (value) => {
|
|
273
|
+
if (settled) return;
|
|
274
|
+
settled = true;
|
|
275
|
+
process.stdin.removeAllListeners('data');
|
|
276
|
+
process.stdin.removeAllListeners('end');
|
|
277
|
+
clearTimeout(timer);
|
|
278
|
+
resolve(value);
|
|
279
|
+
};
|
|
280
|
+
const timer = setTimeout(() => settle(data), STDIN_TIMEOUT_MS);
|
|
281
|
+
process.stdin.setEncoding('utf8');
|
|
282
|
+
process.stdin.on('data', (chunk) => {
|
|
283
|
+
data += chunk;
|
|
284
|
+
if (data.length > MAX_STDIN_BYTES) settle(data);
|
|
285
|
+
});
|
|
286
|
+
process.stdin.on('end', () => {
|
|
287
|
+
settle(data);
|
|
288
|
+
});
|
|
289
|
+
if (process.stdin.isTTY) {
|
|
290
|
+
settle('');
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Resolve <dir>'s git toplevel, the way `git rev-parse --show-toplevel` would, without shelling
|
|
296
|
+
// out — this runner is invoked once per hook event, so avoiding a subprocess here matters for
|
|
297
|
+
// NFR-001. Walks up from <dir> looking for a `.git` entry (directory or file — a submodule/
|
|
298
|
+
// worktree gitlink is a file, not a directory) and returns the directory that contains it, which
|
|
299
|
+
// may differ from <dir> itself when <dir> is a subdirectory of the repo. Returns null if <dir> is
|
|
300
|
+
// not inside a git working tree at all.
|
|
301
|
+
function gitToplevelOf(dir) {
|
|
302
|
+
let d = dir;
|
|
303
|
+
while (true) {
|
|
304
|
+
if (fs.existsSync(path.join(d, '.git'))) return d;
|
|
305
|
+
const parent = path.dirname(d);
|
|
306
|
+
if (parent === d) return null;
|
|
307
|
+
d = parent;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function resolveProjectRoot(workspacePaths) {
|
|
312
|
+
// The replaced Antigravity shim (pre-implementation-gate.sh) iterated every documented
|
|
313
|
+
// workspacePaths entry, verifying each is a real git repo via `git -C "$dir" rev-parse
|
|
314
|
+
// --show-toplevel`, rather than trusting the first existing path — restored here so a
|
|
315
|
+
// multi-root workspace listing a non-git directory first still resolves to the actual
|
|
316
|
+
// DoFlow-tracked repo's toplevel rather than failing the gate open against the wrong directory.
|
|
317
|
+
if (Array.isArray(workspacePaths)) {
|
|
318
|
+
for (const candidate of workspacePaths) {
|
|
319
|
+
if (!candidate || !fs.existsSync(candidate)) continue;
|
|
320
|
+
const toplevel = gitToplevelOf(candidate);
|
|
321
|
+
if (toplevel) return toplevel;
|
|
322
|
+
}
|
|
323
|
+
// No candidate was inside a verified git repo; fall through rather than trusting an
|
|
324
|
+
// unverified one.
|
|
325
|
+
}
|
|
326
|
+
let d = process.cwd();
|
|
327
|
+
while (d !== path.dirname(d)) {
|
|
328
|
+
if (fs.existsSync(path.join(d, '.git')) || fs.existsSync(path.join(d, '.doflow'))) {
|
|
329
|
+
return d;
|
|
330
|
+
}
|
|
331
|
+
d = path.dirname(d);
|
|
332
|
+
}
|
|
333
|
+
return process.cwd();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Resolve a Canonical Policy Library script's absolute path. Primary location is this runner's
|
|
338
|
+
* own `policies/` sibling directory (core/harnesses/shared/hooks/policies/) — the canonical
|
|
339
|
+
* source of truth (design.md C1) that ships alongside this file, so the lookup needs no
|
|
340
|
+
* project-root guessing and works identically in-repo and once installed, as long as the
|
|
341
|
+
* `shared/hooks/` tree is projected together. The per-harness candidate search below is kept only
|
|
342
|
+
* as a fallback for an install that hasn't picked up the canonical policies/ directory yet.
|
|
343
|
+
*/
|
|
344
|
+
function resolvePolicyScript(projectRoot, scriptName) {
|
|
345
|
+
const primary = path.join(__dirname, 'policies', scriptName);
|
|
346
|
+
if (fs.existsSync(primary)) return primary;
|
|
347
|
+
|
|
348
|
+
const legacyName = LEGACY_SCRIPT_NAMES[scriptName] || scriptName;
|
|
349
|
+
const candidates = [
|
|
350
|
+
path.join(projectRoot, '.gemini/hooks', legacyName),
|
|
351
|
+
path.join(projectRoot, '.claude/hooks', legacyName),
|
|
352
|
+
path.join(projectRoot, '.codex/hooks', legacyName),
|
|
353
|
+
path.join(projectRoot, '.doflow/scripts', legacyName),
|
|
354
|
+
];
|
|
355
|
+
for (const candidate of candidates) {
|
|
356
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
357
|
+
}
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Delegate one canonical-decision evaluation to a Phase A policy script (design.md's Canonical
|
|
363
|
+
* Policy Script Contract): the canonical payload goes in on stdin, DOFLOW_PROJECT_DIR/DOFLOW_AGENT
|
|
364
|
+
* are set, and the exit code decides allow/deny — exit 0 = allow, non-zero = deny with the reason
|
|
365
|
+
* read from stderr (falling back to stdout if a script has none). Returns the CANONICAL decision
|
|
366
|
+
* shape ({decision:'allow'} | {decision:'deny', reason}); harness-native translation happens one
|
|
367
|
+
* layer up, in the adapter's toNative, not here — this function never speaks a harness-specific
|
|
368
|
+
* decision vocabulary.
|
|
369
|
+
*/
|
|
370
|
+
function delegateToPolicy(projectRoot, agent, scriptName, canonicalPayload) {
|
|
371
|
+
const scriptPath = resolvePolicyScript(projectRoot, scriptName);
|
|
372
|
+
if (!scriptPath) return { decision: 'allow' };
|
|
373
|
+
|
|
374
|
+
try {
|
|
375
|
+
execFileSync('bash', [scriptPath], {
|
|
376
|
+
cwd: projectRoot,
|
|
377
|
+
input: JSON.stringify(canonicalPayload || {}),
|
|
378
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
379
|
+
env: { ...process.env, DOFLOW_PROJECT_DIR: projectRoot, DOFLOW_AGENT: agent },
|
|
380
|
+
});
|
|
381
|
+
return { decision: 'allow' };
|
|
382
|
+
} catch (err) {
|
|
383
|
+
// A real policy deny has a numeric exit status — the script ran and its own canonical exit
|
|
384
|
+
// code (1 = deny) is the contract this delegates to. `err.status` is null/undefined when the
|
|
385
|
+
// process never produced an exit code at all (bash missing from PATH, EACCES, etc.) — that is
|
|
386
|
+
// an execution fault, not a policy decision, and every sourced .sh policy in this library
|
|
387
|
+
// fails open on its own uncertainty (`command -v jq || exit 0`); this delegator matches that
|
|
388
|
+
// posture instead of turning an environment fault into a silent deny.
|
|
389
|
+
if (typeof err.status !== 'number') {
|
|
390
|
+
// A policy killed by a signal is a fault in DoFlow's own install worth recording (IC-018); a
|
|
391
|
+
// missing `bash` or a permission error is the user's environment and is not. Either way the
|
|
392
|
+
// decision is the same as it always was.
|
|
393
|
+
// SIGTERM, SIGINT and SIGPIPE are the user or the harness stopping the process (DEC-031), not
|
|
394
|
+
// a fault. Any other signal is recorded under its own kind, so a SEGV and a KILL do not share a fingerprint.
|
|
395
|
+
if (typeof err.signal === 'string' && err.signal && !['SIGTERM', 'SIGINT', 'SIGPIPE'].includes(err.signal)) {
|
|
396
|
+
captureHookFailure({ command: scriptName.replace(/\.sh$/, ''), kind: `policy-exec-fault:${err.signal}` });
|
|
397
|
+
}
|
|
398
|
+
return { decision: 'allow' };
|
|
399
|
+
}
|
|
400
|
+
const reason = err.stderr ? err.stderr.toString().trim() : (err.stdout ? err.stdout.toString().trim() : '');
|
|
401
|
+
return {
|
|
402
|
+
decision: 'deny',
|
|
403
|
+
reason: reason || `[${scriptName}] blocked by DoFlow guard policy`,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function classifyPreToolUsePolicy(toolName) {
|
|
409
|
+
if (MCP_TOOL_PATTERN.test(toolName)) return 'mcp-tool-guard.sh';
|
|
410
|
+
if (EDIT_TOOL_PATTERN.test(toolName)) return 'pre-implementation-gate.sh';
|
|
411
|
+
if (COMMAND_TOOL_PATTERN.test(toolName)) return 'pre-bash-guard.sh';
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function evaluatePreToolUsePolicy(canonicalPayload, projectRoot, agent) {
|
|
416
|
+
const toolName = (canonicalPayload && canonicalPayload.tool_name) || '';
|
|
417
|
+
const scriptName = classifyPreToolUsePolicy(toolName);
|
|
418
|
+
if (!scriptName) return { decision: 'allow' };
|
|
419
|
+
return delegateToPolicy(projectRoot, agent, scriptName, canonicalPayload);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Evaluate one lifecycle event against the Canonical Policy Library, returning a CANONICAL
|
|
424
|
+
* decision. Every event this runner drives maps to at most one Phase A script:
|
|
425
|
+
* SessionStart -> session-context.sh (side-effect only; the script's own contract says it never
|
|
426
|
+
* denies, so its exit code is not propagated as a decision)
|
|
427
|
+
* PreToolUse -> mcp-tool-guard.sh | pre-implementation-gate.sh | pre-bash-guard.sh, chosen by
|
|
428
|
+
* the tool-name classification above
|
|
429
|
+
* Stop -> stop-check.sh
|
|
430
|
+
* PostToolUse, PreInvocation -> no Phase A policy owns these yet (post-edit-lint.sh's collector
|
|
431
|
+
* role is out of FR-001's four-policy scope) — ack only; the adapter's toNative
|
|
432
|
+
* decides the harness-native shape regardless of this placeholder decision.
|
|
433
|
+
*/
|
|
434
|
+
function evaluateEvent(event, canonicalPayload, projectRoot, agent) {
|
|
435
|
+
switch (event) {
|
|
436
|
+
case 'SessionStart':
|
|
437
|
+
delegateToPolicy(projectRoot, agent, 'session-context.sh', canonicalPayload);
|
|
438
|
+
return { decision: 'allow' };
|
|
439
|
+
|
|
440
|
+
case 'PreToolUse':
|
|
441
|
+
return evaluatePreToolUsePolicy(canonicalPayload, projectRoot, agent);
|
|
442
|
+
|
|
443
|
+
case 'Stop':
|
|
444
|
+
return delegateToPolicy(projectRoot, agent, 'stop-check.sh', canonicalPayload);
|
|
445
|
+
|
|
446
|
+
case 'PostToolUse':
|
|
447
|
+
case 'PreInvocation':
|
|
448
|
+
default:
|
|
449
|
+
return { decision: 'allow' };
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Falls back to 'antigravity' (this runner's legacy default) for any value other than
|
|
454
|
+
// 'gemini', including unset/empty. An unrecognized non-empty value is likely a typo, so it
|
|
455
|
+
// gets a stderr warning even though the fallback still applies.
|
|
456
|
+
function resolveAgent() {
|
|
457
|
+
const raw = process.env.DOFLOW_AGENT || '';
|
|
458
|
+
const agent = raw.toLowerCase();
|
|
459
|
+
if (agent === 'gemini') return 'gemini';
|
|
460
|
+
if (raw && agent !== 'antigravity') {
|
|
461
|
+
process.stderr.write(`stream-hook-runner: unrecognized DOFLOW_AGENT="${raw}", falling back to antigravity\n`);
|
|
462
|
+
}
|
|
463
|
+
return 'antigravity';
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function resolveAdapter(agent) {
|
|
467
|
+
return agent === 'gemini' ? geminiAdapter : antigravityAdapter;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function sniffEventFromPayload(payload) {
|
|
471
|
+
if (!payload || typeof payload !== 'object') return null;
|
|
472
|
+
// 'toolCall' in payload (key presence), not payload.toolCall (truthiness): a real Antigravity
|
|
473
|
+
// PreToolUse payload can carry a null/empty toolCall alongside stepIdx (also present on
|
|
474
|
+
// PostToolUse payloads) — truthiness alone let such a payload fall through to the stepIdx check
|
|
475
|
+
// below and get misclassified as PostToolUse, silently skipping the gate entirely
|
|
476
|
+
// (022-code-review finding). Checked before Stop/PostToolUse since a missed PreToolUse
|
|
477
|
+
// classification fails the gate open, the worse failure mode of the two.
|
|
478
|
+
if ('toolCall' in payload || payload.tool_name !== undefined) return 'PreToolUse';
|
|
479
|
+
if (payload.invocationNum !== undefined) return 'PreInvocation';
|
|
480
|
+
if (payload.terminationReason !== undefined || payload.executionNum !== undefined) return 'Stop';
|
|
481
|
+
if (payload.stepIdx !== undefined || payload.terminationBehavior !== undefined) return 'PostToolUse';
|
|
482
|
+
if (payload.session_id !== undefined && payload.cwd !== undefined) return 'SessionStart';
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// ── Back-compat helpers ──────────────────────────────────────────────────────
|
|
487
|
+
//
|
|
488
|
+
// evaluateToolCall/evaluatePayload predate the event-dispatcher/adapter split and are kept for
|
|
489
|
+
// callers (including test/hooks/stream-hook-runner.test.js) that evaluate a single Antigravity-
|
|
490
|
+
// shaped payload directly without going through main()'s argv/env-driven dispatch. Both return the
|
|
491
|
+
// CANONICAL decision shape (never harness-native-translated) exactly as they always have.
|
|
492
|
+
|
|
493
|
+
function evaluateToolCall(toolCall, projectRoot) {
|
|
494
|
+
if (!toolCall || typeof toolCall !== 'object') {
|
|
495
|
+
return { decision: 'allow' };
|
|
496
|
+
}
|
|
497
|
+
const canonicalPayload = antigravityAdapter.toCanonical('PreToolUse', { toolCall });
|
|
498
|
+
return evaluatePreToolUsePolicy(canonicalPayload, projectRoot, 'antigravity');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function evaluatePayload(payload, projectRoot) {
|
|
502
|
+
if (!payload || typeof payload !== 'object') {
|
|
503
|
+
return { decision: 'allow' };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// PreToolUse: toolCall object present
|
|
507
|
+
if (payload.toolCall) {
|
|
508
|
+
return evaluateToolCall(payload.toolCall, projectRoot);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// PreInvocation: injection hook
|
|
512
|
+
if (payload.invocationNum !== undefined) {
|
|
513
|
+
return { injectSteps: [] };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Stop hook: delegate to the canonical stop-check policy
|
|
517
|
+
if (payload.terminationReason !== undefined) {
|
|
518
|
+
const canonicalPayload = antigravityAdapter.toCanonical('Stop', payload);
|
|
519
|
+
return delegateToPolicy(projectRoot, 'antigravity', 'stop-check.sh', canonicalPayload);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// PostToolUse / PostInvocation: empty object response
|
|
523
|
+
if (payload.stepIdx !== undefined || payload.terminationBehavior !== undefined) {
|
|
524
|
+
return {};
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return { decision: 'allow' };
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async function main() {
|
|
531
|
+
try {
|
|
532
|
+
const explicitEvent = process.argv[2] || null;
|
|
533
|
+
const rawInput = await readStdin();
|
|
534
|
+
|
|
535
|
+
let payload = {};
|
|
536
|
+
if (rawInput && rawInput.trim()) {
|
|
537
|
+
try {
|
|
538
|
+
payload = JSON.parse(rawInput);
|
|
539
|
+
} catch {
|
|
540
|
+
payload = {};
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const projectRoot = resolveProjectRoot(payload.workspacePaths);
|
|
545
|
+
const agent = resolveAgent();
|
|
546
|
+
const adapter = resolveAdapter(agent);
|
|
547
|
+
const event = explicitEvent || sniffEventFromPayload(payload);
|
|
548
|
+
|
|
549
|
+
if (!event) {
|
|
550
|
+
process.stdout.write(JSON.stringify({ decision: 'allow' }) + '\n');
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const canonicalPayload = adapter.toCanonical(event, payload);
|
|
555
|
+
const decision = evaluateEvent(event, canonicalPayload, projectRoot, agent);
|
|
556
|
+
const native = adapter.toNative(event, decision);
|
|
557
|
+
|
|
558
|
+
// An empty object is a harness's documented "say nothing" answer (e.g. Antigravity's Stop
|
|
559
|
+
// contract: "silence (exit 0, no stdout) lets the session end" — a positive field, not a
|
|
560
|
+
// missing one, is what a real decision looks like). Writing literal "{}" instead of true
|
|
561
|
+
// silence would be a byte-for-byte behavior change from every native shim this runner
|
|
562
|
+
// replaces, all of which stayed genuinely silent on this path.
|
|
563
|
+
if (native && typeof native === 'object' && Object.keys(native).length > 0) {
|
|
564
|
+
process.stdout.write(JSON.stringify(native) + '\n');
|
|
565
|
+
}
|
|
566
|
+
} catch (err) {
|
|
567
|
+
// Recorded silently when it is a programming error (IC-018); the output below is unchanged.
|
|
568
|
+
if (isProgrammingError(err)) captureHookFailure({ command: 'stream-hook-runner', kind: 'runner-exception', frame: runnerFrame(err) });
|
|
569
|
+
process.stderr.write(`[stream-hook-runner error] ${err.message}\n`);
|
|
570
|
+
process.stdout.write(JSON.stringify({ decision: 'allow' }) + '\n');
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (require.main === module) {
|
|
575
|
+
main();
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
module.exports = {
|
|
579
|
+
evaluateToolCall,
|
|
580
|
+
evaluatePayload,
|
|
581
|
+
resolveProjectRoot,
|
|
582
|
+
evaluateEvent,
|
|
583
|
+
resolveAgent,
|
|
584
|
+
resolveAdapter,
|
|
585
|
+
resolvePolicyScript,
|
|
586
|
+
classifyPreToolUsePolicy,
|
|
587
|
+
sniffEventFromPayload,
|
|
588
|
+
isProgrammingError,
|
|
589
|
+
captureHookFailure,
|
|
590
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# DoFlow locator shim — projected to all eight harnesses by the `locator.doflow` asset in
|
|
3
|
+
# core/registry/assets.json (design.md §3 C2, §4.1).
|
|
4
|
+
#
|
|
5
|
+
# Its ONLY job is to find the single dispatcher (at $REL below, under the config dir) and exec it
|
|
6
|
+
# with every argument passed through unmodified. It deliberately holds NO verb table and NO verb
|
|
7
|
+
# knowledge:
|
|
8
|
+
# the verb namespace lives in exactly one place — the dispatcher — so adding a verb never edits
|
|
9
|
+
# seven projected copies of this file. Its content is byte-identical on every harness; only the
|
|
10
|
+
# harness-native path it is written to differs.
|
|
11
|
+
#
|
|
12
|
+
# It is named `doflow-run` like the dispatcher it fronts, not `doflow`, so it is never confused
|
|
13
|
+
# with the installer CLI (`bin/doflow.js`, published as the `doflow` command) — a different program
|
|
14
|
+
# with a different verb surface.
|
|
15
|
+
#
|
|
16
|
+
# Resolution order (design.md §4.1), first hit wins. $REL is defined once below and interpolated
|
|
17
|
+
# into every step, so no step can drift onto a different path than the others:
|
|
18
|
+
# 1. $DOFLOW_CONFIG_DIR/$REL — explicit override; a documented extension point, not an active
|
|
19
|
+
# path. design.md §4.1 justified it as "what harness session hooks already export", which is
|
|
20
|
+
# false: nothing under core/harnesses/*/hooks/ exports DOFLOW_CONFIG_DIR, so step 1 never
|
|
21
|
+
# fires unless a caller sets the variable itself. Kept because that override is the only way
|
|
22
|
+
# to point at a non-standard install; do not restate the hook claim.
|
|
23
|
+
# 2. nearest .doflow/$REL walking up from the working directory — project-local wins over global.
|
|
24
|
+
# In practice this is the step that resolves, and it is the one a skill's own inline resolver
|
|
25
|
+
# reproduces.
|
|
26
|
+
# 3. $HOME/.doflow/$REL — global install
|
|
27
|
+
#
|
|
28
|
+
# Reaching this file is the caller's problem, not this file's. It resolves the dispatcher correctly
|
|
29
|
+
# whenever it is invoked by a path correct for the caller — but a skill cannot name it by a literal
|
|
30
|
+
# relative path: a relative path in a shell command resolves against the working directory, which is
|
|
31
|
+
# the user's project root, not the skill's directory. Skills therefore inline the step 2/3 walk-up
|
|
32
|
+
# directly (see any chain SKILL.md step 1) rather than calling this shim.
|
|
33
|
+
# None found: exit 2 — a resolution error under the dispatcher's uniform exit codes (0 success,
|
|
34
|
+
# 1 actionable finding, 2 usage/resolution error) — with one message naming all three places
|
|
35
|
+
# searched. design.md R3 accepts this outcome deliberately: a harness install with no `.doflow/`
|
|
36
|
+
# payload anywhere cannot reach the runtime, and one clear message is the required failure.
|
|
37
|
+
#
|
|
38
|
+
# Plain POSIX sh, no bashisms: this runs before anything else DoFlow ships, on whatever shell the
|
|
39
|
+
# harness hands it.
|
|
40
|
+
|
|
41
|
+
# The dispatcher ships inside the `scripts.doflow` asset (`core/shared/scripts`), so it sits at
|
|
42
|
+
# `scripts/doflow/bin/doflow-run` under the shared tree. Every harness projects that tree to its
|
|
43
|
+
# scope root (`<project>/.doflow` or `$HOME/.doflow`), so this one path holds for all eight,
|
|
44
|
+
# Antigravity at global scope excepted (it projects no runtime there).
|
|
45
|
+
REL='scripts/doflow/bin/doflow-run'
|
|
46
|
+
dispatcher=''
|
|
47
|
+
|
|
48
|
+
# 1. Explicit override.
|
|
49
|
+
if [ -n "${DOFLOW_CONFIG_DIR:-}" ] && [ -f "$DOFLOW_CONFIG_DIR/$REL" ]; then
|
|
50
|
+
dispatcher="$DOFLOW_CONFIG_DIR/$REL"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# 2. Nearest project-local install, walking up. Terminates on the filesystem root and also on a
|
|
54
|
+
# parent that stops changing, so an unusual root (or a `dirname` that returns its input) cannot
|
|
55
|
+
# spin forever.
|
|
56
|
+
if [ -z "$dispatcher" ]; then
|
|
57
|
+
dir="${PWD:-$(pwd)}"
|
|
58
|
+
while [ -n "$dir" ]; do
|
|
59
|
+
if [ -f "$dir/.doflow/$REL" ]; then
|
|
60
|
+
dispatcher="$dir/.doflow/$REL"
|
|
61
|
+
break
|
|
62
|
+
fi
|
|
63
|
+
parent="$(dirname "$dir")"
|
|
64
|
+
[ "$parent" = "$dir" ] && break
|
|
65
|
+
dir="$parent"
|
|
66
|
+
done
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# 3. Global install.
|
|
70
|
+
if [ -z "$dispatcher" ] && [ -n "${HOME:-}" ] && [ -f "$HOME/.doflow/$REL" ]; then
|
|
71
|
+
dispatcher="$HOME/.doflow/$REL"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
if [ -z "$dispatcher" ]; then
|
|
75
|
+
cat >&2 <<EOF
|
|
76
|
+
doflow-run: no DoFlow runtime found. Searched for '$REL' in, in order:
|
|
77
|
+
1. \$DOFLOW_CONFIG_DIR -> ${DOFLOW_CONFIG_DIR:-(unset)}
|
|
78
|
+
2. nearest .doflow/ above the working directory -> ${PWD:-(unknown)}
|
|
79
|
+
3. \$HOME/.doflow -> ${HOME:-(unset)}/.doflow
|
|
80
|
+
Install the runtime into one of them:
|
|
81
|
+
npx @khoavu882/doflow install -t <harness> # project-local, creates ./.doflow
|
|
82
|
+
npx @khoavu882/doflow install -t <harness> -g # global, creates \$HOME/.doflow
|
|
83
|
+
EOF
|
|
84
|
+
exit 2
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
exec "$dispatcher" "$@"
|