@khoavu882/doflow 0.0.0-stage → 2.0.0-beta.4
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 +1061 -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 +48 -4
- package/src/adapters/antigravity/index.js +631 -0
- package/src/adapters/claude/index.js +548 -0
- package/src/adapters/claude/mcp.js +94 -0
- package/src/adapters/codex/agents.js +208 -0
- package/src/adapters/codex/config.js +272 -0
- package/src/adapters/codex/hooks.js +203 -0
- package/src/adapters/codex/index.js +707 -0
- package/src/adapters/codex/mcp.js +271 -0
- package/src/adapters/codex/native-projection.js +34 -0
- package/src/adapters/copilot/index.js +413 -0
- package/src/adapters/copy-tree.js +620 -0
- package/src/adapters/gemini/hooks.js +210 -0
- package/src/adapters/gemini/index.js +390 -0
- package/src/adapters/hook-commands.js +60 -0
- package/src/adapters/index.js +106 -0
- package/src/adapters/kiro/index.js +310 -0
- package/src/adapters/mcp-entries.js +331 -0
- package/src/adapters/opencode/index.js +466 -0
- package/src/adapters/pi/index.js +279 -0
- package/src/adapters/pi/json-members.js +185 -0
- package/src/adapters/pi/mcp.js +373 -0
- package/src/adapters/resolve-source.js +19 -0
- package/src/cli/commands/install.js +218 -0
- package/src/cli/commands/list-backups.js +21 -0
- package/src/cli/commands/reconcile.js +120 -0
- package/src/cli/commands/remove.js +82 -0
- package/src/cli/commands/rollback.js +172 -0
- package/src/cli/commands/self-update.js +33 -0
- package/src/cli/commands/status.js +138 -0
- package/src/cli/commands/tools.js +92 -0
- package/src/cli/commands/update.js +162 -0
- package/src/cli/index.js +568 -0
- package/src/cli/runtime-commands.js +281 -0
- package/src/cli/shared.js +218 -0
- package/src/helper/bash-helper.js +34 -0
- package/src/helper/git.js +24 -0
- package/src/helper/guardrails.js +49 -0
- package/src/helper/harness-paths.js +140 -0
- package/src/helper/marker-merge.js +116 -0
- package/src/helper/prompt.js +61 -0
- package/src/helper/repo-root.js +29 -0
- package/src/helper/settings-merge.js +151 -0
- package/src/helper/settings-scope.js +33 -0
- package/src/helper/toml.js +244 -0
- package/src/install/backup-set.js +149 -0
- package/src/install/backup.js +724 -0
- package/src/install/context.js +58 -0
- package/src/install/manifest.js +145 -0
- package/src/install/mcp.js +254 -0
- package/src/install/paths.js +84 -0
- package/src/install/targets.js +90 -0
- package/src/install/tool-lifecycle.js +151 -0
- package/src/lifecycle/bash-availability.js +25 -0
- package/src/lifecycle/index.js +812 -0
- package/src/lifecycle/mcp-index.js +28 -0
- package/src/lifecycle/policies.js +73 -0
- package/src/lifecycle/view.js +175 -0
- package/src/registry/index.js +611 -0
- package/src/release/build-package.js +91 -0
- package/src/release/changelog-section.js +70 -0
- package/src/release/sync-plugin-versions.js +66 -0
- package/src/runtime/advice/cli.js +283 -0
- package/src/runtime/advice/client.js +324 -0
- package/src/runtime/advice/config.js +189 -0
- package/src/runtime/advice/doctor.js +169 -0
- package/src/runtime/advice/endpoint-guard.js +134 -0
- package/src/runtime/advice/kinds.js +154 -0
- package/src/runtime/advice/providers/ollama-systemone.js +209 -0
- package/src/runtime/advice/record.js +166 -0
- package/src/runtime/advice/registry.js +170 -0
- package/src/runtime/advice/strength.js +66 -0
- package/src/runtime/brief-tools.js +342 -0
- package/src/runtime/c4-project.js +371 -0
- package/src/runtime/capability-router.js +484 -0
- package/src/runtime/checkouts.js +170 -0
- package/src/runtime/claims.js +468 -0
- package/src/runtime/cli-result.js +55 -0
- package/src/runtime/cli.js +678 -0
- package/src/runtime/command-detect.js +516 -0
- package/src/runtime/context-pack.js +492 -0
- package/src/runtime/decision-register.js +493 -0
- package/src/runtime/evidence-ledger.js +338 -0
- package/src/runtime/failure/capture.js +272 -0
- package/src/runtime/failure/classifier.js +68 -0
- package/src/runtime/failure/cli.js +178 -0
- package/src/runtime/failure/home.js +64 -0
- package/src/runtime/failure/store.js +226 -0
- package/src/runtime/feature-resolve.js +79 -0
- package/src/runtime/freshness.js +177 -0
- package/src/runtime/health.js +863 -0
- package/src/runtime/history-compactor.js +237 -0
- package/src/runtime/implementation-gate.js +321 -0
- package/src/runtime/inventory/coverage.js +218 -0
- package/src/runtime/inventory/identity.js +65 -0
- package/src/runtime/inventory/index.js +705 -0
- package/src/runtime/inventory/precedence.js +134 -0
- package/src/runtime/inventory/read-scopes.js +131 -0
- package/src/runtime/inventory/siblings.js +192 -0
- package/src/runtime/knowledge/chunker.js +124 -0
- package/src/runtime/knowledge/hybrid.js +149 -0
- package/src/runtime/knowledge/index-store.js +145 -0
- package/src/runtime/knowledge/retrieval.js +194 -0
- package/src/runtime/leak-scan.js +185 -0
- package/src/runtime/lifecycle/cli.js +276 -0
- package/src/runtime/lifecycle/event-store.js +340 -0
- package/src/runtime/lifecycle/fold.js +353 -0
- package/src/runtime/lifecycle/followup.js +432 -0
- package/src/runtime/lifecycle/goal.js +249 -0
- package/src/runtime/lifecycle/intent-writer.js +86 -0
- package/src/runtime/lifecycle/overview.js +292 -0
- package/src/runtime/lifecycle/release.js +241 -0
- package/src/runtime/lifecycle/report-store.js +221 -0
- package/src/runtime/lifecycle/retention.js +212 -0
- package/src/runtime/lifecycle/root.js +71 -0
- package/src/runtime/lifecycle/status.js +353 -0
- package/src/runtime/lifecycle/store-upkeep.js +393 -0
- package/src/runtime/locator-resolve.js +158 -0
- package/src/runtime/mask.js +210 -0
- package/src/runtime/mcp-consultations.js +412 -0
- package/src/runtime/mcp-reach.js +157 -0
- package/src/runtime/model-router.js +153 -0
- package/src/runtime/outcome.js +467 -0
- package/src/runtime/reach.js +148 -0
- package/src/runtime/readiness-record.js +150 -0
- package/src/runtime/readiness.js +435 -0
- package/src/runtime/recovery.js +282 -0
- package/src/runtime/research-request.js +231 -0
- package/src/runtime/retrieval-plan.js +578 -0
- package/src/runtime/scaffold/artifacts.js +217 -0
- package/src/runtime/scaffold/fingerprint.js +56 -0
- package/src/runtime/scaffold/generate.js +911 -0
- package/src/runtime/scaffold/languages.js +245 -0
- package/src/runtime/storage/cli.js +64 -0
- package/src/runtime/storage/engine.js +249 -0
- package/src/runtime/storage/registry.js +130 -0
- package/src/runtime/storage/roots.js +63 -0
- package/src/runtime/storage/rules.js +282 -0
- package/src/runtime/storage/upkeep.js +243 -0
- package/src/runtime/storage/work-status.js +326 -0
- package/src/runtime/task-attempts.js +415 -0
- package/src/runtime/task-classifier.js +550 -0
- package/src/runtime/task-scope.js +130 -0
- package/src/runtime/task-state.js +176 -0
- package/src/runtime/trace/indicators.js +235 -0
- package/src/runtime/trace/ledger.js +626 -0
- package/src/runtime/trace/render.js +76 -0
- package/src/runtime/trace/views.js +459 -0
- package/src/runtime/verification/contract-runner.js +341 -0
- package/src/runtime/verification/engine.js +1461 -0
- package/src/runtime/verification/registry.js +130 -0
- package/src/runtime/verification/scope-bound.js +418 -0
- package/src/runtime/verification/test-selection.js +172 -0
- package/src/runtime/workflow-engine.js +588 -0
- package/src/runtime/workflow-orchestrator.js +953 -0
- package/src/runtime/worktree.js +340 -0
- package/src/state/index.js +193 -0
- package/src/state/lockfile.js +136 -0
- package/src/state/run-lock.js +408 -0
- package/types/node-commonjs.d.ts +68 -0
- package/types/runtime.d.ts +91 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// The runtime verbs (design §4.2) — dispatch only.
|
|
3
|
+
//
|
|
4
|
+
// Every verb's own implementation — `scaffold`, `classify`, `workflow`, `route`, `claim`,
|
|
5
|
+
// `context-pack`, `verify` and `recover` — lives in its engine module under src/runtime/ and is
|
|
6
|
+
// imported below; this file only forwards parsed CLI arguments to it, exactly as the verb-table
|
|
7
|
+
// doctrine requires: one implementation per verb, one table that names them. `bin/doflow.js`
|
|
8
|
+
// forwards here from src/cli/index.js; nothing outside src/runtime/ implements a verb.
|
|
9
|
+
//
|
|
10
|
+
// Uniform contract, shared by every one of those handlers: `--json` prints the library's own
|
|
11
|
+
// object, unmodified; exit 0 = answered, 1 = a finding the caller must act on, 2 = the CLI could
|
|
12
|
+
// not do what was asked (a missing or unusable argument, or input the library cannot resolve). No
|
|
13
|
+
// handler ever converts a library's own status into a different one, and none of them substitutes
|
|
14
|
+
// a default for an identity the library refuses to guess.
|
|
15
|
+
//
|
|
16
|
+
// Guard note: test/guards/reachability.test.js and test/guards/runtime-unification.test.js parse
|
|
17
|
+
// this switch — each case written as the single expression `case '<name>': return
|
|
18
|
+
// handle<Name>Command(...)` — to cross-check the dispatcher's verb table in both directions. Keep
|
|
19
|
+
// every case in exactly that shape.
|
|
20
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
21
|
+
const fs = require('node:fs');
|
|
22
|
+
const os = require('node:os');
|
|
23
|
+
const path = require('node:path');
|
|
24
|
+
const {
|
|
25
|
+
handleCapabilitiesCommand, handleReadinessCommand, handleEvidenceCommand,
|
|
26
|
+
} = require('../runtime/cli.js');
|
|
27
|
+
// `doctor` comes from the health module rather than src/runtime/cli.js: the health-probe report
|
|
28
|
+
// (FR-013) supersedes the presence-check version, and one verb must have one implementation.
|
|
29
|
+
const { handleDoctorCommand } = require('../runtime/health.js');
|
|
30
|
+
const { handleTraceCommand, handleStatsCommand, handleDiscoverCommand, resolveRunsLocation } = require('../runtime/trace/ledger.js');
|
|
31
|
+
const { handleIndicatorsCommand } = require('../runtime/trace/indicators.js');
|
|
32
|
+
// The rest of the verb surface design §4.2 declares. `classify`, `workflow`, `route`, `claim`,
|
|
33
|
+
// `context-pack`, `verify`, `recover` and `scaffold` each have exactly one implementation, in the
|
|
34
|
+
// engine module named on the right; this file only dispatches to them.
|
|
35
|
+
const { ReadinessEngine } = require('../runtime/readiness.js');
|
|
36
|
+
const { handleClaimCommand } = require('../runtime/claims.js');
|
|
37
|
+
const { handleClassifyCommand } = require('../runtime/task-classifier.js');
|
|
38
|
+
const { handleWorkflowCommand } = require('../runtime/workflow-engine.js');
|
|
39
|
+
const { handleOrchestrateCommand } = require('../runtime/workflow-orchestrator.js');
|
|
40
|
+
const { handleResearchRequestCommand } = require('../runtime/research-request.js');
|
|
41
|
+
const { handleTaskAttemptCommand } = require('../runtime/task-attempts.js');
|
|
42
|
+
const { handleRetrieveCommand } = require('../runtime/knowledge/retrieval.js');
|
|
43
|
+
const { handleModelRoleCommand } = require('../runtime/model-router.js');
|
|
44
|
+
const { handleRouteCommand } = require('../runtime/capability-router.js');
|
|
45
|
+
const { handleContextPackCommand } = require('../runtime/context-pack.js');
|
|
46
|
+
const { handleRetrievalPlanCommand } = require('../runtime/retrieval-plan.js');
|
|
47
|
+
const { handleOutcomeCommand } = require('../runtime/outcome.js');
|
|
48
|
+
const { handleVerifyCommand } = require('../runtime/verification/engine.js');
|
|
49
|
+
const { handleLeakScanCommand } = require('../runtime/leak-scan.js');
|
|
50
|
+
const { handleRecoverCommand } = require('../runtime/recovery.js');
|
|
51
|
+
const { handleScaffoldCommand } = require('../runtime/scaffold/generate.js');
|
|
52
|
+
const { handleDecisionCommand } = require('../runtime/decision-register.js');
|
|
53
|
+
const { handleFollowupCommand, handleLifecycleCommand, handleGoalCommand } = require('../runtime/lifecycle/cli.js');
|
|
54
|
+
const { projectRoot: lifecycleProjectRoot } = require('../runtime/lifecycle/root.js');
|
|
55
|
+
const { upkeepAfterVerb } = require('../runtime/storage/upkeep.js');
|
|
56
|
+
const { handleStorageCommand } = require('../runtime/storage/cli.js');
|
|
57
|
+
// Required on first use, so a CLI whose failure modules cannot be loaded still runs every other verb.
|
|
58
|
+
function handleFailureCommand(options ) { return require('../runtime/failure/cli.js').handleFailureCommand(options); }
|
|
59
|
+
const { handleInventoryCommand } = require('../runtime/inventory/index.js');
|
|
60
|
+
const { finishRuntime, usageError } = require('../runtime/cli-result.js');
|
|
61
|
+
const { setDefaultSlug, invalidSlugRefusal, slugNamesNoFeature } = require('../runtime/task-scope.js');
|
|
62
|
+
const { listCheckouts } = require('../runtime/checkouts.js');
|
|
63
|
+
const { featureSlugFor } = require('../runtime/implementation-gate.js');
|
|
64
|
+
const { handleBriefToolsCommand } = require('../runtime/brief-tools.js');
|
|
65
|
+
const { handleAdviseCommand } = require('../runtime/advice/cli.js');
|
|
66
|
+
const { REPO_ROOT, buildAdapterRegistry } = require('./shared.js');
|
|
67
|
+
|
|
68
|
+
/** Where `readiness`/`evidence` read and write per-task state. Mirrors scopeOf()'s rules so these
|
|
69
|
+
* commands are scope-aware like the rest of the CLI, rather than defaulting to the DoFlow install
|
|
70
|
+
* directory — which for an npm install is inside node_modules/. */
|
|
71
|
+
function evidenceRoot(o ) {
|
|
72
|
+
return o.global ? os.homedir() : path.resolve(o.positional[0] || '.');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The task class the caller named, or exit 2 naming the valid set.
|
|
77
|
+
*
|
|
78
|
+
* `readiness` previously read `o.taskClass || 'feature'`. That is the identity defect readiness.ts
|
|
79
|
+
* fixed this morning, reinstated one layer up: omitting `--task-class` produced a confident
|
|
80
|
+
* READY/NEEDS_EVIDENCE verdict computed from the wrong contract, and nothing in the output said so.
|
|
81
|
+
* The valid set comes from the readiness registry rather than a list written here, so it cannot
|
|
82
|
+
* drift from the templates that actually exist.
|
|
83
|
+
*/
|
|
84
|
+
function requireTaskClass(o ) {
|
|
85
|
+
if (typeof o.taskClass === 'string' && o.taskClass.trim() !== '') return o.taskClass;
|
|
86
|
+
let valid = '';
|
|
87
|
+
try {
|
|
88
|
+
valid = ` Valid: ${Object.keys(new ReadinessEngine({ repoRoot: REPO_ROOT }).templates).join(', ')}.`;
|
|
89
|
+
} catch { /* the registry is unreadable; the missing argument is still the thing to report */ }
|
|
90
|
+
usageError(o.cmd, `--task-class is required — the contract is per class, so guessing one would grade the wrong task.${valid}`, o.json);
|
|
91
|
+
process.exit(2);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The task id the caller named, or exit 2. Same reasoning as requireTaskClass. */
|
|
95
|
+
function requireTaskId(o ) {
|
|
96
|
+
if (typeof o.taskId === 'string' && o.taskId.trim() !== '') return o.taskId;
|
|
97
|
+
usageError(o.cmd, '--task-id is required — evidence and claims belong to one task, so guessing an id would report on a different one.', o.json);
|
|
98
|
+
process.exit(2);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The single-item spelling of an evidence write, assembled from exactly the flags the caller gave.
|
|
103
|
+
*
|
|
104
|
+
* Absent flags stay absent. Filling a gap here with 'unknown'/'general' would move the omission out
|
|
105
|
+
* of the caller's error message and into the stored record, where it reads as a measurement.
|
|
106
|
+
*/
|
|
107
|
+
function evidenceItemFromFlags(o ) {
|
|
108
|
+
const item = {};
|
|
109
|
+
if (o.kind !== undefined) item.kind = o.kind;
|
|
110
|
+
if (o.provenance !== undefined) item.provenance = o.provenance;
|
|
111
|
+
if (o.locator !== undefined) item.locator = o.locator;
|
|
112
|
+
if (o.content !== undefined) item.content = o.content;
|
|
113
|
+
if (o.establishes !== undefined) item.establishes = o.establishes;
|
|
114
|
+
// Both halves of an observation travel together; a lone half must reach the write boundary as
|
|
115
|
+
// the partial object it is, so the refusal names the missing field instead of dropping the pair.
|
|
116
|
+
if (o.observedCommand !== undefined || o.observedExit !== undefined) {
|
|
117
|
+
item.observation = {};
|
|
118
|
+
if (o.observedCommand !== undefined) item.observation.command = o.observedCommand;
|
|
119
|
+
if (o.observedExit !== undefined) item.observation.exitCode = o.observedExit;
|
|
120
|
+
}
|
|
121
|
+
const source = {};
|
|
122
|
+
if (o.provider !== undefined) source.provider = o.provider;
|
|
123
|
+
if (o.capability !== undefined) source.capability = o.capability;
|
|
124
|
+
if (Object.keys(source).length > 0) item.source = source;
|
|
125
|
+
return item;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Verbs that read or write per-task records, and so accept `--slug` (IC-002). */
|
|
129
|
+
const TASK_STORE_VERBS = new Set(['evidence', 'claim', 'readiness', 'context-pack', 'research-request', 'outcome', 'retrieval-plan']);
|
|
130
|
+
|
|
131
|
+
/** Verbs that build a task's readiness: run from a linked worktree whose feature folder lives only
|
|
132
|
+
* in the main checkout, they keep the task's records there, beside the folder their evidence names. */
|
|
133
|
+
const FEATURE_STORE_VERBS = new Set(['evidence', 'claim', 'readiness']);
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The main checkout, when `o` runs one of FEATURE_STORE_VERBS in a linked worktree that lacks the
|
|
137
|
+
* task's feature folder and the main checkout holds it; null otherwise. The feature is `--slug`,
|
|
138
|
+
* else the branch's. A locator such as `agent-docs/doflow/<slug>/plan.md` resolves only there, and
|
|
139
|
+
* the readiness record written there is the one verify, orchestrate and the edit hook read from
|
|
140
|
+
* the worktree.
|
|
141
|
+
*/
|
|
142
|
+
function featureStoreRoot(o ) {
|
|
143
|
+
if (!FEATURE_STORE_VERBS.has(o.cmd) || o.global) return null;
|
|
144
|
+
const checkouts = listCheckouts({ cwd: evidenceRoot(o) });
|
|
145
|
+
if (!checkouts.isLinked) return null;
|
|
146
|
+
const slug = featureSlugFor({ projectRoot: checkouts.current, slug: o.slug });
|
|
147
|
+
const folder = (root ) => path.join(root, 'agent-docs', 'doflow', slug || '');
|
|
148
|
+
if (!slug || fs.existsSync(folder(checkouts.current)) || !fs.existsSync(folder(checkouts.main))) return null;
|
|
149
|
+
return { root: checkouts.main, slug };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Forward one parsed invocation to its runtime verb's implementation, or exit 1 naming an unknown
|
|
154
|
+
* command. Argument shaping stays here (requireTaskClass/requireTaskId/evidenceItemFromFlags
|
|
155
|
+
* validate parsed CLI arguments, which is the CLI's job, not a library's); everything else belongs
|
|
156
|
+
* to the engine module each case names.
|
|
157
|
+
*/
|
|
158
|
+
function dispatchRuntimeCommandInner(o , moment ) {
|
|
159
|
+
// A slug becomes a directory name and a state key, so one that could name a path is refused
|
|
160
|
+
// here, once, for every verb that reads `--slug`, before any of them reads or writes state
|
|
161
|
+
// (FR-013, IC-009). A well-formed slug that names no feature is the verbs' own business.
|
|
162
|
+
const refusal = invalidSlugRefusal(o.slug);
|
|
163
|
+
if (refusal) {
|
|
164
|
+
if (o.json) console.log(JSON.stringify(refusal, null, 2));
|
|
165
|
+
else console.error(`doflow ${o.cmd}: ${refusal.error}: ${refusal.message}`);
|
|
166
|
+
return finishRuntime(2);
|
|
167
|
+
}
|
|
168
|
+
// `--slug` names the feature a task-store verb's records belong to (task-scope.js). Set once
|
|
169
|
+
// here so the verbs whose handlers build their own ledger (readiness, evidence) route the same
|
|
170
|
+
// way as the ones that take `slug` directly. The orchestration journal is keyed by slug already.
|
|
171
|
+
// Reset on every dispatch, so one invocation's slug can never carry into the next in-process call.
|
|
172
|
+
const featureStore = featureStoreRoot(o);
|
|
173
|
+
const storeRoot = featureStore ? featureStore.root : evidenceRoot(o);
|
|
174
|
+
// Use the same root resolver as the handler: task redirection, the nearest run ledger,
|
|
175
|
+
// and lifecycle's shared main-worktree store are deliberately different scopes.
|
|
176
|
+
try {
|
|
177
|
+
if (['trace', 'stats', 'discover'].includes(o.cmd)) {
|
|
178
|
+
moment.projectRoot = path.dirname(resolveRunsLocation({ start: evidenceRoot(o), global: o.global }).configDir);
|
|
179
|
+
} else if (['followup', 'lifecycle', 'goal', 'task-attempt'].includes(o.cmd)) {
|
|
180
|
+
moment.projectRoot = lifecycleProjectRoot(evidenceRoot(o));
|
|
181
|
+
} else {
|
|
182
|
+
moment.projectRoot = storeRoot;
|
|
183
|
+
}
|
|
184
|
+
} catch { /* a failed upkeep lookup must not change the verb's answer */ }
|
|
185
|
+
const storeSlug = featureStore ? featureStore.slug : o.slug;
|
|
186
|
+
setDefaultSlug(TASK_STORE_VERBS.has(o.cmd) ? storeSlug : null);
|
|
187
|
+
if (featureStore) {
|
|
188
|
+
console.error(`doflow ${o.cmd}: note: feature '${storeSlug}' lives in the main checkout ${storeRoot}; this task's records are read and written there`);
|
|
189
|
+
}
|
|
190
|
+
// A well-formed slug that names no feature changes nothing (the records go to the shared task
|
|
191
|
+
// store), but the caller typed it expecting an effect, so say so once, on stderr only.
|
|
192
|
+
if (!featureStore && TASK_STORE_VERBS.has(o.cmd) && typeof o.slug === 'string' && o.slug !== ''
|
|
193
|
+
&& slugNamesNoFeature({ projectRoot: evidenceRoot(o), slug: o.slug })) {
|
|
194
|
+
console.error(`doflow ${o.cmd}: note: --slug '${o.slug}' names no feature; using the shared task store`);
|
|
195
|
+
}
|
|
196
|
+
switch (o.cmd) {
|
|
197
|
+
// REPO_ROOT locates the capability registry; projectRoot is the tree whose index freshness
|
|
198
|
+
// and build/test commands are being reported on, which follows the usual scope rules.
|
|
199
|
+
case 'capabilities': return handleCapabilitiesCommand({ json: o.json, check: o.check, repoRoot: REPO_ROOT });
|
|
200
|
+
case 'doctor': return handleDoctorCommand({ json: o.json, repoRoot: REPO_ROOT, projectRoot: evidenceRoot(o), adapters: buildAdapterRegistry() });
|
|
201
|
+
// REPO_ROOT locates the registry (templates ship with the package); stateRoot locates the
|
|
202
|
+
// caller's evidence, which follows the same scope rules as every other command: -g means
|
|
203
|
+
// $HOME, otherwise the positional project root (default cwd).
|
|
204
|
+
// `o.taskClass || 'feature'` and `o.taskId || 'default'` used to sit here. Both re-created
|
|
205
|
+
// the identity defect readiness.ts fails closed on: omitting either argument produced a
|
|
206
|
+
// confident verdict about a task or a contract the caller never named. Required now, and the
|
|
207
|
+
// valid class set is named in the refusal.
|
|
208
|
+
// The task-profile arguments are the caller's own statements and are forwarded only when
|
|
209
|
+
// given. They exist because without them no readiness template can ever reach READY —
|
|
210
|
+
// every one of the five has at least one requirement satisfied from the profile rather
|
|
211
|
+
// than from evidence, so the gate had one reachable answer for every task. Forwarding is
|
|
212
|
+
// all this does: the handler names them back under `callerAsserted` so a stated input is
|
|
213
|
+
// never mistaken for a measured one.
|
|
214
|
+
case 'readiness': return handleReadinessCommand({ taskClass: requireTaskClass(o), taskId: requireTaskId(o), verificationPlan: o.verificationPlan, scopeClear: o.scope, invariants: o.invariants, userDecisionPending: o.userDecisionPending, mode: o.mode, slug: storeSlug, json: o.json, repoRoot: REPO_ROOT, stateRoot: storeRoot });
|
|
215
|
+
case 'evidence': return handleEvidenceCommand({ taskId: requireTaskId(o), action: o.action, item: evidenceItemFromFlags(o), batchPath: o.batchPath, evidenceId: o.evidenceId, replacedBy: o.replacedBy, json: o.json, repoRoot: REPO_ROOT, stateRoot: storeRoot });
|
|
216
|
+
// Run-ledger views. They resolve their own ledger the way the dispatcher does (nearest
|
|
217
|
+
// `.doflow` walking up, or the global one) rather than assuming cwd is the project root, so
|
|
218
|
+
// a view invoked from a subdirectory reads the runs that were actually recorded.
|
|
219
|
+
// Exit codes follow design §4.2 and are set by the handler itself: 0 = answered, 1 = a
|
|
220
|
+
// finding the caller must act on.
|
|
221
|
+
case 'trace': return handleTraceCommand({ json: o.json, days: o.days, global: o.global, projectRoot: evidenceRoot(o) });
|
|
222
|
+
case 'stats': return handleStatsCommand({ json: o.json, days: o.days, global: o.global, projectRoot: evidenceRoot(o) });
|
|
223
|
+
case 'indicators': return handleIndicatorsCommand({ json: o.json, projectRoot: evidenceRoot(o) });
|
|
224
|
+
case 'discover': return handleDiscoverCommand({ json: o.json, days: o.days, global: o.global, projectRoot: evidenceRoot(o) });
|
|
225
|
+
// Deliberately NOT evidenceRoot(o): this is the one verb that reads both scopes in one
|
|
226
|
+
// invocation (IC-001), so the project root is always the positional (default cwd) and never
|
|
227
|
+
// $HOME, and `-g` is forwarded to be refused by the handler rather than silently narrowing a
|
|
228
|
+
// report whose whole purpose is to be cross-scope. The global scope's own root is derived from
|
|
229
|
+
// the process home directory inside the lifecycle view (design R7) and is not an argument.
|
|
230
|
+
case 'inventory': return handleInventoryCommand({ json: o.json, global: o.global, targets: o.targets, repoRoot: REPO_ROOT, projectRoot: path.resolve(o.positional[0] || '.') });
|
|
231
|
+
case 'storage': return handleStorageCommand({ action: o.actionGiven ? o.action : 'report', json: o.json, global: o.global, projectRoot: evidenceRoot(o) });
|
|
232
|
+
// No REPO_ROOT: the scaffold's repo root is the *caller's* repo, reported by the resolver,
|
|
233
|
+
// because the plan's `files:` paths are relative to it. Passing the DoFlow install here
|
|
234
|
+
// would detect the wrong language and mirror the wrong tree.
|
|
235
|
+
case 'scaffold': return handleScaffoldCommand({ json: o.json, projectRoot: evidenceRoot(o), slug: o.slug });
|
|
236
|
+
// The feature is resolved from the working directory (or --slug) by the same resolver
|
|
237
|
+
// `scaffold` uses; the flags are the caller's own statements, validated by the register module.
|
|
238
|
+
case 'decision': return handleDecisionCommand({ action: o.action, projectRoot: evidenceRoot(o), slug: o.slug, json: o.json, flags: { topic: o.topic, statement: o.statement, channel: o.channel, stage: o.stage, rationale: o.rationale, supersedes: o.supersedes, refs: o.refs, source: o.source, batch: o.batchPath, all: o.all } });
|
|
239
|
+
// Feature 046: the project's follow-up store lives at the repository root (never `-g`, which
|
|
240
|
+
// the handlers refuse), so the working directory is the starting point, not a scope switch.
|
|
241
|
+
case 'followup': return handleFollowupCommand({ action: o.actionGiven ? o.action : undefined, cwd: evidenceRoot(o), global: o.global, slug: o.slug, json: o.json, flags: { statement: o.statement, stage: o.stage, source: o.source, taskClass: o.taskClass, taskId: o.taskId, release: o.release, batch: o.batchPath, channel: o.channel, state: o.state, ids: o.ids, as: o.as, reason: o.reason, evidence: o.evidence, title: o.title, file: o.file, stdin: o.stdin, text: o.text, feature: o.feature, tag: o.tag, confirm: o.confirm, exclude: o.exclude } });
|
|
242
|
+
case 'lifecycle': return handleLifecycleCommand({ action: o.actionGiven ? o.action : undefined, cwd: evidenceRoot(o), global: o.global, slug: o.slug, json: o.json, flags: { take: o.take, intent: o.intent, goal: o.goal, maintain: o.maintain, since: o.since, tag: o.tag, confirm: o.confirm, feature: o.feature, exclude: o.exclude, reason: o.reason, channel: o.channel } });
|
|
243
|
+
case 'goal': return handleGoalCommand({ action: o.actionGiven ? o.action : undefined, cwd: evidenceRoot(o), global: o.global, slug: o.slug, json: o.json, flags: { goal: o.goal, statement: o.statement, item: o.item, text: o.text, evidence: o.evidence, unmet: o.unmet, replace: o.replace, reason: o.reason, channel: o.channel } });
|
|
244
|
+
// The failure store is per machine, so `-g` changes nothing and the working directory only matters
|
|
245
|
+
// to `settle --as imported`, which looks for the DoFlow repository from there.
|
|
246
|
+
case 'failure': return handleFailureCommand({ action: o.actionGiven ? o.action : undefined, cwd: path.resolve(o.positional[0] || '.'), json: o.json, all: o.all, flags: { fp: o.fp, as: o.as, reason: o.reason, set: o.set } });
|
|
247
|
+
// The rest of design §4.2's Node arm. REPO_ROOT locates the registries that ship with the
|
|
248
|
+
// package (workflows, capabilities, verification); evidenceRoot(o) locates the caller's own
|
|
249
|
+
// state and source tree, following the same scope rules as every other command.
|
|
250
|
+
case 'classify': return handleClassifyCommand({ taskClass: o.taskClass, rationale: o.rationale, proposedBy: o.proposedBy, callingSkill: o.callingSkill, request: o.request, requestFile: o.requestFile, taskId: o.taskId, projectRoot: evidenceRoot(o), json: o.json });
|
|
251
|
+
case 'workflow': return handleWorkflowCommand({ taskClass: o.taskClass, json: o.json });
|
|
252
|
+
case 'orchestrate': return handleOrchestrateCommand({ action: o.action, taskId: o.taskId, taskClass: o.taskClass, stage: o.stage, gate: o.gate, node: o.node, decision: o.decision, note: o.note, reason: o.reason, forced: o.forced, verificationPlan: o.verificationPlan, scope: o.scope, invariants: o.invariants, result: o.result, mcpOutcome: o.mcpOutcome, callingSkill: o.callingSkill, slug: o.slug, json: o.json, repoRoot: REPO_ROOT, stateRoot: evidenceRoot(o), buildAdapters: buildAdapterRegistry });
|
|
253
|
+
case 'research-request': return handleResearchRequestCommand({ slug: o.slug, action: o.action === 'status' ? 'list' : o.action, taskId: requireTaskId(o), stageId: o.stageId, question: o.question, reason: o.reason, blocking: o.blocking === undefined ? undefined : o.blocking === 'true' ? true : o.blocking === 'false' ? false : o.blocking, requestId: o.requestId, outcome: o.researchOutcome, claimId: o.claimId, evidenceIds: o.evidenceIds, gap: o.gap, json: o.json, projectRoot: evidenceRoot(o) });
|
|
254
|
+
// The store is shared by every linked worktree of the clone (lifecycle's main-worktree root); the
|
|
255
|
+
// feature is `--slug`, else the branch's, resolved the way decision and scaffold resolve it.
|
|
256
|
+
case 'task-attempt': return handleTaskAttemptCommand({ action: o.actionGiven ? o.action : undefined, slug: o.slug, task: o.task, owner: o.owner, mode: o.mode, token: o.token, result: o.result, evidenceId: o.evidenceId, reason: o.reason, json: o.json, cwd: evidenceRoot(o), resolveSlug: (cwd ) => featureSlugFor({ projectRoot: cwd }) });
|
|
257
|
+
case 'retrieve': return handleRetrieveCommand({ query: o.query, top: o.top, json: o.json });
|
|
258
|
+
case 'brief-tools': return handleBriefToolsCommand({ owner: o.owner, tier: o.tier, harness: o.harness, slug: o.slug, headingLevel: o.headingLevel, tasks: o.tasks, json: o.json, repoRoot: REPO_ROOT, projectRoot: storeRoot, buildAdapters: buildAdapterRegistry });
|
|
259
|
+
case 'advise': return handleAdviseCommand({ action: o.action, actionGiven: o.actionGiven, kind: o.kind, server: o.server, state: o.state, stateFile: o.stateFile, staticAnswer: o.staticAnswer, questionsFile: o.questionsFile, taskId: o.taskId, pickId: o.pickId, actual: o.actual, model: o.model, endpoint: o.endpoint, off: o.off, project: o.project, force: o.force, global: o.global, json: o.json, projectRoot: evidenceRoot(o) });
|
|
260
|
+
case 'model-role': return handleModelRoleCommand({ role: o.role, exclude: o.exclude, json: o.json, repoRoot: REPO_ROOT });
|
|
261
|
+
case 'route': return handleRouteCommand({ intent: o.intent, query: o.query, check: o.check, json: o.json, projectRoot: evidenceRoot(o) });
|
|
262
|
+
case 'claim': return handleClaimCommand({ slug: storeSlug, taskId: requireTaskId(o), action: o.action, statement: o.statement, claimId: o.claimId, evidenceId: o.evidenceId, replacedBy: o.replacedBy, relation: o.relation, role: o.role, json: o.json, stateRoot: storeRoot });
|
|
263
|
+
case 'context-pack': return handleContextPackCommand({ taskId: requireTaskId(o), taskClass: o.taskClass, objective: o.objective, json: o.json, stateRoot: evidenceRoot(o), slug: o.slug });
|
|
264
|
+
case 'retrieval-plan': return handleRetrievalPlanCommand({ slug: o.slug, taskId: requireTaskId(o), action: o.action, need: o.need, stage: o.stage, json: o.json, repoRoot: REPO_ROOT, stateRoot: evidenceRoot(o) });
|
|
265
|
+
case 'outcome': return handleOutcomeCommand({ slug: o.slug, taskId: requireTaskId(o), action: o.action, state: o.state, taskClass: o.taskClass, stage: o.stage, readiness: o.readiness, verification: o.verification, json: o.json, repoRoot: REPO_ROOT, stateRoot: evidenceRoot(o) });
|
|
266
|
+
case 'verify': return handleVerifyCommand({ slug: o.slug, taskId: requireTaskId(o), action: o.action, risk: o.risk, planPath: o.planPath, scope: o.scope, verificationInput: o.verificationInput, verificationPhase: o.verificationPhase, baselineRef: o.baselineRef, json: o.json, projectRoot: evidenceRoot(o) });
|
|
267
|
+
case 'leak-scan': return handleLeakScanCommand({ paths: o.paths, exclude: o.exclude, json: o.json, repoRoot: evidenceRoot(o) });
|
|
268
|
+
case 'recover': return handleRecoverCommand({ errorMessage: o.errorMessage, failedChecks: o.failedChecks, iteration: o.iteration, agent: o.agent, json: o.json });
|
|
269
|
+
default: console.error(`doflow: unknown command '${o.cmd}'`); process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Await the verb without changing its return value, output or exit code; usage exits never return. */
|
|
274
|
+
async function dispatchRuntimeCommand(o ) {
|
|
275
|
+
const moment = {};
|
|
276
|
+
const result = await dispatchRuntimeCommandInner(o, moment);
|
|
277
|
+
if (o.cmd !== 'storage') upkeepAfterVerb({ projectRoot: moment.projectRoot });
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
module.exports = { dispatchRuntimeCommand };
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Shared plumbing for the CLI command handlers (src/cli/commands/*) and their dispatch
|
|
3
|
+
// (src/cli/index.js, src/cli/runtime-commands.js). This module owns the facts every command
|
|
4
|
+
// needs: where the package root and the entrypoint directory are, the package version, the ONE
|
|
5
|
+
// adapter-registry factory, and the helpers more than one command uses (scope resolution, MCP
|
|
6
|
+
// selection, backup-table printing). Nothing here is harness-specific — native quirks belong in
|
|
7
|
+
// src/adapters/<id>/.
|
|
8
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const os = require('node:os');
|
|
11
|
+
const path = require('node:path');
|
|
12
|
+
const { REPO_ROOT } = require('../helper/repo-root.js');
|
|
13
|
+
const { doflowPaths } = require('../install/paths.js');
|
|
14
|
+
const { readInstallManifest } = require('../install/manifest.js');
|
|
15
|
+
const { DEFAULT_BACKUP_RETENTION, formatBytes } = require('../install/backup.js');
|
|
16
|
+
const { stateRoot, readLedger } = require('../state/index.js');
|
|
17
|
+
const { readLock } = require('../state/lockfile.js');
|
|
18
|
+
const { acquireRunLock, RunLockTimeoutError, RunLockError, RunLockLostError } = require('../state/run-lock.js');
|
|
19
|
+
const { createAdapterRegistry } = require('../adapters/index.js');
|
|
20
|
+
const { declaredHarnessPaths } = require('../helper/harness-paths.js');
|
|
21
|
+
const claudeAdapter = require('../adapters/claude/index.js');
|
|
22
|
+
const codexAdapter = require('../adapters/codex/index.js');
|
|
23
|
+
const { createGeminiAdapter } = require('../adapters/gemini/index.js');
|
|
24
|
+
const { createOpenCodeAdapter } = require('../adapters/opencode/index.js');
|
|
25
|
+
const { createPiAdapter } = require('../adapters/pi/index.js');
|
|
26
|
+
const { createCopilotAdapter } = require('../adapters/copilot/index.js');
|
|
27
|
+
const { createKiroAdapter } = require('../adapters/kiro/index.js');
|
|
28
|
+
const { createAntigravityAdapter } = require('../adapters/antigravity/index.js');
|
|
29
|
+
|
|
30
|
+
// The directory of the `doflow` entrypoint. Derived from the shared package root rather than
|
|
31
|
+
// __dirname so its value survives this code physically moving between directories: manifests,
|
|
32
|
+
// backups and the source-commit lookup all record this exact path (<repo>/bin).
|
|
33
|
+
const SCRIPT_DIR = path.join(REPO_ROOT, 'bin');
|
|
34
|
+
|
|
35
|
+
// Tolerant because the projected runtime under `.doflow/runtime/` ships bin/, src/ and
|
|
36
|
+
// core/registry/ but no package.json — see the `runtime.*` assets in core/registry/assets.json.
|
|
37
|
+
// A hard require here would make every Node-backed verb fail in an install, which is the exact
|
|
38
|
+
// defect that projection exists to fix. Only version reporting depends on this.
|
|
39
|
+
function loadPkg() {
|
|
40
|
+
try { return require('../../package.json'); } catch { return { version: '0.0.0-installed', name: '@khoavu882/doflow' }; }
|
|
41
|
+
}
|
|
42
|
+
const pkg = loadPkg();
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The one adapter registry construction, used by install/update/remove/reconcile. There used to
|
|
46
|
+
* be three inline copies of the same construction (one per mutating command); a fourth variant
|
|
47
|
+
* once grew in src/lifecycle/view.js and silently fell behind (fixed in 96006da) — which is why
|
|
48
|
+
* the registry guard pins every declared harness into each call site by parsing them.
|
|
49
|
+
* Build one here rather than copying the object literal again. Stage 3: each adapter factory
|
|
50
|
+
* receives its harness's declared native paths from core/registry/harnesses.json, so the CLI
|
|
51
|
+
* consumes exactly what the loader validates.
|
|
52
|
+
*/
|
|
53
|
+
function buildAdapterRegistry() {
|
|
54
|
+
const declared = declaredHarnessPaths();
|
|
55
|
+
return createAdapterRegistry({
|
|
56
|
+
claude: claudeAdapter.createClaudeAdapter({ declaredPaths: declared.claude }),
|
|
57
|
+
codex: codexAdapter.createCodexAdapter({ declaredPaths: declared.codex }),
|
|
58
|
+
gemini: createGeminiAdapter({ declaredPaths: declared.gemini }),
|
|
59
|
+
opencode: createOpenCodeAdapter({ declaredPaths: declared.opencode }),
|
|
60
|
+
pi: createPiAdapter({ declaredPaths: declared.pi }),
|
|
61
|
+
copilot: createCopilotAdapter({ declaredPaths: declared.copilot }),
|
|
62
|
+
kiro: createKiroAdapter({ declaredPaths: declared.kiro }),
|
|
63
|
+
antigravity: createAntigravityAdapter({ declaredPaths: declared.antigravity }),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Resolve {global, projectRoot} scope options for src/install/targets.js#toolDirs from parsed args. */
|
|
68
|
+
function scopeOf(o ) {
|
|
69
|
+
return { global: o.global, projectRoot: o.positional[0] || '.' };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function installPaths(scope ) {
|
|
73
|
+
const scopeRoot = scope.global ? os.homedir() : path.resolve(scope.projectRoot);
|
|
74
|
+
return doflowPaths({ scopeRoot });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Whether two paths name the same directory. Device and inode follow symlinks (macOS reaches
|
|
78
|
+
* /var through /private/var) and ignore letter case on a case-insensitive volume; a file system
|
|
79
|
+
* that reports inode 0 falls back to real paths, and paths that do not exist to resolved strings. */
|
|
80
|
+
function sameDirectory(a , b , { fsImpl = fs } = {}) {
|
|
81
|
+
const ra = path.resolve(a);
|
|
82
|
+
const rb = path.resolve(b);
|
|
83
|
+
try {
|
|
84
|
+
const sa = fsImpl.statSync(ra);
|
|
85
|
+
const sb = fsImpl.statSync(rb);
|
|
86
|
+
if (sa.ino !== 0 && sb.ino !== 0) return sa.dev === sb.dev && sa.ino === sb.ino;
|
|
87
|
+
} catch { /* fall through to real paths */ }
|
|
88
|
+
try {
|
|
89
|
+
return fsImpl.realpathSync.native(ra) === fsImpl.realpathSync.native(rb);
|
|
90
|
+
} catch {
|
|
91
|
+
return ra === rb;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A project install, update, remove or reconcile rooted at the home directory would share the
|
|
96
|
+
* global install's files and records, and its remove could delete them. Refuses that run with one
|
|
97
|
+
* line and exit 1 before anything is read, locked or written; `-g` and every other root pass. */
|
|
98
|
+
function refuseHomeRootedProject(o , command ) {
|
|
99
|
+
if (o.global) return;
|
|
100
|
+
const projectRoot = o.positional[0] || '.';
|
|
101
|
+
if (!sameDirectory(projectRoot, os.homedir())) return;
|
|
102
|
+
const targets = o.targets.length ? ` -t ${o.targets.join(',')}` : '';
|
|
103
|
+
console.error(`[ERROR] ${path.resolve(projectRoot)} is your home directory: a project ${command} there shares the global install's files and records and can delete them. Nothing was changed. Run: doflow ${command} -g${targets}`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Takes the scope's run lock for a mutating command, or returns null under --dry-run. A run that
|
|
108
|
+
* cannot take it prints the one reason line and exits 1 before it has read or changed anything. */
|
|
109
|
+
function holdRunLock(o , scope , command ) {
|
|
110
|
+
if (o.dryRun) return null;
|
|
111
|
+
try {
|
|
112
|
+
return acquireRunLock({ scopeRoot: installPaths(scope).scopeRoot, scope: scope.global ? 'global' : 'project', command });
|
|
113
|
+
} catch (err) {
|
|
114
|
+
if (!(err instanceof RunLockTimeoutError || err instanceof RunLockError)) throw err;
|
|
115
|
+
console.error((err ).message);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** After the confirm prompt: renews the hold, and exits 1 before the first write if another run
|
|
121
|
+
* took the lock over while the prompt waited. */
|
|
122
|
+
function checkpointRunLock(hold ) {
|
|
123
|
+
if (!hold) return;
|
|
124
|
+
try {
|
|
125
|
+
hold.checkpoint();
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (!(err instanceof RunLockLostError)) throw err;
|
|
128
|
+
console.error((err ).message);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Surface a reconciled-away MCP server rather than dropping it silently: the user picked it once,
|
|
134
|
+
* so its disappearance from their config should be explained, not discovered. */
|
|
135
|
+
function reportRetiredMcp(retired ) {
|
|
136
|
+
console.error(`[WARN] Dropping MCP server(s) no longer in the registry: ${retired.join(', ')}`);
|
|
137
|
+
console.error(' They were removed from DoFlow; your saved selection is being reconciled.');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The scope's doflow.lock, ledger and 1.18.0 manifest MCP list, as the MCP selection functions in
|
|
141
|
+
* src/install/mcp.js read them. */
|
|
142
|
+
function scopeSelectionState(scope ) {
|
|
143
|
+
const scopeRoot = scope.global ? os.homedir() : path.resolve(scope.projectRoot);
|
|
144
|
+
return {
|
|
145
|
+
lock: readLock(scope.global ? { scope: 'global', homeDir: scopeRoot } : { scope: 'project', projectRoot: scopeRoot }),
|
|
146
|
+
ledger: readLedger(stateRoot({ scope: scope.global ? 'global' : 'project', projectRoot: scopeRoot, homeDir: scopeRoot })),
|
|
147
|
+
manifestServers: readInstallManifest({ scopeRoot })?.mcpServers ?? null,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const MCP_SOURCE_WORDS = Object.freeze({ flag: '--mcp', prompt: 'prompt', recorded: 'recorded', kept: 'kept', manifest: 'remembered', default: 'default' } );
|
|
152
|
+
|
|
153
|
+
/** The one line a run names its MCP selections in, from what the plan resolved for each harness:
|
|
154
|
+
* harnesses with the same servers and source share a group, groups follow target order. Prints
|
|
155
|
+
* nothing when no target takes MCP servers, except that an explicit --mcp is said to do nothing. */
|
|
156
|
+
function printMcpSelection(view , sources , { requested, prefix = '[INFO]' } = {}) {
|
|
157
|
+
const groups = [];
|
|
158
|
+
for (const target of view.plan.targets) {
|
|
159
|
+
if (target.skipped || !Array.isArray(target.mcpSelected) || !(target.harness in sources)) continue;
|
|
160
|
+
const ids = target.mcpSelected.join(', ') || 'none';
|
|
161
|
+
const source = MCP_SOURCE_WORDS[sources[target.harness]];
|
|
162
|
+
const group = groups.find((item) => item.ids === ids && item.source === source);
|
|
163
|
+
if (group) group.harnesses.push(target.harness);
|
|
164
|
+
else groups.push({ harnesses: [target.harness], ids, source });
|
|
165
|
+
}
|
|
166
|
+
if (groups.length) {
|
|
167
|
+
console.log(`${prefix} MCP selection: ${groups.map((group) => `${group.harnesses.join(', ')}: ${group.ids} (${group.source})`).join('; ')}`);
|
|
168
|
+
} else if (requested) {
|
|
169
|
+
console.log(`${prefix} MCP: no targeted harness takes MCP servers; --mcp has no effect.`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Says which MCP entries a run with no native change recorded as DoFlow's (lifecycle
|
|
174
|
+
* recordMcpOwnership), or, under --dry-run, would record. */
|
|
175
|
+
function printRecordedMcpOwnership(recorded , { dryRun = false } = {}) {
|
|
176
|
+
for (const [harness, ids] of Object.entries (recorded)) {
|
|
177
|
+
const what = `${ids.length} MCP ${ids.length === 1 ? 'entry' : 'entries'} (${ids.join(', ')})`;
|
|
178
|
+
console.log(dryRun ? `[DRY] ${harness}: would record DoFlow's ownership of ${what}` : `[INFO] ${harness}: recorded DoFlow's ownership of ${what}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** What each planned harness that takes MCP servers will hold, as doflow.lock records it. */
|
|
183
|
+
function plannedMcpSelections(view ) {
|
|
184
|
+
return Object.fromEntries(view.plan.targets.filter((target ) => !target.skipped && Array.isArray(target.mcpSelected))
|
|
185
|
+
.map((target ) => [target.harness, target.mcpSelected]));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Total size of some rows, noting when any of them could not be measured. */
|
|
189
|
+
function rowsSize(rows ) {
|
|
190
|
+
const total = formatBytes(rows.reduce((sum, r) => sum + (r.bytes ?? 0), 0));
|
|
191
|
+
return rows.some((r) => r.bytes === null) ? `${total} (some sizes unknown)` : total;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function printBackupTable(rows , rootLabel ) {
|
|
195
|
+
if (rows.length === 0) { console.log(`[INFO] No backups found in ${rootLabel}`); return; }
|
|
196
|
+
const header = `${'BACKUP ID'.padEnd(42)} ${'OPERATION'.padEnd(14)} ${'TYPE'.padEnd(9)} ${'ORIGIN'.padEnd(9)} ${'SIZE'.padEnd(11)} TIMESTAMP`;
|
|
197
|
+
console.log(`\n${header}`);
|
|
198
|
+
console.log('─'.repeat(header.length));
|
|
199
|
+
for (const r of rows) {
|
|
200
|
+
const when = r.complete === false ? '- (incomplete: no manifest)' : r.timestamp;
|
|
201
|
+
console.log(`${r.id.padEnd(42)} ${r.operation.padEnd(14)} ${r.type.padEnd(9)} ${r.origin.padEnd(9)} ${formatBytes(r.bytes).padEnd(11)} ${when}`);
|
|
202
|
+
}
|
|
203
|
+
console.log(`\n${rows.length} backup(s) in ${rootLabel}`);
|
|
204
|
+
const current = rows.filter((r) => r.origin === 'current');
|
|
205
|
+
const legacy = rows.filter((r) => r.origin === 'legacy');
|
|
206
|
+
if (current.length) {
|
|
207
|
+
console.log(`current: ${current.length} backup(s), ${rowsSize(current)}; install and update keep the newest ${DEFAULT_BACKUP_RETENTION} (--prune N)`);
|
|
208
|
+
}
|
|
209
|
+
if (legacy.length) {
|
|
210
|
+
console.log(`legacy: ${legacy.length} backup(s), ${rowsSize(legacy)} in ${[...new Set(legacy.map((r) => r.backupRoot))].join(', ')}; read-only, never pruned`);
|
|
211
|
+
}
|
|
212
|
+
console.log('');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = {
|
|
216
|
+
REPO_ROOT, SCRIPT_DIR, pkg, buildAdapterRegistry, scopeOf, installPaths, sameDirectory, refuseHomeRootedProject,
|
|
217
|
+
holdRunLock, checkpointRunLock, reportRetiredMcp, scopeSelectionState, printMcpSelection, printRecordedMcpOwnership, plannedMcpSelections, printBackupTable,
|
|
218
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
|
|
4
|
+
// native type-stripping — no build step, no runtime dependency. Body stays CommonJS so a `.js`
|
|
5
|
+
// caller's `require('../helper/bash-helper.js')` keeps working while the rest of the tree is still
|
|
6
|
+
// `.js`. The `./repo-root` require stays bare while repo-root is still `.js`; it gains `.ts` in the
|
|
7
|
+
// same slice that converts repo-root.
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const path = require('node:path');
|
|
10
|
+
const { REPO_ROOT } = require('./repo-root.js');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Where a bash helper (`do-paths.sh`, `do-git-state.sh`) is: inside the package (a checkout or a
|
|
14
|
+
* source install), else projected next to the runtime. An installed runtime lives at
|
|
15
|
+
* `<install>/.doflow/runtime` and carries only bin/, src/ and core/registry/; the helpers are
|
|
16
|
+
* projected at `<install>/.doflow/scripts/doflow/bash/`, a sibling of the runtime directory.
|
|
17
|
+
* The package copy wins when both exist. Null when neither does, which callers report as a
|
|
18
|
+
* missing install rather than as the question the helper would have answered.
|
|
19
|
+
* @param name file name under `scripts/doflow/bash/`
|
|
20
|
+
* @param repoRoot the runtime root; only a test moves it
|
|
21
|
+
*/
|
|
22
|
+
function resolveBashHelper(
|
|
23
|
+
name ,
|
|
24
|
+
repoRoot = REPO_ROOT,
|
|
25
|
+
existsImpl = fs.existsSync,
|
|
26
|
+
) {
|
|
27
|
+
const candidates = [
|
|
28
|
+
path.join(repoRoot, 'core', 'shared', 'scripts', 'doflow', 'bash', name),
|
|
29
|
+
path.resolve(repoRoot, '..', 'scripts', 'doflow', 'bash', name),
|
|
30
|
+
];
|
|
31
|
+
return candidates.find((candidate ) => existsImpl(candidate)) || null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { resolveBashHelper };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// git.ts — shared git helpers. Single source of truth for `sourceCommit()`, previously duplicated
|
|
3
|
+
// verbatim in src/install/backup.js, src/install/manifest.js, and src/install/context.js (each ran
|
|
4
|
+
// its own `git rev-parse` — up to 3 spawns per `doflow install` for the same value).
|
|
5
|
+
//
|
|
6
|
+
// First module of the TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md). Authored as `.ts` and
|
|
7
|
+
// run directly by Node's native type-stripping (process.features.typescript === 'strip', Node
|
|
8
|
+
// ^22.18.0 || >=24.3.0), so no build step and no runtime dependency are added. The body stays CommonJS
|
|
9
|
+
// (`module.exports`) so a `require('../helper/git.js')` from a `.js` caller keeps working while the
|
|
10
|
+
// rest of the tree is still `.js`; the explicit `.ts` extension on those requires is what the
|
|
11
|
+
// CommonJS resolver needs to find this file.
|
|
12
|
+
const { execFileSync } = require('node:child_process');
|
|
13
|
+
|
|
14
|
+
/** Short HEAD commit of the repo at `repoRoot`, or 'unknown' if not a git repo / git unavailable. */
|
|
15
|
+
function sourceCommit(repoRoot ) {
|
|
16
|
+
try {
|
|
17
|
+
return execFileSync('git', ['-C', repoRoot, 'rev-parse', '--short', 'HEAD'], { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
18
|
+
.toString().trim() || 'unknown';
|
|
19
|
+
} catch {
|
|
20
|
+
return 'unknown';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { sourceCommit };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// DoFlow's destructive-command blocklist has exactly one authored home: the `permissions.deny`
|
|
4
|
+
// array in core/harnesses/claude/settings/settings.json (the projection Claude Code has shipped
|
|
5
|
+
// since the first release). Every other harness's opt-in permission projection DERIVES its
|
|
6
|
+
// entries from that file at plan time rather than restating the list, so the blocklist cannot
|
|
7
|
+
// drift between harnesses.
|
|
8
|
+
//
|
|
9
|
+
// Claude deny rules look like `Bash(<command glob>:*)`; the wrapper syntax is Claude-specific and
|
|
10
|
+
// the inner command glob is what every other harness wants.
|
|
11
|
+
//
|
|
12
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): authored as `.ts`, run directly by Node
|
|
13
|
+
// native type-stripping (process.features.typescript === 'strip', Node ^22.18.0 || >=24.3.0) — no build step and
|
|
14
|
+
// no runtime dependency. The body stays CommonJS so a `.js` caller's `require('../helper/guardrails.js')`
|
|
15
|
+
// keeps working while the rest of the tree is still `.js`; the explicit `.ts` extension is what the
|
|
16
|
+
// CommonJS resolver needs to find this file.
|
|
17
|
+
|
|
18
|
+
const fs = require('node:fs');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
|
|
21
|
+
const DENY_RULE = /^Bash\((.*):\*\)$/;
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/** Read the canonical blocklist as bare command globs, e.g. `git push --force*`. Throws when the
|
|
33
|
+
* authored file is missing or malformed — a silent empty blocklist would look like success while
|
|
34
|
+
* projecting no protection at all. */
|
|
35
|
+
function destructiveCommandGlobs({ repoRoot, fsImpl = fs } = {} ) {
|
|
36
|
+
const file = path.join(repoRoot, 'core', 'harnesses', 'claude', 'settings', 'settings.json');
|
|
37
|
+
const parsed = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
|
|
38
|
+
const deny = parsed?.permissions?.deny;
|
|
39
|
+
if (!Array.isArray(deny) || deny.length === 0) {
|
|
40
|
+
throw new Error(`guardrails: no permissions.deny list in ${file}`);
|
|
41
|
+
}
|
|
42
|
+
return deny.map((rule ) => {
|
|
43
|
+
const m = DENY_RULE.exec(rule);
|
|
44
|
+
if (!m) throw new Error(`guardrails: unrecognized deny rule '${rule}' in ${file}`);
|
|
45
|
+
return m[1];
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { destructiveCommandGlobs };
|