@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,496 @@
1
+ #!/usr/bin/env bash
2
+ # pre-implementation-gate.sh — Canonical Policy Library: PreToolUse(Edit|Write|...)
3
+ # backstop for the doflow implement gate. The HARD half of the one enforced
4
+ # gate (the other half is the prompt-level do-prereqs.sh inside
5
+ # /do-execute-plan — defense in depth).
6
+ #
7
+ # Denies a SOURCE-file edit in two cases:
8
+ # 1. a feature has been STARTED (its feature_dir exists, in this checkout or,
9
+ # from a linked worktree, in the main checkout) but requirement.md,
10
+ # design.md, or plan.md is still missing: "don't write code before you've
11
+ # planned";
12
+ # 2. the task is held to a readiness record and has no READY one for the
13
+ # template of its workflow's first source-mutating stage. The task is held
14
+ # when its run (in this checkout or exactly one other) is open with that
15
+ # stage pending, or, with no run, on a feature branch whose folder has a
16
+ # decision register and all three artifacts.
17
+ # It is deliberately SCOPED so it never fires outside the doflow chain:
18
+ # - trunk -> allow
19
+ # - branch is a fix, bugfix, refactor, chore, release or hotfix branch
20
+ # (classes that cannot be a feature) -> no artifact check, run check only
21
+ # - edit target is under agent-docs/ -> allow (editing the artifacts themselves)
22
+ # - edit target outside the repo -> allow
23
+ # It reads files with jq and lists checkouts with one git call; it never runs
24
+ # the DoFlow runtime. Self-contained + fail-open: any uncertainty -> allow (exit 0).
25
+ #
26
+ # Canonical Policy Script Contract (design.md §4):
27
+ # env DOFLOW_PROJECT_DIR (repo root, if the front door sets it),
28
+ # DOFLOW_AGENT (harness id, informational only here),
29
+ # CLAUDE_CONFIG_DIR / CLAUDE_PROJECT_DIR / GEMINI_CONFIG_DIR / CODEX_HOME
30
+ # (each already read by the harness-specific dispatcher this policy
31
+ # replaces — kept so an unmodified front door still resolves its own
32
+ # harness's do-paths.sh resolver)
33
+ # stdin a PreToolUse-shaped JSON payload for an edit tool. Every harness's
34
+ # own field names differ (Claude/Gemini: tool_name/tool_input.file_path;
35
+ # Codex: tool_name=apply_patch with a unified diff in
36
+ # tool_input.command; Kiro: field names undocumented) — this script
37
+ # reads the union of known shapes rather than assuming one, since a
38
+ # native front door (Claude/Codex/Kiro) execs this script directly
39
+ # with no payload translation of its own.
40
+ # exit 0 = allow, non-zero = deny with the reason on stderr. A front door
41
+ # translates this into whatever JSON/exit-code shape its harness
42
+ # natively expects — this script never speaks a harness-specific
43
+ # decision shape.
44
+ #
45
+ # Reconciliation note (022-normalize-hooks, Phase A): claude/gemini/codex/kiro
46
+ # each shipped a separately-adapted copy of this gate; antigravity's copy
47
+ # additionally never depended on a do-paths.sh resolver at all — it computed
48
+ # feature_dir itself from the current branch name and checked the three
49
+ # artifact files directly on disk. That fallback path is folded in here as a
50
+ # resilience net for every harness (not only antigravity): if no do-paths.sh
51
+ # resolver is found, this script now computes feature_dir from the branch
52
+ # instead of silently allowing everything, which is a real behavior
53
+ # improvement for Kiro in particular — its own script's header already noted
54
+ # that its resolver is never installed on a real Kiro install today, so the
55
+ # gate has been a permanent no-op there until this fallback.
56
+ #
57
+ # Depends on nothing beyond bash, jq, and git being on PATH.
58
+
59
+ set -uo pipefail
60
+
61
+ command -v jq >/dev/null 2>&1 || exit 0 # no jq -> cannot evaluate -> allow
62
+
63
+ INPUT=$(cat)
64
+
65
+ # ── Normalized envelope (preferred; review R7) ────────────────────────────────
66
+ # A host adapter that has already decoded its native event sends
67
+ # {"doflow_event": {"operation": "edit", "paths": [..], "projectRoot": "..", "taskId": ".."}}
68
+ # and this policy consumes it verbatim. The union decoder below remains the
69
+ # fallback for front doors that still forward raw native payloads; per-harness
70
+ # knowledge belongs in those adapters, and each one that starts sending the
71
+ # envelope retires its share of the union.
72
+ ENVELOPE_ROOT=""
73
+ ENVELOPE_TASK=""
74
+ ENVELOPE_OP=$(printf '%s' "$INPUT" | jq -r '.doflow_event.operation // empty' 2>/dev/null)
75
+ if [ -n "$ENVELOPE_OP" ]; then
76
+ [ "$ENVELOPE_OP" = "edit" ] || exit 0 # only edits are gated
77
+ FILES=$(printf '%s' "$INPUT" | jq -r '.doflow_event.paths[]? // empty' 2>/dev/null | sed '/^$/d' | sort -u)
78
+ [ -n "$FILES" ] || exit 0
79
+ ENVELOPE_ROOT=$(printf '%s' "$INPUT" | jq -r '.doflow_event.projectRoot // empty' 2>/dev/null)
80
+ ENVELOPE_TASK=$(printf '%s' "$INPUT" | jq -r '.doflow_event.taskId // empty' 2>/dev/null)
81
+ else
82
+
83
+ # ── Tool name (union of every known field name across harnesses; LEGACY decoder) ──
84
+ tool=""
85
+ for field in '.tool_name' '.tool' '.toolName' '.name'; do
86
+ tool=$(printf '%s' "$INPUT" | jq -r "${field} // empty" 2>/dev/null)
87
+ [ -n "$tool" ] && break
88
+ done
89
+
90
+ is_patch_tool=false
91
+ case "$tool" in
92
+ Edit|Write|MultiEdit|edit_file|write_file|create_file|replace_file|replace) ;;
93
+ write_to_file|replace_file_content|multi_replace_file_content) ;;
94
+ apply_patch) is_patch_tool=true ;;
95
+ *) exit 0 ;;
96
+ esac
97
+
98
+ # ── Candidate files to gate ────────────────────────────────────────────────────
99
+ # Normal edit tools: one file, from the union of field names harnesses use.
100
+ # apply_patch (Codex): the tool has no file_path field at all — the paths are
101
+ # embedded in a unified diff under tool_input.command.
102
+ FILES=""
103
+ if [ "$is_patch_tool" = true ]; then
104
+ PATCH=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
105
+ [ -n "$PATCH" ] || exit 0
106
+ # "#" (not the original codex script's "|") is the sed delimiter here: BSD
107
+ # sed (macOS, no GNU coreutils) misparses "s|...(Add|Update|Delete)...|...|"
108
+ # because it splits on every unescaped "|", including the ones meant as ERE
109
+ # alternation inside the pattern — a portability bug in the source script
110
+ # this reconciliation fixes rather than ports, since it silently broke
111
+ # apply_patch parsing under this exact invocation on any non-GNU-sed host.
112
+ FILES=$(printf '%s\n' "$PATCH" | sed -nE \
113
+ -e 's#^\*\*\* (Add|Update|Delete) File: (.+)$#\2#p' \
114
+ -e 's#^\+\+\+ (a/|b/)?(.+)$#\2#p' \
115
+ | sed '/^\/dev\/null$/d' | sort -u)
116
+ [ -n "$FILES" ] || exit 0
117
+ else
118
+ file=""
119
+ for field in '.tool_input.file_path' '.tool_input.path' '.tool_input.target' '.file_path' '.path'; do
120
+ file=$(printf '%s' "$INPUT" | jq -r "${field} // empty" 2>/dev/null)
121
+ [ -n "$file" ] && break
122
+ done
123
+ [ -n "$file" ] || exit 0
124
+ FILES="$file"
125
+ fi
126
+
127
+ fi # end legacy union decoder
128
+
129
+ # ── Resolve repo root ───────────────────────────────────────────────────────
130
+ # DOFLOW_PROJECT_DIR (explicit) beats the envelope's projectRoot beats git discovery.
131
+ ROOT="${DOFLOW_PROJECT_DIR:-$ENVELOPE_ROOT}"
132
+ if [ -z "$ROOT" ]; then
133
+ ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
134
+ fi
135
+
136
+ # ── Resolve doflow state (feature_dir, repo_root, has_requirement/design/plan) ─
137
+ # Preferred path: exec whichever do-paths.sh resolver this install actually
138
+ # shipped, trying every known per-harness location (a front door invoking
139
+ # this script directly gives us no signal about which one, so this tries all
140
+ # of them rather than assuming DOFLOW_AGENT names the right one).
141
+ # Project-scoped candidates come before global ones; within each group the
142
+ # canonical, install-agnostic `.doflow/` location comes first — that is the
143
+ # two-step walk-up (project `.doflow`, then `$HOME/.doflow`) every SKILL.md
144
+ # and `doflow-run`'s own resolve_config_dir already uses. Without those two
145
+ # entries this list only ever found the per-harness MIRRORED copies, which a
146
+ # real install need never have projected — leaving the resolver unfound and
147
+ # the branch fallback below (flat paths only) as the whole gate.
148
+ RESOLVER_CANDIDATES=(
149
+ "$ROOT/.doflow/scripts/doflow/bash/do-paths.sh"
150
+ "$ROOT/.claude/scripts/doflow/bash/do-paths.sh"
151
+ "$ROOT/.codex/scripts/doflow/bash/do-paths.sh"
152
+ "$ROOT/.gemini/scripts/doflow/bash/do-paths.sh"
153
+ "$ROOT/.kiro/scripts/doflow/bash/do-paths.sh"
154
+ "$HOME/.doflow/scripts/doflow/bash/do-paths.sh"
155
+ "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/scripts/doflow/bash/do-paths.sh"
156
+ "${CLAUDE_PROJECT_DIR:-}/.claude/scripts/doflow/bash/do-paths.sh"
157
+ "${CODEX_HOME:-$HOME/.codex}/scripts/doflow/bash/do-paths.sh"
158
+ "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/scripts/doflow/bash/do-paths.sh"
159
+ "$HOME/.kiro/scripts/doflow/bash/do-paths.sh"
160
+ )
161
+
162
+ RESOLVER=""
163
+ for candidate in "${RESOLVER_CANDIDATES[@]}"; do
164
+ [ -n "$candidate" ] && [ -x "$candidate" ] && { RESOLVER="$candidate"; break; }
165
+ done
166
+
167
+ feature_dir=""
168
+ repo_root=""
169
+ has_requirement=""
170
+ has_design=""
171
+ has_plan=""
172
+
173
+ if [ -n "$RESOLVER" ]; then
174
+ json=$("$RESOLVER" --json 2>/dev/null) || json=""
175
+ if [ -n "$json" ]; then
176
+ feature_dir=$(printf '%s' "$json" | jq -r '.feature_dir // empty' 2>/dev/null)
177
+ repo_root=$(printf '%s' "$json" | jq -r '.repo_root // empty' 2>/dev/null)
178
+ has_requirement=$(printf '%s' "$json" | jq -r '.has_requirement // false' 2>/dev/null)
179
+ has_design=$(printf '%s' "$json" | jq -r '.has_design // false' 2>/dev/null)
180
+ has_plan=$(printf '%s' "$json" | jq -r '.has_plan // false' 2>/dev/null)
181
+ fi
182
+ fi
183
+
184
+ # Classes that cannot be a feature are not held to requirement/design/plan, even when they share a
185
+ # slug with a started feature folder: fix, bugfix, refactor, chore, release, hotfix and trunk. Any
186
+ # other branch (feat/, feature/, 043-auth, kai/043-auth) runs the feature class and is gated. do-paths.sh
187
+ # classifies fix/bugfix/release/hotfix/trunk the same way (refactor/ and chore/ are its `other`); the
188
+ # chain test fails if the two disagree.
189
+ branch=$(git -C "${repo_root:-$ROOT}" branch --show-current 2>/dev/null || true)
190
+ exempt=false
191
+ case "$branch" in
192
+ ""|master|main|develop|trunk|HEAD) exit 0 ;;
193
+ fix/*|bugfix/*|refactor/*|chore/*|release/*|hotfix/*) exempt=true ;;
194
+ esac
195
+
196
+ # Layout is decided ONCE from intention/requirement.md's presence, then every
197
+ # path below follows from it — the same single-probe rule do-paths.sh applies,
198
+ # so this probe can never report a self-contradictory mix of layouts. Without
199
+ # the structured arm a fully-planned feature (artifacts under intention/, design/,
200
+ # plan/) read as unplanned here and blocked every source edit.
201
+ probe_artifacts() {
202
+ local dir="$1/$feature_dir"
203
+ if [ -f "$dir/intention/requirement.md" ]; then
204
+ has_requirement=true
205
+ if [ -f "$dir/design/design.md" ]; then has_design=true; else has_design=false; fi
206
+ if [ -f "$dir/plan.md" ]; then has_plan=true; else has_plan=false; fi
207
+ elif [ -f "$dir/requirement.md" ]; then
208
+ has_requirement=true
209
+ if [ -f "$dir/design.md" ]; then has_design=true; else has_design=false; fi
210
+ if [ -f "$dir/plan.md" ]; then has_plan=true; else has_plan=false; fi
211
+ else
212
+ has_requirement=false; has_design=false; has_plan=false
213
+ fi
214
+ }
215
+
216
+ # Fallback: no resolver installed for this harness (or it produced nothing
217
+ # usable) -> compute state directly from the branch-coupled feature
218
+ # convention (feat/<slug> -> agent-docs/doflow/<slug>/), the same self
219
+ # contained approach antigravity's own copy of this gate already used.
220
+ # The task's slug is the resolver's, else the branch without its first
221
+ # prefix with any further / flattened to -, as the resolver derives it.
222
+ if [ -z "$feature_dir" ]; then
223
+ repo_root="$ROOT"
224
+ slug=${branch#*/}
225
+ feature_dir="agent-docs/doflow/$slug"
226
+ slug=${slug//\//-}
227
+ [ -d "$repo_root/$feature_dir" ] && probe_artifacts "$repo_root"
228
+ else
229
+ slug=${feature_dir##*/}
230
+ fi
231
+ [ -n "$feature_dir" ] && [ -n "$repo_root" ] || exit 0
232
+
233
+ # A task id stated by the envelope names the run and the record; one that is
234
+ # not a plain name cannot name either, so the slug stands in for it.
235
+ task_id="$ENVELOPE_TASK"
236
+ case "$task_id" in ""|.*|*..*|*[!A-Za-z0-9._-]*) task_id="$slug" ;; esac
237
+ [ -n "$task_id" ] || exit 0
238
+
239
+ # The real path of $1: its longest existing ancestor resolved with cd -P, the
240
+ # rest appended, so a path reached through a symlink (macOS /tmp and /var, a
241
+ # linked project folder) compares equal to the real repository root.
242
+ real_path() {
243
+ local p="$1" rest="" dir
244
+ while [ ! -e "$p" ] && [ "$p" != "/" ] && [ -n "$p" ]; do
245
+ rest="/${p##*/}$rest"
246
+ p=$(dirname "$p")
247
+ done
248
+ if [ -d "$p" ]; then
249
+ dir=$(cd -P "$p" 2>/dev/null && pwd) || return 1
250
+ p="$dir"
251
+ else
252
+ dir=$(cd -P "$(dirname "$p")" 2>/dev/null && pwd) || return 1
253
+ p="$dir/${p##*/}"
254
+ fi
255
+ printf '%s%s' "${p%/}" "$rest"
256
+ }
257
+ repo_real=$(cd -P "$repo_root" 2>/dev/null && pwd) || repo_real="$repo_root"
258
+
259
+ # Only source files inside this repo are gated: edits to doflow artifacts are
260
+ # always allowed, and an absolute path elsewhere is not this repo's.
261
+ GATED=false
262
+ while IFS= read -r file; do
263
+ [ -z "$file" ] && continue
264
+ case "$file" in *"/agent-docs/"*|agent-docs/*) continue ;; esac
265
+ case "$file" in
266
+ /*)
267
+ real=$(real_path "$file") || real="$file"
268
+ case "$real" in *"/agent-docs/"*) continue ;; esac
269
+ case "$real" in "$repo_real"/*) ;; *) continue ;; esac
270
+ ;;
271
+ esac
272
+ GATED=true
273
+ break
274
+ done <<< "$FILES"
275
+ [ "$GATED" = true ] || exit 0
276
+
277
+ # ── Checkouts (one `git worktree list`, read at most once) ───────────────────
278
+ # MAIN is the main working tree when this checkout is a linked worktree of it;
279
+ # OTHERS are the repository's other checkouts. A bare main entry, a checkout
280
+ # whose directory is gone, and a DoFlow sandbox (.doflow-worktree-base at its
281
+ # root) are never read from; a sandbox sees no other checkout at all.
282
+ CHECKOUTS_READ=false
283
+ MAIN=""
284
+ OTHERS=()
285
+ checkouts() {
286
+ [ "$CHECKOUTS_READ" = true ] && return 0
287
+ CHECKOUTS_READ=true
288
+ local list here line real current="" main="" paths=() bare=() i
289
+ list=$(git -C "$repo_root" worktree list --porcelain 2>/dev/null) || return 0
290
+ here=$(cd -P "$repo_root" 2>/dev/null && pwd) || return 0
291
+ while IFS= read -r line; do
292
+ case "$line" in
293
+ "worktree "*) paths+=("${line#worktree }"); bare+=(false) ;;
294
+ bare) [ "${#paths[@]}" -gt 0 ] && bare[${#paths[@]}-1]=true ;;
295
+ esac
296
+ done <<< "$list"
297
+ [ "${#paths[@]}" -gt 0 ] || return 0
298
+ local reals=()
299
+ for i in "${!paths[@]}"; do
300
+ real=""
301
+ [ "${bare[$i]}" = false ] && real=$(cd -P "${paths[$i]}" 2>/dev/null && pwd)
302
+ reals+=("$real")
303
+ [ "$i" -eq 0 ] && main="$real"
304
+ if [ -n "$real" ] && { [ "$here" = "$real" ] || [ "${here#"$real"/}" != "$here" ]; }; then
305
+ [ "${#real}" -gt "${#current}" ] && current="$real"
306
+ fi
307
+ done
308
+ [ -n "$current" ] || return 0
309
+ [ -e "$current/.doflow-worktree-base" ] && return 0
310
+ [ "$main" != "$current" ] && MAIN="$main"
311
+ for real in "${reals[@]}"; do
312
+ [ -n "$real" ] && [ "$real" != "$current" ] && [ ! -e "$real/.doflow-worktree-base" ] && OTHERS+=("$real")
313
+ done
314
+ return 0
315
+ }
316
+
317
+ # A state file under this checkout, else under exactly one other checkout, at
318
+ # the first of the given paths that exists in each; held in two or more others,
319
+ # it cannot be told which is meant -> allow.
320
+ FOUND=""
321
+ find_state() {
322
+ local rel root count=0 hit
323
+ FOUND=""
324
+ for rel in "$@"; do
325
+ if [ -f "$repo_root/$rel" ]; then FOUND="$repo_root/$rel"; return 0; fi
326
+ done
327
+ checkouts
328
+ for root in ${OTHERS[@]+"${OTHERS[@]}"}; do
329
+ hit=""
330
+ for rel in "$@"; do
331
+ [ -f "$root/$rel" ] && { hit="$root/$rel"; break; }
332
+ done
333
+ [ -n "$hit" ] || continue
334
+ count=$((count + 1))
335
+ FOUND="$hit"
336
+ done
337
+ [ "$count" -le 1 ] || exit 0
338
+ }
339
+
340
+ # The feature a run file belongs to: the slug it recorded when it started, else
341
+ # its task id when that names a feature folder in the checkout holding it.
342
+ run_feature() {
343
+ local recorded owner
344
+ recorded=$(jq -r '.featureSlug // empty' "$1" 2>/dev/null) || recorded=""
345
+ if [ -n "$recorded" ]; then printf '%s' "$recorded"; return 0; fi
346
+ owner=${1%/.doflow/state/orchestration/*}
347
+ [ -d "$owner/agent-docs/doflow/$task_id" ] && printf '%s' "$task_id"
348
+ return 0
349
+ }
350
+
351
+ # The task's run: this checkout's, else the one other checkout's run of the same
352
+ # feature. A run with the same id for another feature is a different task.
353
+ find_run() {
354
+ local rel=".doflow/state/orchestration/$task_id.json" root count=0
355
+ FOUND=""
356
+ if [ -f "$repo_root/$rel" ]; then FOUND="$repo_root/$rel"; return 0; fi
357
+ checkouts
358
+ for root in ${OTHERS[@]+"${OTHERS[@]}"}; do
359
+ [ -f "$root/$rel" ] || continue
360
+ [ "$(run_feature "$root/$rel")" = "$slug" ] || continue
361
+ count=$((count + 1))
362
+ FOUND="$root/$rel"
363
+ done
364
+ [ "$count" -le 1 ] || exit 0
365
+ }
366
+
367
+ # ── Feature folder: this checkout's, else the main checkout's ────────────────
368
+ FEATURE_ROOT=""
369
+ if [ -d "$repo_root/$feature_dir" ]; then
370
+ FEATURE_ROOT="$repo_root"
371
+ else
372
+ checkouts
373
+ if [ -n "$MAIN" ] && [ -d "$MAIN/$feature_dir" ]; then
374
+ FEATURE_ROOT="$MAIN"
375
+ probe_artifacts "$MAIN"
376
+ fi
377
+ fi
378
+ HAS_REGISTER=false
379
+ [ -n "$FEATURE_ROOT" ] && [ -f "$FEATURE_ROOT/$feature_dir/decisions/register.json" ] && HAS_REGISTER=true
380
+ PLANNED=false
381
+ [ "$has_requirement" = "true" ] && [ "$has_design" = "true" ] && [ "$has_plan" = "true" ] && PLANNED=true
382
+
383
+ # In the flow: block a source edit until requirement.md, design.md, AND
384
+ # plan.md all exist. Classes that cannot be a feature skip this check.
385
+ if [ "$exempt" = false ] && [ -n "$FEATURE_ROOT" ] && [ "$PLANNED" = false ]; then
386
+ echo "[pre-implementation-gate] doflow gate: feature $feature_dir is missing requirement.md, design.md, or plan.md — run /do-brainstorm, /do-design, then /do-plan before editing source. (Edits under agent-docs/ are always allowed; skip the flow by removing the feature dir.)" >&2
387
+ exit 2
388
+ fi
389
+
390
+ # ── Readiness ────────────────────────────────────────────────────────────────
391
+ # The workflow registry the installed runtime reads: beside this policy in an
392
+ # install, the source tree's own, the project's install, the global install.
393
+ POLICY_DIR=$(cd -P "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd) || POLICY_DIR=""
394
+ REGISTRY=""
395
+ for candidate in \
396
+ "${POLICY_DIR:+$POLICY_DIR/../../../runtime/core/registry/workflows.json}" \
397
+ "${POLICY_DIR:+$POLICY_DIR/../../../../registry/workflows.json}" \
398
+ "$repo_root/.doflow/runtime/core/registry/workflows.json" \
399
+ "$HOME/.doflow/runtime/core/registry/workflows.json"; do
400
+ [ -n "$candidate" ] && [ -f "$candidate" ] && { REGISTRY="$candidate"; break; }
401
+ done
402
+
403
+ # The class's gated stage: its first stage whose kind mutates source and that
404
+ # names a readiness template. Sets STAGE_ID, STAGE_TEMPLATE and STAGE_EDIT
405
+ # (false when the stage opts out of this edit-time check); all empty for none.
406
+ gated_stage() {
407
+ local out
408
+ STAGE_ID=""; STAGE_TEMPLATE=""; STAGE_EDIT=""
409
+ [ -n "$REGISTRY" ] || return 0
410
+ out=$(jq -r --arg c "$1" '. as $r
411
+ | [($r.classes[$c].stages // [])[]
412
+ | select(($r.stageKinds[.kind].mutatesSource // false) == true and (.readinessTemplate // "") != "")]
413
+ | first // empty
414
+ | .id, .readinessTemplate, (.editTimeGate != false)' "$REGISTRY" 2>/dev/null) || return 0
415
+ { IFS= read -r STAGE_ID; IFS= read -r STAGE_TEMPLATE; IFS= read -r STAGE_EDIT; } <<< "$out"
416
+ return 0
417
+ }
418
+
419
+ # Whether the run file's program shows that stage completed or skipped.
420
+ stage_done() {
421
+ jq -e --arg id "$1" 'any(.program[]?; .type == "stage" and .id == $id
422
+ and (.status == "completed" or .status == "skipped"))' "$RUN" >/dev/null 2>&1
423
+ }
424
+
425
+ RUN=""
426
+ find_run
427
+ RUN="$FOUND"
428
+ run_slug=""
429
+ if [ -n "$RUN" ]; then
430
+ run_slug=$(run_feature "$RUN")
431
+ run_info=$(jq -r '(.state // ""), (.taskClass // "")' "$RUN" 2>/dev/null) || exit 0
432
+ { IFS= read -r run_state; IFS= read -r run_class; } <<< "$run_info"
433
+ case "$run_state" in COMPLETED|REJECTED) exit 0 ;; esac
434
+ fi
435
+
436
+ if [ -n "$RUN" ]; then
437
+ class="$run_class"
438
+ elif [ "$exempt" = false ] && [ -n "$FEATURE_ROOT" ] && [ "$PLANNED" = true ] && [ "$HAS_REGISTER" = true ]; then
439
+ class=feature
440
+ else
441
+ exit 0
442
+ fi
443
+
444
+ gated_stage "$class"
445
+ [ -n "$STAGE_ID" ] && [ "$STAGE_EDIT" = true ] || exit 0
446
+ if [ -n "$RUN" ]; then
447
+ # A run whose program does not name that stage (missing, malformed, or compiled
448
+ # from a registry that named it differently) cannot be judged here -> allow, as
449
+ # the runtime reads such a run as having no gated stage.
450
+ jq -e --arg id "$STAGE_ID" 'any(.program[]?; .type == "stage" and .id == $id)' "$RUN" >/dev/null 2>&1 || exit 0
451
+ stage_done "$STAGE_ID" && exit 0
452
+ fi
453
+
454
+ # The record, where the runtime looks: under the feature's namespace (the run's
455
+ # own feature when it recorded one), then flat; this checkout first.
456
+ record_slug="${run_slug:-$slug}"
457
+ if [ -n "$record_slug" ] && [ "$record_slug" != "$task_id" ]; then
458
+ find_state ".doflow/state/readiness/$record_slug/$task_id.json" ".doflow/state/readiness/$task_id.json"
459
+ else
460
+ find_state ".doflow/state/readiness/$task_id.json"
461
+ fi
462
+ record_state=""; record_class=""; record_at=""
463
+ if [ -n "$FOUND" ]; then
464
+ record_info=$(jq -r '(.state // ""), (.taskClass // ""), (.evaluatedAt // "")' "$FOUND" 2>/dev/null) || exit 0
465
+ { IFS= read -r record_state; IFS= read -r record_class; IFS= read -r record_at; } <<< "$record_info"
466
+ [ -n "$record_state" ] || exit 0
467
+ fi
468
+
469
+ # The runtime's refusal texts, byte for byte. The next command names the slug
470
+ # only when its feature folder exists here or in the main checkout, which is
471
+ # when the runtime names it too (and when `readiness --slug` resolves it).
472
+ next="doflow-run readiness --task-class $STAGE_TEMPLATE --task-id $task_id"
473
+ if [ -n "$record_slug" ] && [ "$record_slug" != "$task_id" ]; then
474
+ checkouts
475
+ if [ -d "$repo_root/agent-docs/doflow/$record_slug" ] || { [ -n "$MAIN" ] && [ -d "$MAIN/agent-docs/doflow/$record_slug" ]; }; then
476
+ next="$next --slug=$record_slug"
477
+ fi
478
+ fi
479
+ gate="doflow gate readiness-before-implementation: task '$task_id'"
480
+ rest="Next: $next, then gather what it lists until it reports READY. Nothing was changed."
481
+ if [ -z "$FOUND" ]; then
482
+ printf "%s has no readiness record for the '%s' template. %s\n" "$gate" "$STAGE_TEMPLATE" "$rest" >&2
483
+ exit 2
484
+ fi
485
+ if [ "$record_state" != READY ]; then
486
+ printf "%s was last evaluated %s at %s against the '%s' template, not READY. %s\n" \
487
+ "$gate" "$record_state" "$record_at" "$STAGE_TEMPLATE" "$rest" >&2
488
+ exit 2
489
+ fi
490
+ if [ "$record_class" != "$STAGE_TEMPLATE" ]; then
491
+ printf "%s has a READY record for the '%s' template, and this stage needs '%s'. %s\n" \
492
+ "$gate" "$record_class" "$STAGE_TEMPLATE" "$rest" >&2
493
+ exit 2
494
+ fi
495
+
496
+ exit 0
@@ -0,0 +1,131 @@
1
+ # prompt-nudge.jq — decision program for the standalone-prompt nudge (IC-005)
2
+ #
3
+ # Invocation, identical in the UserPromptSubmit hook and in the corpus runner:
4
+ # printf '%s' "$INPUT" | jq -r --slurpfile R <registry> -f prompt-nudge.jq
5
+ # Input is the hook payload on stdin; $R[0] is core/registry/workflows.json and its
6
+ # `promptNudge` object holds every rule (IC-007). Output is one line: `nudge`, `suppress`,
7
+ # or empty. jq exits non-zero only on an error, and the caller treats that as silence.
8
+ #
9
+ # The rules are data; this file is the algorithm. It reads only `.` and $R[0].promptNudge,
10
+ # keeps no state, and gives the same output for the same input. A missing or mistyped
11
+ # rule, or a pathPattern that does not compile, is an error on every prompt, never a default.
12
+ #
13
+ # Portability: jq 1.5 built with Oniguruma. No builtin newer than 1.5 (no IN, trim, pick,
14
+ # abs, toarray, splits, $__loc__, halt_error), no if without else, no input or environment.
15
+ # Only the step 2 pattern and pathPattern are regexes; every list entry is matched literally.
16
+ # Whole-prompt anchors use \A and \z so a newline inside the prompt never ends a match.
17
+ #
18
+ # Cost: a prompt can be a pasted log of hundreds of KB, and every prompt of a session is
19
+ # decided until the session is nudged or suppressed. Work over the whole prompt is limited to
20
+ # single regex scans, slicing and splitting, which jq does natively; lowercasing and the line
21
+ # test, which jq does one character or one line at a time, touch only bounded pieces. Each
22
+ # such shortcut gives the answer IC-005's steps give on the whole prompt (see the differential
23
+ # case in test/hooks/prompt-nudge-decision.test.js).
24
+
25
+ # Whitespace removed from both ends. The trailing match may start only where a whitespace run
26
+ # starts: a plain \s+$ retries from every position of a long interior run, which is quadratic
27
+ # (a 20 KB run of spaces took 1.7 s). It runs on the last 1024 characters, and on the whole
28
+ # text only when those are all whitespace.
29
+ def strip_ws:
30
+ sub("^\\s+"; "")
31
+ | length as $n
32
+ | if $n <= 1024 then sub("(?<!\\s)\\s+$"; "")
33
+ else (.[$n - 1024:] | sub("(?<!\\s)\\s+$"; "")) as $tail
34
+ | if $tail != "" then .[0:$n - 1024] + $tail else sub("(?<!\\s)\\s+$"; "") end
35
+ end;
36
+
37
+ # A list entry with every regex metacharacter escaped. An empty entry is an error rather than
38
+ # a pattern that matches anything. Only an entry holding a metacharacter is rebuilt character by
39
+ # character; that loop is the costly part of building the patterns on every prompt.
40
+ def esc:
41
+ if . == "" then error("promptNudge holds an empty list entry")
42
+ elif test("[.*+?()\\[\\]{}|^$\\\\]") | not then .
43
+ else ("\\.*+?()[]{}|^$" | explode) as $meta
44
+ | explode
45
+ | map(. as $c | if any($meta[]; . == $c) then [92, $c] else [$c] end)
46
+ | add
47
+ | implode
48
+ end;
49
+
50
+ def string_list($v; $name):
51
+ if ($v | type) == "array" and ($v | length) > 0 and all($v[]; type == "string")
52
+ then $v
53
+ else error("promptNudge.\($name) is not a non-empty list of strings")
54
+ end;
55
+
56
+ def int($v; $name):
57
+ if ($v | type) == "number" and $v == ($v | floor)
58
+ then $v
59
+ else error("promptNudge.\($name) is not an integer")
60
+ end;
61
+
62
+ # One alternation of literal entries, for use inside (?: ... ).
63
+ def alt($v; $name): string_list($v; $name) | map(esc) | join("|");
64
+
65
+ # Each entry as a pattern that matches where the ASCII-lowercased text holds the entry: every
66
+ # lowercase ASCII letter becomes [xX], everything else is literal. An entry with an uppercase
67
+ # ASCII letter can never occur in lowercased text, so it is left out. One pattern per entry: an
68
+ # alternation of [xX]-led entries cannot skip ahead and scans about twice as slowly.
69
+ def ascii_ci_patterns($v; $name):
70
+ [string_list($v; $name)[] | (esc | explode) as $e | select(all($e[]; . < 65 or . > 90))
71
+ | $e | map(if 97 <= . and . <= 122 then [91, ., . - 32, 93] else [.] end) | add | implode];
72
+
73
+ # The line test: one line, already trimmed and non-empty, judged against the compiled rules.
74
+ def line_ok($re):
75
+ .[0:$re.maxScan] as $l
76
+ | ($l | ascii_downcase | sub($re.lead; "")) as $ll
77
+ | ($ll | sub($re.verb; "")) as $after
78
+ | ($ll | test("\\?\\s*$") | not)
79
+ and ($ll | test($re.startWord) | not)
80
+ and ($ll | test($re.verb))
81
+ and ($after | test($re.objectBlocker) | not)
82
+ and ($ll | test($re.contextBlocker) | not)
83
+ and (($after | test($re.noun)) or ($l | test($re.path)));
84
+
85
+ ($R[0].promptNudge) as $n
86
+ | if ($n | type) == "object" then . else error("promptNudge is missing or not an object") end
87
+ | {
88
+ minChars: int($n.minChars; "minChars"),
89
+ maxScan: int($n.maxScanChars; "maxScanChars"),
90
+ prefixes: string_list($n.excludePrefixes; "excludePrefixes"),
91
+ phrases: ascii_ci_patterns($n.excludeContainsPhrases; "excludeContainsPhrases"),
92
+ stallWords: (alt($n.stallWords; "stallWords") | $n.stallWords),
93
+ stallWidth: ([$n.stallWords[] | length] | max),
94
+ lead: ("^(?:(?:" + alt($n.leadIns; "leadIns") + "),?\\s+)+"),
95
+ startWord: ("^(?:" + alt($n.excludeStartWords; "excludeStartWords") + ")\\b"),
96
+ verb: ("^(?:" + alt($n.verbs; "verbs") + ")\\b"),
97
+ objectBlocker: ("^\\s+(?:" + alt($n.objectBlockers; "objectBlockers") + ")\\b"),
98
+ contextBlocker: ("\\b(?:" + alt($n.contextBlockers; "contextBlockers") + ")\\b"),
99
+ noun: ("\\b(?:" + alt($n.nouns; "nouns") + ")s?\\b"),
100
+ path: $n.pathPattern
101
+ } as $re
102
+ # pathPattern is compiled here on every prompt, so a broken pattern never hides behind an
103
+ # early return.
104
+ | ("" | test($re.path)) as $path_compiles
105
+ # Step 1: the prompt as a string, carriage returns removed, trimmed. IC-005 states the steps
106
+ # on the ASCII-lowercased prompt `lt`; lowercasing costs a jq step per character (about 80 ms
107
+ # on 200 KB), so only bounded slices are lowercased and every whole-prompt check below runs on
108
+ # `t` in a form that gives the same answer as on `lt`.
109
+ | (if type == "object" then .prompt else null end) as $raw
110
+ | (if ($raw | type) == "string" then $raw else "" end
111
+ | if contains("\r") then split("\r") | join("") else . end | strip_ws) as $t
112
+ # Step 2 runs before the length floor so a short /do-plan also ends evaluation (suppress).
113
+ # IC-005's (^|[\s`'"(])/do(-[a-z]+)?\b on lt, as /[dD][oO]\b on t not preceded by a character
114
+ # outside that class: the optional -name never changes whether it matches, and a pattern that
115
+ # starts with `/` lets the regex engine skip to each slash.
116
+ | if ($t | test("(?<![^\\s`'\"(])/[dD][oO]\\b")) then "suppress"
117
+ elif ($t | length) < $re.minChars then ""
118
+ elif any($re.prefixes[]; . as $x | $t | startswith($x)) then ""
119
+ # Step 5: lt is a stall word followed only by punctuation and spaces. Lowercasing touches
120
+ # neither, so the head is lowercased and the rest checked on t.
121
+ elif (($t[0:$re.stallWidth] | ascii_downcase) as $h
122
+ | any($re.stallWords[]; . as $w
123
+ | ($h | startswith($w)) and ($t[($w | length):] | test("\\A[[:punct:]\\s]*\\z")))) then ""
124
+ elif ($t | endswith("?")) then ""
125
+ elif any($re.phrases[]; . as $x | $t | test($x)) then ""
126
+ # Step 8: only the first and the last non-empty lines are judged. t starts and ends with a
127
+ # non-space character, so its first and last lines are the first and last non-empty ones.
128
+ elif ($t | split("\n") | [.[0], .[-1]] | unique
129
+ | any(.[]; strip_ws | line_ok($re))) then "nudge"
130
+ else ""
131
+ end