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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (409) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -2
  3. package/bin/doflow.js +44 -0
  4. package/core/.antigravity-plugin/hooks.json +39 -0
  5. package/core/.antigravity-plugin/mcp_config.json +18 -0
  6. package/core/.antigravity-plugin/plugin.json +31 -0
  7. package/core/.claude-plugin/marketplace.json +20 -0
  8. package/core/.claude-plugin/plugin.json +21 -0
  9. package/core/.codex-plugin/marketplace.json +20 -0
  10. package/core/.codex-plugin/plugin.json +35 -0
  11. package/core/.plugin/plugin.json +19 -0
  12. package/core/harnesses/antigravity/hooks/pre-implementation-gate.sh +9 -0
  13. package/core/harnesses/antigravity/hooks/stop-check.sh +10 -0
  14. package/core/harnesses/claude/hooks/lib.sh +304 -0
  15. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +20 -0
  16. package/core/harnesses/claude/hooks/permission-audit.sh +47 -0
  17. package/core/harnesses/claude/hooks/post-compact.sh +74 -0
  18. package/core/harnesses/claude/hooks/post-edit-lint.sh +36 -0
  19. package/core/harnesses/claude/hooks/post-tool-failure.sh +47 -0
  20. package/core/harnesses/claude/hooks/pre-bash-guard.sh +20 -0
  21. package/core/harnesses/claude/hooks/pre-compact.sh +43 -0
  22. package/core/harnesses/claude/hooks/pre-implement-gate.sh +21 -0
  23. package/core/harnesses/claude/hooks/session-end.sh +89 -0
  24. package/core/harnesses/claude/hooks/session-start.sh +5 -0
  25. package/core/harnesses/claude/hooks/skill-config-audit.sh +39 -0
  26. package/core/harnesses/claude/hooks/stop-check.sh +5 -0
  27. package/core/harnesses/claude/hooks/subagent-audit.sh +7 -0
  28. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -0
  29. package/core/harnesses/claude/settings/keybindings.json +20 -0
  30. package/core/harnesses/claude/settings/settings.json +201 -0
  31. package/core/harnesses/claude/statusline/doflow-statusline.sh +49 -0
  32. package/core/harnesses/codex/agents/core-implementer.toml +7 -0
  33. package/core/harnesses/codex/agents/quality-guardian.toml +8 -0
  34. package/core/harnesses/codex/agents/research-writer.toml +8 -0
  35. package/core/harnesses/codex/agents/spec-analyst.toml +8 -0
  36. package/core/harnesses/codex/agents/system-architect.toml +9 -0
  37. package/core/harnesses/codex/config/config.toml +10 -0
  38. package/core/harnesses/codex/hooks/hooks.json +122 -0
  39. package/core/harnesses/codex/hooks/lib.sh +335 -0
  40. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +18 -0
  41. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +52 -0
  42. package/core/harnesses/codex/hooks/post-edit-lint.sh +6 -0
  43. package/core/harnesses/codex/hooks/pre-bash-guard.sh +18 -0
  44. package/core/harnesses/codex/hooks/pre-compact.impl.sh +46 -0
  45. package/core/harnesses/codex/hooks/pre-compact.sh +9 -0
  46. package/core/harnesses/codex/hooks/pre-implement-gate.sh +19 -0
  47. package/core/harnesses/codex/hooks/session-end.impl.sh +92 -0
  48. package/core/harnesses/codex/hooks/session-end.sh +5 -0
  49. package/core/harnesses/codex/hooks/session-start.sh +5 -0
  50. package/core/harnesses/codex/hooks/stop-check.sh +8 -0
  51. package/core/harnesses/codex/hooks/subagent-audit.sh +8 -0
  52. package/core/harnesses/codex/hooks/user-prompt-submit.sh +6 -0
  53. package/core/harnesses/gemini/hooks/hooks.json +77 -0
  54. package/core/harnesses/gemini/hooks/lib.sh +302 -0
  55. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +21 -0
  56. package/core/harnesses/gemini/hooks/post-edit-lint.sh +39 -0
  57. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +21 -0
  58. package/core/harnesses/gemini/hooks/pre-compact.sh +38 -0
  59. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +22 -0
  60. package/core/harnesses/gemini/hooks/session-end.impl.sh +89 -0
  61. package/core/harnesses/gemini/hooks/session-end.sh +5 -0
  62. package/core/harnesses/gemini/hooks/session-start.sh +5 -0
  63. package/core/harnesses/gemini/settings/adapter-defaults.json +4 -0
  64. package/core/harnesses/kiro/agents/doflow.json +58 -0
  65. package/core/harnesses/kiro/hooks/doflow.json +53 -0
  66. package/core/harnesses/kiro/hooks/lib.sh +324 -0
  67. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +9 -0
  68. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +9 -0
  69. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +9 -0
  70. package/core/harnesses/kiro/hooks/session-start.sh +9 -0
  71. package/core/harnesses/kiro/hooks/stop-check.sh +43 -0
  72. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +39 -0
  73. package/core/harnesses/shared/hooks/adapters/antigravity.js +111 -0
  74. package/core/harnesses/shared/hooks/adapters/arg-fields.js +19 -0
  75. package/core/harnesses/shared/hooks/adapters/gemini.js +114 -0
  76. package/core/harnesses/shared/hooks/hooks.json +47 -0
  77. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +91 -0
  78. package/core/harnesses/shared/hooks/policies/capture-failure.sh +97 -0
  79. package/core/harnesses/shared/hooks/policies/deny-json.sh +29 -0
  80. package/core/harnesses/shared/hooks/policies/lib.sh +501 -0
  81. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +21 -0
  82. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +91 -0
  83. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +328 -0
  84. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +496 -0
  85. package/core/harnesses/shared/hooks/policies/prompt-nudge.jq +131 -0
  86. package/core/harnesses/shared/hooks/policies/session-context.sh +141 -0
  87. package/core/harnesses/shared/hooks/policies/stop-check.sh +264 -0
  88. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +46 -0
  89. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +235 -0
  90. package/core/harnesses/shared/hooks/stream-hook-runner.js +590 -0
  91. package/core/harnesses/shared/locator/doflow-run +87 -0
  92. package/core/registry/advice.json +101 -0
  93. package/core/registry/assets.json +792 -0
  94. package/core/registry/capabilities.json +120 -0
  95. package/core/registry/contracts.json +189 -0
  96. package/core/registry/external-tools.json +191 -0
  97. package/core/registry/harnesses.json +2062 -0
  98. package/core/registry/lifecycle.json +231 -0
  99. package/core/registry/mcp.json +18 -0
  100. package/core/registry/models.json +79 -0
  101. package/core/registry/readiness-templates.json +118 -0
  102. package/core/registry/routes.json +47 -0
  103. package/core/registry/storage.json +1061 -0
  104. package/core/registry/verification.json +258 -0
  105. package/core/registry/workflows.json +790 -0
  106. package/core/shared/agent-specs/core-implementer.md +73 -0
  107. package/core/shared/agent-specs/quality-guardian.md +28 -0
  108. package/core/shared/agent-specs/research-writer.md +31 -0
  109. package/core/shared/agent-specs/spec-analyst.md +33 -0
  110. package/core/shared/agent-specs/system-architect.md +29 -0
  111. package/core/shared/guidance/DOFLOW_CORE.md +8 -0
  112. package/core/shared/guidance/FLAGS.md +25 -0
  113. package/core/shared/guidance/PRINCIPLES.md +61 -0
  114. package/core/shared/guidance/VERSION +1 -0
  115. package/core/shared/guidance/mcp/MCP_Context7.md +18 -0
  116. package/core/shared/guidance/mcp/MCP_Sequential.md +17 -0
  117. package/core/shared/guidance/modes/MODE_Brainstorming.md +46 -0
  118. package/core/shared/guidance/modes/MODE_DeepResearch.md +69 -0
  119. package/core/shared/guidance/modes/MODE_Introspection.md +33 -0
  120. package/core/shared/guidance/modes/MODE_Orchestration.md +57 -0
  121. package/core/shared/guidance/modes/MODE_Task_Management.md +74 -0
  122. package/core/shared/guidance/pointers/claude-gemini.md +2 -0
  123. package/core/shared/guidance/pointers/codex.md +6 -0
  124. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +373 -0
  125. package/core/shared/guidance/references/ARTIFACT_VOICE.md +104 -0
  126. package/core/shared/guidance/references/CONSTITUTION_BASE.md +47 -0
  127. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +161 -0
  128. package/core/shared/guidance/references/DOFLOW_CHAIN.md +87 -0
  129. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +93 -0
  130. package/core/shared/guidance/references/MODEL_SELECTION.md +74 -0
  131. package/core/shared/guidance/references/ORCHESTRATION.md +36 -0
  132. package/core/shared/guidance/references/RESEARCH_CONFIG.md +21 -0
  133. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +81 -0
  134. package/core/shared/guidance/rules/RULE_01_SAFETY.md +25 -0
  135. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +23 -0
  136. package/core/shared/guidance/rules/RULE_03_QUALITY.md +14 -0
  137. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +46 -0
  138. package/core/shared/scripts/doflow/assets/mermaid-theme.json +46 -0
  139. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +167 -0
  140. package/core/shared/scripts/doflow/bash/do-git-state.sh +546 -0
  141. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +208 -0
  142. package/core/shared/scripts/doflow/bash/do-paths.sh +347 -0
  143. package/core/shared/scripts/doflow/bash/do-prereqs.sh +66 -0
  144. package/core/shared/scripts/doflow/bash/do-review-package.sh +127 -0
  145. package/core/shared/scripts/doflow/bash/do-task-brief.sh +698 -0
  146. package/core/shared/scripts/doflow/bash/render-audit.sh +224 -0
  147. package/core/shared/scripts/doflow/bash/render-diagrams.sh +177 -0
  148. package/core/shared/scripts/doflow/bash/render-puml.sh +223 -0
  149. package/core/shared/scripts/doflow/bash/sync-context.sh +66 -0
  150. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +526 -0
  151. package/core/shared/scripts/doflow/bin/doflow-run +592 -0
  152. package/core/shared/skills/do/SKILL.md +128 -0
  153. package/core/shared/skills/do/references/estimation.md +13 -0
  154. package/core/shared/skills/do/references/pm_routing.md +25 -0
  155. package/core/shared/skills/do/references/tool_matrix.md +52 -0
  156. package/core/shared/skills/do-brainstorm/SKILL.md +252 -0
  157. package/core/shared/skills/do-code-review/README.md +49 -0
  158. package/core/shared/skills/do-code-review/SKILL.md +333 -0
  159. package/core/shared/skills/do-code-review/assets/sample_c_clean.c +71 -0
  160. package/core/shared/skills/do-code-review/assets/sample_c_smells.c +66 -0
  161. package/core/shared/skills/do-code-review/assets/sample_cpp_shift.cpp +19 -0
  162. package/core/shared/skills/do-code-review/assets/sample_csharp_clean.cs +100 -0
  163. package/core/shared/skills/do-code-review/assets/sample_csharp_smells.cs +78 -0
  164. package/core/shared/skills/do-code-review/assets/sample_java_clean.java +55 -0
  165. package/core/shared/skills/do-code-review/assets/sample_java_smells.java +56 -0
  166. package/core/shared/skills/do-code-review/assets/sample_json_clean.json +7 -0
  167. package/core/shared/skills/do-code-review/assets/sample_markdown_clean.md +30 -0
  168. package/core/shared/skills/do-code-review/assets/sample_markdown_smells.md +29 -0
  169. package/core/shared/skills/do-code-review/assets/sample_shell_heredoc.sh +38 -0
  170. package/core/shared/skills/do-code-review/assets/sample_shell_smells.sh +15 -0
  171. package/core/shared/skills/do-code-review/assets/sample_typescript_strings.ts +36 -0
  172. package/core/shared/skills/do-code-review/assets/sample_yaml_smells.yaml +6 -0
  173. package/core/shared/skills/do-code-review/content-types/config.md +83 -0
  174. package/core/shared/skills/do-code-review/content-types/markdown.md +132 -0
  175. package/core/shared/skills/do-code-review/expected_outputs/sample_c_clean_quality.json +79 -0
  176. package/core/shared/skills/do-code-review/expected_outputs/sample_c_smells_quality.json +169 -0
  177. package/core/shared/skills/do-code-review/expected_outputs/sample_cpp_shift_quality.json +49 -0
  178. package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_clean_quality.json +149 -0
  179. package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_smells_quality.json +157 -0
  180. package/core/shared/skills/do-code-review/expected_outputs/sample_java_clean_quality.json +67 -0
  181. package/core/shared/skills/do-code-review/expected_outputs/sample_java_smells_quality.json +97 -0
  182. package/core/shared/skills/do-code-review/expected_outputs/sample_json_clean_quality.json +33 -0
  183. package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_clean_quality.json +1 -0
  184. package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_smells_quality.json +16 -0
  185. package/core/shared/skills/do-code-review/expected_outputs/sample_shell_heredoc_quality.json +54 -0
  186. package/core/shared/skills/do-code-review/expected_outputs/sample_shell_smells_quality.json +91 -0
  187. package/core/shared/skills/do-code-review/expected_outputs/sample_typescript_strings_quality.json +60 -0
  188. package/core/shared/skills/do-code-review/expected_outputs/sample_yaml_smells_quality.json +46 -0
  189. package/core/shared/skills/do-code-review/languages/c.md +103 -0
  190. package/core/shared/skills/do-code-review/languages/cpp.md +103 -0
  191. package/core/shared/skills/do-code-review/languages/csharp.md +97 -0
  192. package/core/shared/skills/do-code-review/languages/dart.md +108 -0
  193. package/core/shared/skills/do-code-review/languages/go.md +92 -0
  194. package/core/shared/skills/do-code-review/languages/java.md +103 -0
  195. package/core/shared/skills/do-code-review/languages/kotlin.md +88 -0
  196. package/core/shared/skills/do-code-review/languages/php.md +109 -0
  197. package/core/shared/skills/do-code-review/languages/python.md +94 -0
  198. package/core/shared/skills/do-code-review/languages/ruby.md +105 -0
  199. package/core/shared/skills/do-code-review/languages/rust.md +99 -0
  200. package/core/shared/skills/do-code-review/languages/shell.md +90 -0
  201. package/core/shared/skills/do-code-review/languages/swift.md +88 -0
  202. package/core/shared/skills/do-code-review/languages/typescript.md +97 -0
  203. package/core/shared/skills/do-code-review/review-policy.json +47 -0
  204. package/core/shared/skills/do-code-review/rules/universal.md +49 -0
  205. package/core/shared/skills/do-code-review/scripts/code_quality_checker.py +1696 -0
  206. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +532 -0
  207. package/core/shared/skills/do-code-review/scripts/pr_analyzer.py +620 -0
  208. package/core/shared/skills/do-code-review/scripts/review_report_generator.py +505 -0
  209. package/core/shared/skills/do-constitution/SKILL.md +72 -0
  210. package/core/shared/skills/do-design/SKILL.md +212 -0
  211. package/core/shared/skills/do-diagnose/SKILL.md +156 -0
  212. package/core/shared/skills/do-diagnose/references/code_audit.md +15 -0
  213. package/core/shared/skills/do-diagnose/references/refactoring.md +12 -0
  214. package/core/shared/skills/do-diagnose/references/root_cause.md +17 -0
  215. package/core/shared/skills/do-document/SKILL.md +81 -0
  216. package/core/shared/skills/do-document/references/api-reference.md +42 -0
  217. package/core/shared/skills/do-document/references/deep_research.md +18 -0
  218. package/core/shared/skills/do-document/references/embedded-research.md +11 -0
  219. package/core/shared/skills/do-document/references/implementation-flow.md +105 -0
  220. package/core/shared/skills/do-document/references/index_generator.md +9 -0
  221. package/core/shared/skills/do-document/references/user-guide.md +22 -0
  222. package/core/shared/skills/do-execute-plan/SKILL.md +261 -0
  223. package/core/shared/skills/do-execute-plan/references/handoff_record.md +57 -0
  224. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +92 -0
  225. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +155 -0
  226. package/core/shared/skills/do-execute-plan/references/run_position.md +63 -0
  227. package/core/shared/skills/do-execute-plan/references/scaffold.md +441 -0
  228. package/core/shared/skills/do-execute-plan/references/task_attempts.md +41 -0
  229. package/core/shared/skills/do-flow/SKILL.md +186 -0
  230. package/core/shared/skills/do-flow/references/task_classes.md +134 -0
  231. package/core/shared/skills/do-git/SKILL.md +115 -0
  232. package/core/shared/skills/do-git/references/hotfix-ritual.md +44 -0
  233. package/core/shared/skills/do-git/references/lifecycle-policy.md +54 -0
  234. package/core/shared/skills/do-git/references/release-ritual.md +64 -0
  235. package/core/shared/skills/do-implement/SKILL.md +222 -0
  236. package/core/shared/skills/do-plan/SKILL.md +219 -0
  237. package/core/shared/skills/do-test/SKILL.md +113 -0
  238. package/core/shared/templates/doflow/constitution-template.md +28 -0
  239. package/core/shared/templates/doflow/data-model-template.md +169 -0
  240. package/core/shared/templates/doflow/design-template.md +193 -0
  241. package/core/shared/templates/doflow/external-contract-template.md +35 -0
  242. package/core/shared/templates/doflow/intent-template.md +45 -0
  243. package/core/shared/templates/doflow/plan-template.md +151 -0
  244. package/core/shared/templates/doflow/question-log-template.md +51 -0
  245. package/core/shared/templates/doflow/requirement-template.md +126 -0
  246. package/core/shared/templates/doflow/specs-template.md +55 -0
  247. package/core/shared/templates/doflow/state-template.md +59 -0
  248. package/core/shared/workflows/do-flow-chain.md +17 -0
  249. package/package.json +48 -4
  250. package/src/adapters/antigravity/index.js +631 -0
  251. package/src/adapters/claude/index.js +548 -0
  252. package/src/adapters/claude/mcp.js +94 -0
  253. package/src/adapters/codex/agents.js +208 -0
  254. package/src/adapters/codex/config.js +272 -0
  255. package/src/adapters/codex/hooks.js +203 -0
  256. package/src/adapters/codex/index.js +707 -0
  257. package/src/adapters/codex/mcp.js +271 -0
  258. package/src/adapters/codex/native-projection.js +34 -0
  259. package/src/adapters/copilot/index.js +413 -0
  260. package/src/adapters/copy-tree.js +620 -0
  261. package/src/adapters/gemini/hooks.js +210 -0
  262. package/src/adapters/gemini/index.js +390 -0
  263. package/src/adapters/hook-commands.js +60 -0
  264. package/src/adapters/index.js +106 -0
  265. package/src/adapters/kiro/index.js +310 -0
  266. package/src/adapters/mcp-entries.js +331 -0
  267. package/src/adapters/opencode/index.js +466 -0
  268. package/src/adapters/pi/index.js +279 -0
  269. package/src/adapters/pi/json-members.js +185 -0
  270. package/src/adapters/pi/mcp.js +373 -0
  271. package/src/adapters/resolve-source.js +19 -0
  272. package/src/cli/commands/install.js +218 -0
  273. package/src/cli/commands/list-backups.js +21 -0
  274. package/src/cli/commands/reconcile.js +120 -0
  275. package/src/cli/commands/remove.js +82 -0
  276. package/src/cli/commands/rollback.js +172 -0
  277. package/src/cli/commands/self-update.js +33 -0
  278. package/src/cli/commands/status.js +138 -0
  279. package/src/cli/commands/tools.js +92 -0
  280. package/src/cli/commands/update.js +162 -0
  281. package/src/cli/index.js +568 -0
  282. package/src/cli/runtime-commands.js +281 -0
  283. package/src/cli/shared.js +218 -0
  284. package/src/helper/bash-helper.js +34 -0
  285. package/src/helper/git.js +24 -0
  286. package/src/helper/guardrails.js +49 -0
  287. package/src/helper/harness-paths.js +140 -0
  288. package/src/helper/marker-merge.js +116 -0
  289. package/src/helper/prompt.js +61 -0
  290. package/src/helper/repo-root.js +29 -0
  291. package/src/helper/settings-merge.js +151 -0
  292. package/src/helper/settings-scope.js +33 -0
  293. package/src/helper/toml.js +244 -0
  294. package/src/install/backup-set.js +149 -0
  295. package/src/install/backup.js +724 -0
  296. package/src/install/context.js +58 -0
  297. package/src/install/manifest.js +145 -0
  298. package/src/install/mcp.js +254 -0
  299. package/src/install/paths.js +84 -0
  300. package/src/install/targets.js +90 -0
  301. package/src/install/tool-lifecycle.js +151 -0
  302. package/src/lifecycle/bash-availability.js +25 -0
  303. package/src/lifecycle/index.js +812 -0
  304. package/src/lifecycle/mcp-index.js +28 -0
  305. package/src/lifecycle/policies.js +73 -0
  306. package/src/lifecycle/view.js +175 -0
  307. package/src/registry/index.js +611 -0
  308. package/src/release/build-package.js +91 -0
  309. package/src/release/changelog-section.js +70 -0
  310. package/src/release/sync-plugin-versions.js +66 -0
  311. package/src/runtime/advice/cli.js +283 -0
  312. package/src/runtime/advice/client.js +324 -0
  313. package/src/runtime/advice/config.js +189 -0
  314. package/src/runtime/advice/doctor.js +169 -0
  315. package/src/runtime/advice/endpoint-guard.js +134 -0
  316. package/src/runtime/advice/kinds.js +154 -0
  317. package/src/runtime/advice/providers/ollama-systemone.js +209 -0
  318. package/src/runtime/advice/record.js +166 -0
  319. package/src/runtime/advice/registry.js +170 -0
  320. package/src/runtime/advice/strength.js +66 -0
  321. package/src/runtime/brief-tools.js +342 -0
  322. package/src/runtime/c4-project.js +371 -0
  323. package/src/runtime/capability-router.js +484 -0
  324. package/src/runtime/checkouts.js +170 -0
  325. package/src/runtime/claims.js +468 -0
  326. package/src/runtime/cli-result.js +55 -0
  327. package/src/runtime/cli.js +678 -0
  328. package/src/runtime/command-detect.js +516 -0
  329. package/src/runtime/context-pack.js +492 -0
  330. package/src/runtime/decision-register.js +493 -0
  331. package/src/runtime/evidence-ledger.js +338 -0
  332. package/src/runtime/failure/capture.js +272 -0
  333. package/src/runtime/failure/classifier.js +68 -0
  334. package/src/runtime/failure/cli.js +178 -0
  335. package/src/runtime/failure/home.js +64 -0
  336. package/src/runtime/failure/store.js +226 -0
  337. package/src/runtime/feature-resolve.js +79 -0
  338. package/src/runtime/freshness.js +177 -0
  339. package/src/runtime/health.js +863 -0
  340. package/src/runtime/history-compactor.js +237 -0
  341. package/src/runtime/implementation-gate.js +321 -0
  342. package/src/runtime/inventory/coverage.js +218 -0
  343. package/src/runtime/inventory/identity.js +65 -0
  344. package/src/runtime/inventory/index.js +705 -0
  345. package/src/runtime/inventory/precedence.js +134 -0
  346. package/src/runtime/inventory/read-scopes.js +131 -0
  347. package/src/runtime/inventory/siblings.js +192 -0
  348. package/src/runtime/knowledge/chunker.js +124 -0
  349. package/src/runtime/knowledge/hybrid.js +149 -0
  350. package/src/runtime/knowledge/index-store.js +145 -0
  351. package/src/runtime/knowledge/retrieval.js +194 -0
  352. package/src/runtime/leak-scan.js +185 -0
  353. package/src/runtime/lifecycle/cli.js +276 -0
  354. package/src/runtime/lifecycle/event-store.js +340 -0
  355. package/src/runtime/lifecycle/fold.js +353 -0
  356. package/src/runtime/lifecycle/followup.js +432 -0
  357. package/src/runtime/lifecycle/goal.js +249 -0
  358. package/src/runtime/lifecycle/intent-writer.js +86 -0
  359. package/src/runtime/lifecycle/overview.js +292 -0
  360. package/src/runtime/lifecycle/release.js +241 -0
  361. package/src/runtime/lifecycle/report-store.js +221 -0
  362. package/src/runtime/lifecycle/retention.js +212 -0
  363. package/src/runtime/lifecycle/root.js +71 -0
  364. package/src/runtime/lifecycle/status.js +353 -0
  365. package/src/runtime/lifecycle/store-upkeep.js +393 -0
  366. package/src/runtime/locator-resolve.js +158 -0
  367. package/src/runtime/mask.js +210 -0
  368. package/src/runtime/mcp-consultations.js +412 -0
  369. package/src/runtime/mcp-reach.js +157 -0
  370. package/src/runtime/model-router.js +153 -0
  371. package/src/runtime/outcome.js +467 -0
  372. package/src/runtime/reach.js +148 -0
  373. package/src/runtime/readiness-record.js +150 -0
  374. package/src/runtime/readiness.js +435 -0
  375. package/src/runtime/recovery.js +282 -0
  376. package/src/runtime/research-request.js +231 -0
  377. package/src/runtime/retrieval-plan.js +578 -0
  378. package/src/runtime/scaffold/artifacts.js +217 -0
  379. package/src/runtime/scaffold/fingerprint.js +56 -0
  380. package/src/runtime/scaffold/generate.js +911 -0
  381. package/src/runtime/scaffold/languages.js +245 -0
  382. package/src/runtime/storage/cli.js +64 -0
  383. package/src/runtime/storage/engine.js +249 -0
  384. package/src/runtime/storage/registry.js +130 -0
  385. package/src/runtime/storage/roots.js +63 -0
  386. package/src/runtime/storage/rules.js +282 -0
  387. package/src/runtime/storage/upkeep.js +243 -0
  388. package/src/runtime/storage/work-status.js +326 -0
  389. package/src/runtime/task-attempts.js +415 -0
  390. package/src/runtime/task-classifier.js +550 -0
  391. package/src/runtime/task-scope.js +130 -0
  392. package/src/runtime/task-state.js +176 -0
  393. package/src/runtime/trace/indicators.js +235 -0
  394. package/src/runtime/trace/ledger.js +626 -0
  395. package/src/runtime/trace/render.js +76 -0
  396. package/src/runtime/trace/views.js +459 -0
  397. package/src/runtime/verification/contract-runner.js +341 -0
  398. package/src/runtime/verification/engine.js +1461 -0
  399. package/src/runtime/verification/registry.js +130 -0
  400. package/src/runtime/verification/scope-bound.js +418 -0
  401. package/src/runtime/verification/test-selection.js +172 -0
  402. package/src/runtime/workflow-engine.js +588 -0
  403. package/src/runtime/workflow-orchestrator.js +953 -0
  404. package/src/runtime/worktree.js +340 -0
  405. package/src/state/index.js +193 -0
  406. package/src/state/lockfile.js +136 -0
  407. package/src/state/run-lock.js +408 -0
  408. package/types/node-commonjs.d.ts +68 -0
  409. package/types/runtime.d.ts +91 -0
