@khoavu882/doflow 0.0.0-stage → 2.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (407) 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 +1057 -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 +46 -4
  250. package/src/adapters/antigravity/index.ts +631 -0
  251. package/src/adapters/claude/index.ts +548 -0
  252. package/src/adapters/claude/mcp.ts +94 -0
  253. package/src/adapters/codex/agents.ts +208 -0
  254. package/src/adapters/codex/config.ts +272 -0
  255. package/src/adapters/codex/hooks.ts +203 -0
  256. package/src/adapters/codex/index.ts +707 -0
  257. package/src/adapters/codex/mcp.ts +271 -0
  258. package/src/adapters/codex/native-projection.ts +34 -0
  259. package/src/adapters/copilot/index.ts +413 -0
  260. package/src/adapters/copy-tree.ts +620 -0
  261. package/src/adapters/gemini/hooks.ts +210 -0
  262. package/src/adapters/gemini/index.ts +390 -0
  263. package/src/adapters/hook-commands.ts +60 -0
  264. package/src/adapters/index.ts +106 -0
  265. package/src/adapters/kiro/index.ts +310 -0
  266. package/src/adapters/mcp-entries.ts +331 -0
  267. package/src/adapters/opencode/index.ts +466 -0
  268. package/src/adapters/pi/index.ts +279 -0
  269. package/src/adapters/pi/json-members.ts +185 -0
  270. package/src/adapters/pi/mcp.ts +373 -0
  271. package/src/adapters/resolve-source.ts +19 -0
  272. package/src/cli/commands/install.ts +218 -0
  273. package/src/cli/commands/list-backups.ts +21 -0
  274. package/src/cli/commands/reconcile.ts +120 -0
  275. package/src/cli/commands/remove.ts +82 -0
  276. package/src/cli/commands/rollback.ts +172 -0
  277. package/src/cli/commands/self-update.ts +33 -0
  278. package/src/cli/commands/status.ts +138 -0
  279. package/src/cli/commands/tools.ts +92 -0
  280. package/src/cli/commands/update.ts +162 -0
  281. package/src/cli/index.ts +568 -0
  282. package/src/cli/runtime-commands.ts +281 -0
  283. package/src/cli/shared.ts +218 -0
  284. package/src/helper/bash-helper.ts +34 -0
  285. package/src/helper/git.ts +24 -0
  286. package/src/helper/guardrails.ts +49 -0
  287. package/src/helper/harness-paths.ts +140 -0
  288. package/src/helper/marker-merge.ts +116 -0
  289. package/src/helper/prompt.ts +61 -0
  290. package/src/helper/repo-root.ts +29 -0
  291. package/src/helper/settings-merge.ts +151 -0
  292. package/src/helper/settings-scope.ts +33 -0
  293. package/src/helper/toml.ts +244 -0
  294. package/src/install/backup-set.ts +149 -0
  295. package/src/install/backup.ts +724 -0
  296. package/src/install/context.ts +58 -0
  297. package/src/install/manifest.ts +145 -0
  298. package/src/install/mcp.ts +254 -0
  299. package/src/install/paths.ts +84 -0
  300. package/src/install/targets.ts +90 -0
  301. package/src/install/tool-lifecycle.ts +151 -0
  302. package/src/lifecycle/bash-availability.ts +25 -0
  303. package/src/lifecycle/index.ts +812 -0
  304. package/src/lifecycle/mcp-index.ts +28 -0
  305. package/src/lifecycle/policies.ts +73 -0
  306. package/src/lifecycle/view.ts +175 -0
  307. package/src/registry/index.ts +611 -0
  308. package/src/release/sync-plugin-versions.ts +66 -0
  309. package/src/runtime/advice/cli.ts +283 -0
  310. package/src/runtime/advice/client.ts +324 -0
  311. package/src/runtime/advice/config.ts +189 -0
  312. package/src/runtime/advice/doctor.ts +169 -0
  313. package/src/runtime/advice/endpoint-guard.ts +134 -0
  314. package/src/runtime/advice/kinds.ts +154 -0
  315. package/src/runtime/advice/providers/ollama-systemone.ts +209 -0
  316. package/src/runtime/advice/record.ts +166 -0
  317. package/src/runtime/advice/registry.ts +170 -0
  318. package/src/runtime/advice/strength.ts +66 -0
  319. package/src/runtime/brief-tools.ts +342 -0
  320. package/src/runtime/c4-project.ts +371 -0
  321. package/src/runtime/capability-router.ts +484 -0
  322. package/src/runtime/checkouts.ts +170 -0
  323. package/src/runtime/claims.ts +468 -0
  324. package/src/runtime/cli-result.ts +55 -0
  325. package/src/runtime/cli.ts +678 -0
  326. package/src/runtime/command-detect.ts +516 -0
  327. package/src/runtime/context-pack.ts +492 -0
  328. package/src/runtime/decision-register.ts +493 -0
  329. package/src/runtime/evidence-ledger.ts +338 -0
  330. package/src/runtime/failure/capture.ts +272 -0
  331. package/src/runtime/failure/classifier.ts +68 -0
  332. package/src/runtime/failure/cli.ts +178 -0
  333. package/src/runtime/failure/home.ts +64 -0
  334. package/src/runtime/failure/store.ts +226 -0
  335. package/src/runtime/feature-resolve.ts +79 -0
  336. package/src/runtime/freshness.ts +177 -0
  337. package/src/runtime/health.ts +863 -0
  338. package/src/runtime/history-compactor.ts +237 -0
  339. package/src/runtime/implementation-gate.ts +321 -0
  340. package/src/runtime/inventory/coverage.ts +218 -0
  341. package/src/runtime/inventory/identity.ts +65 -0
  342. package/src/runtime/inventory/index.ts +705 -0
  343. package/src/runtime/inventory/precedence.ts +134 -0
  344. package/src/runtime/inventory/read-scopes.ts +131 -0
  345. package/src/runtime/inventory/siblings.ts +192 -0
  346. package/src/runtime/knowledge/chunker.ts +124 -0
  347. package/src/runtime/knowledge/hybrid.ts +149 -0
  348. package/src/runtime/knowledge/index-store.ts +145 -0
  349. package/src/runtime/knowledge/retrieval.ts +194 -0
  350. package/src/runtime/leak-scan.ts +185 -0
  351. package/src/runtime/lifecycle/cli.ts +276 -0
  352. package/src/runtime/lifecycle/event-store.ts +340 -0
  353. package/src/runtime/lifecycle/fold.ts +353 -0
  354. package/src/runtime/lifecycle/followup.ts +432 -0
  355. package/src/runtime/lifecycle/goal.ts +249 -0
  356. package/src/runtime/lifecycle/intent-writer.ts +86 -0
  357. package/src/runtime/lifecycle/overview.ts +292 -0
  358. package/src/runtime/lifecycle/release.ts +241 -0
  359. package/src/runtime/lifecycle/report-store.ts +221 -0
  360. package/src/runtime/lifecycle/retention.ts +212 -0
  361. package/src/runtime/lifecycle/root.ts +71 -0
  362. package/src/runtime/lifecycle/status.ts +353 -0
  363. package/src/runtime/lifecycle/store-upkeep.ts +393 -0
  364. package/src/runtime/locator-resolve.ts +158 -0
  365. package/src/runtime/mask.ts +210 -0
  366. package/src/runtime/mcp-consultations.ts +412 -0
  367. package/src/runtime/mcp-reach.ts +157 -0
  368. package/src/runtime/model-router.ts +153 -0
  369. package/src/runtime/outcome.ts +467 -0
  370. package/src/runtime/reach.ts +148 -0
  371. package/src/runtime/readiness-record.ts +150 -0
  372. package/src/runtime/readiness.ts +435 -0
  373. package/src/runtime/recovery.ts +282 -0
  374. package/src/runtime/research-request.ts +231 -0
  375. package/src/runtime/retrieval-plan.ts +578 -0
  376. package/src/runtime/scaffold/artifacts.ts +217 -0
  377. package/src/runtime/scaffold/fingerprint.ts +56 -0
  378. package/src/runtime/scaffold/generate.ts +911 -0
  379. package/src/runtime/scaffold/languages.ts +245 -0
  380. package/src/runtime/storage/cli.ts +64 -0
  381. package/src/runtime/storage/engine.ts +249 -0
  382. package/src/runtime/storage/registry.ts +130 -0
  383. package/src/runtime/storage/roots.ts +63 -0
  384. package/src/runtime/storage/rules.ts +282 -0
  385. package/src/runtime/storage/upkeep.ts +243 -0
  386. package/src/runtime/storage/work-status.ts +326 -0
  387. package/src/runtime/task-attempts.ts +415 -0
  388. package/src/runtime/task-classifier.ts +550 -0
  389. package/src/runtime/task-scope.ts +130 -0
  390. package/src/runtime/task-state.ts +176 -0
  391. package/src/runtime/trace/indicators.ts +235 -0
  392. package/src/runtime/trace/ledger.ts +626 -0
  393. package/src/runtime/trace/render.ts +76 -0
  394. package/src/runtime/trace/views.ts +459 -0
  395. package/src/runtime/verification/contract-runner.ts +341 -0
  396. package/src/runtime/verification/engine.ts +1461 -0
  397. package/src/runtime/verification/registry.ts +130 -0
  398. package/src/runtime/verification/scope-bound.ts +418 -0
  399. package/src/runtime/verification/test-selection.ts +172 -0
  400. package/src/runtime/workflow-engine.ts +588 -0
  401. package/src/runtime/workflow-orchestrator.ts +953 -0
  402. package/src/runtime/worktree.ts +340 -0
  403. package/src/state/index.ts +193 -0
  404. package/src/state/lockfile.ts +136 -0
  405. package/src/state/run-lock.ts +408 -0
  406. package/types/node-commonjs.d.ts +68 -0
  407. package/types/runtime.d.ts +91 -0
