@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,66 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ // ── One release version, projected into every distribution manifest ─────────────────────────────
6
+ //
7
+ // package.json is the single source of the release version. The three plugin manifests (Codex,
8
+ // Claude, Copilot) each repeat it, and v1.2.1 shipped with all three still reading 1.2.0 — the
9
+ // guard tests comparing them were failing correctly (architecture review R8). Hand-editing four
10
+ // files in step is exactly the drift class this repo removes elsewhere by generation, so the
11
+ // npm `version` lifecycle hook runs this script: `npm version <bump>` rewrites the manifests and
12
+ // stages them into the same release commit. The equality guards in test/install/doflow.test.js
13
+ // stay as the independent check that the projection actually happened.
14
+
15
+ const fs = require('node:fs');
16
+ const path = require('node:path');
17
+ const { REPO_ROOT } = require('../helper/repo-root.ts');
18
+
19
+ /** Every distribution manifest that repeats the release version, relative to the repo root. */
20
+ const PLUGIN_MANIFESTS = [
21
+ 'core/.codex-plugin/plugin.json',
22
+ 'core/.claude-plugin/plugin.json',
23
+ 'core/.plugin/plugin.json',
24
+ ];
25
+
26
+ /**
27
+ * Rewrites each plugin manifest's version to package.json's, preserving formatting by targeted
28
+ * string replacement rather than re-serializing the whole file.
29
+ * @param {Object} [options]
30
+ * @param {string} [options.repoRoot]
31
+ * @param {Object} [options.fsImpl]
32
+ * @returns {{version: string, updated: Array<string>, unchanged: Array<string>}}
33
+ */
34
+ function syncPluginVersions({ repoRoot = REPO_ROOT, fsImpl = fs }: { repoRoot?: string, fsImpl?: any } = {}): { version: string, updated: string[], unchanged: string[] } {
35
+ const pkg = JSON.parse(fsImpl.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
36
+ if (typeof pkg.version !== 'string' || pkg.version.trim() === '') {
37
+ throw new Error('package.json declares no version to project');
38
+ }
39
+
40
+ const updated: string[] = [];
41
+ const unchanged: string[] = [];
42
+ for (const rel of PLUGIN_MANIFESTS) {
43
+ const file = path.join(repoRoot, rel);
44
+ const text = fsImpl.readFileSync(file, 'utf8');
45
+ const manifest = JSON.parse(text);
46
+ if (manifest.version === pkg.version) {
47
+ unchanged.push(rel);
48
+ continue;
49
+ }
50
+ const next = text.replace(/"version":\s*"[^"]*"/, `"version": "${pkg.version}"`);
51
+ if (!/"version":\s*"/.test(text)) {
52
+ throw new Error(`${rel} has no version field to rewrite — the manifest shape changed; update PLUGIN_MANIFESTS`);
53
+ }
54
+ fsImpl.writeFileSync(file, next, 'utf8');
55
+ updated.push(rel);
56
+ }
57
+ return { version: pkg.version, updated, unchanged };
58
+ }
59
+
60
+ if (require.main === module) {
61
+ const result = syncPluginVersions();
62
+ for (const rel of result.updated) console.log(`synced ${rel} -> ${result.version}`);
63
+ if (result.updated.length === 0) console.log(`all plugin manifests already at ${result.version}`);
64
+ }
65
+
66
+ module.exports = { syncPluginVersions, PLUGIN_MANIFESTS };
@@ -0,0 +1,283 @@
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 { finishRuntime, usageError } = require('../cli-result.ts');
8
+ const { REPO_ROOT } = require('../../helper/repo-root.ts');
9
+ const { loadAdviceRegistry } = require('./registry.ts');
10
+ const { loadStorageRegistry } = require('../storage/registry.ts');
11
+ const { resolveRoots } = require('../storage/roots.ts');
12
+ const { resolveSetting, writeSetting } = require('./config.ts');
13
+ const { createAdviceClient } = require('./client.ts');
14
+ const { materializeKind, validateQuestionSet, validateStaticAnswer } = require('./kinds.ts');
15
+ const { findPick, appendOutcome } = require('./record.ts');
16
+ const { strengthWords } = require('./strength.ts');
17
+
18
+ function loadSources(repoRoot: any) {
19
+ const read = (name: any) => JSON.parse(fs.readFileSync(path.join(repoRoot, 'core/registry', name), 'utf8'));
20
+ return {
21
+ workflows: read('workflows.json'),
22
+ verification: read('verification.json'),
23
+ mcp: read('mcp.json'),
24
+ };
25
+ }
26
+
27
+ function loadRuntime({ projectRoot, global = false }: any = {}) {
28
+ const advice = loadAdviceRegistry({ repoRoot: REPO_ROOT });
29
+ const storage = loadStorageRegistry({ repoRoot: REPO_ROOT });
30
+ const roots = resolveRoots({ registry: storage, projectRoot, global });
31
+ const sources = loadSources(REPO_ROOT);
32
+ const adviceRegistry = advice.ok ? advice.registry : null;
33
+ return { adviceRegistry, adviceError: advice.ok ? null : advice.detail, storage, roots, sources };
34
+ }
35
+
36
+ function emitResult(result: any, { json, kind }: any) {
37
+ if (result.reason === 'over-limit') {
38
+ return usageError('advise', result.detail || 'request exceeds an advice limit', json);
39
+ }
40
+ if (json) {
41
+ console.log(JSON.stringify(result, null, 2));
42
+ } else if (result.status === 'answered') {
43
+ for (const answer of result.answers) {
44
+ const staticText = answer.staticAnswer === null ? '' : ` Static answer: ${answer.staticAnswer}.`;
45
+ console.log(`Advice (${kind}): ${answer.pick}, ${strengthWords(answer.strength)}.${staticText}`);
46
+ }
47
+ } else {
48
+ console.log(`Advice (${kind}): none; the static answer stands.`);
49
+ }
50
+ return finishRuntime(0);
51
+ }
52
+
53
+ function isPresent(value: any) {
54
+ return value !== undefined && value !== null;
55
+ }
56
+
57
+ function readState({ state, stateFile, json }: any) {
58
+ if (isPresent(state) === isPresent(stateFile)) {
59
+ usageError('advise', 'provide exactly one of --state or --state-file', json);
60
+ return null;
61
+ }
62
+ if (isPresent(state)) return state;
63
+ try {
64
+ const content = fs.readFileSync(stateFile, 'utf8');
65
+ if (!content.length) throw new Error('file is empty');
66
+ return content;
67
+ } catch (error: any) {
68
+ usageError('advise', `cannot read state file ${stateFile}: ${error.message}`, json);
69
+ return null;
70
+ }
71
+ }
72
+
73
+ function readQuestions(file: any, json: any) {
74
+ try {
75
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
76
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || !Array.isArray(parsed.questions)) {
77
+ throw new Error('expected an object with a questions array');
78
+ }
79
+ return parsed.questions;
80
+ } catch (error: any) {
81
+ usageError('advise', `cannot read questions file ${file}: ${error.message}`, json);
82
+ return null;
83
+ }
84
+ }
85
+
86
+ function parseAskInput(options: any) {
87
+ const hasKind = typeof options.kind === 'string' && options.kind.length > 0;
88
+ const hasQuestionsFile = isPresent(options.questionsFile);
89
+ if (hasKind === hasQuestionsFile) {
90
+ usageError('advise', 'provide exactly one of --kind or --questions-file', options.json);
91
+ return null;
92
+ }
93
+ const state = readState({ state: options.state, stateFile: options.stateFile, json: options.json });
94
+ if (state === null) return null;
95
+ if (options.server !== undefined && !hasKind) {
96
+ usageError('advise', '--server is only valid with --kind mcp-need', options.json);
97
+ return null;
98
+ }
99
+ if (options.staticAnswer !== undefined && !hasKind) {
100
+ usageError('advise', '--static is only valid with a named --kind', options.json);
101
+ return null;
102
+ }
103
+ return { state, hasKind, hasQuestionsFile };
104
+ }
105
+
106
+ function endpointSource(userSetting: any, endpoint: any) {
107
+ if (userSetting?.decidedBy?.user) {
108
+ try {
109
+ const file = JSON.parse(fs.readFileSync(userSetting.decidedBy.user, 'utf8'));
110
+ if (file.endpoint === endpoint) return userSetting.decidedBy.user;
111
+ } catch { /* an invalid file will be reported by its resolver/writer */ }
112
+ }
113
+ return 'provider default';
114
+ }
115
+
116
+ function inspectFailure(inspected: any, { endpoint, model, provider }: any) {
117
+ if (inspected.endpoint.state === 'refused') return inspected.endpoint.message;
118
+ if (inspected.endpoint.state !== 'reached') {
119
+ return `advise: Ollama did not answer at ${endpoint}; start it yourself and re-run. Nothing was written.`;
120
+ }
121
+ if (!inspected.endpoint.versionOk) {
122
+ return `advise: Ollama ${inspected.endpoint.version || 'unknown'} at ${endpoint} is older than ${provider.minVersion}. Nothing was written.`;
123
+ }
124
+ if (!inspected.model.installed) {
125
+ return `advise: model ${model} is not installed; run 'ollama pull ${model}' yourself. Nothing was written.`;
126
+ }
127
+ if (!inspected.model.decisionCapable) {
128
+ const capable = inspected.models
129
+ .filter((entry: any) => Array.isArray(entry.capabilities) && entry.capabilities.includes(provider.capability))
130
+ .map((entry: any) => entry.name);
131
+ return `advise: model ${model} is not decision-capable; decision-capable here: ${capable.length ? capable.join(', ') : 'none'}. Nothing was written.`;
132
+ }
133
+ return null;
134
+ }
135
+
136
+ function writeFailure(message: any, json: any) {
137
+ if (json) console.log(JSON.stringify({ ok: false, error: 'write-failed', message }, null, 2));
138
+ else if (message.startsWith('decision model: endpoint ') || message.startsWith('advise:')) console.error(message);
139
+ else console.error(`advise: ${message}`);
140
+ return finishRuntime(1);
141
+ }
142
+
143
+ async function handleConfigure(options: any, runtime: any) {
144
+ const { adviceRegistry, adviceError, roots, sources } = runtime;
145
+ const { json, project, global, force } = options;
146
+ const hasModel = typeof options.model === 'string' && options.model.length > 0;
147
+ const hasOff = options.off === true;
148
+ if (hasModel && hasOff) return usageError('advise', 'cannot combine --model and --off', json);
149
+ if (!hasModel && !hasOff) return usageError('advise', 'provide exactly one of --model or --off', json);
150
+ if (project && options.endpoint !== undefined) return usageError('advise', '--endpoint cannot be used with --project', json);
151
+ if (hasOff && options.endpoint !== undefined) return usageError('advise', '--endpoint cannot be used with --off', json);
152
+ if (project && (global || !options.projectRoot)) return usageError('advise', '--project requires a project root', json);
153
+ if (adviceError || !adviceRegistry) return writeFailure(`advice registry is invalid: ${adviceError || 'missing registry'}`, json);
154
+
155
+ const file = project
156
+ ? path.join(roots['project-doflow'], 'decision-model.json')
157
+ : path.join(roots['config-home'], 'decision-model.json');
158
+ const fsImpl = fs;
159
+ if (hasOff) {
160
+ const setting = project ? { schema: 1, enabled: false } : { schema: 1, enabled: false };
161
+ const written = writeSetting({ file, level: project ? 'project' : 'user', setting, force, fsImpl });
162
+ if (!written.ok) return writeFailure(written.detail, json);
163
+ const effective = resolveSetting({ adviceRegistry, roots, fsImpl });
164
+ if (json) console.log(JSON.stringify({ written: file, setting: effective }, null, 2));
165
+ else console.log(project ? `Decision model for this project: off (wrote ${file}).` : `Decision model off (wrote ${file}).`);
166
+ return finishRuntime(0);
167
+ }
168
+
169
+ let userRoots = roots;
170
+ if (project) userRoots = { ...roots, 'project-doflow': null };
171
+ const userSetting = resolveSetting({ adviceRegistry, roots: userRoots, fsImpl });
172
+ if (project && userSetting.state !== 'on') {
173
+ return writeFailure('turn the decision model on at user level first; a project cannot enable it', json);
174
+ }
175
+ const providerId = userSetting.provider || 'ollama';
176
+ const provider = adviceRegistry.providers.find((entry: any) => entry.id === providerId);
177
+ if (!provider) return writeFailure(`provider '${providerId}' is not in core/registry/advice.json`, json);
178
+ const endpoint = project
179
+ ? userSetting.endpoint
180
+ : options.endpoint || userSetting.endpoint || provider.defaultEndpoint;
181
+ const endpointFile = options.endpoint ? '--endpoint' : endpointSource(userSetting, endpoint);
182
+ const client = createAdviceClient({ adviceRegistry, roots, sources });
183
+ const inspected = await client.inspect({ endpoint, model: options.model, provider: provider.id, minVersion: provider.minVersion, file: endpointFile });
184
+ const failure = inspectFailure(inspected, { endpoint, model: options.model, provider });
185
+ if (failure) return writeFailure(failure, json);
186
+
187
+ const setting = project
188
+ ? { schema: 1, model: options.model }
189
+ : { schema: 1, enabled: true, provider: provider.id, endpoint, model: options.model };
190
+ const written = writeSetting({ file, level: project ? 'project' : 'user', setting, force, fsImpl });
191
+ if (!written.ok) return writeFailure(written.detail, json);
192
+ const effective = resolveSetting({ adviceRegistry, roots, fsImpl });
193
+ if (json) console.log(JSON.stringify({ written: file, setting: effective }, null, 2));
194
+ else console.log(project
195
+ ? `Decision model for this project: ${options.model} (wrote ${file}).`
196
+ : `Decision model on: ${options.model} at ${endpoint} (wrote ${file}).`);
197
+ return finishRuntime(0);
198
+ }
199
+
200
+ async function handleAsk(options: any, runtime: any) {
201
+ const parsed = parseAskInput(options);
202
+ if (!parsed) return finishRuntime(2);
203
+ const { adviceRegistry, adviceError, roots, sources } = runtime;
204
+ if (adviceError || !adviceRegistry) {
205
+ const result = {
206
+ status: 'off', reason: 'config-invalid', detail: adviceError || 'advice registry is missing',
207
+ provider: null, model: null, endpoint: null, decidedBy: { user: null, project: null }, durationMs: 0, answers: [],
208
+ };
209
+ return emitResult(result, { json: options.json, kind: options.kind || 'adhoc' });
210
+ }
211
+
212
+ let kind = 'adhoc';
213
+ let questionSet;
214
+ let questionError = null;
215
+ if (parsed.hasKind) {
216
+ kind = options.kind;
217
+ const built = materializeKind(kind, { adviceRegistry, sources, server: options.server });
218
+ if (!built.ok && built.reason === 'usage-error') return usageError('advise', built.detail, options.json);
219
+ if (built.ok) questionSet = { state: parsed.state, questions: [built.question] };
220
+ else {
221
+ questionSet = { state: parsed.state, questions: [] };
222
+ questionError = built.detail;
223
+ }
224
+ if (options.staticAnswer !== undefined && built.ok) {
225
+ const validation = validateStaticAnswer(built.question, options.staticAnswer);
226
+ if (!validation.ok) return usageError('advise', validation.detail, options.json);
227
+ }
228
+ } else {
229
+ const questions = readQuestions(options.questionsFile, options.json);
230
+ if (!questions) return finishRuntime(2);
231
+ questionSet = { state: parsed.state, questions };
232
+ const validation = validateQuestionSet(questionSet, { adviceRegistry });
233
+ if (!validation.ok) return usageError('advise', validation.detail, options.json);
234
+ }
235
+
236
+ const client = createAdviceClient({ adviceRegistry, roots, sources });
237
+ const result = await client.ask({
238
+ questionSet, kind, staticAnswer: options.staticAnswer, caller: 'advise', taskId: options.taskId,
239
+ record: true,
240
+ });
241
+ // Preserve the kind materialiser's source-specific diagnostic while the client owns miss recording.
242
+ if (questionError && result.reason === 'config-invalid') result.detail = questionError;
243
+ return emitResult(result, { json: options.json, kind });
244
+ }
245
+
246
+ function handleOutcome(options: any, runtime: any) {
247
+ const { json } = options;
248
+ if (typeof options.pickId !== 'string' || !options.pickId) return usageError('advise', '--pick-id is required', json);
249
+ if (typeof options.actual !== 'string' || !options.actual) return usageError('advise', '--actual is required', json);
250
+ const found = findPick({ pickId: options.pickId, roots: runtime.roots });
251
+ if (!found) {
252
+ if (json) console.log(JSON.stringify({ ok: false, error: 'not-found', message: `no pick ${options.pickId} in the retained record` }, null, 2));
253
+ else console.error(`advise: no pick ${options.pickId} in the retained record`);
254
+ return finishRuntime(1);
255
+ }
256
+ if (!found.pick.options?.includes(options.actual)) {
257
+ return usageError('advise', `--actual must be one of: ${found.pick.options.join(', ')}`, json);
258
+ }
259
+ const written = appendOutcome({
260
+ pickId: options.pickId, actual: options.actual, source: 'caller', roots: runtime.roots, store: found.store,
261
+ });
262
+ if (!written) return writeFailure(`could not record outcome for ${options.pickId}`, json);
263
+ if (json) console.log(JSON.stringify({ pickId: options.pickId, actual: options.actual, recorded: true, file: written.file }, null, 2));
264
+ else console.log(`Recorded outcome ${options.actual} for ${options.pickId}.`);
265
+ return finishRuntime(0);
266
+ }
267
+
268
+ async function handleAdviseCommand(options: any = {}) {
269
+ const action = options.actionGiven ? options.action : 'ask';
270
+ if (!['ask', 'outcome', 'configure'].includes(action)) return usageError('advise', `unknown action '${action}'`, options.json);
271
+ try {
272
+ const projectRoot = options.global ? null : path.resolve(options.projectRoot || process.cwd());
273
+ const runtime = loadRuntime({ projectRoot, global: options.global });
274
+ const shaped = { ...options, projectRoot };
275
+ if (action === 'configure') return await handleConfigure(shaped, runtime);
276
+ if (action === 'outcome') return handleOutcome(shaped, runtime);
277
+ return await handleAsk(shaped, runtime);
278
+ } catch (error: any) {
279
+ return writeFailure(error?.message || 'advice command failed', options.json);
280
+ }
281
+ }
282
+
283
+ module.exports = { handleAdviseCommand };
@@ -0,0 +1,324 @@
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 http = require('node:http');
7
+ const https = require('node:https');
8
+ const { resolveSetting } = require('./config.ts');
9
+ const { checkEndpoint } = require('./endpoint-guard.ts');
10
+ const { strengthOf } = require('./strength.ts');
11
+ const { validateQuestionSet, validateStaticAnswer } = require('./kinds.ts');
12
+ const { appendPick, appendMiss } = require('./record.ts');
13
+
14
+ // Keep this map literal: module reachability must be auditable without dynamic require paths.
15
+ const ADAPTERS: Record<string, any> = Object.freeze({
16
+ 'ollama-systemone': require('./providers/ollama-systemone.ts'),
17
+ });
18
+
19
+ function durationSince(startedAt: any) {
20
+ return Math.max(0, Date.now() - startedAt);
21
+ }
22
+
23
+ function versionTuple(version: any) {
24
+ const match = typeof version === 'string' && version.match(/^v?(\d+)\.(\d+)\.(\d+)/);
25
+ return match ? match.slice(1).map(Number) : null;
26
+ }
27
+
28
+ function versionAtLeast(version: any, minimum: any) {
29
+ const actual = versionTuple(version);
30
+ const required = versionTuple(minimum);
31
+ if (!actual || !required) return false;
32
+ for (let index = 0; index < 3; index += 1) {
33
+ if (actual[index] > required[index]) return true;
34
+ if (actual[index] < required[index]) return false;
35
+ }
36
+ return true;
37
+ }
38
+
39
+ function networkReason(error: any) {
40
+ return error?.code === 'ADVICE_TIMEOUT' || error?.code === 'ETIMEDOUT' || error?.code === 'ESOCKETTIMEDOUT'
41
+ ? 'timeout' : 'unreachable';
42
+ }
43
+
44
+ function requestAction({ endpoint, action, pinnedLookup, timeoutMs, httpModule = http, httpsModule = https }: any) {
45
+ return new Promise<any>((resolve, reject) => {
46
+ let request: any;
47
+ let settled = false;
48
+ const finish = (error: any, response?: any) => {
49
+ if (settled) return;
50
+ settled = true;
51
+ clearTimeout(timer);
52
+ if (error) reject(error);
53
+ else resolve(response);
54
+ };
55
+ const timer = setTimeout(() => {
56
+ const error: any = new Error(`decision model request timed out after ${timeoutMs}ms`);
57
+ error.code = 'ADVICE_TIMEOUT';
58
+ request?.destroy(error);
59
+ finish(error);
60
+ }, timeoutMs);
61
+
62
+ try {
63
+ const url = new URL(endpoint);
64
+ const transport = url.protocol === 'https:' ? httpsModule : httpModule;
65
+ const prefix = url.pathname.replace(/\/+$/, '');
66
+ const requestPath = `${prefix}${action.path}` || action.path;
67
+ const body = action.body === undefined ? undefined : JSON.stringify(action.body);
68
+ const options = {
69
+ protocol: url.protocol,
70
+ hostname: url.hostname.replace(/^\[|\]$/g, ''),
71
+ port: url.port ? Number(url.port) : undefined,
72
+ path: requestPath,
73
+ method: action.method,
74
+ agent: false,
75
+ lookup: pinnedLookup,
76
+ headers: body === undefined ? undefined : {
77
+ 'content-type': 'application/json',
78
+ 'content-length': Buffer.byteLength(body),
79
+ },
80
+ };
81
+ request = transport.request(options, (response: any) => {
82
+ const chunks: any[] = [];
83
+ response.on('data', (chunk: any) => chunks.push(Buffer.from(chunk)));
84
+ response.on('error', (error: any) => finish(error));
85
+ response.on('end', () => finish(null, {
86
+ status: response.statusCode || 0,
87
+ bodyText: Buffer.concat(chunks).toString('utf8'),
88
+ }));
89
+ });
90
+ request.on('error', (error: any) => finish(error));
91
+ if (body === undefined) request.end();
92
+ else request.end(body);
93
+ } catch (error: any) {
94
+ finish(error);
95
+ }
96
+ });
97
+ }
98
+
99
+ function emptyAdviceResult(status: any, reason: any, detail: any, setting: any, durationMs: any) {
100
+ return {
101
+ status, reason, detail: detail ?? null,
102
+ provider: setting?.provider ?? null,
103
+ model: setting?.model ?? null,
104
+ endpoint: setting?.endpoint ?? null,
105
+ decidedBy: setting?.decidedBy ?? { user: null, project: null },
106
+ durationMs,
107
+ answers: [],
108
+ };
109
+ }
110
+
111
+ function createAdviceClient({
112
+ adviceRegistry,
113
+ roots,
114
+ fsImpl = fs,
115
+ lookup,
116
+ httpModule = http,
117
+ httpsModule = https,
118
+ clock = () => new Date(),
119
+ }: any = {}) {
120
+ async function ask({ questionSet, kind, staticAnswer, caller, taskId = null, record = true }: any = {}) {
121
+ const startedAt = Date.now();
122
+ let setting: any = null;
123
+ const effectiveKind = typeof kind === 'string' && kind ? kind : 'adhoc';
124
+ const shouldRecord = record !== false;
125
+
126
+ function unavailable(reason: any, detail: any) {
127
+ const durationMs = durationSince(startedAt);
128
+ if (shouldRecord && setting?.state === 'on') {
129
+ try { appendMiss({ roots, now: clock, fsImpl, kind: effectiveKind, caller, reason, model: setting.model, durationMs }); }
130
+ catch { /* recording failure does not change an AdviceResult */ }
131
+ }
132
+ return emptyAdviceResult('unavailable', reason, detail, setting, durationMs);
133
+ }
134
+
135
+ try {
136
+ setting = resolveSetting({ adviceRegistry, roots, fsImpl });
137
+ if (setting.state !== 'on') {
138
+ return emptyAdviceResult('off', setting.reason, setting.notes?.[0] || null, setting, durationSince(startedAt));
139
+ }
140
+
141
+ const provider = adviceRegistry?.providers?.find((entry: any) => entry.id === setting.provider);
142
+ const adapter = provider && ADAPTERS[provider.adapter];
143
+ if (!provider || !adapter) return unavailable('config-invalid', `provider adapter '${provider?.adapter || setting.provider}' is unavailable`);
144
+
145
+ if (effectiveKind !== 'adhoc') {
146
+ const namedKind = adviceRegistry.kinds?.find((entry: any) => entry.id === effectiveKind);
147
+ if (!namedKind || questionSet?.questions?.length !== 1 || questionSet.questions[0]?.name !== effectiveKind) {
148
+ return unavailable('config-invalid', `question set does not match kind '${effectiveKind}'`);
149
+ }
150
+ }
151
+ const questionValidation = validateQuestionSet(questionSet, { adviceRegistry });
152
+ if (!questionValidation.ok) return unavailable('config-invalid', questionValidation.detail);
153
+ if (staticAnswer !== undefined && staticAnswer !== null) {
154
+ const staticValidation = validateStaticAnswer(questionSet.questions[0], staticAnswer);
155
+ if (!staticValidation.ok) return unavailable('config-invalid', staticValidation.detail);
156
+ }
157
+
158
+ const stateBytes = Buffer.byteLength(questionSet.state, 'utf8');
159
+ if (stateBytes > adviceRegistry.limits.maxStateBytes) {
160
+ return unavailable('over-limit', `state is ${stateBytes} bytes; the limit is ${adviceRegistry.limits.maxStateBytes} (core/registry/advice.json limits.maxStateBytes)`);
161
+ }
162
+ const request = adapter.buildRequest(questionSet, { model: setting.model });
163
+ const bodyText = JSON.stringify(request.body);
164
+ const bodyBytes = Buffer.byteLength(bodyText, 'utf8');
165
+ if (bodyBytes > adviceRegistry.limits.maxBodyBytes) {
166
+ return unavailable('over-limit', `request body is ${bodyBytes} bytes; the limit is ${adviceRegistry.limits.maxBodyBytes} (core/registry/advice.json limits.maxBodyBytes)`);
167
+ }
168
+
169
+ const endpointCheck = await checkEndpoint(setting.endpoint, {
170
+ lookup,
171
+ file: setting.decidedBy?.user || 'provider default',
172
+ timeoutMs: Math.max(0, adviceRegistry.timeoutMs - durationSince(startedAt)),
173
+ });
174
+ if (!endpointCheck.ok) return unavailable(endpointCheck.reason || 'non-loopback', endpointCheck.message);
175
+ const remainingMs = adviceRegistry.timeoutMs - durationSince(startedAt);
176
+ if (remainingMs <= 0) return unavailable('timeout', 'decision model request budget exhausted');
177
+
178
+ let response;
179
+ try {
180
+ response = await requestAction({
181
+ endpoint: setting.endpoint,
182
+ action: request,
183
+ pinnedLookup: endpointCheck.pinnedLookup,
184
+ timeoutMs: remainingMs,
185
+ httpModule,
186
+ httpsModule,
187
+ });
188
+ } catch (error: any) {
189
+ const reason = networkReason(error);
190
+ return unavailable(reason, error.message);
191
+ }
192
+
193
+ let parsed;
194
+ try { parsed = adapter.parseReply(response.status, response.bodyText, questionSet); }
195
+ catch (error: any) { return unavailable('malformed-reply', error.message); }
196
+ if (!parsed?.ok) return unavailable(parsed?.reason || 'malformed-reply', parsed?.detail || 'reply could not be parsed');
197
+
198
+ const answers = parsed.answers.map((answer: any, index: any) => {
199
+ const strength = strengthOf(answer, adviceRegistry.strength);
200
+ const answerStatic = questionSet.questions.length === 1 ? staticAnswer ?? null : null;
201
+ const result: any = {
202
+ name: answer.name,
203
+ kind: effectiveKind,
204
+ type: answer.type,
205
+ pick: answer.pick,
206
+ strength,
207
+ probabilities: answer.probabilities,
208
+ confidence: answer.confidence ?? null,
209
+ staticAnswer: answerStatic,
210
+ agrees: answerStatic === null ? null : answer.pick === answerStatic,
211
+ pickId: null,
212
+ };
213
+ if (answer.type === 'score') {
214
+ result.pickIndex = answer.pickIndex;
215
+ result.score = answer.score;
216
+ result.criteria = answer.criteria;
217
+ }
218
+ if (shouldRecord) {
219
+ const written = appendPick({
220
+ roots, now: clock, fsImpl, kind: effectiveKind,
221
+ answer: { ...answer, strength },
222
+ question: questionSet.questions[index],
223
+ staticAnswer: answerStatic,
224
+ provider: setting.provider,
225
+ model: setting.model,
226
+ state: questionSet.state,
227
+ caller,
228
+ taskId,
229
+ durationMs: durationSince(startedAt),
230
+ });
231
+ if (written) result.pickId = written.pickId;
232
+ }
233
+ return result;
234
+ });
235
+ return {
236
+ status: 'answered', reason: null, detail: null,
237
+ provider: setting.provider, model: setting.model, endpoint: setting.endpoint,
238
+ decidedBy: setting.decidedBy, durationMs: durationSince(startedAt), answers,
239
+ };
240
+ } catch (error: any) {
241
+ return unavailable('config-invalid', error?.message || 'advice request failed');
242
+ }
243
+ }
244
+
245
+ async function inspect({ endpoint, model, provider: providerId = 'ollama', file, minVersion, timeoutMs }: any = {}) {
246
+ const unavailable = (state: any, message: any, version: any = null, versionOk = false) => ({
247
+ endpoint: { state, version, versionOk, message: message ?? null },
248
+ models: [],
249
+ model: { installed: false, decisionCapable: false },
250
+ });
251
+ try {
252
+ const provider = adviceRegistry?.providers?.find((entry: any) => entry.id === providerId);
253
+ const adapter = provider && ADAPTERS[provider.adapter];
254
+ if (!provider || !adapter) return unavailable('refused', `provider '${providerId}' is unavailable`);
255
+ const probeLimit = Number.isFinite(timeoutMs) ? timeoutMs : adviceRegistry.probeTimeoutMs;
256
+ const budgetMs = Number.isFinite(adviceRegistry.doctorBudgetMs) ? adviceRegistry.doctorBudgetMs : probeLimit * 4;
257
+ const startedAt = Date.now();
258
+ const remaining = () => Math.max(0, budgetMs - (Date.now() - startedAt));
259
+ const endpointCheck = await checkEndpoint(endpoint, { lookup, file, timeoutMs: remaining() });
260
+ if (!endpointCheck.ok) return unavailable(endpointCheck.reason === 'timeout' ? 'unreachable' : 'refused', endpointCheck.message);
261
+ const requestProbe = (action: any) => {
262
+ const left = remaining();
263
+ if (left <= 0) throw Object.assign(new Error('decision model probe budget exhausted'), { code: 'ADVICE_TIMEOUT' });
264
+ return requestAction({
265
+ endpoint, action, pinnedLookup: endpointCheck.pinnedLookup,
266
+ timeoutMs: Math.min(probeLimit, left), httpModule, httpsModule,
267
+ });
268
+ };
269
+
270
+ let versionResponse;
271
+ try { versionResponse = await requestProbe(adapter.versionRequest()); }
272
+ catch (error: any) { return unavailable('unreachable', error.message); }
273
+ const version = adapter.parseVersion(versionResponse.status, versionResponse.bodyText)?.version ?? null;
274
+ const minimum = minVersion || provider.minVersion;
275
+ const versionOk = version ? versionAtLeast(version, minimum) : false;
276
+ const endpointResult = {
277
+ state: 'reached',
278
+ version,
279
+ versionOk,
280
+ message: version ? null : `Ollama version endpoint returned HTTP ${versionResponse.status}`,
281
+ };
282
+
283
+ let models = [];
284
+ try {
285
+ const listResponse = await requestProbe(adapter.listRequest());
286
+ models = adapter.parseList(listResponse.status, listResponse.bodyText);
287
+ } catch {
288
+ return { endpoint: endpointResult, models, model: { installed: false, decisionCapable: false } };
289
+ }
290
+
291
+ const configuredIndex = models.findIndex((entry: any) => adapter.modelMatches(model, entry.name));
292
+ const fallbackIndexes = models.map((entry: any, index: any) => index)
293
+ .filter((index: any) => !Array.isArray(models[index].capabilities));
294
+ if (configuredIndex >= 0 && fallbackIndexes.includes(configuredIndex)) {
295
+ fallbackIndexes.splice(fallbackIndexes.indexOf(configuredIndex), 1);
296
+ fallbackIndexes.unshift(configuredIndex);
297
+ }
298
+ for (const index of fallbackIndexes.slice(0, 8)) {
299
+ try {
300
+ const showResponse = await requestProbe(adapter.showRequest(models[index].name));
301
+ const shown = adapter.parseShow(showResponse.status, showResponse.bodyText);
302
+ if (shown) models[index] = { ...models[index], capabilities: shown.capabilities };
303
+ } catch {
304
+ // Keep capabilities unknown when the read-only fallback is unavailable.
305
+ }
306
+ }
307
+ const match = models.find((entry: any) => adapter.modelMatches(model, entry.name));
308
+ return {
309
+ endpoint: endpointResult,
310
+ models,
311
+ model: {
312
+ installed: Boolean(match),
313
+ decisionCapable: Boolean(match?.capabilities?.includes(provider.capability)),
314
+ },
315
+ };
316
+ } catch (error: any) {
317
+ return unavailable('unreachable', error?.message || 'decision model inspection failed');
318
+ }
319
+ }
320
+
321
+ return { ask, inspect };
322
+ }
323
+
324
+ module.exports = { createAdviceClient, versionAtLeast };