@@ -0,0 +1,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.js');
9
+
10
+ const RETAINED_DAYS = 90;
11
+ const PARTITION_NAME = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
12
+
13
+ function timestamp(now = () => new Date()) {
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 , requestedStore ) {
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 } ) {
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 ) { 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
+ } = {}) {
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 = {
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 } = {}) {
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 } = {}) {
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 , maxAgeDays = RETAINED_DAYS) {
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 , cutoff , fsImpl ) {
123
+ if (!root) return [];
124
+ const directory = path.join(root, 'advice', 'picks');
125
+ try {
126
+ return fsImpl.readdirSync(directory)
127
+ .filter((name ) => PARTITION_NAME.test(name) && name.slice(0, 10) >= cutoff)
128
+ .sort()
129
+ .map((name ) => path.join(directory, name));
130
+ } catch (error ) {
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 } = {}) {
137
+ const cutoff = cutoffDate(now, maxAgeDays);
138
+ if (!cutoff) return { records: [], unreadable: 0 };
139
+ const records = [];
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 ;
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 } = {}) {
160
+ if (typeof pickId !== 'string') return null;
161
+ const window = readWindow({ roots, now, fsImpl });
162
+ const found = window.records.find((entry ) => 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.js');
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 = {
15
+ choice: ['clearAt', 'splitWithin'],
16
+ yesno: ['clearAbove', 'clearBelow', 'splitFrom', 'splitTo'],
17
+ };
18
+
19
+ function isObject(value ) {
20
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
21
+ }
22
+
23
+ function addError(errors , key , message ) {
24
+ errors.push(`${key} ${message}`);
25
+ }
26
+
27
+ function validateAdviceRegistry(registry , { repoRoot = REPO_ROOT, models, fsImpl = fs } = {}) {
28
+ const errors = [];
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 ) => 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 ) { addError(errors, MODELS_FILE, `could not be loaded: ${error.message}`); }
93
+ }
94
+ const modelProviders = Array.isArray(models?.providers) ? new Set(models.providers.map((provider ) => provider?.id).filter((id ) => 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 , index ) => {
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 ) => 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 ) => typeof caller !== 'string')) addError(errors, `${base}.askedBy`, 'must be an array of strings');
143
+ }
144
+
145
+ const tier = registry.kinds.find((kind ) => 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 } = {}) {
157
+ const root = path.resolve(repoRoot);
158
+ const target = file || path.join(root, ADVICE_FILE);
159
+ let registry ;
160
+ try {
161
+ registry = JSON.parse(fsImpl.readFileSync(target, 'utf8'));
162
+ } catch (error ) {
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 , bands ) {
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 = Object.entries(probabilities)
15
+ .filter(([, probability]) => typeof probability === 'number' && Number.isFinite(probability))
16
+ .sort((left , right ) => 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 , index ) => [criterion, probabilities[String(index)]])
34
+ .filter(([, probability] ) => typeof probability === 'number' && Number.isFinite(probability))
35
+ .sort((left , right ) => 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 ) {
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 };