@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.
Files changed (409) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -2
  3. package/bin/doflow.js +44 -0
  4. package/core/.antigravity-plugin/hooks.json +39 -0
  5. package/core/.antigravity-plugin/mcp_config.json +18 -0
  6. package/core/.antigravity-plugin/plugin.json +31 -0
  7. package/core/.claude-plugin/marketplace.json +20 -0
  8. package/core/.claude-plugin/plugin.json +21 -0
  9. package/core/.codex-plugin/marketplace.json +20 -0
  10. package/core/.codex-plugin/plugin.json +35 -0
  11. package/core/.plugin/plugin.json +19 -0
  12. package/core/harnesses/antigravity/hooks/pre-implementation-gate.sh +9 -0
  13. package/core/harnesses/antigravity/hooks/stop-check.sh +10 -0
  14. package/core/harnesses/claude/hooks/lib.sh +304 -0
  15. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +20 -0
  16. package/core/harnesses/claude/hooks/permission-audit.sh +47 -0
  17. package/core/harnesses/claude/hooks/post-compact.sh +74 -0
  18. package/core/harnesses/claude/hooks/post-edit-lint.sh +36 -0
  19. package/core/harnesses/claude/hooks/post-tool-failure.sh +47 -0
  20. package/core/harnesses/claude/hooks/pre-bash-guard.sh +20 -0
  21. package/core/harnesses/claude/hooks/pre-compact.sh +43 -0
  22. package/core/harnesses/claude/hooks/pre-implement-gate.sh +21 -0
  23. package/core/harnesses/claude/hooks/session-end.sh +89 -0
  24. package/core/harnesses/claude/hooks/session-start.sh +5 -0
  25. package/core/harnesses/claude/hooks/skill-config-audit.sh +39 -0
  26. package/core/harnesses/claude/hooks/stop-check.sh +5 -0
  27. package/core/harnesses/claude/hooks/subagent-audit.sh +7 -0
  28. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -0
  29. package/core/harnesses/claude/settings/keybindings.json +20 -0
  30. package/core/harnesses/claude/settings/settings.json +201 -0
  31. package/core/harnesses/claude/statusline/doflow-statusline.sh +49 -0
  32. package/core/harnesses/codex/agents/core-implementer.toml +7 -0
  33. package/core/harnesses/codex/agents/quality-guardian.toml +8 -0
  34. package/core/harnesses/codex/agents/research-writer.toml +8 -0
  35. package/core/harnesses/codex/agents/spec-analyst.toml +8 -0
  36. package/core/harnesses/codex/agents/system-architect.toml +9 -0
  37. package/core/harnesses/codex/config/config.toml +10 -0
  38. package/core/harnesses/codex/hooks/hooks.json +122 -0
  39. package/core/harnesses/codex/hooks/lib.sh +335 -0
  40. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +18 -0
  41. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +52 -0
  42. package/core/harnesses/codex/hooks/post-edit-lint.sh +6 -0
  43. package/core/harnesses/codex/hooks/pre-bash-guard.sh +18 -0
  44. package/core/harnesses/codex/hooks/pre-compact.impl.sh +46 -0
  45. package/core/harnesses/codex/hooks/pre-compact.sh +9 -0
  46. package/core/harnesses/codex/hooks/pre-implement-gate.sh +19 -0
  47. package/core/harnesses/codex/hooks/session-end.impl.sh +92 -0
  48. package/core/harnesses/codex/hooks/session-end.sh +5 -0
  49. package/core/harnesses/codex/hooks/session-start.sh +5 -0
  50. package/core/harnesses/codex/hooks/stop-check.sh +8 -0
  51. package/core/harnesses/codex/hooks/subagent-audit.sh +8 -0
  52. package/core/harnesses/codex/hooks/user-prompt-submit.sh +6 -0
  53. package/core/harnesses/gemini/hooks/hooks.json +77 -0
  54. package/core/harnesses/gemini/hooks/lib.sh +302 -0
  55. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +21 -0
  56. package/core/harnesses/gemini/hooks/post-edit-lint.sh +39 -0
  57. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +21 -0
  58. package/core/harnesses/gemini/hooks/pre-compact.sh +38 -0
  59. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +22 -0
  60. package/core/harnesses/gemini/hooks/session-end.impl.sh +89 -0
  61. package/core/harnesses/gemini/hooks/session-end.sh +5 -0
  62. package/core/harnesses/gemini/hooks/session-start.sh +5 -0
  63. package/core/harnesses/gemini/settings/adapter-defaults.json +4 -0
  64. package/core/harnesses/kiro/agents/doflow.json +58 -0
  65. package/core/harnesses/kiro/hooks/doflow.json +53 -0
  66. package/core/harnesses/kiro/hooks/lib.sh +324 -0
  67. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +9 -0
  68. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +9 -0
  69. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +9 -0
  70. package/core/harnesses/kiro/hooks/session-start.sh +9 -0
  71. package/core/harnesses/kiro/hooks/stop-check.sh +43 -0
  72. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +39 -0
  73. package/core/harnesses/shared/hooks/adapters/antigravity.js +111 -0
  74. package/core/harnesses/shared/hooks/adapters/arg-fields.js +19 -0
  75. package/core/harnesses/shared/hooks/adapters/gemini.js +114 -0
  76. package/core/harnesses/shared/hooks/hooks.json +47 -0
  77. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +91 -0
  78. package/core/harnesses/shared/hooks/policies/capture-failure.sh +97 -0
  79. package/core/harnesses/shared/hooks/policies/deny-json.sh +29 -0
  80. package/core/harnesses/shared/hooks/policies/lib.sh +501 -0
  81. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +21 -0
  82. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +91 -0
  83. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +328 -0
  84. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +496 -0
  85. package/core/harnesses/shared/hooks/policies/prompt-nudge.jq +131 -0
  86. package/core/harnesses/shared/hooks/policies/session-context.sh +141 -0
  87. package/core/harnesses/shared/hooks/policies/stop-check.sh +264 -0
  88. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +46 -0
  89. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +235 -0
  90. package/core/harnesses/shared/hooks/stream-hook-runner.js +590 -0
  91. package/core/harnesses/shared/locator/doflow-run +87 -0
  92. package/core/registry/advice.json +101 -0
  93. package/core/registry/assets.json +792 -0
  94. package/core/registry/capabilities.json +120 -0
  95. package/core/registry/contracts.json +189 -0
  96. package/core/registry/external-tools.json +191 -0
  97. package/core/registry/harnesses.json +2062 -0
  98. package/core/registry/lifecycle.json +231 -0
  99. package/core/registry/mcp.json +18 -0
  100. package/core/registry/models.json +79 -0
  101. package/core/registry/readiness-templates.json +118 -0
  102. package/core/registry/routes.json +47 -0
  103. package/core/registry/storage.json +1061 -0
  104. package/core/registry/verification.json +258 -0
  105. package/core/registry/workflows.json +790 -0
  106. package/core/shared/agent-specs/core-implementer.md +73 -0
  107. package/core/shared/agent-specs/quality-guardian.md +28 -0
  108. package/core/shared/agent-specs/research-writer.md +31 -0
  109. package/core/shared/agent-specs/spec-analyst.md +33 -0
  110. package/core/shared/agent-specs/system-architect.md +29 -0
  111. package/core/shared/guidance/DOFLOW_CORE.md +8 -0
  112. package/core/shared/guidance/FLAGS.md +25 -0
  113. package/core/shared/guidance/PRINCIPLES.md +61 -0
  114. package/core/shared/guidance/VERSION +1 -0
  115. package/core/shared/guidance/mcp/MCP_Context7.md +18 -0
  116. package/core/shared/guidance/mcp/MCP_Sequential.md +17 -0
  117. package/core/shared/guidance/modes/MODE_Brainstorming.md +46 -0
  118. package/core/shared/guidance/modes/MODE_DeepResearch.md +69 -0
  119. package/core/shared/guidance/modes/MODE_Introspection.md +33 -0
  120. package/core/shared/guidance/modes/MODE_Orchestration.md +57 -0
  121. package/core/shared/guidance/modes/MODE_Task_Management.md +74 -0
  122. package/core/shared/guidance/pointers/claude-gemini.md +2 -0
  123. package/core/shared/guidance/pointers/codex.md +6 -0
  124. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +373 -0
  125. package/core/shared/guidance/references/ARTIFACT_VOICE.md +104 -0
  126. package/core/shared/guidance/references/CONSTITUTION_BASE.md +47 -0
  127. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +161 -0
  128. package/core/shared/guidance/references/DOFLOW_CHAIN.md +87 -0
  129. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +93 -0
  130. package/core/shared/guidance/references/MODEL_SELECTION.md +74 -0
  131. package/core/shared/guidance/references/ORCHESTRATION.md +36 -0
  132. package/core/shared/guidance/references/RESEARCH_CONFIG.md +21 -0
  133. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +81 -0
  134. package/core/shared/guidance/rules/RULE_01_SAFETY.md +25 -0
  135. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +23 -0
  136. package/core/shared/guidance/rules/RULE_03_QUALITY.md +14 -0
  137. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +46 -0
  138. package/core/shared/scripts/doflow/assets/mermaid-theme.json +46 -0
  139. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +167 -0
  140. package/core/shared/scripts/doflow/bash/do-git-state.sh +546 -0
  141. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +208 -0
  142. package/core/shared/scripts/doflow/bash/do-paths.sh +347 -0
  143. package/core/shared/scripts/doflow/bash/do-prereqs.sh +66 -0
  144. package/core/shared/scripts/doflow/bash/do-review-package.sh +127 -0
  145. package/core/shared/scripts/doflow/bash/do-task-brief.sh +698 -0
  146. package/core/shared/scripts/doflow/bash/render-audit.sh +224 -0
  147. package/core/shared/scripts/doflow/bash/render-diagrams.sh +177 -0
  148. package/core/shared/scripts/doflow/bash/render-puml.sh +223 -0
  149. package/core/shared/scripts/doflow/bash/sync-context.sh +66 -0
  150. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +526 -0
  151. package/core/shared/scripts/doflow/bin/doflow-run +592 -0
  152. package/core/shared/skills/do/SKILL.md +128 -0
  153. package/core/shared/skills/do/references/estimation.md +13 -0
  154. package/core/shared/skills/do/references/pm_routing.md +25 -0
  155. package/core/shared/skills/do/references/tool_matrix.md +52 -0
  156. package/core/shared/skills/do-brainstorm/SKILL.md +252 -0
  157. package/core/shared/skills/do-code-review/README.md +49 -0
  158. package/core/shared/skills/do-code-review/SKILL.md +333 -0
  159. package/core/shared/skills/do-code-review/assets/sample_c_clean.c +71 -0
  160. package/core/shared/skills/do-code-review/assets/sample_c_smells.c +66 -0
  161. package/core/shared/skills/do-code-review/assets/sample_cpp_shift.cpp +19 -0
  162. package/core/shared/skills/do-code-review/assets/sample_csharp_clean.cs +100 -0
  163. package/core/shared/skills/do-code-review/assets/sample_csharp_smells.cs +78 -0
  164. package/core/shared/skills/do-code-review/assets/sample_java_clean.java +55 -0
  165. package/core/shared/skills/do-code-review/assets/sample_java_smells.java +56 -0
  166. package/core/shared/skills/do-code-review/assets/sample_json_clean.json +7 -0
  167. package/core/shared/skills/do-code-review/assets/sample_markdown_clean.md +30 -0
  168. package/core/shared/skills/do-code-review/assets/sample_markdown_smells.md +29 -0
  169. package/core/shared/skills/do-code-review/assets/sample_shell_heredoc.sh +38 -0
  170. package/core/shared/skills/do-code-review/assets/sample_shell_smells.sh +15 -0
  171. package/core/shared/skills/do-code-review/assets/sample_typescript_strings.ts +36 -0
  172. package/core/shared/skills/do-code-review/assets/sample_yaml_smells.yaml +6 -0
  173. package/core/shared/skills/do-code-review/content-types/config.md +83 -0
  174. package/core/shared/skills/do-code-review/content-types/markdown.md +132 -0
  175. package/core/shared/skills/do-code-review/expected_outputs/sample_c_clean_quality.json +79 -0
  176. package/core/shared/skills/do-code-review/expected_outputs/sample_c_smells_quality.json +169 -0
  177. package/core/shared/skills/do-code-review/expected_outputs/sample_cpp_shift_quality.json +49 -0
  178. package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_clean_quality.json +149 -0
  179. package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_smells_quality.json +157 -0
  180. package/core/shared/skills/do-code-review/expected_outputs/sample_java_clean_quality.json +67 -0
  181. package/core/shared/skills/do-code-review/expected_outputs/sample_java_smells_quality.json +97 -0
  182. package/core/shared/skills/do-code-review/expected_outputs/sample_json_clean_quality.json +33 -0
  183. package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_clean_quality.json +1 -0
  184. package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_smells_quality.json +16 -0
  185. package/core/shared/skills/do-code-review/expected_outputs/sample_shell_heredoc_quality.json +54 -0
  186. package/core/shared/skills/do-code-review/expected_outputs/sample_shell_smells_quality.json +91 -0
  187. package/core/shared/skills/do-code-review/expected_outputs/sample_typescript_strings_quality.json +60 -0
  188. package/core/shared/skills/do-code-review/expected_outputs/sample_yaml_smells_quality.json +46 -0
  189. package/core/shared/skills/do-code-review/languages/c.md +103 -0
  190. package/core/shared/skills/do-code-review/languages/cpp.md +103 -0
  191. package/core/shared/skills/do-code-review/languages/csharp.md +97 -0
  192. package/core/shared/skills/do-code-review/languages/dart.md +108 -0
  193. package/core/shared/skills/do-code-review/languages/go.md +92 -0
  194. package/core/shared/skills/do-code-review/languages/java.md +103 -0
  195. package/core/shared/skills/do-code-review/languages/kotlin.md +88 -0
  196. package/core/shared/skills/do-code-review/languages/php.md +109 -0
  197. package/core/shared/skills/do-code-review/languages/python.md +94 -0
  198. package/core/shared/skills/do-code-review/languages/ruby.md +105 -0
  199. package/core/shared/skills/do-code-review/languages/rust.md +99 -0
  200. package/core/shared/skills/do-code-review/languages/shell.md +90 -0
  201. package/core/shared/skills/do-code-review/languages/swift.md +88 -0
  202. package/core/shared/skills/do-code-review/languages/typescript.md +97 -0
  203. package/core/shared/skills/do-code-review/review-policy.json +47 -0
  204. package/core/shared/skills/do-code-review/rules/universal.md +49 -0
  205. package/core/shared/skills/do-code-review/scripts/code_quality_checker.py +1696 -0
  206. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +532 -0
  207. package/core/shared/skills/do-code-review/scripts/pr_analyzer.py +620 -0
  208. package/core/shared/skills/do-code-review/scripts/review_report_generator.py +505 -0
  209. package/core/shared/skills/do-constitution/SKILL.md +72 -0
  210. package/core/shared/skills/do-design/SKILL.md +212 -0
  211. package/core/shared/skills/do-diagnose/SKILL.md +156 -0
  212. package/core/shared/skills/do-diagnose/references/code_audit.md +15 -0
  213. package/core/shared/skills/do-diagnose/references/refactoring.md +12 -0
  214. package/core/shared/skills/do-diagnose/references/root_cause.md +17 -0
  215. package/core/shared/skills/do-document/SKILL.md +81 -0
  216. package/core/shared/skills/do-document/references/api-reference.md +42 -0
  217. package/core/shared/skills/do-document/references/deep_research.md +18 -0
  218. package/core/shared/skills/do-document/references/embedded-research.md +11 -0
  219. package/core/shared/skills/do-document/references/implementation-flow.md +105 -0
  220. package/core/shared/skills/do-document/references/index_generator.md +9 -0
  221. package/core/shared/skills/do-document/references/user-guide.md +22 -0
  222. package/core/shared/skills/do-execute-plan/SKILL.md +261 -0
  223. package/core/shared/skills/do-execute-plan/references/handoff_record.md +57 -0
  224. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +92 -0
  225. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +155 -0
  226. package/core/shared/skills/do-execute-plan/references/run_position.md +63 -0
  227. package/core/shared/skills/do-execute-plan/references/scaffold.md +441 -0
  228. package/core/shared/skills/do-execute-plan/references/task_attempts.md +41 -0
  229. package/core/shared/skills/do-flow/SKILL.md +186 -0
  230. package/core/shared/skills/do-flow/references/task_classes.md +134 -0
  231. package/core/shared/skills/do-git/SKILL.md +115 -0
  232. package/core/shared/skills/do-git/references/hotfix-ritual.md +44 -0
  233. package/core/shared/skills/do-git/references/lifecycle-policy.md +54 -0
  234. package/core/shared/skills/do-git/references/release-ritual.md +64 -0
  235. package/core/shared/skills/do-implement/SKILL.md +222 -0
  236. package/core/shared/skills/do-plan/SKILL.md +219 -0
  237. package/core/shared/skills/do-test/SKILL.md +113 -0
  238. package/core/shared/templates/doflow/constitution-template.md +28 -0
  239. package/core/shared/templates/doflow/data-model-template.md +169 -0
  240. package/core/shared/templates/doflow/design-template.md +193 -0
  241. package/core/shared/templates/doflow/external-contract-template.md +35 -0
  242. package/core/shared/templates/doflow/intent-template.md +45 -0
  243. package/core/shared/templates/doflow/plan-template.md +151 -0
  244. package/core/shared/templates/doflow/question-log-template.md +51 -0
  245. package/core/shared/templates/doflow/requirement-template.md +126 -0
  246. package/core/shared/templates/doflow/specs-template.md +55 -0
  247. package/core/shared/templates/doflow/state-template.md +59 -0
  248. package/core/shared/workflows/do-flow-chain.md +17 -0
  249. package/package.json +48 -4
  250. package/src/adapters/antigravity/index.js +631 -0
  251. package/src/adapters/claude/index.js +548 -0
  252. package/src/adapters/claude/mcp.js +94 -0
  253. package/src/adapters/codex/agents.js +208 -0
  254. package/src/adapters/codex/config.js +272 -0
  255. package/src/adapters/codex/hooks.js +203 -0
  256. package/src/adapters/codex/index.js +707 -0
  257. package/src/adapters/codex/mcp.js +271 -0
  258. package/src/adapters/codex/native-projection.js +34 -0
  259. package/src/adapters/copilot/index.js +413 -0
  260. package/src/adapters/copy-tree.js +620 -0
  261. package/src/adapters/gemini/hooks.js +210 -0
  262. package/src/adapters/gemini/index.js +390 -0
  263. package/src/adapters/hook-commands.js +60 -0
  264. package/src/adapters/index.js +106 -0
  265. package/src/adapters/kiro/index.js +310 -0
  266. package/src/adapters/mcp-entries.js +331 -0
  267. package/src/adapters/opencode/index.js +466 -0
  268. package/src/adapters/pi/index.js +279 -0
  269. package/src/adapters/pi/json-members.js +185 -0
  270. package/src/adapters/pi/mcp.js +373 -0
  271. package/src/adapters/resolve-source.js +19 -0
  272. package/src/cli/commands/install.js +218 -0
  273. package/src/cli/commands/list-backups.js +21 -0
  274. package/src/cli/commands/reconcile.js +120 -0
  275. package/src/cli/commands/remove.js +82 -0
  276. package/src/cli/commands/rollback.js +172 -0
  277. package/src/cli/commands/self-update.js +33 -0
  278. package/src/cli/commands/status.js +138 -0
  279. package/src/cli/commands/tools.js +92 -0
  280. package/src/cli/commands/update.js +162 -0
  281. package/src/cli/index.js +568 -0
  282. package/src/cli/runtime-commands.js +281 -0
  283. package/src/cli/shared.js +218 -0
  284. package/src/helper/bash-helper.js +34 -0
  285. package/src/helper/git.js +24 -0
  286. package/src/helper/guardrails.js +49 -0
  287. package/src/helper/harness-paths.js +140 -0
  288. package/src/helper/marker-merge.js +116 -0
  289. package/src/helper/prompt.js +61 -0
  290. package/src/helper/repo-root.js +29 -0
  291. package/src/helper/settings-merge.js +151 -0
  292. package/src/helper/settings-scope.js +33 -0
  293. package/src/helper/toml.js +244 -0
  294. package/src/install/backup-set.js +149 -0
  295. package/src/install/backup.js +724 -0
  296. package/src/install/context.js +58 -0
  297. package/src/install/manifest.js +145 -0
  298. package/src/install/mcp.js +254 -0
  299. package/src/install/paths.js +84 -0
  300. package/src/install/targets.js +90 -0
  301. package/src/install/tool-lifecycle.js +151 -0
  302. package/src/lifecycle/bash-availability.js +25 -0
  303. package/src/lifecycle/index.js +812 -0
  304. package/src/lifecycle/mcp-index.js +28 -0
  305. package/src/lifecycle/policies.js +73 -0
  306. package/src/lifecycle/view.js +175 -0
  307. package/src/registry/index.js +611 -0
  308. package/src/release/build-package.js +91 -0
  309. package/src/release/changelog-section.js +70 -0
  310. package/src/release/sync-plugin-versions.js +66 -0
  311. package/src/runtime/advice/cli.js +283 -0
  312. package/src/runtime/advice/client.js +324 -0
  313. package/src/runtime/advice/config.js +189 -0
  314. package/src/runtime/advice/doctor.js +169 -0
  315. package/src/runtime/advice/endpoint-guard.js +134 -0
  316. package/src/runtime/advice/kinds.js +154 -0
  317. package/src/runtime/advice/providers/ollama-systemone.js +209 -0
  318. package/src/runtime/advice/record.js +166 -0
  319. package/src/runtime/advice/registry.js +170 -0
  320. package/src/runtime/advice/strength.js +66 -0
  321. package/src/runtime/brief-tools.js +342 -0
  322. package/src/runtime/c4-project.js +371 -0
  323. package/src/runtime/capability-router.js +484 -0
  324. package/src/runtime/checkouts.js +170 -0
  325. package/src/runtime/claims.js +468 -0
  326. package/src/runtime/cli-result.js +55 -0
  327. package/src/runtime/cli.js +678 -0
  328. package/src/runtime/command-detect.js +516 -0
  329. package/src/runtime/context-pack.js +492 -0
  330. package/src/runtime/decision-register.js +493 -0
  331. package/src/runtime/evidence-ledger.js +338 -0
  332. package/src/runtime/failure/capture.js +272 -0
  333. package/src/runtime/failure/classifier.js +68 -0
  334. package/src/runtime/failure/cli.js +178 -0
  335. package/src/runtime/failure/home.js +64 -0
  336. package/src/runtime/failure/store.js +226 -0
  337. package/src/runtime/feature-resolve.js +79 -0
  338. package/src/runtime/freshness.js +177 -0
  339. package/src/runtime/health.js +863 -0
  340. package/src/runtime/history-compactor.js +237 -0
  341. package/src/runtime/implementation-gate.js +321 -0
  342. package/src/runtime/inventory/coverage.js +218 -0
  343. package/src/runtime/inventory/identity.js +65 -0
  344. package/src/runtime/inventory/index.js +705 -0
  345. package/src/runtime/inventory/precedence.js +134 -0
  346. package/src/runtime/inventory/read-scopes.js +131 -0
  347. package/src/runtime/inventory/siblings.js +192 -0
  348. package/src/runtime/knowledge/chunker.js +124 -0
  349. package/src/runtime/knowledge/hybrid.js +149 -0
  350. package/src/runtime/knowledge/index-store.js +145 -0
  351. package/src/runtime/knowledge/retrieval.js +194 -0
  352. package/src/runtime/leak-scan.js +185 -0
  353. package/src/runtime/lifecycle/cli.js +276 -0
  354. package/src/runtime/lifecycle/event-store.js +340 -0
  355. package/src/runtime/lifecycle/fold.js +353 -0
  356. package/src/runtime/lifecycle/followup.js +432 -0
  357. package/src/runtime/lifecycle/goal.js +249 -0
  358. package/src/runtime/lifecycle/intent-writer.js +86 -0
  359. package/src/runtime/lifecycle/overview.js +292 -0
  360. package/src/runtime/lifecycle/release.js +241 -0
  361. package/src/runtime/lifecycle/report-store.js +221 -0
  362. package/src/runtime/lifecycle/retention.js +212 -0
  363. package/src/runtime/lifecycle/root.js +71 -0
  364. package/src/runtime/lifecycle/status.js +353 -0
  365. package/src/runtime/lifecycle/store-upkeep.js +393 -0
  366. package/src/runtime/locator-resolve.js +158 -0
  367. package/src/runtime/mask.js +210 -0
  368. package/src/runtime/mcp-consultations.js +412 -0
  369. package/src/runtime/mcp-reach.js +157 -0
  370. package/src/runtime/model-router.js +153 -0
  371. package/src/runtime/outcome.js +467 -0
  372. package/src/runtime/reach.js +148 -0
  373. package/src/runtime/readiness-record.js +150 -0
  374. package/src/runtime/readiness.js +435 -0
  375. package/src/runtime/recovery.js +282 -0
  376. package/src/runtime/research-request.js +231 -0
  377. package/src/runtime/retrieval-plan.js +578 -0
  378. package/src/runtime/scaffold/artifacts.js +217 -0
  379. package/src/runtime/scaffold/fingerprint.js +56 -0
  380. package/src/runtime/scaffold/generate.js +911 -0
  381. package/src/runtime/scaffold/languages.js +245 -0
  382. package/src/runtime/storage/cli.js +64 -0
  383. package/src/runtime/storage/engine.js +249 -0
  384. package/src/runtime/storage/registry.js +130 -0
  385. package/src/runtime/storage/roots.js +63 -0
  386. package/src/runtime/storage/rules.js +282 -0
  387. package/src/runtime/storage/upkeep.js +243 -0
  388. package/src/runtime/storage/work-status.js +326 -0
  389. package/src/runtime/task-attempts.js +415 -0
  390. package/src/runtime/task-classifier.js +550 -0
  391. package/src/runtime/task-scope.js +130 -0
  392. package/src/runtime/task-state.js +176 -0
  393. package/src/runtime/trace/indicators.js +235 -0
  394. package/src/runtime/trace/ledger.js +626 -0
  395. package/src/runtime/trace/render.js +76 -0
  396. package/src/runtime/trace/views.js +459 -0
  397. package/src/runtime/verification/contract-runner.js +341 -0
  398. package/src/runtime/verification/engine.js +1461 -0
  399. package/src/runtime/verification/registry.js +130 -0
  400. package/src/runtime/verification/scope-bound.js +418 -0
  401. package/src/runtime/verification/test-selection.js +172 -0
  402. package/src/runtime/workflow-engine.js +588 -0
  403. package/src/runtime/workflow-orchestrator.js +953 -0
  404. package/src/runtime/worktree.js +340 -0
  405. package/src/state/index.js +193 -0
  406. package/src/state/lockfile.js +136 -0
  407. package/src/state/run-lock.js +408 -0
  408. package/types/node-commonjs.d.ts +68 -0
  409. 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