@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,189 @@
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 { resolveRoots } = require('../storage/roots.ts');
8
+
9
+ const SETTING_FILE = 'decision-model.json';
10
+ const PROJECT_NOTE = 'a project cannot enable the decision model or choose its endpoint';
11
+ const KNOWN_KEYS = new Set(['schema', 'enabled', 'provider', 'endpoint', 'model']);
12
+
13
+ function isObject(value: any): boolean {
14
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
15
+ }
16
+
17
+ function offResult(reason: string, decidedBy: any, notes: string[] = [], setting: any = {}): any {
18
+ return {
19
+ state: 'off', reason,
20
+ provider: setting.provider ?? null,
21
+ endpoint: setting.endpoint ?? null,
22
+ model: setting.model ?? null,
23
+ decidedBy,
24
+ modelFrom: setting.model ? setting.modelFrom ?? null : null,
25
+ notes,
26
+ };
27
+ }
28
+
29
+ function readJson(file: string | null, fsImpl: any): { exists: boolean; value?: any; error?: any } {
30
+ if (!file) return { exists: false };
31
+ try {
32
+ return { exists: true, value: JSON.parse(fsImpl.readFileSync(file, 'utf8')) };
33
+ } catch (error: any) {
34
+ if (error?.code === 'ENOENT') return { exists: false };
35
+ return { exists: true, error };
36
+ }
37
+ }
38
+
39
+ function parseUserSetting(value: any, adviceRegistry: any): any {
40
+ if (!isObject(value)) return { error: 'user setting must be a JSON object' };
41
+ if (value.schema !== 1) return { error: 'user setting schema must be 1' };
42
+ if (typeof value.enabled !== 'boolean') return { error: 'user setting enabled must be a boolean' };
43
+
44
+ const providers = Array.isArray(adviceRegistry?.providers) ? adviceRegistry.providers : [];
45
+ const providerId = value.provider === undefined ? 'ollama' : value.provider;
46
+ const provider = providers.find((item: any) => item?.id === providerId);
47
+ if (typeof providerId !== 'string' || !provider) return { error: `user setting provider '${providerId}' is not in advice.json providers` };
48
+
49
+ let endpoint = provider.defaultEndpoint;
50
+ if (value.endpoint !== undefined) {
51
+ if (typeof value.endpoint !== 'string' || !value.endpoint.trim()) return { error: 'user setting endpoint must be a URL string' };
52
+ try { new URL(value.endpoint); } catch { return { error: 'user setting endpoint must be a URL string' }; }
53
+ endpoint = value.endpoint;
54
+ }
55
+ if (typeof endpoint !== 'string' || !endpoint) return { error: `providers.${providerId}.defaultEndpoint is missing` };
56
+
57
+ if (value.model !== undefined && (typeof value.model !== 'string' || !value.model.trim())) {
58
+ return { error: 'user setting model must be a non-empty string' };
59
+ }
60
+ if (value.enabled && (typeof value.model !== 'string' || !value.model.trim())) {
61
+ return { error: 'user setting model is required when enabled is true' };
62
+ }
63
+
64
+ const notes = Object.keys(value)
65
+ .filter((key) => !KNOWN_KEYS.has(key))
66
+ .map((key) => `unknown user key '${key}' ignored`);
67
+ return {
68
+ value: { enabled: value.enabled, provider: providerId, endpoint, model: value.model ?? null },
69
+ notes,
70
+ };
71
+ }
72
+
73
+ function parseProjectSetting(value: any): any {
74
+ if (!isObject(value)) return { error: 'project setting must be a JSON object' };
75
+ if (value.schema !== 1) return { error: 'project setting schema must be 1' };
76
+ if (value.enabled !== undefined && typeof value.enabled !== 'boolean') return { error: 'project setting enabled must be a boolean' };
77
+ if (value.model !== undefined && (typeof value.model !== 'string' || !value.model.trim())) return { error: 'project setting model must be a non-empty string' };
78
+
79
+ const notes: string[] = [];
80
+ if (value.enabled === true) notes.push(`project key 'enabled' ignored: ${PROJECT_NOTE}`);
81
+ for (const key of ['endpoint', 'provider']) {
82
+ if (Object.hasOwn(value, key)) notes.push(`project key '${key}' ignored: ${PROJECT_NOTE}`);
83
+ }
84
+ for (const key of Object.keys(value)) {
85
+ if (!['schema', 'enabled', 'model', 'endpoint', 'provider'].includes(key)) notes.push(`unknown project key '${key}' ignored`);
86
+ }
87
+ return { value: { disabled: value.enabled === false, model: value.model ?? null }, notes };
88
+ }
89
+
90
+ function resolveSetting({
91
+ adviceRegistry,
92
+ roots,
93
+ storageRegistry,
94
+ projectRoot,
95
+ global = false,
96
+ env = process.env,
97
+ fsImpl = fs,
98
+ }: any = {}): any {
99
+ const notes: string[] = [];
100
+ let resolvedRoots = roots;
101
+ try {
102
+ if (!resolvedRoots) resolvedRoots = resolveRoots({ registry: storageRegistry, projectRoot, global, env });
103
+ } catch (error: any) {
104
+ return offResult('config-invalid', { user: null, project: null }, [`storage roots could not be resolved: ${error.message}`]);
105
+ }
106
+ const userFile = resolvedRoots?.['config-home'] ? path.join(resolvedRoots['config-home'], SETTING_FILE) : null;
107
+ const projectFile = resolvedRoots?.['project-doflow'] ? path.join(resolvedRoots['project-doflow'], SETTING_FILE) : null;
108
+ const decidedBy = { user: null, project: null };
109
+ const userRead = readJson(userFile, fsImpl);
110
+ if (!userRead.exists) return offResult('not-configured', decidedBy, notes);
111
+ if (userRead.error) {
112
+ return offResult('config-invalid', { user: userFile, project: null }, [`user setting at ${userFile} could not be read: ${userRead.error.message}`]);
113
+ }
114
+ const user = parseUserSetting(userRead.value, adviceRegistry);
115
+ if (user.error) return offResult('config-invalid', { user: userFile, project: null }, [`${user.error} (${userFile})`]);
116
+ decidedBy.user = userFile;
117
+ notes.push(...user.notes);
118
+
119
+ const base = { ...user.value, modelFrom: user.value.model ? 'user' : null };
120
+ if (!user.value.enabled) return offResult('user-disabled', decidedBy, notes, base);
121
+
122
+ const projectRead = readJson(projectFile, fsImpl);
123
+ if (projectRead.exists) {
124
+ if (projectRead.error) {
125
+ notes.push(`project setting at ${projectFile} ignored: ${projectRead.error.message}`);
126
+ } else {
127
+ const project = parseProjectSetting(projectRead.value);
128
+ if (project.error) {
129
+ notes.push(`project setting at ${projectFile} ignored: ${project.error}`);
130
+ } else {
131
+ notes.push(...project.notes);
132
+ if (project.value.disabled) {
133
+ decidedBy.project = projectFile;
134
+ return offResult('project-disabled', decidedBy, notes, base);
135
+ }
136
+ if (project.value.model) {
137
+ decidedBy.project = projectFile;
138
+ return {
139
+ state: 'on', reason: null, provider: base.provider, endpoint: base.endpoint,
140
+ model: project.value.model, decidedBy, modelFrom: 'project', notes,
141
+ };
142
+ }
143
+ }
144
+ }
145
+ }
146
+ return {
147
+ state: 'on', reason: null, provider: base.provider, endpoint: base.endpoint,
148
+ model: base.model, decidedBy, modelFrom: base.modelFrom, notes,
149
+ };
150
+ }
151
+
152
+ function writeSetting({ file, setting, level = 'user', force = false, fsImpl = fs }: any = {}): any {
153
+ if (typeof file !== 'string' || !file || !isObject(setting)) {
154
+ return { ok: false, reason: 'config-invalid', detail: 'file and setting object are required' };
155
+ }
156
+ const directory = path.dirname(file);
157
+ let existing: any = {};
158
+ try {
159
+ if (fsImpl.existsSync(file)) {
160
+ try {
161
+ const parsed = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
162
+ if (!isObject(parsed)) throw new Error('setting must be a JSON object');
163
+ existing = parsed;
164
+ } catch (error: any) {
165
+ if (!force) return { ok: false, reason: 'config-invalid', detail: `${file}: ${error.message}` };
166
+ }
167
+ }
168
+
169
+ const preserved = Object.fromEntries(Object.entries(existing).filter(([key]) => !KNOWN_KEYS.has(key)));
170
+ const retainedUserFields = level === 'user' && setting.enabled === false
171
+ ? Object.fromEntries(['provider', 'endpoint', 'model'].filter((key) => existing[key] !== undefined).map((key) => [key, existing[key]]))
172
+ : {};
173
+ const output = { ...preserved, ...retainedUserFields, ...setting };
174
+ fsImpl.mkdirSync(directory, { recursive: true });
175
+ const suffix = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
176
+ const temporary = path.join(directory, `.${path.basename(file)}.${suffix}.tmp`);
177
+ try {
178
+ fsImpl.writeFileSync(temporary, `${JSON.stringify(output, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
179
+ fsImpl.renameSync(temporary, file);
180
+ } finally {
181
+ try { if (fsImpl.existsSync(temporary)) fsImpl.unlinkSync(temporary); } catch { /* keep the primary result */ }
182
+ }
183
+ return { ok: true, file, setting: output };
184
+ } catch (error: any) {
185
+ return { ok: false, reason: 'config-invalid', detail: `${file}: ${error.message}` };
186
+ }
187
+ }
188
+
189
+ module.exports = { resolveSetting, writeSetting };
@@ -0,0 +1,169 @@
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 { resolveRoots } = require('../storage/roots.ts');
7
+ const { loadStorageRegistry } = require('../storage/registry.ts');
8
+ const { loadAdviceRegistry } = require('./registry.ts');
9
+ const { resolveSetting } = require('./config.ts');
10
+ const { createAdviceClient } = require('./client.ts');
11
+ const { readWindow } = require('./record.ts');
12
+ const { REPO_ROOT } = require('../../helper/repo-root.ts');
13
+
14
+ const WINDOW_DAYS = 90;
15
+ const DONE_PHRASES: Record<string, string> = Object.freeze({
16
+ 'classify-verdict': 'accepted class',
17
+ 'brief-record': 'tier the brief named',
18
+ caller: 'value the caller recorded',
19
+ });
20
+
21
+ function selectedStore(roots: any) {
22
+ if (roots?.['project-state']) return { id: 'advice-picks-project', root: roots['project-state'] };
23
+ return { id: 'advice-picks-user', root: roots?.['user-state'] || null };
24
+ }
25
+
26
+ function latestCall(records: any) {
27
+ const calls = records.filter(({ record }: any) => record.type === 'pick' || record.type === 'miss');
28
+ if (!calls.length) return null;
29
+ const record = calls.reduce((latest: any, entry: any) => !latest || String(entry.record.at).localeCompare(String(latest.record.at)) >= 0
30
+ ? entry : latest).record;
31
+ return record.type === 'pick'
32
+ ? { at: record.at, status: 'answered', reason: null }
33
+ : { at: record.at, status: 'unavailable', reason: record.reason || null };
34
+ }
35
+
36
+ function agreementRows(registry: any, records: any) {
37
+ const picks = records.filter(({ record }: any) => record.type === 'pick');
38
+ const outcomes = new Map();
39
+ for (const { record } of records) {
40
+ if (record.type !== 'outcome' || typeof record.pickId !== 'string') continue;
41
+ const current = outcomes.get(record.pickId);
42
+ if (!current || String(record.at).localeCompare(String(current.at)) >= 0) outcomes.set(record.pickId, record);
43
+ }
44
+ const misses = records.filter(({ record }: any) => record.type === 'miss');
45
+ const ids = [...(registry.kinds || []).map((kind: any) => kind.id)];
46
+ if (picks.some(({ record }: any) => record.kind === 'adhoc')) ids.push('adhoc');
47
+ return ids.map((kind) => {
48
+ const kindPicks = picks.filter(({ record }: any) => record.kind === kind);
49
+ const completed = kindPicks.map(({ record }: any) => ({ pick: record.pick, outcome: outcomes.get(record.pickId) }))
50
+ .filter(({ outcome }: any) => outcome);
51
+ const sources = [...new Set(completed.map(({ outcome }: any) => outcome.source).filter((source: any) => DONE_PHRASES[source]))];
52
+ return {
53
+ kind,
54
+ picks: kindPicks.length,
55
+ withOutcome: completed.length,
56
+ matched: completed.filter(({ pick, outcome }: any) => outcome.actual === pick).length,
57
+ unavailable: misses.filter(({ record }: any) => record.kind === kind).length,
58
+ outcomeSources: sources,
59
+ };
60
+ });
61
+ }
62
+
63
+ function offReason(setting: any) {
64
+ if (setting.reason === 'not-configured') return 'not configured';
65
+ if (setting.reason === 'config-invalid') return `unreadable setting in ${setting.decidedBy?.user || setting.decidedBy?.project || 'decision-model.json'}`;
66
+ if (setting.reason === 'project-disabled') return `switched off in ${setting.decidedBy?.project}`;
67
+ return `switched off in ${setting.decidedBy?.user || 'decision-model.json'}`;
68
+ }
69
+
70
+ async function buildAdviceDoctor({ repoRoot = REPO_ROOT, projectRoot = process.cwd(), env = process.env, fsImpl = fs,
71
+ adviceRegistry: suppliedRegistry, storageRegistry: suppliedStorage, client: suppliedClient, lookup, httpModule, httpsModule,
72
+ probeTimeoutMs, doctorBudgetMs, now = () => new Date() }: any = {}) {
73
+ try {
74
+ const root = repoRoot;
75
+ const loaded = suppliedRegistry ? { ok: true, registry: suppliedRegistry } : loadAdviceRegistry({ repoRoot: root, fsImpl });
76
+ const registry = loaded.registry;
77
+ const storage = suppliedStorage || loadStorageRegistry({ repoRoot: root, fsImpl });
78
+ const roots = resolveRoots({ registry: storage, projectRoot, env });
79
+ const setting = loaded.ok ? resolveSetting({ adviceRegistry: registry, roots, fsImpl }) : {
80
+ state: 'off', reason: 'config-invalid', decidedBy: { user: null, project: null }, modelFrom: null, notes: [loaded.detail],
81
+ };
82
+ const store = selectedStore(roots);
83
+ const selectedRoots = store.id === 'advice-picks-project'
84
+ ? { 'project-state': store.root }
85
+ : { 'user-state': store.root };
86
+ const window = readWindow({ roots: selectedRoots, now, fsImpl, maxAgeDays: WINDOW_DAYS });
87
+ const advice = {
88
+ windowDays: WINDOW_DAYS,
89
+ store: store.id,
90
+ kinds: agreementRows(registry || { kinds: [] }, window.records),
91
+ unreadableLines: window.unreadable,
92
+ };
93
+ let decisionModel;
94
+ let inspection = null;
95
+ if (setting.state === 'on' && loaded.ok) {
96
+ const effectiveRegistry = { ...registry, probeTimeoutMs: probeTimeoutMs ?? registry.probeTimeoutMs,
97
+ doctorBudgetMs: doctorBudgetMs ?? registry.doctorBudgetMs };
98
+ const client = suppliedClient || createAdviceClient({ adviceRegistry: effectiveRegistry, roots, fsImpl, lookup, httpModule, httpsModule });
99
+ inspection = await client.inspect({ endpoint: setting.endpoint, model: setting.model, provider: setting.provider,
100
+ file: setting.decidedBy?.user, minVersion: registry.providers.find((p: any) => p.id === setting.provider)?.minVersion,
101
+ timeoutMs: probeTimeoutMs ?? registry.probeTimeoutMs });
102
+ const provider = registry.providers.find((p: any) => p.id === setting.provider) || {};
103
+ decisionModel = {
104
+ setting: { state: 'on', reason: null, decidedBy: setting.decidedBy, modelFrom: setting.modelFrom, notes: setting.notes || [] },
105
+ endpoint: { url: setting.endpoint, state: inspection.endpoint.state, version: inspection.endpoint.version,
106
+ minVersion: provider.minVersion || null, versionOk: inspection.endpoint.versionOk, message: inspection.endpoint.message },
107
+ model: { name: setting.model, installed: inspection.model.installed, decisionCapable: inspection.model.decisionCapable },
108
+ decisionModels: inspection.models.filter((model: any) => model.capabilities?.includes(provider.capability)).map((model: any) => model.name),
109
+ lastCall: latestCall(window.records),
110
+ };
111
+ } else {
112
+ decisionModel = {
113
+ setting: { state: 'off', reason: setting.reason, decidedBy: setting.decidedBy || { user: null, project: null }, modelFrom: setting.modelFrom || null, notes: setting.notes || [] },
114
+ endpoint: { url: setting.endpoint || null, state: 'not-checked', version: null, minVersion: registry?.providers?.[0]?.minVersion || null, versionOk: false, message: null },
115
+ model: { name: setting.model || null, installed: false, decisionCapable: false },
116
+ decisionModels: [], lastCall: latestCall(window.records),
117
+ };
118
+ }
119
+ return { decisionModel, advice, _inspection: inspection, _offReason: setting.state === 'off' ? offReason(setting) : null };
120
+ } catch (error: any) {
121
+ return { decisionModel: { setting: { state: 'off', reason: 'config-invalid', decidedBy: { user: null, project: null }, modelFrom: null, notes: [error.message] },
122
+ endpoint: { url: null, state: 'not-checked', version: null, minVersion: null, versionOk: false, message: null },
123
+ model: { name: null, installed: false, decisionCapable: false }, decisionModels: [], lastCall: null },
124
+ advice: { windowDays: WINDOW_DAYS, store: 'advice-picks-user', kinds: [], unreadableLines: 0 }, _inspection: null, _offReason: 'unreadable setting in decision-model.json' };
125
+ }
126
+ }
127
+
128
+ function printAdviceDoctor(report: any) {
129
+ const decision = report.decisionModel;
130
+ const inspection = report._inspection;
131
+ console.log('\n[Decision Model] — advisory, never blocking');
132
+ if (decision.setting.state === 'off') {
133
+ console.log(` off (${report._offReason}). To turn it on: doflow advise --action configure --model <a decision-capable model>`);
134
+ console.log(' or write ~/.config/doflow/decision-model.json:');
135
+ console.log(' {"schema": 1, "enabled": true, "endpoint": "http://127.0.0.1:11434", "model": "<a decision-capable model>"}');
136
+ } else {
137
+ const setting = decision.setting;
138
+ console.log(` setting on (${setting.decidedBy.user || 'user setting'}${setting.modelFrom === 'project' ? `; model from ${setting.decidedBy.project}` : ''})`);
139
+ const endpoint = decision.endpoint;
140
+ if (endpoint.state === 'refused') console.log(` endpoint ${endpoint.url} ✗ REFUSED ${endpoint.message || inspection?.endpoint.message || 'endpoint refused'}`);
141
+ else if (endpoint.state === 'unreachable') console.log(` endpoint ${endpoint.url} ○ UNREACHABLE start Ollama yourself; DoFlow does not start it`);
142
+ else if (endpoint.version && !endpoint.versionOk) console.log(` endpoint ${endpoint.url} ▲ Ollama ${endpoint.version} is older than ${endpoint.minVersion}`);
143
+ else if (endpoint.state === 'reached') console.log(` endpoint ${endpoint.url} ✓ REACHED Ollama ${endpoint.version || 'version unknown'}`);
144
+ else console.log(` endpoint ${endpoint.url} ○ UNREACHABLE start Ollama yourself; DoFlow does not start it`);
145
+ const model = decision.model;
146
+ if (!model.installed) console.log(` model ${model.name} ✗ not installed; run 'ollama pull ${model.name}' yourself; DoFlow does not download models`);
147
+ else if (!model.decisionCapable) console.log(` model ${model.name} ✗ installed, not decision-capable`);
148
+ else console.log(` model ${model.name} ✓ installed, decision-capable`);
149
+ console.log(` decision-capable here: ${decision.decisionModels.length ? decision.decisionModels.join(', ') : 'none'}`);
150
+ for (const model of inspection?.models || []) {
151
+ if (!Array.isArray(model.capabilities)) console.log(` ${model.name} capability unknown`);
152
+ }
153
+ if (!decision.lastCall) console.log(' last call none recorded');
154
+ else if (decision.lastCall.status === 'unavailable') console.log(` last call unavailable (${decision.lastCall.reason}) ${decision.lastCall.at}`);
155
+ else console.log(` last call answered ${decision.lastCall.at}`);
156
+ }
157
+
158
+ console.log('\n[Advice Agreement] — picks kept for 90 days in this project; agreement with what was done, not correctness');
159
+ if (!report.advice.kinds.some((row: any) => row.picks) && !report.advice.unreadableLines) console.log(' No picks recorded here.');
160
+ for (const row of report.advice.kinds) {
161
+ if (!row.picks) { console.log(` ${row.kind.padEnd(17)}no picks recorded`); continue; }
162
+ const phrases = row.outcomeSources.map((source: any) => DONE_PHRASES[source]).filter(Boolean);
163
+ const done = phrases.length ? ` (done = ${phrases.join(' or ')})` : '';
164
+ console.log(` ${row.kind.padEnd(17)}${row.picks} picks, ${row.withOutcome} with a known outcome, matched ${row.matched} of ${row.withOutcome}${done}; ${row.unavailable} calls unavailable`);
165
+ }
166
+ if (report.advice.unreadableLines) console.log(` ${report.advice.unreadableLines} line(s) could not be read`);
167
+ }
168
+
169
+ module.exports = { buildAdviceDoctor, printAdviceDoctor };
@@ -0,0 +1,134 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const dns = require('node:dns');
6
+
7
+ function displayEndpoint(value: any): string {
8
+ return typeof value === 'string' ? value : String(value ?? '');
9
+ }
10
+
11
+ function ipv4Octets(address: any): number[] | null {
12
+ if (typeof address !== 'string' || !/^\d{1,3}(?:\.\d{1,3}){3}$/.test(address)) return null;
13
+ const octets = address.split('.').map(Number);
14
+ return octets.every((octet) => octet >= 0 && octet <= 255) ? octets : null;
15
+ }
16
+
17
+ function ipv6Words(address: any): number[] | null {
18
+ if (typeof address !== 'string' || !address.includes(':')) return null;
19
+ let value = address.toLowerCase();
20
+ if (value.includes('.')) {
21
+ const lastColon = value.lastIndexOf(':');
22
+ const octets = ipv4Octets(value.slice(lastColon + 1));
23
+ if (!octets) return null;
24
+ const high = ((octets[0] << 8) | octets[1]).toString(16);
25
+ const low = ((octets[2] << 8) | octets[3]).toString(16);
26
+ value = `${value.slice(0, lastColon + 1)}${high}:${low}`;
27
+ }
28
+ const doubleColon = value.indexOf('::');
29
+ if (doubleColon !== -1 && value.indexOf('::', doubleColon + 2) !== -1) return null;
30
+ let words: string[];
31
+ if (doubleColon === -1) {
32
+ words = value.split(':');
33
+ if (words.length !== 8) return null;
34
+ } else {
35
+ const left = value.slice(0, doubleColon) ? value.slice(0, doubleColon).split(':') : [];
36
+ const right = value.slice(doubleColon + 2) ? value.slice(doubleColon + 2).split(':') : [];
37
+ const zeros = 8 - left.length - right.length;
38
+ if (zeros < 1) return null;
39
+ words = [...left, ...Array(zeros).fill('0'), ...right];
40
+ }
41
+ if (words.length !== 8 || words.some((word) => !/^[0-9a-f]{1,4}$/.test(word))) return null;
42
+ return words.map((word) => Number.parseInt(word, 16));
43
+ }
44
+
45
+ function isLoopback(address: any): boolean {
46
+ const octets = ipv4Octets(address);
47
+ if (octets) return octets[0] === 127;
48
+ const words = ipv6Words(address);
49
+ if (!words) return false;
50
+ if (words.slice(0, 7).every((word) => word === 0) && words[7] === 1) return true;
51
+ const mapped = words.slice(0, 5).every((word) => word === 0) && words[5] === 0xffff;
52
+ if (!mapped) return false;
53
+ return (words[6] >> 8) === 127;
54
+ }
55
+
56
+ function literalFamily(address: any): 4 | 6 {
57
+ return ipv4Octets(address) ? 4 : 6;
58
+ }
59
+
60
+ function isLiteralIp(host: any): boolean {
61
+ return Boolean(ipv4Octets(host) || ipv6Words(host));
62
+ }
63
+
64
+ function refuse(value: any, file?: string): { ok: false; message: string } {
65
+ return {
66
+ ok: false,
67
+ message: `decision model: endpoint ${value} refused: only loopback addresses are allowed (127.0.0.0/8, ::1); set in ${file || 'provider default'}`,
68
+ };
69
+ }
70
+
71
+ function lookupAll(host: string, lookup: any, timeoutMs?: number): Promise<any> {
72
+ return new Promise((resolve, reject) => {
73
+ let settled = false;
74
+ const finish = (error: any, addresses?: any) => {
75
+ if (settled) return;
76
+ settled = true;
77
+ clearTimeout(timer);
78
+ if (error) reject(error);
79
+ else resolve(addresses);
80
+ };
81
+ const timer = Number.isFinite(timeoutMs) ? setTimeout(() => {
82
+ finish(Object.assign(new Error('decision model DNS validation timed out'), { code: 'ADVICE_TIMEOUT' }));
83
+ }, Math.max(0, timeoutMs as number)) : undefined;
84
+ try {
85
+ const returned = lookup(host, { all: true, verbatim: true }, finish);
86
+ if (returned && typeof returned.then === 'function') returned.then((addresses: any) => finish(null, addresses), finish);
87
+ else if (Array.isArray(returned)) finish(null, returned);
88
+ } catch (error) {
89
+ finish(error);
90
+ }
91
+ });
92
+ }
93
+
94
+ function pinnedLookup(address: string, family: number) {
95
+ return (hostname: string, options: any, callback?: any) => {
96
+ if (typeof options === 'function') {
97
+ callback = options;
98
+ options = {};
99
+ }
100
+ if (typeof callback !== 'function') throw new TypeError('lookup callback is required');
101
+ if (options?.all) callback(null, [{ address, family }]);
102
+ else callback(null, address, family);
103
+ };
104
+ }
105
+
106
+ async function checkEndpoint(value: any, { lookup = dns.lookup, file, timeoutMs }: any = {}): Promise<any> {
107
+ const display = displayEndpoint(value);
108
+ let url: any;
109
+ try { url = new URL(display); } catch { return refuse(display, file); }
110
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) return refuse(display, file);
111
+
112
+ const host = url.hostname.replace(/^\[|\]$/g, '');
113
+ if (isLiteralIp(host)) {
114
+ if (!isLoopback(host)) return refuse(display, file);
115
+ const family = literalFamily(host);
116
+ return { ok: true, address: host, family, pinnedLookup: pinnedLookup(host, family) };
117
+ }
118
+
119
+ let addresses: any;
120
+ try { addresses = await lookupAll(host, lookup, timeoutMs); } catch (error: any) {
121
+ if (error?.code === 'ADVICE_TIMEOUT') return { ok: false, reason: 'timeout', message: error.message };
122
+ return refuse(display, file);
123
+ }
124
+ if (!Array.isArray(addresses) || addresses.length === 0) return refuse(display, file);
125
+ const checked: any[] = addresses.map((entry: any) => {
126
+ if (!entry || typeof entry.address !== 'string') return null;
127
+ return { address: entry.address, family: literalFamily(entry.address) };
128
+ });
129
+ if (checked.some((entry: any) => !entry || !isLoopback(entry.address))) return refuse(display, file);
130
+ const first = checked[0];
131
+ return { ok: true, address: first.address, family: first.family, pinnedLookup: pinnedLookup(first.address, first.family) };
132
+ }
133
+
134
+ module.exports = { checkEndpoint };
@@ -0,0 +1,154 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ function isObject(value: any): boolean {
6
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
7
+ }
8
+
9
+ function invalid(detail: string, reason: string = 'config-invalid'): { ok: false; reason: string; detail: string } {
10
+ return { ok: false, reason, detail };
11
+ }
12
+
13
+ function optionCount(question: any): number {
14
+ if (question.type === 'score') return Array.isArray(question.criteria) ? question.criteria.length : 0;
15
+ return question.type === 'choice' ? Object.keys(question.options || {}).length : 2;
16
+ }
17
+
18
+ function checkOptionRange(count: number, limits: any, owner: string): { ok: false; reason: string; detail: string } | null {
19
+ if (!Number.isInteger(limits?.minOptions) || !Number.isInteger(limits?.maxOptions)) {
20
+ return invalid(`${owner} cannot be built: invalid limits in core/registry/advice.json`);
21
+ }
22
+ if (count < limits.minOptions) {
23
+ return invalid(`${owner} has ${count} options; minimum is ${limits.minOptions} (core/registry/advice.json limits.minOptions)`);
24
+ }
25
+ if (count > limits.maxOptions) {
26
+ return invalid(`${owner} has ${count} options; maximum is ${limits.maxOptions} (core/registry/advice.json limits.maxOptions)`);
27
+ }
28
+ return null;
29
+ }
30
+
31
+ function criteriaFrom(source: string, sources: any): Record<string, any> | null {
32
+ let entries: [string, any][];
33
+ if (source === 'workflows.classes') {
34
+ const classes = sources?.workflows?.classes;
35
+ if (!isObject(classes)) return null;
36
+ entries = Object.entries(classes).map(([id, item]: [string, any]) => [id, item?.description]);
37
+ } else if (source === 'verification.riskLevels') {
38
+ const levels = sources?.verification?.riskLevels;
39
+ if (!isObject(levels)) return null;
40
+ entries = Object.entries(levels).map(([id, item]: [string, any]) => [id, item?.description]);
41
+ } else {
42
+ return null;
43
+ }
44
+ if (entries.some(([, description]) => typeof description !== 'string' || !description.trim())) return null;
45
+ return Object.fromEntries(entries);
46
+ }
47
+
48
+ function materializeKind(kindId: string, { adviceRegistry, sources = {}, server }: any = {}): any {
49
+ const kinds = adviceRegistry?.kinds;
50
+ if (!Array.isArray(kinds)) return invalid('kinds are missing from core/registry/advice.json');
51
+ const kind = kinds.find((entry: any) => entry?.id === kindId);
52
+ if (!kind) return invalid(`unknown kind '${kindId}'`, 'usage-error');
53
+
54
+ const serverSource = kind.criteriaFrom === 'mcp.servers[].trigger';
55
+ if (server !== undefined && !serverSource) return invalid('--server is valid only for mcp-need', 'usage-error');
56
+
57
+ let question: any;
58
+ if (serverSource) {
59
+ if (typeof server !== 'string' || !server.trim()) return invalid('--server is required for mcp-need', 'usage-error');
60
+ const servers = sources?.mcp?.servers;
61
+ if (!Array.isArray(servers)) return invalid('mcp.servers is missing from core/registry/mcp.json');
62
+ const match = servers.find((entry: any) => entry?.id === server);
63
+ if (!match) return invalid(`server '${server}' is not present in core/registry/mcp.json`, 'usage-error');
64
+ if (typeof match.trigger !== 'string' || !match.trigger.trim()) return invalid(`mcp.servers.${server}.trigger is missing`);
65
+ question = { name: kind.id, type: 'yesno', instructions: match.trigger, no: kind.no, yes: kind.yes };
66
+ } else if (kind.type === 'choice') {
67
+ const options = Object.hasOwn(kind, 'criteria') ? kind.criteria : criteriaFrom(kind.criteriaFrom, sources);
68
+ if (!isObject(options) || Object.values(options).some((value) => typeof value !== 'string' || !value.trim())) {
69
+ return invalid(`criteria source for kind '${kind.id}' is unavailable or invalid`);
70
+ }
71
+ question = { name: kind.id, type: 'choice', instructions: kind.instructions, options };
72
+ } else if (kind.type === 'yesno') {
73
+ question = { name: kind.id, type: 'yesno', instructions: kind.instructions, no: kind.no, yes: kind.yes };
74
+ } else {
75
+ return invalid(`kind '${kind.id}' has an unsupported type`);
76
+ }
77
+
78
+ const rangeError = checkOptionRange(optionCount(question), adviceRegistry.limits, `kind '${kind.id}'`);
79
+ if (rangeError) return rangeError;
80
+ const validation = validateQuestionSet({ state: '', questions: [question] }, { adviceRegistry });
81
+ if (!validation.ok) return invalid(`kind '${kind.id}': ${validation.detail}`);
82
+ return { ok: true, kind: kind.id, question };
83
+ }
84
+
85
+ function validateQuestionSet(questionSet: any, { adviceRegistry }: any = {}): any {
86
+ const limits = adviceRegistry?.limits;
87
+ if (!isObject(limits)) return invalid('limits are missing from core/registry/advice.json');
88
+ if (!isObject(questionSet)) return invalid('question set must be an object (core/registry/advice.json limits.minQuestions)');
89
+ if (typeof questionSet.state !== 'string') return invalid('question set state must be a string (IC-005)');
90
+ if (!Array.isArray(questionSet.questions)) return invalid('questions must be an array (core/registry/advice.json limits.minQuestions)');
91
+ if (questionSet.questions.length < limits.minQuestions) {
92
+ return invalid(`question count is below ${limits.minQuestions} (core/registry/advice.json limits.minQuestions)`);
93
+ }
94
+ if (questionSet.questions.length > limits.maxQuestions) {
95
+ return invalid(`question count exceeds ${limits.maxQuestions} (core/registry/advice.json limits.maxQuestions)`);
96
+ }
97
+
98
+ let namePattern: RegExp;
99
+ try { namePattern = new RegExp(limits.questionName); } catch {
100
+ return invalid('questionName does not compile (core/registry/advice.json limits.questionName)');
101
+ }
102
+ const names = new Set();
103
+ for (const [index, question] of questionSet.questions.entries()) {
104
+ const prefix = `questions[${index}]`;
105
+ if (!isObject(question)) return invalid(`${prefix} must be an object (core/registry/advice.json limits.questionName)`);
106
+ if (typeof question.name !== 'string' || !namePattern.test(question.name)) {
107
+ return invalid(`${prefix}.name must match ${limits.questionName} (core/registry/advice.json limits.questionName)`);
108
+ }
109
+ if (names.has(question.name)) return invalid(`question names must be unique: '${question.name}' (core/registry/advice.json limits.questionName)`);
110
+ names.add(question.name);
111
+ if (typeof question.instructions !== 'string' || !question.instructions.trim()) {
112
+ return invalid(`${prefix}.instructions must be a non-empty string (core/registry/advice.json)`);
113
+ }
114
+ if (question.type === 'choice') {
115
+ if (!isObject(question.options)) return invalid(`${prefix}.options must be an object (core/registry/advice.json limits.minOptions)`);
116
+ const count = Object.keys(question.options).length;
117
+ const rangeError = checkOptionRange(count, limits, `${prefix}.options`);
118
+ if (rangeError) return rangeError;
119
+ if (Object.values(question.options).some((description) => typeof description !== 'string' || !description.trim())) {
120
+ return invalid(`${prefix}.options values must be non-empty descriptions (core/registry/advice.json limits.minOptions)`);
121
+ }
122
+ } else if (question.type === 'yesno') {
123
+ if (typeof question.no !== 'string' || !question.no.trim()) return invalid(`${prefix}.no is required (core/registry/advice.json limits.minOptions)`);
124
+ if (typeof question.yes !== 'string' || !question.yes.trim()) return invalid(`${prefix}.yes is required (core/registry/advice.json limits.minOptions)`);
125
+ const rangeError = checkOptionRange(2, limits, prefix);
126
+ if (rangeError) return rangeError;
127
+ } else if (question.type === 'score') {
128
+ if (!Array.isArray(question.criteria)) return invalid(`${prefix}.criteria must be an array of criterion strings (core/registry/advice.json limits.minOptions)`);
129
+ const rangeError = checkOptionRange(question.criteria.length, limits, `${prefix}.criteria`);
130
+ if (rangeError) return rangeError;
131
+ if (question.criteria.some((criterion: any) => typeof criterion !== 'string' || !criterion.trim())) {
132
+ return invalid(`${prefix}.criteria must be non-empty strings (core/registry/advice.json limits.minOptions)`);
133
+ }
134
+ if (new Set(question.criteria).size !== question.criteria.length) {
135
+ return invalid(`${prefix}.criteria must be distinct (core/registry/advice.json limits.minOptions)`);
136
+ }
137
+ } else {
138
+ return invalid(`${prefix}.type must be choice, yesno or score (core/registry/advice.json)`);
139
+ }
140
+ }
141
+ return { ok: true, questionSet };
142
+ }
143
+
144
+ function validateStaticAnswer(question: any, value: any): { ok: boolean; reason?: string; detail?: string } {
145
+ if (value === undefined || value === null) return { ok: true };
146
+ const validOptions = question?.type === 'choice'
147
+ ? Object.keys(question.options || {})
148
+ : question?.type === 'yesno' ? ['no', 'yes']
149
+ : question?.type === 'score' && Array.isArray(question.criteria) ? question.criteria : [];
150
+ if (validOptions.includes(value)) return { ok: true };
151
+ return invalid(`static answer must be one of: ${validOptions.join(', ')}`, 'usage-error');
152
+ }
153
+
154
+ module.exports = { materializeKind, validateQuestionSet, validateStaticAnswer };