@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,209 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const SYSTEMONE_PATH = '/v1/systemone';
6
+ const VERSION_PATH = '/api/version';
7
+ const TAGS_PATH = '/api/tags';
8
+ const SHOW_PATH = '/api/show';
9
+
10
+ function parseJson(bodyText: any): any {
11
+ if (typeof bodyText !== 'string') return null;
12
+ try {
13
+ const value = JSON.parse(bodyText);
14
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ function failure(reason: string, detail: string): { ok: false; reason: string; detail: string } {
21
+ return { ok: false, reason, detail };
22
+ }
23
+
24
+ function buildRequest(questionSet: any, { model }: any = {}): { method: string; path: string; body: any } {
25
+ const questions = Object.fromEntries(questionSet.questions.map((question: any) => {
26
+ if (question.type === 'choice') {
27
+ return [question.name, {
28
+ type: 'choice',
29
+ instructions: question.instructions,
30
+ criteria: question.options,
31
+ }];
32
+ }
33
+ if (question.type === 'score') {
34
+ return [question.name, {
35
+ type: 'score',
36
+ instructions: question.instructions,
37
+ criteria: question.criteria,
38
+ }];
39
+ }
40
+ return [question.name, {
41
+ type: 'noul',
42
+ instructions: question.instructions,
43
+ criteria: { false: question.no, true: question.yes },
44
+ }];
45
+ }));
46
+ return {
47
+ method: 'POST',
48
+ path: SYSTEMONE_PATH,
49
+ body: { model, state: questionSet.state, questions },
50
+ };
51
+ }
52
+
53
+ const SCORE_TOLERANCE = 1e-6;
54
+
55
+ function inUnit(value: any): boolean {
56
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1;
57
+ }
58
+
59
+ function parseScore(question: any, value: any): any {
60
+ const bad = (detail: string) => failure('malformed-reply', detail);
61
+ const { criteria, name } = question;
62
+ const last = criteria.length - 1;
63
+ if (value.type !== 'score') return bad(`answer for ${name} is not a score`);
64
+ const keys: string[] = criteria.map((criterion: any, index: number) => String(index));
65
+ const exact = (object: any) => object && typeof object === 'object' && !Array.isArray(object)
66
+ && Object.keys(object).length === keys.length && keys.every((key) => Object.hasOwn(object, key));
67
+ if (!exact(value.legend) || keys.some((key: string, index: number) => value.legend[key] !== criteria[index])) {
68
+ return bad(`legend for ${name} does not match its criteria`);
69
+ }
70
+ if (!exact(value.probabilities)) return bad(`probabilities for ${name} do not match its criteria`);
71
+ if (keys.some((key) => !inUnit(value.probabilities[key]))) return bad(`probability for ${name} is not a number from 0 to 1`);
72
+ if (!inUnit(value.confidence)) return bad(`confidence for ${name} is not a number from 0 to 1`);
73
+ if (typeof value.score !== 'number' || !Number.isFinite(value.score) || value.score < 0 || value.score > last) {
74
+ return bad(`score for ${name} is outside 0 to ${last}`);
75
+ }
76
+ const sum = keys.reduce((total, key) => total + value.probabilities[key], 0);
77
+ if (Math.abs(sum - 1) > SCORE_TOLERANCE) return bad(`probabilities for ${name} do not sum to 1`);
78
+ const weighted = keys.reduce((total, key, index) => total + index * value.probabilities[key], 0);
79
+ if (Math.abs(weighted - value.score) > SCORE_TOLERANCE) return bad(`score for ${name} does not match its probabilities`);
80
+ let pickIndex = 0;
81
+ keys.forEach((key, index) => { if (value.probabilities[key] > value.probabilities[String(pickIndex)]) pickIndex = index; });
82
+ const probabilities = Object.fromEntries(keys.map((key) => [key, value.probabilities[key]]));
83
+ return {
84
+ ok: true,
85
+ answer: {
86
+ name, type: 'score', pick: criteria[pickIndex], pickIndex, score: value.score,
87
+ criteria: [...criteria], probabilities, confidence: value.confidence,
88
+ },
89
+ };
90
+ }
91
+
92
+ function parseReply(status: number, bodyText: any, questionSet: any): any {
93
+ if (status === 400) {
94
+ const parsed = parseJson(bodyText);
95
+ return failure('endpoint-rejected', typeof parsed?.error === 'string' ? parsed.error : 'Ollama rejected the request');
96
+ }
97
+ if (status === 404) {
98
+ const parsed = parseJson(bodyText);
99
+ if (typeof parsed?.error === 'string' && parsed.error) return failure('model-missing', parsed.error);
100
+ return failure('endpoint-missing', 'decision endpoint is missing');
101
+ }
102
+ if (status === 413) return failure('over-limit', 'request exceeds the server limit');
103
+ if (status >= 300 && status < 400) return failure('endpoint-rejected', `Ollama returned HTTP ${status}`);
104
+ if (status !== 200) return failure('server-error', `Ollama returned HTTP ${status}`);
105
+
106
+ const parsed = parseJson(bodyText);
107
+ if (!parsed || !parsed.answers || typeof parsed.answers !== 'object' || Array.isArray(parsed.answers)) {
108
+ return failure('malformed-reply', 'reply has no answers object');
109
+ }
110
+
111
+ const answers: any[] = [];
112
+ for (const question of questionSet.questions) {
113
+ const value = parsed.answers[question.name];
114
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
115
+ return failure('malformed-reply', `answer missing for question ${question.name}`);
116
+ }
117
+ if (question.type === 'choice') {
118
+ const options = Object.keys(question.options);
119
+ if (typeof value.choice !== 'string' || !options.includes(value.choice)) {
120
+ return failure('malformed-reply', `choice for question ${question.name} is outside its options`);
121
+ }
122
+ const probabilities = value.probabilities;
123
+ if (!probabilities || typeof probabilities !== 'object' || Array.isArray(probabilities)) {
124
+ return failure('malformed-reply', `probabilities for ${question.name} are missing`);
125
+ }
126
+ for (const option of options) {
127
+ if (typeof probabilities[option] !== 'number' || !Number.isFinite(probabilities[option])
128
+ || probabilities[option] < 0 || probabilities[option] > 1) {
129
+ return failure('malformed-reply', `probability for ${question.name}.${option} is not a number from 0 to 1`);
130
+ }
131
+ }
132
+ if (typeof value.confidence !== 'number' || !Number.isFinite(value.confidence)
133
+ || value.confidence < 0 || value.confidence > 1) {
134
+ return failure('malformed-reply', `confidence for ${question.name} is not a number from 0 to 1`);
135
+ }
136
+ answers.push({ name: question.name, type: 'choice', pick: value.choice, probabilities, confidence: value.confidence });
137
+ } else if (question.type === 'score') {
138
+ const scored = parseScore(question, value);
139
+ if (!scored.ok) return scored;
140
+ answers.push(scored.answer);
141
+ } else {
142
+ const probability = value.noul;
143
+ if (typeof probability !== 'number' || !Number.isFinite(probability) || probability < 0 || probability > 1) {
144
+ return failure('malformed-reply', `noul for ${question.name} is not a probability from 0 to 1`);
145
+ }
146
+ answers.push({
147
+ name: question.name,
148
+ type: 'yesno',
149
+ pick: probability >= 0.5 ? 'yes' : 'no',
150
+ probabilities: { no: 1 - probability, yes: probability },
151
+ confidence: null,
152
+ });
153
+ }
154
+ }
155
+ return { ok: true, answers };
156
+ }
157
+
158
+ function versionRequest() {
159
+ return { method: 'GET', path: VERSION_PATH };
160
+ }
161
+
162
+ function parseVersion(status: number, bodyText: any): { version: string } | null {
163
+ const parsed = status === 200 ? parseJson(bodyText) : null;
164
+ return typeof parsed?.version === 'string' && parsed.version.trim() ? { version: parsed.version.trim() } : null;
165
+ }
166
+
167
+ function listRequest() {
168
+ return { method: 'GET', path: TAGS_PATH };
169
+ }
170
+
171
+ function parseList(status: number, bodyText: any): any[] {
172
+ const parsed = status === 200 ? parseJson(bodyText) : null;
173
+ if (!Array.isArray(parsed?.models)) return [];
174
+ return parsed.models
175
+ .filter((model: any) => model && typeof model.name === 'string' && model.name.trim())
176
+ .map((model: any) => ({
177
+ name: model.name,
178
+ capabilities: Array.isArray(model.capabilities) && model.capabilities.every((capability: any) => typeof capability === 'string')
179
+ ? model.capabilities : null,
180
+ }));
181
+ }
182
+
183
+ function showRequest(name: string): { method: string; path: string; body: any } {
184
+ return { method: 'POST', path: SHOW_PATH, body: { model: name } };
185
+ }
186
+
187
+ function parseShow(status: number, bodyText: any): { capabilities: string[] } | null {
188
+ const parsed = status === 200 ? parseJson(bodyText) : null;
189
+ return Array.isArray(parsed?.capabilities) && parsed.capabilities.every((capability: any) => typeof capability === 'string')
190
+ ? { capabilities: parsed.capabilities }
191
+ : null;
192
+ }
193
+
194
+ /** A configured name without a tag also matches Ollama's listed `<name>:latest` form. */
195
+ function modelMatches(configured: any, listed: any): boolean {
196
+ return configured === listed || (typeof configured === 'string' && !configured.includes(':') && listed === `${configured}:latest`);
197
+ }
198
+
199
+ module.exports = {
200
+ buildRequest,
201
+ parseReply,
202
+ versionRequest,
203
+ parseVersion,
204
+ listRequest,
205
+ parseList,
206
+ showRequest,
207
+ parseShow,
208
+ modelMatches,
209
+ };
@@ -0,0 +1,166 @@
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 crypto = require('node:crypto');
8
+ const { resolveStorePath } = require('../storage/roots.ts');
9
+
10
+ const RETAINED_DAYS = 90;
11
+ const PARTITION_NAME = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
12
+
13
+ function timestamp(now: any = () => new Date()): string | null {
14
+ const value = typeof now === 'function' ? now() : now;
15
+ const date = new Date(value);
16
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
17
+ }
18
+
19
+ function selectedStore(roots: any, requestedStore?: string): { store: string; root: string } | null {
20
+ if (requestedStore && roots?.[requestedStore]) return { store: requestedStore, root: roots[requestedStore] };
21
+ if (roots?.['project-state']) return { store: 'project-state', root: roots['project-state'] };
22
+ if (roots?.['user-state']) return { store: 'user-state', root: roots['user-state'] };
23
+ return null;
24
+ }
25
+
26
+ function appendLine({ roots, at, line, fsImpl = fs, store }: any): { file: string; store: string } | null {
27
+ try {
28
+ const selected = selectedStore(roots, store);
29
+ if (!selected) return null;
30
+ const directory = resolveStorePath({ store: { root: selected.store, path: 'advice/picks' }, roots, fsImpl });
31
+ if (!directory) return null;
32
+ const file = path.join(directory, `${at.slice(0, 10)}.jsonl`);
33
+ try { if (fsImpl.lstatSync(file).isSymbolicLink()) return null; }
34
+ catch (error: any) { if (error.code !== 'ENOENT') return null; }
35
+ fsImpl.mkdirSync(path.dirname(file), { recursive: true });
36
+ fsImpl.appendFileSync(file, `${JSON.stringify(line)}\n`, 'utf8');
37
+ return { file, store: selected.store };
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ function appendPick({
44
+ roots, now = () => new Date(), fsImpl = fs, kind, answer, question,
45
+ staticAnswer = null, provider = null, model = null, state, caller, taskId = null, durationMs = 0,
46
+ }: any = {}): { pickId: string; file: string; store: string } | null {
47
+ try {
48
+ if (typeof state !== 'string' || !answer || !question) return null;
49
+ const at = timestamp(now);
50
+ if (!at) return null;
51
+ const epoch = Date.parse(at);
52
+ const pickId = `adv-${String(epoch).padStart(13, '0')}-${crypto.randomBytes(3).toString('hex')}`;
53
+ const options = question.type === 'choice' ? Object.keys(question.options || {})
54
+ : question.type === 'score' ? [...(question.criteria || [])] : ['no', 'yes'];
55
+ const instructions = kind === 'adhoc' && typeof question.instructions === 'string'
56
+ ? question.instructions.slice(0, 500) : null;
57
+ const line: any = {
58
+ schema: 1,
59
+ type: 'pick',
60
+ pickId,
61
+ at,
62
+ kind,
63
+ question: answer.name,
64
+ qtype: answer.type,
65
+ options,
66
+ instructions,
67
+ pick: answer.pick,
68
+ strength: answer.strength?.word ?? null,
69
+ between: answer.strength?.between ?? null,
70
+ probabilities: answer.probabilities,
71
+ confidence: answer.confidence ?? null,
72
+ static: staticAnswer ?? null,
73
+ provider: provider ?? null,
74
+ model: model ?? null,
75
+ stateDigest: `sha256:${crypto.createHash('sha256').update(state, 'utf8').digest('hex')}`,
76
+ stateBytes: Buffer.byteLength(state, 'utf8'),
77
+ caller,
78
+ taskId: taskId ?? null,
79
+ durationMs: Number.isFinite(durationMs) ? durationMs : 0,
80
+ };
81
+ if (answer.type === 'score') {
82
+ line.criteria = [...(question.criteria || [])];
83
+ line.pickIndex = answer.pickIndex;
84
+ line.score = answer.score;
85
+ }
86
+ const written = appendLine({ roots, at, line, fsImpl });
87
+ return written ? { pickId, file: written.file, store: written.store } : null;
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+
93
+ function appendMiss({ roots, now = () => new Date(), fsImpl = fs, kind, caller, reason, model = null, durationMs = 0 }: any = {}): any {
94
+ try {
95
+ const at = timestamp(now);
96
+ if (!at) return null;
97
+ const line = {
98
+ schema: 1, type: 'miss', at, kind, caller, reason,
99
+ model: model ?? null,
100
+ durationMs: Number.isFinite(durationMs) ? durationMs : 0,
101
+ };
102
+ return appendLine({ roots, at, line, fsImpl });
103
+ } catch { return null; }
104
+ }
105
+
106
+ function appendOutcome({ roots, now = () => new Date(), fsImpl = fs, pickId, actual, source, store }: any = {}): any {
107
+ try {
108
+ const at = timestamp(now);
109
+ if (!at) return null;
110
+ const line = { schema: 1, type: 'outcome', pickId, at, actual, source };
111
+ return appendLine({ roots, at, line, fsImpl, store });
112
+ } catch { return null; }
113
+ }
114
+
115
+ function cutoffDate(now: any, maxAgeDays: number = RETAINED_DAYS): string | null {
116
+ const at = timestamp(now);
117
+ if (!at) return null;
118
+ const cutoff = new Date(Date.parse(at) - maxAgeDays * 24 * 60 * 60 * 1000);
119
+ return cutoff.toISOString().slice(0, 10);
120
+ }
121
+
122
+ function partitionFiles(root: string | undefined, cutoff: string, fsImpl: any): any[] {
123
+ if (!root) return [];
124
+ const directory = path.join(root, 'advice', 'picks');
125
+ try {
126
+ return fsImpl.readdirSync(directory)
127
+ .filter((name: string) => PARTITION_NAME.test(name) && name.slice(0, 10) >= cutoff)
128
+ .sort()
129
+ .map((name: string) => path.join(directory, name));
130
+ } catch (error: any) {
131
+ if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') return [];
132
+ return [{ unreadable: true, file: directory }];
133
+ }
134
+ }
135
+
136
+ function readWindow({ roots, now = () => new Date(), maxAgeDays = RETAINED_DAYS, fsImpl = fs }: any = {}): { records: any[]; unreadable: number } {
137
+ const cutoff = cutoffDate(now, maxAgeDays);
138
+ if (!cutoff) return { records: [], unreadable: 0 };
139
+ const records: any[] = [];
140
+ let unreadable = 0;
141
+ for (const store of ['project-state', 'user-state']) {
142
+ for (const item of partitionFiles(roots?.[store], cutoff, fsImpl)) {
143
+ if (typeof item !== 'string') { unreadable += 1; continue; }
144
+ let contents: string;
145
+ try { contents = fsImpl.readFileSync(item, 'utf8'); } catch { unreadable += 1; continue; }
146
+ for (const line of contents.split(/\r?\n/)) {
147
+ if (!line.trim()) continue;
148
+ try {
149
+ const record = JSON.parse(line);
150
+ if (!record || typeof record !== 'object' || Array.isArray(record)) throw new Error('record must be an object');
151
+ records.push({ record, file: item, store });
152
+ } catch { unreadable += 1; }
153
+ }
154
+ }
155
+ }
156
+ return { records, unreadable };
157
+ }
158
+
159
+ function findPick({ pickId, roots, now = () => new Date(), fsImpl = fs }: any = {}): any {
160
+ if (typeof pickId !== 'string') return null;
161
+ const window = readWindow({ roots, now, fsImpl });
162
+ const found = window.records.find((entry: any) => entry.record.type === 'pick' && entry.record.pickId === pickId);
163
+ return found ? { pick: found.record, file: found.file, store: found.store } : null;
164
+ }
165
+
166
+ module.exports = { appendPick, appendMiss, appendOutcome, readWindow, findPick };
@@ -0,0 +1,170 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { REPO_ROOT } = require('../../helper/repo-root.ts');
8
+
9
+ const ADVICE_FILE = 'core/registry/advice.json';
10
+ const MODELS_FILE = 'core/registry/models.json';
11
+ const ADAPTER_DIR = 'src/runtime/advice/providers';
12
+ const TIER_KIND = 'tier';
13
+ const LIMIT_FIELDS = ['maxStateBytes', 'maxBodyBytes', 'minQuestions', 'maxQuestions', 'minOptions', 'maxOptions'];
14
+ const BAND_FIELDS: Record<string, string[]> = {
15
+ choice: ['clearAt', 'splitWithin'],
16
+ yesno: ['clearAbove', 'clearBelow', 'splitFrom', 'splitTo'],
17
+ };
18
+
19
+ function isObject(value: any): boolean {
20
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
21
+ }
22
+
23
+ function addError(errors: string[], key: string, message: string): void {
24
+ errors.push(`${key} ${message}`);
25
+ }
26
+
27
+ function validateAdviceRegistry(registry: any, { repoRoot = REPO_ROOT, models, fsImpl = fs }: any = {}): { ok: boolean; errors: string[] } {
28
+ const errors: string[] = [];
29
+ if (!isObject(registry)) return { ok: false, errors: ['registry must be an object'] };
30
+
31
+ if (registry.version !== 1) addError(errors, 'version', 'must be 1');
32
+ for (const key of ['timeoutMs', 'probeTimeoutMs']) {
33
+ if (!Number.isInteger(registry[key]) || registry[key] < 100 || registry[key] > 30000) {
34
+ addError(errors, key, 'must be an integer from 100 to 30000');
35
+ }
36
+ }
37
+ if (!Number.isInteger(registry.doctorBudgetMs) || registry.doctorBudgetMs <= 0) {
38
+ addError(errors, 'doctorBudgetMs', 'must be a positive integer');
39
+ }
40
+
41
+ const limits = registry.limits;
42
+ if (!isObject(limits)) {
43
+ addError(errors, 'limits', 'must be an object');
44
+ } else {
45
+ for (const field of LIMIT_FIELDS) {
46
+ if (!Number.isInteger(limits[field]) || limits[field] <= 0) addError(errors, `limits.${field}`, 'must be a positive integer');
47
+ }
48
+ for (const [min, max] of [['minQuestions', 'maxQuestions'], ['minOptions', 'maxOptions']]) {
49
+ if (Number.isInteger(limits[min]) && Number.isInteger(limits[max]) && limits[min] > limits[max]) {
50
+ addError(errors, `limits.${min}`, `must not exceed limits.${max}`);
51
+ }
52
+ }
53
+ if (Number.isInteger(limits.maxBodyBytes) && limits.maxBodyBytes >= 65536) {
54
+ addError(errors, 'limits.maxBodyBytes', 'must be below 65536');
55
+ }
56
+ if (Number.isInteger(limits.maxOptions) && limits.maxOptions > 26) {
57
+ addError(errors, 'limits.maxOptions', 'must be at most 26');
58
+ }
59
+ if (typeof limits.questionName !== 'string') {
60
+ addError(errors, 'limits.questionName', 'must be a regular expression string');
61
+ } else {
62
+ try { new RegExp(limits.questionName); } catch { addError(errors, 'limits.questionName', 'must compile'); }
63
+ }
64
+ }
65
+
66
+ const strength = registry.strength;
67
+ if (!isObject(strength)) {
68
+ addError(errors, 'strength', 'must be an object');
69
+ } else {
70
+ for (const [type, fields] of Object.entries(BAND_FIELDS)) {
71
+ const bands = strength[type];
72
+ if (!isObject(bands)) {
73
+ addError(errors, `strength.${type}`, 'must be an object');
74
+ continue;
75
+ }
76
+ for (const field of fields) {
77
+ if (typeof bands[field] !== 'number' || !Number.isFinite(bands[field]) || bands[field] < 0 || bands[field] > 1) {
78
+ addError(errors, `strength.${type}.${field}`, 'must be a number from 0 to 1');
79
+ }
80
+ }
81
+ }
82
+ const yesno = strength.yesno;
83
+ if (isObject(yesno) && ['clearBelow', 'splitFrom', 'splitTo', 'clearAbove'].every((field: string) => typeof yesno[field] === 'number')) {
84
+ if (!(yesno.clearBelow < yesno.splitFrom && yesno.splitFrom < yesno.splitTo && yesno.splitTo < yesno.clearAbove)) {
85
+ addError(errors, 'strength.yesno', 'bands must satisfy clearBelow < splitFrom < splitTo < clearAbove');
86
+ }
87
+ }
88
+ }
89
+
90
+ if (!models) {
91
+ try { models = JSON.parse(fsImpl.readFileSync(path.join(repoRoot, MODELS_FILE), 'utf8')); }
92
+ catch (error: any) { addError(errors, MODELS_FILE, `could not be loaded: ${error.message}`); }
93
+ }
94
+ const modelProviders = Array.isArray(models?.providers) ? new Set(models.providers.map((provider: any) => provider?.id).filter((id: any) => typeof id === 'string')) : new Set();
95
+
96
+ if (!Array.isArray(registry.providers) || registry.providers.length === 0) {
97
+ addError(errors, 'providers', 'must be a non-empty array');
98
+ } else {
99
+ const adapterRoot = path.resolve(repoRoot, ADAPTER_DIR);
100
+ registry.providers.forEach((provider: any, index: number) => {
101
+ const base = `providers[${index}]`;
102
+ if (!isObject(provider)) { addError(errors, base, 'must be an object'); return; }
103
+ for (const field of ['id', 'adapter', 'modelProvider', 'defaultEndpoint', 'route', 'minVersion', 'capability']) {
104
+ if (typeof provider[field] !== 'string' || !provider[field].trim()) addError(errors, `${base}.${field}`, 'must be a non-empty string');
105
+ }
106
+ if (typeof provider.adapter === 'string') {
107
+ if (!/^[a-z][a-z0-9-]*$/.test(provider.adapter)) {
108
+ addError(errors, `${base}.adapter`, 'must be a safe adapter id');
109
+ } else {
110
+ const adapterFile = path.resolve(adapterRoot, `${provider.adapter}.ts`);
111
+ if (!adapterFile.startsWith(`${adapterRoot}${path.sep}`) || !fsImpl.existsSync(adapterFile)) {
112
+ addError(errors, `${base}.adapter`, `must name a file under ${ADAPTER_DIR}`);
113
+ }
114
+ }
115
+ }
116
+ if (typeof provider.modelProvider === 'string' && !modelProviders.has(provider.modelProvider)) {
117
+ addError(errors, `${base}.modelProvider`, `must name a provider in ${MODELS_FILE}`);
118
+ }
119
+ });
120
+ }
121
+
122
+ if (!Array.isArray(registry.kinds) || registry.kinds.length === 0) {
123
+ addError(errors, 'kinds', 'must be a non-empty array');
124
+ } else {
125
+ const ids = new Set();
126
+ for (const [index, kind] of registry.kinds.entries()) {
127
+ const base = `kinds[${index}]`;
128
+ if (!isObject(kind)) { addError(errors, base, 'must be an object'); continue; }
129
+ if (typeof kind.id !== 'string' || !/^[a-z][a-z0-9-]*$/.test(kind.id)) addError(errors, `${base}.id`, 'must be a safe kind id');
130
+ else if (ids.has(kind.id)) addError(errors, `${base}.id`, `duplicates '${kind.id}'`);
131
+ else ids.add(kind.id);
132
+ if (!['choice', 'yesno'].includes(kind.type)) addError(errors, `${base}.type`, 'must be choice or yesno');
133
+ if (typeof kind.instructions !== 'string' || !kind.instructions.trim()) addError(errors, `${base}.instructions`, 'must be a non-empty string');
134
+ if (Object.hasOwn(kind, 'criteria') === Object.hasOwn(kind, 'criteriaFrom')) addError(errors, base, 'must have exactly one of criteria or criteriaFrom');
135
+ if (Object.hasOwn(kind, 'criteria') && (!isObject(kind.criteria) || Object.values(kind.criteria).some((value: any) => typeof value !== 'string' || !value.trim()))) {
136
+ addError(errors, `${base}.criteria`, 'must map option ids to non-empty descriptions');
137
+ }
138
+ if (Object.hasOwn(kind, 'criteriaFrom') && (typeof kind.criteriaFrom !== 'string' || !kind.criteriaFrom.trim())) {
139
+ addError(errors, `${base}.criteriaFrom`, 'must be a non-empty string');
140
+ }
141
+ if (typeof kind.staticFrom !== 'string') addError(errors, `${base}.staticFrom`, 'must be a string');
142
+ if (!Array.isArray(kind.askedBy) || kind.askedBy.some((caller: any) => typeof caller !== 'string')) addError(errors, `${base}.askedBy`, 'must be an array of strings');
143
+ }
144
+
145
+ const tier = registry.kinds.find((kind: any) => kind?.id === TIER_KIND);
146
+ const dispatchTiers = models?.dispatchTiers?.tiers;
147
+ if (!tier || !isObject(tier.criteria) || !Array.isArray(dispatchTiers)
148
+ || JSON.stringify(Object.keys(tier.criteria)) !== JSON.stringify(dispatchTiers)) {
149
+ addError(errors, 'kinds.tier.criteria', 'keys must equal models.dispatchTiers.tiers in order');
150
+ }
151
+ }
152
+
153
+ return { ok: errors.length === 0, errors };
154
+ }
155
+
156
+ function loadAdviceRegistry({ repoRoot = REPO_ROOT, file, fsImpl = fs }: any = {}): { ok: boolean; registry?: any; reason?: string; detail?: string } {
157
+ const root = path.resolve(repoRoot);
158
+ const target = file || path.join(root, ADVICE_FILE);
159
+ let registry: any;
160
+ try {
161
+ registry = JSON.parse(fsImpl.readFileSync(target, 'utf8'));
162
+ } catch (error: any) {
163
+ return { ok: false, reason: 'config-invalid', detail: `${target}: ${error.message}` };
164
+ }
165
+ const validation = validateAdviceRegistry(registry, { repoRoot: root, fsImpl });
166
+ if (!validation.ok) return { ok: false, reason: 'config-invalid', detail: validation.errors.join('; ') };
167
+ return { ok: true, registry };
168
+ }
169
+
170
+ module.exports = { loadAdviceRegistry, validateAdviceRegistry };
@@ -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
+ function strengthOf(answer: any, bands: any): { word: string; between: string[] | null } {
6
+ const type = answer?.type;
7
+ const thresholds = bands?.[type] || bands;
8
+ const probabilities = answer?.probabilities;
9
+ if (!thresholds || !probabilities || typeof probabilities !== 'object' || Array.isArray(probabilities)) {
10
+ return { word: 'leaning', between: null };
11
+ }
12
+
13
+ if (type === 'choice') {
14
+ const ordered: any[] = Object.entries(probabilities)
15
+ .filter(([, probability]) => typeof probability === 'number' && Number.isFinite(probability))
16
+ .sort((left: any, right: any) => right[1] - left[1]);
17
+ if (!ordered.length) return { word: 'leaning', between: null };
18
+ if (ordered[0][1] >= thresholds.clearAt) return { word: 'clear', between: null };
19
+ if (ordered.length > 1) {
20
+ const gap = ordered[0][1] - ordered[1][1];
21
+ const boundaryNoise = Number.EPSILON * Math.max(1, Math.abs(ordered[0][1]), Math.abs(ordered[1][1])) * 4;
22
+ if (gap < thresholds.splitWithin && thresholds.splitWithin - gap > boundaryNoise) {
23
+ return { word: 'split', between: [ordered[0][0], ordered[1][0]] };
24
+ }
25
+ }
26
+ return { word: 'leaning', between: null };
27
+ }
28
+
29
+ if (type === 'score') {
30
+ const choice = bands?.choice;
31
+ const criteria = answer.criteria;
32
+ if (!choice || !Array.isArray(criteria)) return { word: 'leaning', between: null };
33
+ const ordered = criteria.map((criterion: any, index: number) => [criterion, probabilities[String(index)]])
34
+ .filter(([, probability]: any) => typeof probability === 'number' && Number.isFinite(probability))
35
+ .sort((left: any, right: any) => right[1] - left[1]);
36
+ if (!ordered.length) return { word: 'leaning', between: null };
37
+ if (ordered[0][1] >= choice.clearAt) return { word: 'clear', between: null };
38
+ if (ordered.length > 1) {
39
+ const gap = ordered[0][1] - ordered[1][1];
40
+ const noise = Number.EPSILON * Math.max(1, Math.abs(ordered[0][1]), Math.abs(ordered[1][1])) * 4;
41
+ if (gap < choice.splitWithin && choice.splitWithin - gap > noise) {
42
+ return { word: 'split', between: [ordered[0][0], ordered[1][0]] };
43
+ }
44
+ }
45
+ return { word: 'leaning', between: null };
46
+ }
47
+
48
+ if (type === 'yesno') {
49
+ const probability = probabilities.yes;
50
+ if (typeof probability !== 'number' || !Number.isFinite(probability)) return { word: 'leaning', between: null };
51
+ if (probability >= thresholds.clearAbove || probability <= thresholds.clearBelow) return { word: 'clear', between: null };
52
+ if (probability >= thresholds.splitFrom && probability <= thresholds.splitTo) {
53
+ return { word: 'split', between: ['yes', 'no'] };
54
+ }
55
+ }
56
+ return { word: 'leaning', between: null };
57
+ }
58
+
59
+ function strengthWords(strength: any): string {
60
+ if (strength?.word === 'split' && Array.isArray(strength.between) && strength.between.length === 2) {
61
+ return `split between ${strength.between[0]} and ${strength.between[1]}`;
62
+ }
63
+ return strength?.word === 'clear' ? 'clear' : 'leaning';
64
+ }
65
+
66
+ module.exports = { strengthOf, strengthWords };