@@ -0,0 +1,484 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { execFileSync } = require('node:child_process');
8
+ const { finishRuntime, usageError } = require('./cli-result.ts');
9
+ const { REPO_ROOT } = require('../helper/repo-root.ts');
10
+ const { validateCapabilityProviders } = require('../registry/index.ts');
11
+
12
+ // Mirrors bin/doflow.js's own REPO_ROOT computation, relative to this file's location, so
13
+ // handleRouteCommand resolves the same repo root it did before relocation. (D8)
14
+
15
+ /** POSIX single-quote escaping, for the DISPLAY string only — execution always uses the argv
16
+ * vector, never a shell string, so retrieved data can never become shell syntax (review R5). */
17
+ function shellQuote(word: any): string {
18
+ const text = String(word);
19
+ if (text !== '' && /^[A-Za-z0-9_@%+=:,.\/-]+$/.test(text)) return text;
20
+ return `'${text.replace(/'/g, `'\\''`)}'`;
21
+ }
22
+
23
+ function parseYamlFile(filePath: string, fsImpl: any = fs): any {
24
+ try {
25
+ const text = fsImpl.readFileSync(filePath, 'utf8');
26
+ return JSON.parse(text);
27
+ } catch (error: any) {
28
+ throw new Error(`Failed to parse registry file '${filePath}': ${error.message}`);
29
+ }
30
+ }
31
+
32
+ class CapabilityRouter {
33
+ fsImpl: any;
34
+ repoRoot: string;
35
+ projectRoot: string;
36
+ registryDir: string;
37
+ binaryChecker: ((binary: string) => boolean) | null;
38
+ binaryCache: Map<string, boolean>;
39
+ capabilities!: Record<string, any>;
40
+ routes!: Record<string, any>;
41
+
42
+ /**
43
+ * @param {Object} [options]
44
+ * @param {string} [options.repoRoot]
45
+ * @param {string} [options.registryDir]
46
+ * @param {Object} [options.capabilities]
47
+ * @param {Object} [options.routes]
48
+ * @param {Object} [options.fsImpl]
49
+ * @param {Function} [options.binaryChecker]
50
+ */
51
+ constructor(options: any = {}) {
52
+ this.fsImpl = options.fsImpl || fs;
53
+ this.repoRoot = options.repoRoot || REPO_ROOT;
54
+ // Two roots, deliberately distinct (review A3, surfaced by R5's tests): `repoRoot` locates the
55
+ // DoFlow install — the registry ships inside it — while `projectRoot` is the repository under
56
+ // work, whose manifests native.test detection reads. Under an npm install they differ; a
57
+ // single root made detection read the DoFlow package's own package.json.
58
+ this.projectRoot = options.projectRoot || options.repoRoot || process.cwd();
59
+ this.registryDir = options.registryDir || path.join(this.repoRoot, 'core', 'registry');
60
+ this.binaryChecker = options.binaryChecker || null;
61
+ this.binaryCache = new Map();
62
+
63
+ if (options.capabilities && options.routes) {
64
+ this.capabilities = options.capabilities;
65
+ this.routes = options.routes;
66
+ } else {
67
+ this.loadRegistries();
68
+ }
69
+ }
70
+
71
+ loadRegistries(): void {
72
+ const capsPath = path.join(this.registryDir, 'capabilities.json');
73
+ const routesPath = path.join(this.registryDir, 'routes.json');
74
+
75
+ const capsData = parseYamlFile(capsPath, this.fsImpl);
76
+ const routesData = parseYamlFile(routesPath, this.fsImpl);
77
+
78
+ this.capabilities = capsData.capabilities || {};
79
+ // A provider without a valid `sourceMode` cannot be told apart from an index-backed one, so the
80
+ // file is refused whole rather than loaded with a guess (068).
81
+ const providerErrors: string[] = [];
82
+ validateCapabilityProviders(this.capabilities, providerErrors);
83
+ if (providerErrors.length > 0) {
84
+ throw new Error(`Invalid registry file '${capsPath}':\n- ${providerErrors.join('\n- ')}`);
85
+ }
86
+ this.routes = routesData.routes || {};
87
+ }
88
+
89
+ /**
90
+ * Check if an executable binary is present in PATH or environment with caching.
91
+ * @param {string} binary
92
+ * @returns {boolean}
93
+ */
94
+ isBinaryAvailable(binary: string): boolean {
95
+ if (!binary) return false;
96
+ if (this.binaryChecker) {
97
+ return this.binaryChecker(binary);
98
+ }
99
+ if (this.binaryCache.has(binary)) {
100
+ return this.binaryCache.get(binary) as boolean;
101
+ }
102
+ try {
103
+ const isWin = process.platform === 'win32';
104
+ const cmd = isWin ? 'where' : 'which';
105
+ execFileSync(cmd, [binary], { stdio: 'ignore', timeout: 1000 });
106
+ this.binaryCache.set(binary, true);
107
+ return true;
108
+ } catch {
109
+ this.binaryCache.set(binary, false);
110
+ return false;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Executes a smoke check command for a provider.
116
+ * @param {Array<string>} checkCommand
117
+ * @returns {{ ok: boolean, output?: string, error?: string }}
118
+ */
119
+ executeSmokeCheck(checkCommand: any): { ok: boolean; output?: string; error?: string } {
120
+ if (!Array.isArray(checkCommand) || checkCommand.length === 0) {
121
+ return { ok: true };
122
+ }
123
+ const [bin, ...args] = checkCommand;
124
+ try {
125
+ const stdout = execFileSync(bin, args, {
126
+ encoding: 'utf8',
127
+ timeout: 2500,
128
+ stdio: ['ignore', 'pipe', 'pipe'],
129
+ });
130
+ return { ok: true, output: stdout.trim() };
131
+ } catch (error: any) {
132
+ return { ok: false, error: error.message };
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Evaluates the health of a single provider.
138
+ * @param {Object} provider
139
+ * @param {boolean} [deepCheck=false]
140
+ * @returns {{ status: 'HEALTHY'|'UNAVAILABLE', details?: string }}
141
+ */
142
+ evaluateProviderHealth(provider: any, deepCheck: boolean = false): any {
143
+ if (!provider) return { status: 'UNAVAILABLE', details: 'No provider declared' };
144
+
145
+ // Declared / installed / responsive are DISTINCT facts (review R5): `facts` reports each one
146
+ // as true, false, or null-for-not-measured, so "healthy" can never silently mean only
147
+ // "declared in the registry". The summary `status` stays for existing callers.
148
+ const facts: { declared: boolean; installed: boolean | null; responsive: boolean | null } = { declared: true, installed: null, responsive: null };
149
+
150
+ // `native.test` has no binary — its installedness is whether this project's manifests declare
151
+ // a test command at all. Reporting HEALTHY without that check routed verify-runtime-behavior
152
+ // to an invented `npm test` on machines where every binary probe failed (review R5).
153
+ if (provider.id === 'native.test') {
154
+ const command = this.detectTestCommand();
155
+ facts.installed = Boolean(command);
156
+ if (!command) {
157
+ return { status: 'UNAVAILABLE', details: 'No test command detected in project manifests', facts };
158
+ }
159
+ return { status: 'HEALTHY', details: `Project test command: ${command}`, facts };
160
+ }
161
+
162
+ // For native providers, check binary if specified (or fallback to basic availability)
163
+ if (provider.kind === 'native') {
164
+ if (provider.binary) {
165
+ const available = this.isBinaryAvailable(provider.binary);
166
+ facts.installed = available;
167
+ if (!available && provider.binary === 'rg') {
168
+ // If rg is not present, check for standard grep as baseline fallback
169
+ const grepAvailable = this.isBinaryAvailable('grep');
170
+ if (grepAvailable) {
171
+ facts.installed = true;
172
+ return { status: 'HEALTHY', details: 'Using grep fallback', facts };
173
+ }
174
+ }
175
+ if (!available) {
176
+ return { status: 'UNAVAILABLE', details: `Native binary '${provider.binary}' not found`, facts };
177
+ }
178
+ }
179
+ return { status: 'HEALTHY', facts };
180
+ }
181
+
182
+ if (provider.binary) {
183
+ const available = this.isBinaryAvailable(provider.binary);
184
+ facts.installed = available;
185
+ if (!available) {
186
+ return { status: 'UNAVAILABLE', details: `Tool binary '${provider.binary}' not found`, facts };
187
+ }
188
+ if (deepCheck && provider.checkCommand) {
189
+ const smoke = this.executeSmokeCheck(provider.checkCommand);
190
+ facts.responsive = smoke.ok;
191
+ if (!smoke.ok) {
192
+ return { status: 'UNAVAILABLE', details: `Smoke check failed: ${smoke.error}`, facts };
193
+ }
194
+ }
195
+ return { status: 'HEALTHY', facts };
196
+ }
197
+
198
+ return { status: 'HEALTHY', facts };
199
+ }
200
+
201
+ /**
202
+ * Resolves a capability to its highest-priority healthy provider.
203
+ * @param {string} capabilityId
204
+ * @param {Object} [options]
205
+ * @param {boolean} [options.deepCheck=false]
206
+ * @returns {{ capabilityId: string, provider: Object|null, status: string, fallbackProviders: Array<Object> }}
207
+ */
208
+ resolveCapability(capabilityId: string, { deepCheck = false }: { deepCheck?: boolean } = {}): any {
209
+ const capability = this.capabilities[capabilityId];
210
+ if (!capability) {
211
+ throw new Error(`Unknown capability '${capabilityId}'`);
212
+ }
213
+
214
+ const providers = capability.providers || [];
215
+ let selectedProvider: any = null;
216
+ let selectedStatus = 'UNAVAILABLE';
217
+ const fallbackProviders: any[] = [];
218
+
219
+ for (let i = 0; i < providers.length; i++) {
220
+ const prov = providers[i];
221
+ const health = this.evaluateProviderHealth(prov, deepCheck);
222
+
223
+ if (health.status === 'HEALTHY') {
224
+ if (!selectedProvider) {
225
+ selectedProvider = prov;
226
+ selectedStatus = i === 0 ? 'HEALTHY' : 'FALLBACK';
227
+ } else {
228
+ fallbackProviders.push(prov);
229
+ }
230
+ } else {
231
+ fallbackProviders.push({ ...prov, status: 'UNAVAILABLE', reason: health.details });
232
+ }
233
+ }
234
+
235
+ return {
236
+ capabilityId,
237
+ description: capability.description,
238
+ provider: selectedProvider,
239
+ status: selectedStatus,
240
+ fallbackProviders,
241
+ };
242
+ }
243
+
244
+ /**
245
+ * Formats concrete execution instructions for a provider based on intent parameters.
246
+ *
247
+ * The executable contract is `argv` — an argument VECTOR, executed without a shell (review R5:
248
+ * the old interpolated strings turned a query containing `$(...)` into executable shell syntax
249
+ * and split a path containing spaces). `cliCommand` remains for display and for a human typing
250
+ * it, built by quoting every argv word; it is never the primary contract. MCP invocations get a
251
+ * validated tool name plus an argument object using the tool's own parameter names.
252
+ *
253
+ * @param {string} intent
254
+ * @param {Object} provider
255
+ * @param {Object} params
256
+ * @returns {Object}
257
+ */
258
+ formatExecution(intent: string, provider: any, params: any = {}): any {
259
+ const query = params.query || params.symbol || params.concept || '';
260
+ const targetPath = params.path || '.';
261
+ // The MCP tool name comes from the provider's registry descriptor (`mcp: {server, tool}`), so a
262
+ // new MCP-backed provider needs no code change here. A caller may pass a bare `{ id }`, so fall
263
+ // back to the registry entry with that id.
264
+ const mcp = provider.mcp
265
+ || Object.values(this.capabilities).flatMap((cap: any) => cap.providers || []).find((p: any) => p.id === provider.id && p.mcp)?.mcp;
266
+ const mcpTool = mcp ? { mcpTool: `mcp__${mcp.server}__${mcp.tool}` } : {};
267
+ const withDisplay = (result: any) => ({ ...mcpTool, ...result, ...(result.argv ? { cliCommand: result.argv.map(shellQuote).join(' ') } : {}) });
268
+
269
+ if (provider.id === 'semble.search') {
270
+ const content = params.content || 'code';
271
+ return withDisplay({
272
+ // `repo`, not `path`: the MCP tool's schema names the project root `repo` and rejects a
273
+ // call without it (review R5 — the routed arguments did not fit the tool they named).
274
+ args: { query, repo: targetPath, content },
275
+ argv: ['semble', 'search', query, targetPath, '--content', content],
276
+ });
277
+ }
278
+
279
+ if (provider.id === 'graphify.query') {
280
+ return withDisplay({
281
+ args: { query, project_path: targetPath },
282
+ argv: ['graphify', 'query', query],
283
+ });
284
+ }
285
+
286
+ if (provider.id === 'native.rg') {
287
+ const isRg = this.isBinaryAvailable('rg');
288
+ return withDisplay({
289
+ argv: isRg ? ['rg', '-i', query, targetPath] : ['grep', '-rn', '-i', query, targetPath],
290
+ args: { query, path: targetPath },
291
+ });
292
+ }
293
+
294
+ if (provider.id === 'git.native') {
295
+ return withDisplay({
296
+ argv: ['git', 'log', '-n', String(params.maxCommits || 10), `--grep=${query}`],
297
+ args: { query },
298
+ });
299
+ }
300
+
301
+ if (provider.id === 'rtk') {
302
+ // `params.command` is by contract a command the caller already owns, not retrieved data, so
303
+ // it passes through; there is no argv because splitting a caller's command string here would
304
+ // corrupt any quoted argument it contains.
305
+ const rawCmd = params.command || 'git status';
306
+ return {
307
+ cliCommand: `rtk ${rawCmd}`,
308
+ args: { rawCommand: rawCmd },
309
+ };
310
+ }
311
+
312
+ // `native.test` stands for "whatever this project runs its tests with", read from the project's
313
+ // own manifests via the shared command detector. No detection, no command: inventing `npm test`
314
+ // for a project that never declared it hands the model a plausible-looking command that answers
315
+ // a question nobody asked (review R5).
316
+ if (provider.id === 'native.test') {
317
+ const command = params.testCommand || this.detectTestCommand();
318
+ if (!command) {
319
+ return {
320
+ cliCommand: null,
321
+ argv: null,
322
+ args: {},
323
+ reason: 'No test command detected in this project\'s manifests; declare one (plan.md override or package manifest) before routing verify-runtime-behavior here.',
324
+ };
325
+ }
326
+ return withDisplay({
327
+ argv: [...command.split(' '), ...(query ? [query] : [])],
328
+ args: { testCommand: command, ...(query ? { filter: query } : {}) },
329
+ });
330
+ }
331
+
332
+ return withDisplay({
333
+ argv: [provider.binary || provider.id, query],
334
+ args: params,
335
+ });
336
+ }
337
+
338
+ /** The project's own test entrypoint, read from its manifests by the shared command detector —
339
+ * the same detector `doflow verify` uses, so the two agree. Returns null when no manifest
340
+ * declares one: a wrong guess here becomes a command the model runs. */
341
+ detectTestCommand(): string | null {
342
+ try {
343
+ const { detectCommands } = require('./command-detect.ts');
344
+ const detected = detectCommands({ projectRoot: this.projectRoot });
345
+ return detected.commands?.test?.command || null;
346
+ } catch {
347
+ return null;
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Resolves a user/task information intent to a structured RetrievalPlan.
353
+ * @param {string} intentId
354
+ * @param {Object} [params={}]
355
+ * @param {Object} [options={}]
356
+ * @returns {Object} RetrievalPlan
357
+ */
358
+ resolveIntent(intentId: string, params: any = {}, { deepCheck = false }: { deepCheck?: boolean } = {}): any {
359
+ const route = this.routes[intentId];
360
+ if (!route) {
361
+ throw new Error(`Unknown route/intent '${intentId}'`);
362
+ }
363
+
364
+ const primaryCapId = route.capability;
365
+ const primaryResolution = this.resolveCapability(primaryCapId, { deepCheck });
366
+
367
+ if (primaryResolution.provider) {
368
+ return {
369
+ intent: intentId,
370
+ description: route.description,
371
+ capability: primaryCapId,
372
+ selectedProvider: primaryResolution.provider,
373
+ status: primaryResolution.status,
374
+ execution: this.formatExecution(intentId, primaryResolution.provider, params),
375
+ fallbackChain: (route.fallback || []).map((fbCap: string) => ({
376
+ capability: fbCap,
377
+ resolved: this.resolveCapability(fbCap, { deepCheck }),
378
+ })),
379
+ };
380
+ }
381
+
382
+ // Primary capability had no healthy providers. Evaluate route fallbacks in order.
383
+ for (const fallbackCapId of route.fallback || []) {
384
+ const fallbackResolution = this.resolveCapability(fallbackCapId, { deepCheck });
385
+ if (fallbackResolution.provider) {
386
+ return {
387
+ intent: intentId,
388
+ description: route.description,
389
+ capability: fallbackCapId,
390
+ selectedProvider: fallbackResolution.provider,
391
+ status: 'FALLBACK',
392
+ execution: this.formatExecution(intentId, fallbackResolution.provider, params),
393
+ fallbackChain: [
394
+ {
395
+ capability: primaryCapId,
396
+ status: 'UNAVAILABLE',
397
+ reason: 'No healthy providers available',
398
+ },
399
+ ],
400
+ };
401
+ }
402
+ }
403
+
404
+ return {
405
+ intent: intentId,
406
+ description: route.description,
407
+ capability: primaryCapId,
408
+ selectedProvider: null,
409
+ status: 'UNAVAILABLE',
410
+ execution: null,
411
+ fallbackChain: (route.fallback || []).map((cap: string) => ({ capability: cap, status: 'UNAVAILABLE' })),
412
+ };
413
+ }
414
+
415
+ /**
416
+ * Returns a complete diagnostic health report for all registered capabilities and routes.
417
+ * @param {boolean} [deepCheck=false]
418
+ * @returns {Array<Object>}
419
+ */
420
+ getAllCapabilitiesHealth(deepCheck: boolean = false): any[] {
421
+ const report: any[] = [];
422
+ for (const [capId, cap] of Object.entries(this.capabilities)) {
423
+ const resolution = this.resolveCapability(capId, { deepCheck });
424
+ report.push({
425
+ capability: capId,
426
+ description: cap.description,
427
+ status: resolution.status,
428
+ activeProvider: resolution.provider ? resolution.provider.name : 'None',
429
+ providerId: resolution.provider ? resolution.provider.id : null,
430
+ totalProviders: cap.providers ? cap.providers.length : 0,
431
+ });
432
+ }
433
+ return report;
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Handles `doflow route` — resolve an information need to a provider that is actually healthy.
439
+ *
440
+ * Exits 1 when no provider can serve the intent: that is a finding the caller must act on (the
441
+ * work still has to be done, by hand or by a different route), not an error in the request.
442
+ *
443
+ * @param {Object} options
444
+ * @param {string|null} options.intent
445
+ * @param {string} [options.query]
446
+ * @param {boolean} [options.check=false] deep smoke check instead of a presence check
447
+ * @param {boolean} [options.json=false]
448
+ * @param {string} [options.projectRoot]
449
+ * @returns {number} exit code
450
+ */
451
+ function handleRouteCommand({ intent, query, check = false, json = false, projectRoot }: any = {}): number {
452
+ const router = new CapabilityRouter({ repoRoot: REPO_ROOT, projectRoot });
453
+ if (typeof intent !== 'string' || intent.trim() === '') {
454
+ return usageError('route', `--intent is required. Declared intents: ${Object.keys(router.routes).join(', ')}`, json);
455
+ }
456
+
457
+ let resolution: any;
458
+ try {
459
+ resolution = router.resolveIntent(intent, { query, path: projectRoot || '.' }, { deepCheck: check });
460
+ } catch (error: any) {
461
+ return usageError('route', `${error.message}. Declared intents: ${Object.keys(router.routes).join(', ')}`, json, error);
462
+ }
463
+
464
+ if (json) console.log(JSON.stringify(resolution, null, 2));
465
+ else {
466
+ console.log(`\nDoFlow Route [${resolution.intent}] — ${resolution.status}:`);
467
+ console.log('═'.repeat(78));
468
+ console.log(`Need: ${resolution.description}`);
469
+ console.log(`Capability: ${resolution.capability}`);
470
+ console.log(`Provider: ${resolution.selectedProvider ? resolution.selectedProvider.name : 'none — no provider on this machine can answer'}`);
471
+ if (resolution.execution) {
472
+ console.log('─'.repeat(78));
473
+ for (const [key, value] of Object.entries(resolution.execution)) console.log(` ${key.padEnd(12)} ${value}`);
474
+ }
475
+ console.log('═'.repeat(78) + '\n');
476
+ }
477
+ return finishRuntime(resolution.selectedProvider ? 0 : 1);
478
+ }
479
+
480
+ module.exports = {
481
+ CapabilityRouter,
482
+ parseYamlFile,
483
+ handleRouteCommand,
484
+ };
@@ -0,0 +1,170 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { spawnSync } = require('node:child_process');
8
+ const { BASE_FILE } = require('./worktree.ts');
9
+
10
+ // The checkouts of one repository: the main working tree and its linked worktrees.
11
+ //
12
+ // Feature folders, runs and records live in whichever checkout wrote them; a linked worktree has
13
+ // none of the main checkout's gitignored files. A reader that looks only where it stands misses
14
+ // them, and a reader that guesses another path reads the wrong one. `git worktree list` is the one
15
+ // source that names every checkout, and it is asked once per process.
16
+
17
+ /** realpath(cwd) -> listCheckouts result. */
18
+ const cache = new Map<string, any>();
19
+
20
+ function realOrNull(p: string, fsImpl: any): string | null {
21
+ try {
22
+ return fsImpl.realpathSync(p);
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ function failed(reason: string, detail: string): any {
29
+ return {
30
+ ok: false, reason, detail, current: null, main: null, mainReason: null, others: [], isLinked: false, sandbox: false,
31
+ };
32
+ }
33
+
34
+ /**
35
+ * The repository's checkouts as seen from `cwd`, from one `git worktree list --porcelain`.
36
+ *
37
+ * `main` is the first porcelain entry (null when it is bare, no longer exists, or `cwd` is in a
38
+ * DoFlow sandbox); `current` is the entry that is the longest real-path prefix of `cwd`, so a
39
+ * sandbox nested under the main checkout is told apart from it. A sandbox (a checkout holding
40
+ * `.doflow-worktree-base`) is never offered as another checkout and sees none itself: it exists
41
+ * to be isolated. Writes nothing.
42
+ * @param {Object} options
43
+ * @param {string} options.cwd
44
+ * @param {Function} [options.exec] spawnSync-compatible
45
+ * @param {Object} [options.fsImpl]
46
+ * @returns {{ok: boolean, reason: string|null, detail: string|null, current: string|null, main: string|null,
47
+ * mainReason: string|null, others: Array<string>, isLinked: boolean, sandbox: boolean}}
48
+ */
49
+ function listCheckouts({ cwd, exec = spawnSync, fsImpl = fs }: { cwd: string; exec?: any; fsImpl?: any }): any {
50
+ const realCwd = realOrNull(cwd, fsImpl) || path.resolve(cwd);
51
+ if (cache.has(realCwd)) return cache.get(realCwd);
52
+
53
+ let res: any;
54
+ try {
55
+ res = exec('git', ['-C', realCwd, 'worktree', 'list', '--porcelain'], { encoding: 'utf8', timeout: 10000 });
56
+ } catch (error: any) {
57
+ res = { error };
58
+ }
59
+ let result: any;
60
+ if (res && res.error) {
61
+ result = res.error.code === 'ENOENT'
62
+ ? failed('git-unavailable', String(res.error.message || '').trim().slice(0, 200))
63
+ : failed('worktree-list-failed', String(res.error.message || '').trim().slice(0, 200));
64
+ } else if (!res || res.status !== 0) {
65
+ const stderr = String((res && res.stderr) || '').trim().slice(0, 200);
66
+ result = failed(/not a git repository/.test(stderr) ? 'not-a-git-repository' : 'worktree-list-failed', stderr);
67
+ } else {
68
+ result = fromPorcelain(String(res.stdout || ''), realCwd, fsImpl);
69
+ }
70
+ cache.set(realCwd, result);
71
+ return result;
72
+ }
73
+
74
+ function fromPorcelain(stdout: string, realCwd: string, fsImpl: any): any {
75
+ const entries: Array<{ real: string | null; bare: boolean; sandbox: boolean }> = [];
76
+ for (const block of stdout.split(/\n\s*\n/)) {
77
+ const lines = block.split('\n').filter(Boolean);
78
+ if (lines.length === 0 || !lines[0].startsWith('worktree ')) continue;
79
+ const real = realOrNull(lines[0].slice('worktree '.length), fsImpl);
80
+ entries.push({
81
+ real,
82
+ bare: lines.includes('bare'),
83
+ sandbox: real !== null && fsImpl.existsSync(path.join(real, BASE_FILE)),
84
+ });
85
+ }
86
+
87
+ let current: any = null;
88
+ for (const e of entries) {
89
+ if (e.real === null || e.bare) continue;
90
+ if ((realCwd === e.real || realCwd.startsWith(e.real + path.sep)) && (current === null || e.real.length > current.real.length)) {
91
+ current = e;
92
+ }
93
+ }
94
+ const sandbox = Boolean(current && current.sandbox);
95
+ const first = entries[0];
96
+ let main: string | null = null;
97
+ let mainReason: string | null = null;
98
+ if (sandbox) mainReason = 'sandbox';
99
+ else if (first && first.bare) mainReason = 'bare';
100
+ else if (!first || first.real === null) mainReason = 'missing';
101
+ else main = first.real;
102
+
103
+ const others = sandbox ? [] : entries
104
+ .filter((e) => e.real !== null && !e.bare && !e.sandbox && e !== current)
105
+ .map((e) => e.real as string);
106
+ const currentPath = current ? current.real : null;
107
+ return {
108
+ ok: true,
109
+ reason: null,
110
+ detail: null,
111
+ current: currentPath,
112
+ main,
113
+ mainReason,
114
+ others: [...new Set(others)],
115
+ isLinked: currentPath !== null && main !== null && currentPath !== main,
116
+ sandbox,
117
+ };
118
+ }
119
+
120
+ /** Forgets every memoized `listCheckouts` result (tests that rebuild a repository in place). */
121
+ function clearCheckoutCache(): void {
122
+ cache.clear();
123
+ }
124
+
125
+ /**
126
+ * A state file in the checkout the verb resolves to, else in exactly one other checkout.
127
+ *
128
+ * The current checkout is read first and, when the file is there, no `git` call is made. Two or
129
+ * more other checkouts holding it is `ambiguous` rather than a pick: which one is meant is not
130
+ * something a path can decide. Writes nothing.
131
+ * @param {Object} options
132
+ * @param {string} options.stateRoot the root the verb resolves today
133
+ * @param {string|Array<string>|Function} options.relPath a path under each root, candidate paths taken in
134
+ * order (the first that exists in a root is that root's file), or `(root, isCurrent) => string|null`
135
+ * for a store whose location differs per checkout (null skips that root)
136
+ * @param {Function} [options.exec]
137
+ * @param {Object} [options.fsImpl]
138
+ * @returns {{status: 'found'|'missing'|'ambiguous', file: string|null, root: string|null,
139
+ * origin: 'current'|'other'|null, candidates: Array<string>}}
140
+ */
141
+ function findStateFile({ stateRoot, relPath, exec = spawnSync, fsImpl = fs }: { stateRoot: string; relPath: any; exec?: any; fsImpl?: any }): any {
142
+ const at = (root: string, isCurrent: boolean): string | null => {
143
+ const rel = typeof relPath === 'function' ? relPath(root, isCurrent) : relPath;
144
+ for (const candidate of Array.isArray(rel) ? rel : [rel]) {
145
+ if (typeof candidate !== 'string' || candidate === '') continue;
146
+ const file = path.join(root, candidate);
147
+ if (fsImpl.existsSync(file)) return file;
148
+ }
149
+ return null;
150
+ };
151
+ const missing = { status: 'missing', file: null, root: null, origin: null, candidates: [] as string[] };
152
+
153
+ const here = at(stateRoot, true);
154
+ if (here) return { status: 'found', file: here, root: stateRoot, origin: 'current', candidates: [] };
155
+
156
+ const checkouts = listCheckouts({ cwd: stateRoot, exec, fsImpl });
157
+ if (!checkouts.ok || checkouts.sandbox) return missing;
158
+ const matches: Array<{ file: string; root: string }> = [];
159
+ for (const root of checkouts.others as string[]) {
160
+ const file = at(root, false);
161
+ if (file) matches.push({ file, root });
162
+ }
163
+ if (matches.length === 0) return missing;
164
+ if (matches.length > 1) {
165
+ return { status: 'ambiguous', file: null, root: null, origin: null, candidates: matches.map((m: { file: string }) => m.file) };
166
+ }
167
+ return { status: 'found', file: matches[0].file, root: matches[0].root, origin: 'other', candidates: [] };
168
+ }
169
+
170
+ module.exports = { listCheckouts, findStateFile, clearCheckoutCache };