@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,501 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# lib.sh — shared constants and helpers for the Canonical Policy Library
|
|
3
|
+
#
|
|
4
|
+
# Usage: source "$(dirname "$0")/lib.sh"
|
|
5
|
+
#
|
|
6
|
+
# All hooks must:
|
|
7
|
+
# 1. source this file at the top
|
|
8
|
+
# 2. call require_jq immediately after
|
|
9
|
+
# 3. capture stdin once: INPUT=$(cat)
|
|
10
|
+
# 4. use: json_field "$INPUT" ".field_name"
|
|
11
|
+
#
|
|
12
|
+
# Deployment:
|
|
13
|
+
# This file is installed by the doflow CLI and overwritten on update — do
|
|
14
|
+
# not edit an installed copy directly.
|
|
15
|
+
#
|
|
16
|
+
# Consolidation note (022-normalize-hooks, Phase B): this is the single
|
|
17
|
+
# canonical copy every front door's dispatched policy script sources — the
|
|
18
|
+
# claude/gemini/kiro/codex copies it replaces were already byte-identical
|
|
19
|
+
# except for one Claude-only default (see DOFLOW_AGENT below), confirmed via
|
|
20
|
+
# diff before consolidating.
|
|
21
|
+
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
|
|
24
|
+
# ── State directories ────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
# XDG-compliant, agent-agnostic store shared by every harness DoFlow supports.
|
|
27
|
+
DOFLOW_HOME="${XDG_CONFIG_HOME:-$HOME/.config}/doflow"
|
|
28
|
+
STATE_DIR="$DOFLOW_HOME/session-env"
|
|
29
|
+
SESSION_DIR="$STATE_DIR/sessions"
|
|
30
|
+
PROJECTS_DIR="$STATE_DIR/projects"
|
|
31
|
+
# shellcheck disable=SC2034 # used by session-context.sh and stop-check.sh which source this file
|
|
32
|
+
SESSIONS_LOG="$DOFLOW_HOME/sessions.log"
|
|
33
|
+
|
|
34
|
+
# Identifies which harness is running. A front door sets this before sourcing
|
|
35
|
+
# this file (e.g. `export DOFLOW_AGENT=gemini; exec bash .../policies/...sh`);
|
|
36
|
+
# "unknown" here — not any one harness's name — is the honest fallback when a
|
|
37
|
+
# front door omits it, since every prior per-harness copy of this file
|
|
38
|
+
# defaulted to "claude-code" regardless of which harness actually shipped it.
|
|
39
|
+
export DOFLOW_AGENT="${DOFLOW_AGENT:-unknown}"
|
|
40
|
+
|
|
41
|
+
# ── canonicalize_path ─────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
# Print an absolute, symlink-resolved form of <path> to stdout when <path>
|
|
44
|
+
# exists; otherwise print <path> unchanged.
|
|
45
|
+
#
|
|
46
|
+
# Replaces `realpath -e "$1" 2>/dev/null || echo "$1"`, which already fails
|
|
47
|
+
# silently on stock macOS (BSD realpath has no -e flag there) and falls back
|
|
48
|
+
# to an uncanonicalized path. Uses only primitives present on every target
|
|
49
|
+
# (cd, pwd -P, dirname, basename) — no GNU-only flag, no assumed Homebrew
|
|
50
|
+
# coreutils.
|
|
51
|
+
#
|
|
52
|
+
# Hardening notes:
|
|
53
|
+
# - `CDPATH=` resets CDPATH for the internal `cd` so a user's exported
|
|
54
|
+
# CDPATH can't redirect it to an unrelated directory of the same name
|
|
55
|
+
# (and can't make `cd` echo a stray "found via CDPATH" line to stdout).
|
|
56
|
+
# - `--` guards `dirname`/`basename`/`cd` against a path that begins with
|
|
57
|
+
# `-` being parsed as an option.
|
|
58
|
+
# - if the internal `cd` fails for any reason, `||` falls through to the
|
|
59
|
+
# uncanonicalized-path branch instead of aborting under this file's
|
|
60
|
+
# `set -e` (matching the old `realpath ... || echo "$1"` degrade path).
|
|
61
|
+
# - a root-level parent directory ("/") is special-cased so the result
|
|
62
|
+
# never gets a leading `//`, which POSIX leaves undefined and which
|
|
63
|
+
# Cygwin/MSYS2 (a DoFlow target platform) interprets as a UNC path.
|
|
64
|
+
canonicalize_path() {
|
|
65
|
+
local path="$1" dir base
|
|
66
|
+
if [[ -e "$path" ]] \
|
|
67
|
+
&& dir=$(CDPATH= cd -P -- "$(dirname -- "$path")" 2>/dev/null && pwd -P); then
|
|
68
|
+
base=$(basename -- "$path")
|
|
69
|
+
if [[ "$dir" == "/" ]]; then
|
|
70
|
+
printf '/%s\n' "$base"
|
|
71
|
+
else
|
|
72
|
+
printf '%s/%s\n' "$dir" "$base"
|
|
73
|
+
fi
|
|
74
|
+
else
|
|
75
|
+
printf '%s\n' "$path"
|
|
76
|
+
fi
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# ── cwd_hash ─────────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
# Derive a stable 16-char hash of an absolute directory path.
|
|
82
|
+
# Used to namespace per-project state (compact summaries, warnings).
|
|
83
|
+
# Normalizes symlinks and ../ components so equivalent paths hash identically.
|
|
84
|
+
#
|
|
85
|
+
# For a path that is (or resolves to) an existing directory — the case this
|
|
86
|
+
# function exists for — fully resolves it, including a symlink at the path's
|
|
87
|
+
# own leaf component, via `(cd "$1" && pwd -P)`, so equivalent directories
|
|
88
|
+
# always hash identically. Callers that ever pass a non-directory or
|
|
89
|
+
# nonexistent path fall back to the general-purpose canonicalize_path, which
|
|
90
|
+
# — being usable by any file or missing path, not just directories — only
|
|
91
|
+
# resolves symlinks in the parent chain, not the leaf itself.
|
|
92
|
+
#
|
|
93
|
+
# Hash fallback chain: sha256sum (Linux, Git Bash) -> shasum -a 256 (macOS,
|
|
94
|
+
# every release since 10.6) -> cksum (last resort). The cksum branch is
|
|
95
|
+
# normalized via printf '%016x' to the same 16-lowercase-hex-char contract
|
|
96
|
+
# every other branch provides (raw cksum output is decimal and shorter).
|
|
97
|
+
# Collision risk from the cksum fallback is acceptable here — this is only a
|
|
98
|
+
# cache-key namespace, not security-relevant.
|
|
99
|
+
cwd_hash() {
|
|
100
|
+
local canonical
|
|
101
|
+
if [ -d "$1" ]; then
|
|
102
|
+
canonical=$(cd "$1" && pwd -P)
|
|
103
|
+
else
|
|
104
|
+
canonical=$(canonicalize_path "$1")
|
|
105
|
+
fi
|
|
106
|
+
if command -v sha256sum &>/dev/null; then
|
|
107
|
+
echo "$canonical" | sha256sum | cut -c1-16
|
|
108
|
+
elif command -v shasum &>/dev/null; then
|
|
109
|
+
echo "$canonical" | shasum -a 256 | cut -c1-16
|
|
110
|
+
else
|
|
111
|
+
printf '%016x\n' "$(echo "$canonical" | cksum | cut -d' ' -f1)"
|
|
112
|
+
fi
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# ── with_file_lock / release_file_lock ────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
# Cross-platform mutual exclusion via atomic `mkdir` (atomic on every
|
|
118
|
+
# filesystem DoFlow runs on, including NTFS via MSYS2). Replaces
|
|
119
|
+
# `flock -x -w 5 200`, which is entirely absent on stock macOS (util-linux
|
|
120
|
+
# only, causing the session-end log-trimming block to silently no-op today).
|
|
121
|
+
#
|
|
122
|
+
# Usage (caller acquires, then is responsible for releasing — including on
|
|
123
|
+
# failure paths, ideally via `trap`):
|
|
124
|
+
#
|
|
125
|
+
# if with_file_lock "$LOCK_FILE" 5; then
|
|
126
|
+
# trap 'release_file_lock "$LOCK_FILE"' EXIT
|
|
127
|
+
# ...critical section...
|
|
128
|
+
# release_file_lock "$LOCK_FILE"
|
|
129
|
+
# fi
|
|
130
|
+
#
|
|
131
|
+
# Polls for the lock (creating "<lockfile>.d" as the lock token) until
|
|
132
|
+
# acquired or until <timeout_seconds> elapses. Returns 0 once acquired
|
|
133
|
+
# (lock held), or 1 on timeout — callers should treat a timeout the same
|
|
134
|
+
# way the old `flock -w 5 200 || exit 0` did: skip the guarded section
|
|
135
|
+
# rather than block indefinitely.
|
|
136
|
+
#
|
|
137
|
+
# Stale-lock recovery: unlike `flock`, an `mkdir` lock is not released
|
|
138
|
+
# automatically if the holder is killed or crashes — a leftover lockdir
|
|
139
|
+
# would otherwise wedge every future acquisition permanently. To guard
|
|
140
|
+
# against that, the holder's PID is written to "<lockfile>.d/pid" at
|
|
141
|
+
# acquire time; when this function finds the lockdir already taken, it
|
|
142
|
+
# checks whether that PID is still alive (`kill -0`) and, if not, treats
|
|
143
|
+
# the lock as abandoned and reclaims it immediately instead of waiting out
|
|
144
|
+
# the full timeout. This is a best-effort check (a PID can in principle be
|
|
145
|
+
# reused by an unrelated process before we look), not a hard guarantee —
|
|
146
|
+
# acceptable here because the guarded sections this protects are idempotent
|
|
147
|
+
# log/state maintenance, not a correctness-critical resource.
|
|
148
|
+
with_file_lock() {
|
|
149
|
+
local lockfile="$1"
|
|
150
|
+
local timeout_seconds="$2"
|
|
151
|
+
local lockdir="${lockfile}.d"
|
|
152
|
+
local pidfile="${lockdir}/pid"
|
|
153
|
+
local waited=0 holder_pid
|
|
154
|
+
while true; do
|
|
155
|
+
if mkdir "$lockdir" 2>/dev/null; then
|
|
156
|
+
echo "$$" >"$pidfile" 2>/dev/null || true
|
|
157
|
+
return 0
|
|
158
|
+
fi
|
|
159
|
+
# Someone else holds the lock (or a stale one was left behind) — check
|
|
160
|
+
# whether the recorded holder is still alive before waiting on it.
|
|
161
|
+
holder_pid=$(cat "$pidfile" 2>/dev/null || echo "")
|
|
162
|
+
if [[ -n "$holder_pid" ]] && ! kill -0 "$holder_pid" 2>/dev/null; then
|
|
163
|
+
rm -rf "$lockdir" 2>/dev/null || true
|
|
164
|
+
continue
|
|
165
|
+
fi
|
|
166
|
+
if (( waited >= timeout_seconds )); then
|
|
167
|
+
return 1
|
|
168
|
+
fi
|
|
169
|
+
sleep 1
|
|
170
|
+
waited=$((waited + 1))
|
|
171
|
+
done
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# Companion release for with_file_lock. Idempotent — safe to call even if
|
|
175
|
+
# the lock was never acquired or was already released.
|
|
176
|
+
release_file_lock() {
|
|
177
|
+
local lockfile="$1"
|
|
178
|
+
local lockdir="${lockfile}.d"
|
|
179
|
+
rm -f "${lockdir}/pid" 2>/dev/null || true
|
|
180
|
+
rmdir "$lockdir" 2>/dev/null || true
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# ── run_with_timeout ─────────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
# Run <command...>, terminating it if it exceeds <seconds>. When no
|
|
186
|
+
# GNU-compatible timeout-enforcing binary (`timeout`/`gtimeout`) is on
|
|
187
|
+
# PATH — stock macOS lacks GNU coreutils' `timeout` unless Homebrew is
|
|
188
|
+
# installed — runs <command...> directly with no enforced budget rather
|
|
189
|
+
# than failing (a soft safety net, not a correctness requirement; NFR-002
|
|
190
|
+
# forbids requiring a new dependency).
|
|
191
|
+
#
|
|
192
|
+
# GNU-ness check: on Git Bash, PATH includes Windows' System32, which ships
|
|
193
|
+
# its own `timeout.exe` — an interactive countdown/delay command with
|
|
194
|
+
# incompatible syntax, not GNU coreutils' `timeout`. If it resolved first
|
|
195
|
+
# and were trusted blindly, `timeout 5 git status` would fail on argument
|
|
196
|
+
# parsing and the wrapped command would never run at all — strictly worse
|
|
197
|
+
# than the no-timeout-found fallback below. So a candidate binary is only
|
|
198
|
+
# trusted after `<bin> --version` succeeds (GNU coreutils understands
|
|
199
|
+
# `--version`; Windows' `timeout.exe` does not and errors out), otherwise
|
|
200
|
+
# the next candidate is tried and, failing that, the command runs directly.
|
|
201
|
+
#
|
|
202
|
+
# Usage: run_with_timeout 5 -- git status
|
|
203
|
+
run_with_timeout() {
|
|
204
|
+
local seconds="$1"
|
|
205
|
+
shift
|
|
206
|
+
if [[ "${1:-}" == "--" ]]; then
|
|
207
|
+
shift
|
|
208
|
+
fi
|
|
209
|
+
local timeout_bin=""
|
|
210
|
+
if command -v timeout &>/dev/null && timeout --version >/dev/null 2>&1; then
|
|
211
|
+
timeout_bin="timeout"
|
|
212
|
+
elif command -v gtimeout &>/dev/null && gtimeout --version >/dev/null 2>&1; then
|
|
213
|
+
timeout_bin="gtimeout"
|
|
214
|
+
fi
|
|
215
|
+
if [[ -n "$timeout_bin" ]]; then
|
|
216
|
+
"$timeout_bin" "$seconds" "$@"
|
|
217
|
+
else
|
|
218
|
+
"$@"
|
|
219
|
+
fi
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# ── Git state helpers ────────────────────────────────────────────────────────
|
|
223
|
+
#
|
|
224
|
+
# Thin wrappers around `git -C <cwd> ...` with the project's standard 1s
|
|
225
|
+
# timeout. Each echoes git's own output (or nothing/0 on failure) and leaves
|
|
226
|
+
# stderr suppressed — callers keep their own `|| echo <fallback>` around a
|
|
227
|
+
# call for a script-specific default, exactly as the inline git calls these
|
|
228
|
+
# replace did (022-hooks-remaining-duplication: the same 4 invocations were
|
|
229
|
+
# duplicated verbatim across pre-compact.sh/session-end.sh/session-context.sh).
|
|
230
|
+
|
|
231
|
+
is_git_worktree() {
|
|
232
|
+
run_with_timeout 1 -- git -C "$1" rev-parse --is-inside-work-tree &>/dev/null
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
git_branch_of() {
|
|
236
|
+
run_with_timeout 1 -- git -C "$1" branch --show-current 2>/dev/null
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
git_short_sha_of() {
|
|
240
|
+
run_with_timeout 1 -- git -C "$1" rev-parse --short HEAD 2>/dev/null
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
git_uncommitted_count_of() {
|
|
244
|
+
run_with_timeout 1 -- git -C "$1" status --porcelain 2>/dev/null | wc -l | tr -d ' '
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
has_uncommitted_changes() {
|
|
248
|
+
run_with_timeout 1 -- git -C "$1" status --porcelain 2>/dev/null | grep -q .
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
# ── Directory helpers ─────────────────────────────────────────────────────────
|
|
252
|
+
|
|
253
|
+
# Create and return the session-scoped state directory for a given session_id.
|
|
254
|
+
# Safe to call multiple times (mkdir -p is idempotent).
|
|
255
|
+
ensure_session_dir() {
|
|
256
|
+
local session_id="$1"
|
|
257
|
+
mkdir -p "$SESSION_DIR/$session_id"
|
|
258
|
+
echo "$SESSION_DIR/$session_id"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# Create and return the project-scoped state directory for a given cwd.
|
|
262
|
+
# Shared across all sessions in the same directory.
|
|
263
|
+
ensure_project_dir() {
|
|
264
|
+
local cwd="$1"
|
|
265
|
+
local hash
|
|
266
|
+
hash=$(cwd_hash "$cwd")
|
|
267
|
+
mkdir -p "$PROJECTS_DIR/$hash"
|
|
268
|
+
echo "$PROJECTS_DIR/$hash"
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# ── JSON helpers ──────────────────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
# Extract a field from a JSON string.
|
|
274
|
+
# Usage: json_field "$INPUT" ".field_name"
|
|
275
|
+
# Returns empty string if field is null or jq fails.
|
|
276
|
+
json_field() {
|
|
277
|
+
local json="$1"
|
|
278
|
+
local query="$2"
|
|
279
|
+
echo "$json" | jq -r "$query // empty" 2>/dev/null || echo ""
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# ── Dependency guard ──────────────────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
# Verify jq is available at runtime. If absent, emit a diagnostic to stderr
|
|
285
|
+
# and exit 0 (never block the harness — degraded operation is preferable to
|
|
286
|
+
# failure).
|
|
287
|
+
require_jq() {
|
|
288
|
+
if ! command -v jq &>/dev/null; then
|
|
289
|
+
echo "[hooks] jq not found — install jq to enable session lifecycle hooks (apt install jq / brew install jq)" >&2
|
|
290
|
+
exit 0
|
|
291
|
+
fi
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
# ── Session role ──────────────────────────────────────────────────────────────
|
|
295
|
+
|
|
296
|
+
# Print `main` or `worker` for this session, read from DOFLOW_SESSION_ROLE (matched
|
|
297
|
+
# case-insensitively). Unset or empty is `main`; a value that is neither `main` nor
|
|
298
|
+
# `worker` is `worker` and writes one line to stderr. A worker never writes or claims the
|
|
299
|
+
# project's compact summary or warning. Always returns 0. bash 3.2 safe (no ${var,,}).
|
|
300
|
+
doflow_session_role() {
|
|
301
|
+
local role
|
|
302
|
+
role=$(printf '%s' "${DOFLOW_SESSION_ROLE:-}" | tr '[:upper:]' '[:lower:]') || role=""
|
|
303
|
+
case "$role" in
|
|
304
|
+
""|main) printf 'main\n' ;;
|
|
305
|
+
worker) printf 'worker\n' ;;
|
|
306
|
+
*)
|
|
307
|
+
echo "[doflow] unknown DOFLOW_SESSION_ROLE; treating session as worker" >&2
|
|
308
|
+
printf 'worker\n'
|
|
309
|
+
;;
|
|
310
|
+
esac
|
|
311
|
+
return 0
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
# Start storage cleanup without changing the hook's output, status, or latency.
|
|
315
|
+
storage_upkeep_async() {
|
|
316
|
+
local cwd="${1:-}" root run home config_dir
|
|
317
|
+
[ -n "$cwd" ] || return 0
|
|
318
|
+
home="${HOME:-}"
|
|
319
|
+
config_dir="${CLAUDE_CONFIG_DIR:-$home/.claude}"
|
|
320
|
+
root=$(nudge_repo_root "$cwd" 2>/dev/null) || root="$cwd"
|
|
321
|
+
for run in \
|
|
322
|
+
"$root/.doflow/scripts/doflow/bin/doflow-run" \
|
|
323
|
+
"$config_dir/scripts/doflow/bin/doflow-run" \
|
|
324
|
+
"$home/.doflow/scripts/doflow/bin/doflow-run"
|
|
325
|
+
do
|
|
326
|
+
[ -x "$run" ] || continue
|
|
327
|
+
# Background the fully redirected subshell; an inner `&` leaves this shell
|
|
328
|
+
# holding the hook's captured stdout open until the dispatcher exits. The
|
|
329
|
+
# storage verb takes its project from the working directory: run it at the root.
|
|
330
|
+
( cd "$root" 2>/dev/null && nohup "$run" storage --action upkeep </dev/null >/dev/null 2>&1 ) >/dev/null 2>&1 &
|
|
331
|
+
return 0
|
|
332
|
+
done
|
|
333
|
+
return 0
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
# ── Prompt nudge gate helpers ────────────────────────────────────────────────
|
|
337
|
+
#
|
|
338
|
+
# Used by user-prompt-submit.sh for the once-per-session `/do` suggestion. Function
|
|
339
|
+
# definitions only: no top-level statement, no `set`, no `trap`. Each is safe under
|
|
340
|
+
# the `set -euo pipefail` above, bash 3.2 and BSD tools. The first four start no
|
|
341
|
+
# process; none of them reads the prompt. A probe that cannot decide returns
|
|
342
|
+
# non-zero, and the caller treats that as "no nudge".
|
|
343
|
+
|
|
344
|
+
# Print the nearest ancestor of <path> (itself included) for which `test <op>
|
|
345
|
+
# <ancestor>/<entry>` holds, spelled as in <path>; fail when none does. The walk runs
|
|
346
|
+
# on a copy of <path> with every `\` read as `/` (a drive path such as C:\Users\x from
|
|
347
|
+
# Git Bash or MSYS2) and stops as soon as dropping the last component no longer
|
|
348
|
+
# shortens it, so it ends for every input: `relative/dir` stops at `relative`, `C:`
|
|
349
|
+
# and `.` at themselves. Only the path's own components are walked: a relative path
|
|
350
|
+
# is not resolved against $PWD. No process.
|
|
351
|
+
nudge_find_up() {
|
|
352
|
+
local path="${1%/}" entry="${2:-}" op="${3:--e}" walk up
|
|
353
|
+
walk="${path//\\//}"
|
|
354
|
+
while [ -n "$walk" ]; do
|
|
355
|
+
if test "$op" "$walk/$entry"; then
|
|
356
|
+
printf '%s\n' "${path:0:${#walk}}"
|
|
357
|
+
return 0
|
|
358
|
+
fi
|
|
359
|
+
up="${walk%/*}"
|
|
360
|
+
[ "${#up}" -lt "${#walk}" ] || return 1
|
|
361
|
+
walk="$up"
|
|
362
|
+
done
|
|
363
|
+
return 1
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
# Print the nearest ancestor of <cwd> (itself included) holding a `.git` entry — a
|
|
367
|
+
# directory, or the file a linked worktree and a submodule have — else <cwd>.
|
|
368
|
+
nudge_repo_root() {
|
|
369
|
+
local dir="${1%/}"
|
|
370
|
+
[ -n "$dir" ] || { printf '%s\n' "$1"; return 0; }
|
|
371
|
+
nudge_find_up "$dir" .git -e || printf '%s\n' "$dir"
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
# Print `on` or `off`. The project file <root>/.doflow/prompt-nudge wins, then the
|
|
375
|
+
# user file <DoFlow home>/prompt-nudge; neither present is `on`. A present file
|
|
376
|
+
# decides alone: its first line trimmed and lowercased must be `on` or `off`, and a
|
|
377
|
+
# file that cannot be read, is empty or holds anything else is `off`.
|
|
378
|
+
nudge_setting() {
|
|
379
|
+
local root="${1:-}" file line=""
|
|
380
|
+
for file in "${root:+$root/.doflow/prompt-nudge}" "$DOFLOW_HOME/prompt-nudge"; do
|
|
381
|
+
[ -n "$file" ] && [ -e "$file" ] || continue
|
|
382
|
+
line=""
|
|
383
|
+
{ IFS= read -r line || true; } 2>/dev/null < "$file" || line=""
|
|
384
|
+
line="${line#"${line%%[![:space:]]*}"}"
|
|
385
|
+
line="${line%"${line##*[![:space:]]}"}"
|
|
386
|
+
case "$line" in
|
|
387
|
+
[Oo][Nn]) printf 'on\n' ;;
|
|
388
|
+
*) printf 'off\n' ;;
|
|
389
|
+
esac
|
|
390
|
+
return 0
|
|
391
|
+
done
|
|
392
|
+
printf 'on\n'
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
# Succeed when <id> is a safe session id: 1 to 128 of [A-Za-z0-9._-], first one
|
|
396
|
+
# alphanumeric. No process.
|
|
397
|
+
nudge_safe_id() {
|
|
398
|
+
local LC_ALL=C
|
|
399
|
+
local id="${1:-}"
|
|
400
|
+
[ -n "$id" ] && [ "${#id}" -le 128 ] || return 1
|
|
401
|
+
case "$id" in
|
|
402
|
+
[!A-Za-z0-9]*|*[!A-Za-z0-9._-]*) return 1 ;;
|
|
403
|
+
esac
|
|
404
|
+
return 0
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
# Print the marker path for a session id. The caller checks nudge_safe_id first.
|
|
408
|
+
nudge_marker_file() {
|
|
409
|
+
printf '%s\n' "$STATE_DIR/nudge/$1"
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
# Create the marker for <id> holding <nudged|suppressed>. Succeeds only when this call
|
|
413
|
+
# created it: the create is exclusive, so a second call for the same id fails and
|
|
414
|
+
# leaves the first content. After a create, markers untouched for over 30 days are
|
|
415
|
+
# removed. Silent on every failure.
|
|
416
|
+
nudge_mark() {
|
|
417
|
+
local id="${1:-}" word="${2:-}" dir="$STATE_DIR/nudge" file
|
|
418
|
+
nudge_safe_id "$id" || return 1
|
|
419
|
+
case "$word" in nudged|suppressed) ;; *) return 1 ;; esac
|
|
420
|
+
file="$dir/$id"
|
|
421
|
+
mkdir -p "$dir" 2>/dev/null || return 1
|
|
422
|
+
( set -o noclobber; : > "$file" ) 2>/dev/null || return 1
|
|
423
|
+
printf '%s\n' "$word" > "$file" 2>/dev/null || true
|
|
424
|
+
find "$dir" -type f -mtime +30 -delete 2>/dev/null || true
|
|
425
|
+
return 0
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
# S2: succeed when the active branch has chain artifacts at <root>/agent-docs/doflow/<slug>
|
|
429
|
+
# (slug = the branch after its last `/`). <session_path> is the per-session folder; the
|
|
430
|
+
# branch is the one its git-context.json recorded, else the one git reports for <cwd>.
|
|
431
|
+
# The branches main, master, develop, trunk, HEAD and the empty one never fire.
|
|
432
|
+
nudge_feature_active() {
|
|
433
|
+
local cwd="${1:-}" root="${2:-}" session="${3:-}" branch="" slug
|
|
434
|
+
[ -n "$root" ] || return 1
|
|
435
|
+
if [ -n "$session" ] && [ -f "$session/git-context.json" ]; then
|
|
436
|
+
branch=$(jq -r 'if (.branch | type) == "string" then "B:" + .branch else empty end' \
|
|
437
|
+
"$session/git-context.json" 2>/dev/null) || branch=""
|
|
438
|
+
fi
|
|
439
|
+
if [ -n "$branch" ]; then
|
|
440
|
+
branch="${branch#B:}"
|
|
441
|
+
elif [ -n "$cwd" ]; then
|
|
442
|
+
branch=$(git_branch_of "$cwd" 2>/dev/null) || branch=""
|
|
443
|
+
fi
|
|
444
|
+
case "$branch" in ""|main|master|develop|trunk|HEAD) return 1 ;; esac
|
|
445
|
+
slug="${branch##*/}"
|
|
446
|
+
case "$slug" in ""|.|..) return 1 ;; esac
|
|
447
|
+
[ -d "$root/agent-docs/doflow/$slug" ]
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
# S3: succeed when a `/do-*` skill already ran this session, read from the run ledger:
|
|
451
|
+
# the lexically last <config>/state/runs/*.jsonl, whose last line's timestamp is not
|
|
452
|
+
# earlier than the session's captured_at (both YYYY-MM-DDTHH:MM:SSZ). Config dir:
|
|
453
|
+
# $DOFLOW_CONFIG_DIR, else the nearest `.doflow` at or above <cwd>, else $HOME/.doflow.
|
|
454
|
+
# Without a captured_at the ledger counts when it is named for today's UTC date; a last
|
|
455
|
+
# line without a usable timestamp counts too. No ledger, or an empty one, does not.
|
|
456
|
+
nudge_ledger_active() {
|
|
457
|
+
local LC_ALL=C
|
|
458
|
+
local cwd="${1:-}" session="${2:-}" config dir f ledger="" last ts captured="" tsre
|
|
459
|
+
tsre='^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$'
|
|
460
|
+
if [ -n "${DOFLOW_CONFIG_DIR:-}" ]; then
|
|
461
|
+
config="$DOFLOW_CONFIG_DIR"
|
|
462
|
+
else
|
|
463
|
+
config="$HOME/.doflow"
|
|
464
|
+
# The ancestor in the walk's own spelling (`\` read as `/`), the form its test succeeded on.
|
|
465
|
+
if dir=$(nudge_find_up "$cwd" .doflow -d); then config="${dir//\\//}/.doflow"; fi
|
|
466
|
+
fi
|
|
467
|
+
for f in "$config"/state/runs/*.jsonl; do
|
|
468
|
+
[ -f "$f" ] && ledger="$f"
|
|
469
|
+
done
|
|
470
|
+
[ -n "$ledger" ] && [ -s "$ledger" ] || return 1
|
|
471
|
+
if [ -n "$session" ] && [ -f "$session/git-context.json" ]; then
|
|
472
|
+
captured=$(jq -r '.captured_at // empty | strings' "$session/git-context.json" 2>/dev/null) || captured=""
|
|
473
|
+
fi
|
|
474
|
+
last=$(tail -n 1 "$ledger" 2>/dev/null) || last=""
|
|
475
|
+
ts=$(printf '%s' "$last" | jq -r '.timestamp // empty | strings' 2>/dev/null) || ts=""
|
|
476
|
+
[[ "$ts" =~ $tsre ]] || return 0
|
|
477
|
+
if [[ "$captured" =~ $tsre ]]; then
|
|
478
|
+
[[ ! "$ts" < "$captured" ]]
|
|
479
|
+
else
|
|
480
|
+
[ "${ledger##*/}" = "$(date -u +%Y-%m-%d).jsonl" ]
|
|
481
|
+
fi
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
# Print the workflow registry's path: <policy_dir> is the physical directory of the
|
|
485
|
+
# policy script (empty when unknown), <root> the repository root. First existing
|
|
486
|
+
# regular file of IC-006's list wins: the install copy, the source tree's, the
|
|
487
|
+
# project's install, the global install. Fails when none exists.
|
|
488
|
+
nudge_registry() {
|
|
489
|
+
local policy_dir="${1:-}" root="${2:-}" candidate
|
|
490
|
+
for candidate in \
|
|
491
|
+
"${policy_dir:+$policy_dir/../../../runtime/core/registry/workflows.json}" \
|
|
492
|
+
"${policy_dir:+$policy_dir/../../../../registry/workflows.json}" \
|
|
493
|
+
"${root:+$root/.doflow/runtime/core/registry/workflows.json}" \
|
|
494
|
+
"$HOME/.doflow/runtime/core/registry/workflows.json"; do
|
|
495
|
+
if [ -n "$candidate" ] && [ -f "$candidate" ]; then
|
|
496
|
+
printf '%s\n' "$candidate"
|
|
497
|
+
return 0
|
|
498
|
+
fi
|
|
499
|
+
done
|
|
500
|
+
return 1
|
|
501
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# mcp-policy.conf — MCP tool deny patterns for mcp-tool-guard.sh
|
|
2
|
+
#
|
|
3
|
+
# Format (TAB-separated), identical convention to blocked-patterns.conf:
|
|
4
|
+
# regex<TAB>reason shown to the harness
|
|
5
|
+
#
|
|
6
|
+
# Rules:
|
|
7
|
+
# - Lines beginning with # are comments (skipped)
|
|
8
|
+
# - Empty lines are skipped
|
|
9
|
+
# - Regex is matched case-insensitively against the tool_name field
|
|
10
|
+
# (e.g. "mcp__github__create_issue"), not a command string
|
|
11
|
+
# - First matching pattern wins — order matters
|
|
12
|
+
# - A malformed regex on any line is skipped (guard continues safely)
|
|
13
|
+
# - This file ships with ZERO active patterns — every mcp__* call is allowed by
|
|
14
|
+
# default. Uncomment/add patterns below once subagent-audit.sh's log (or your
|
|
15
|
+
# own usage data) shows which MCP tools are actually in play; writing deny
|
|
16
|
+
# rules before that is policy on a guess, not evidence (see
|
|
17
|
+
# agent-docs/research/hook-governance-agent-tool-mcp-skill.md §3.2/§3.3).
|
|
18
|
+
#
|
|
19
|
+
# Examples (commented out — uncomment and adapt to activate):
|
|
20
|
+
# mcp__.*__delete.* Destructive MCP operation blocked — confirm with user first
|
|
21
|
+
# mcp__.*__write.* Write-shaped MCP tool blocked pending review
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# mcp-tool-guard.sh — Canonical Policy Library: PreToolUse(mcp_*) hook
|
|
3
|
+
#
|
|
4
|
+
# Intercepts every MCP tool call and blocks matches against mcp-policy.conf, the
|
|
5
|
+
# same TAB-separated pattern<TAB>reason convention pre-bash-guard.sh uses for
|
|
6
|
+
# shell commands. Ships with mcp-policy.conf carrying zero active patterns, so
|
|
7
|
+
# this is pure infrastructure until real usage data justifies specific deny
|
|
8
|
+
# rules — see agent-docs/research/hook-governance-agent-tool-mcp-skill.md §3.2.
|
|
9
|
+
#
|
|
10
|
+
# Multi-session safe: stateless — reads only the conf file, no shared state.
|
|
11
|
+
# Must complete in <50ms.
|
|
12
|
+
#
|
|
13
|
+
# Canonical Policy Script Contract (design.md §4):
|
|
14
|
+
# env DOFLOW_PROJECT_DIR, DOFLOW_AGENT (both optional; not required to
|
|
15
|
+
# evaluate this policy)
|
|
16
|
+
# stdin a PreToolUse-shaped JSON payload; tool name is read from the union
|
|
17
|
+
# of field names harnesses use (tool_name/tool/toolName/name), since
|
|
18
|
+
# a native front door (Claude/Codex/Kiro) execs this script directly
|
|
19
|
+
# with no payload translation of its own.
|
|
20
|
+
# exit 0 = allow, non-zero = deny with the reason on stderr.
|
|
21
|
+
#
|
|
22
|
+
# Reconciliation note (022-normalize-hooks, Phase A): MCP tool-name
|
|
23
|
+
# vocabularies differ per harness — Claude/Codex/Kiro use a double-underscore
|
|
24
|
+
# "mcp__<server>__<tool>" convention, Gemini documents a single-underscore
|
|
25
|
+
# "mcp_<server>_<tool>" convention. A single-underscore prefix check ("^mcp_")
|
|
26
|
+
# matches both conventions (the double-underscore form is a superset match of
|
|
27
|
+
# the single-underscore prefix), so this script uses that one generic check
|
|
28
|
+
# instead of hardcoding either harness's exact convention.
|
|
29
|
+
#
|
|
30
|
+
# mcp-policy.conf's shape (TAB-separated regex<TAB>reason, '#'-comments,
|
|
31
|
+
# blank lines skipped) is identical across every harness that ships one today
|
|
32
|
+
# (claude/codex/gemini/kiro) — reconciliation found no divergence to resolve
|
|
33
|
+
# there.
|
|
34
|
+
|
|
35
|
+
set -uo pipefail
|
|
36
|
+
|
|
37
|
+
command -v jq >/dev/null 2>&1 || exit 0 # no jq -> cannot evaluate -> allow
|
|
38
|
+
|
|
39
|
+
INPUT=$(cat)
|
|
40
|
+
|
|
41
|
+
TOOL_NAME=""
|
|
42
|
+
for field in '.tool_name' '.tool' '.toolName' '.name'; do
|
|
43
|
+
TOOL_NAME=$(printf '%s' "$INPUT" | jq -r "${field} // empty" 2>/dev/null)
|
|
44
|
+
[ -n "$TOOL_NAME" ] && break
|
|
45
|
+
done
|
|
46
|
+
|
|
47
|
+
# Fast exit for non-MCP tool events (also covers "tool name unresolved").
|
|
48
|
+
# "mcp_" (single underscore) matches both Claude/Codex/Kiro's "mcp__" and
|
|
49
|
+
# Gemini's "mcp_" naming conventions. Matched case-insensitively: the caller
|
|
50
|
+
# (stream-hook-runner.js's MCP_TOOL_PATTERN) classifies case-insensitively
|
|
51
|
+
# before ever routing here, so this check must not be stricter than the
|
|
52
|
+
# classification that decided to invoke it (022-code-review finding).
|
|
53
|
+
shopt -s nocasematch 2>/dev/null || true
|
|
54
|
+
case "$TOOL_NAME" in
|
|
55
|
+
mcp_*) ;;
|
|
56
|
+
*) exit 0 ;;
|
|
57
|
+
esac
|
|
58
|
+
shopt -u nocasematch 2>/dev/null || true
|
|
59
|
+
|
|
60
|
+
POLICY_FILE="$(dirname "$0")/mcp-policy.conf"
|
|
61
|
+
|
|
62
|
+
# If policy file is missing, allow everything (fail open — don't block the agent).
|
|
63
|
+
if [ ! -f "$POLICY_FILE" ]; then
|
|
64
|
+
# The policy file ships with DoFlow, so a missing one means a broken install: record it on this
|
|
65
|
+
# machine (feature 046, IC-018) and fail open exactly as before. Subshell, output discarded.
|
|
66
|
+
( . "$(dirname "$0")/capture-failure.sh"; doflow_capture_failure mcp-tool-guard policy-file-missing ) >/dev/null 2>&1 || true
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
# ── Pattern matching ──────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
while IFS=$'\t' read -r pattern reason || [ -n "$pattern" ]; do
|
|
73
|
+
# Skip comments and empty lines
|
|
74
|
+
[ -z "$pattern" ] && continue
|
|
75
|
+
case "$pattern" in \#*) continue ;; esac
|
|
76
|
+
|
|
77
|
+
# "--" stops option parsing so a pattern starting with "-" is never
|
|
78
|
+
# mistaken for a grep flag.
|
|
79
|
+
matched=false
|
|
80
|
+
if (echo "$TOOL_NAME" | grep -qiE -- "$pattern" 2>/dev/null); then
|
|
81
|
+
matched=true
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
if [ "$matched" = "true" ]; then
|
|
85
|
+
echo "[mcp-tool-guard] ${reason:-MCP tool call blocked by mcp-tool-guard} (tool: $TOOL_NAME)" >&2
|
|
86
|
+
exit 2
|
|
87
|
+
fi
|
|
88
|
+
done < "$POLICY_FILE"
|
|
89
|
+
|
|
90
|
+
# No match — allow
|
|
91
|
+
exit 0
|