@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,578 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * Declared retrieval, and the report over it (feature 011, plan task B.1, design component C1;
7
+ * FR-010, FR-013, FR-014, FR-015).
8
+ *
9
+ * The distinction this module exists to make: **"the provider found nothing" and "the provider was
10
+ * never asked" are not the same answer**, and neither of them is "the provider answered but its
11
+ * index could not be located". Collapsing any pair of those is how a stage concludes there are no
12
+ * dependents when what actually happened is that no graph exists. So retrieval is declared before
13
+ * it runs, and every declared item is reported afterwards — including the ones the run never
14
+ * reached, which are reported as unreached rather than omitted.
15
+ *
16
+ * It mirrors `verification-contract-runner.js`'s declare-then-report split, and owns nothing else:
17
+ * it performs no retrieval, chooses no provider (it asks `capability-router` for each need), and
18
+ * does not decide whether an unreached item matters — that judgement belongs to the stage that
19
+ * declared it.
20
+ *
21
+ * **Freshness qualifies an answer; it never routes one.** FR-009 freezes routing, so a provider's
22
+ * index state is recorded against the need and reported, and is not consulted when resolving which
23
+ * provider a need goes to. `probeFreshness` is reused unchanged rather than reimplemented — this
24
+ * module owns the qualification, `health.ts` owns the measurement.
25
+ *
26
+ * Two shape decisions are load-bearing and are enforced structurally rather than by convention:
27
+ *
28
+ * 1. **One probe per distinct provider, at declare time** (design R8, plan D2). `probeFreshness`
29
+ * walks the project tree to `SCAN_FILE_LIMIT` (4000 files), so probing per *need* would pay
30
+ * one full scan per need for answers that are identical per provider. A plan of six needs
31
+ * across two providers pays two scans, not six.
32
+ * 2. **Freshness is stored once per provider and referenced, never copied onto a need** (plan
33
+ * D7). `needs[]` carries a provider id and nothing about that provider's index; a reader
34
+ * dereferences `providers{}`. The record is therefore structurally unable to represent two
35
+ * needs resolving to the same provider while disagreeing about its index.
36
+ *
37
+ * No state here is a number, a percentage or a confidence (NFR-001). The only integers this module
38
+ * emits are counts of records.
39
+ */
40
+
41
+ const fs = require('node:fs');
42
+ const path = require('node:path');
43
+ const { CapabilityRouter } = require('./capability-router.ts');
44
+ const { EvidenceLedger, assertSafeTaskId } = require('./evidence-ledger.ts');
45
+ const { taskStoreDir } = require('./task-scope.ts');
46
+ const { probeFreshness } = require('./health.ts');
47
+ // One definition of the score refusal, applied at a second boundary. See the note on
48
+ // EVIDENCE_SCORE_FIELDS in cli.js: relevance is a property of a search, not of a fact, and a plan
49
+ // that recorded one would hand the next worker a confidence dressed as a measurement.
50
+ const { assertNoScoreFields } = require('./cli.ts');
51
+ const { finishRuntime, usageError } = require('./cli-result.ts');
52
+ const { REPO_ROOT } = require('../helper/repo-root.ts');
53
+
54
+ // ── the closed result vocabulary (design §4) ─────────────────────────────────────────────────
55
+
56
+ /** The provider was asked and answered with something; the ledger holds what it yielded. */
57
+ const RETRIEVED = 'RETRIEVED';
58
+ /** The provider was asked and answered with nothing — a negative finding, from a locatable index. */
59
+ const EMPTY = 'EMPTY';
60
+ /** The provider was never asked. The state this component exists to keep separate from EMPTY. */
61
+ const UNREACHED = 'UNREACHED';
62
+ /** It answered, but its index could not be located, so the answer carries no weight. */
63
+ const UNVERIFIED = 'UNVERIFIED';
64
+
65
+ const RESULTS = Object.freeze([RETRIEVED, EMPTY, UNREACHED, UNVERIFIED]);
66
+
67
+ // ── the freshness vocabulary, reused rather than reinvented (plan D1) ────────────────────────
68
+ //
69
+ // FRESH / STALE / UNKNOWN come from `probeFreshness` and are not respelled here. NOT_APPLICABLE is
70
+ // the runtime's existing word for "the alternative was not available, so nothing was missed"
71
+ // (trace-views.js) and covers a provider with no index concept at all — ripgrep and git read the
72
+ // working tree and history directly, so their answers are current by construction.
73
+ const NOT_APPLICABLE = 'NOT_APPLICABLE';
74
+ const FRESHNESS_STATES = Object.freeze(['FRESH', 'STALE', 'UNKNOWN', NOT_APPLICABLE]);
75
+
76
+ /**
77
+ * Design §4's mapping table, as data. It maps only the *empty* answer: a provider that returned
78
+ * something is RETRIEVED whatever the age of its index, because the thing it returned is a
79
+ * locator a reader can check.
80
+ *
81
+ * `STALE` deliberately does not produce `UNVERIFIED` (design R10). A stale index answered from
82
+ * real data and its limitation is known and stated; an unlocatable index answered from nothing.
83
+ * Collapsing the two would repeat, one level down, exactly the empty-versus-unreached conflation
84
+ * this module exists to prevent — so staleness travels as a marker beside `EMPTY`, and the
85
+ * provider's `refresh` command travels with it so the remedy is one step away.
86
+ */
87
+ const EMPTY_ANSWER_BY_FRESHNESS: Record<string, any> = Object.freeze({
88
+ UNKNOWN: UNVERIFIED,
89
+ STALE: EMPTY,
90
+ FRESH: EMPTY,
91
+ [NOT_APPLICABLE]: EMPTY,
92
+ });
93
+
94
+ /**
95
+ * What an empty answer becomes, given the freshness of the index behind it.
96
+ * @param {string} freshnessState one of FRESHNESS_STATES
97
+ * @returns {string} one of RESULTS
98
+ */
99
+ function resultForEmptyAnswer(freshnessState: any) {
100
+ const mapped = EMPTY_ANSWER_BY_FRESHNESS[freshnessState];
101
+ if (!mapped) {
102
+ // Fail closed rather than pick a default: an unrecognised freshness token means the record was
103
+ // written by something this table does not describe, and calling that a negative finding would
104
+ // be a verdict over an unread state.
105
+ return UNVERIFIED;
106
+ }
107
+ return mapped;
108
+ }
109
+
110
+ // ── the record ────────────────────────────────────────────────────────────────────────────────
111
+
112
+ /**
113
+ * Where a task's plan lives, under the invoking project's state root.
114
+ *
115
+ * The task id is validated by the evidence ledger's own rule, unchanged: it becomes a filename, so
116
+ * it must never be able to name a path.
117
+ * @param {string} projectRoot
118
+ * @param {string} taskId
119
+ * @param {string|null} [slug] the feature the task belongs to, when not the branch's (task-scope.js)
120
+ * @returns {string}
121
+ */
122
+ function planPath(projectRoot: any, taskId: any, slug: any = null) {
123
+ const dir = taskStoreDir({ projectRoot, store: 'retrieval', taskId: assertSafeTaskId(taskId), slug });
124
+ return path.join(dir, `${taskId}.json`);
125
+ }
126
+
127
+ /**
128
+ * @param {string} projectRoot
129
+ * @param {string} taskId
130
+ * @returns {Object|null} the record, or null when no plan was declared
131
+ */
132
+ function readPlan(projectRoot: any, taskId: any, slug: any = null) {
133
+ const file = planPath(projectRoot, taskId, slug);
134
+ if (!fs.existsSync(file)) return null;
135
+ try {
136
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
137
+ } catch (error: any) {
138
+ throw new Error(`the retrieval plan at '${file}' is not readable: ${error.message}`);
139
+ }
140
+ }
141
+
142
+ /**
143
+ * @param {string} projectRoot
144
+ * @param {string} taskId
145
+ * @param {Object} record
146
+ * @returns {string} the file written
147
+ */
148
+ function writePlan(projectRoot: any, taskId: any, record: any, slug: any = null) {
149
+ const file = planPath(projectRoot, taskId, slug);
150
+ fs.mkdirSync(path.dirname(file), { recursive: true });
151
+ fs.writeFileSync(file, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
152
+ return file;
153
+ }
154
+
155
+ /**
156
+ * The `sourceMode` each provider id declares, as a lookup the plan builds once per declare.
157
+ *
158
+ * A provider id can appear under several capabilities (`native.rg`, `graphify.query`); `index` wins
159
+ * so a provider declared index-backed anywhere is never read as live.
160
+ *
161
+ * @param {Object} capabilities the router's `capabilities` map
162
+ * @returns {(providerId: string) => string|null} the declared mode, or null for an unknown id
163
+ */
164
+ function sourceModeLookup(capabilities: any) {
165
+ const modes = new Map();
166
+ for (const capability of Object.values<any>(capabilities || {})) {
167
+ for (const provider of (capability && capability.providers) || []) {
168
+ if (!provider || !provider.id) continue;
169
+ if (modes.get(provider.id) === 'index') continue;
170
+ modes.set(provider.id, provider.sourceMode || null);
171
+ }
172
+ }
173
+ return (providerId: any) => (modes.has(providerId) ? modes.get(providerId) : null);
174
+ }
175
+
176
+ /**
177
+ * One provider's index freshness, in the shape the record stores it.
178
+ *
179
+ * Called once per distinct provider per plan and never per need — that bound is the whole of R8's
180
+ * mitigation, and it is why this function is called from a loop over a provider set rather than
181
+ * from the loop over needs.
182
+ *
183
+ * @param {string} providerId
184
+ * @param {string} projectRoot the project whose source the index is compared against
185
+ * @param {(providerId: string) => string|null} [sourceModeOf] the provider's declared `sourceMode`;
186
+ * absent, every provider without an artifact layout is treated as unknown (fail closed)
187
+ * @returns {{state: string, artifact: string|null, refresh: string|null, label: string|null, reason: string|null}}
188
+ */
189
+ function freshnessEntry(providerId: any, projectRoot: any, sourceModeOf: any = () => null) {
190
+ const probe = probeFreshness(providerId, { root: projectRoot });
191
+ if (!probe) {
192
+ // `probeFreshness` returns null for a provider it has no artifact layout for. What that means
193
+ // depends on how the provider answers: a `live` provider reads current files or commands, so
194
+ // there is no index to age; an `index` provider (or one the registry does not declare) answers
195
+ // from an artifact DoFlow cannot locate, so its freshness is UNKNOWN rather than trusted.
196
+ if (sourceModeOf(providerId) === 'live') {
197
+ return {
198
+ state: NOT_APPLICABLE,
199
+ artifact: null,
200
+ refresh: null,
201
+ label: null,
202
+ reason: 'this provider keeps no index DoFlow knows how to locate — a live filesystem or history '
203
+ + 'scan is current by construction, so there is nothing whose age could be measured',
204
+ };
205
+ }
206
+ return {
207
+ state: 'UNKNOWN',
208
+ artifact: null,
209
+ refresh: null,
210
+ label: null,
211
+ reason: 'index-backed provider with no artifact layout DoFlow knows; freshness cannot be established',
212
+ };
213
+ }
214
+ return {
215
+ state: probe.state,
216
+ artifact: probe.artifact,
217
+ // Carried through from PROVIDER_ARTIFACTS so the report can state the remedy, not only the
218
+ // problem: an index that cannot be verified is actionable, and the action is one command.
219
+ refresh: probe.refresh || null,
220
+ label: probe.label || null,
221
+ reason: probe.reason || null,
222
+ };
223
+ }
224
+
225
+ /**
226
+ * The freshness state a need's answer is qualified by, read from the plan's provider cache.
227
+ *
228
+ * Always a dereference, never a stored copy (plan D7).
229
+ * @param {Object} record
230
+ * @param {Object} need
231
+ * @returns {string} one of FRESHNESS_STATES
232
+ */
233
+ function freshnessOf(record: any, need: any) {
234
+ if (!need.provider) return NOT_APPLICABLE;
235
+ const entry: any = record.providers && record.providers[need.provider];
236
+ return entry && entry.state ? entry.state : NOT_APPLICABLE;
237
+ }
238
+
239
+ /**
240
+ * The ledger items a need produced: anything recorded for this task whose source names the
241
+ * provider the need resolved to, or the capability it resolved through.
242
+ *
243
+ * Both are checked because the evidence boundary requires `source.provider` and
244
+ * `source.capability` on every item but constrains neither to a registry id — a stage that
245
+ * recorded `grep` under `code.exact-search` still linked its find to the need that asked for it.
246
+ *
247
+ * @param {Object} need
248
+ * @param {Array<Object>} items
249
+ * @returns {Array<string>} evidence ids
250
+ */
251
+ function evidenceIdsFor(need: any, items: any) {
252
+ return items
253
+ .filter((item: any) => {
254
+ const source = item.source || {};
255
+ if (need.provider && source.provider === need.provider) return true;
256
+ return Boolean(need.capability) && source.capability === need.capability;
257
+ })
258
+ .map((item: any) => item.id);
259
+ }
260
+
261
+ // ── actions ───────────────────────────────────────────────────────────────────────────────────
262
+
263
+ /**
264
+ * `--action declare` — record the needs and the provider each resolves to, before retrieval runs.
265
+ *
266
+ * Exits 0 even when a need resolves to nothing. A need no provider can answer is recorded as
267
+ * declared-unresolvable (`provider: null`) rather than dropped, and it surfaces as UNREACHED at
268
+ * report time, which is where the exit-1 contract lives. Failing the declare would stop the stage
269
+ * before it retrieves the needs that *are* resolvable.
270
+ *
271
+ * A declare replaces any previous plan for the task: design §5 keys the record by task id and
272
+ * stamps one `stage` and one `declaredAt` on it, so a second stage declaring a plan is declaring
273
+ * its own, not appending to somebody else's.
274
+ *
275
+ * @returns {number} exit code
276
+ */
277
+ function declarePlan({ router, projectRoot, taskId, stage, intents, json, slug }: any) {
278
+ const needs = [];
279
+ for (const intent of intents) {
280
+ let resolution;
281
+ try {
282
+ resolution = router.resolveIntent(intent);
283
+ } catch (error: any) {
284
+ // A misspelled intent is a caller error, not an unresolvable need. Recording it as
285
+ // declared-unresolvable would file a typo as a machine limitation and hide it until report.
286
+ return usageError('retrieval-plan',
287
+ `${error.message}. Declared intents: ${Object.keys(router.routes).sort().join(', ')}`, json, error);
288
+ }
289
+ needs.push({
290
+ intent,
291
+ // Stored because it is what the evidence ledger records under `source.capability`, so the
292
+ // report can link a need to the ids it yielded without re-resolving the route.
293
+ capability: resolution.capability,
294
+ provider: resolution.selectedProvider ? resolution.selectedProvider.id : null,
295
+ result: UNREACHED,
296
+ evidenceIds: [],
297
+ });
298
+ }
299
+
300
+ // R8, in one loop: the probe set is the set of DISTINCT providers, so cost is bounded by
301
+ // provider count and not by need count. Six needs across two providers pay two scans.
302
+ const providers: Record<string, any> = {};
303
+ const sourceModeOf: any = sourceModeLookup(router.capabilities);
304
+ for (const need of needs) {
305
+ if (!need.provider || providers[need.provider]) continue;
306
+ providers[need.provider] = freshnessEntry(need.provider, projectRoot, sourceModeOf);
307
+ }
308
+
309
+ const record = {
310
+ version: 1,
311
+ taskId,
312
+ stage: stage || null,
313
+ declaredAt: new Date().toISOString(),
314
+ providers,
315
+ needs,
316
+ };
317
+ const file = writePlan(projectRoot, taskId, record, slug);
318
+
319
+ const unresolvable = needs.filter((need) => !need.provider).map((need) => need.intent);
320
+ if (json) {
321
+ console.log(JSON.stringify({ action: 'declare', ...record, stateFile: file, unresolvable }, null, 2));
322
+ return finishRuntime(0);
323
+ }
324
+
325
+ console.log(`\nDoFlow Retrieval Plan declared [Task: ${taskId}]:`);
326
+ console.log('═'.repeat(78));
327
+ console.log(`Stage: ${record.stage || 'not stated'}`);
328
+ console.log('─'.repeat(78));
329
+ console.log('Intent'.padEnd(26) + 'Provider'.padEnd(22) + 'Index');
330
+ for (const need of needs) {
331
+ console.log(
332
+ need.intent.padEnd(26)
333
+ + (need.provider || 'none — unresolvable').padEnd(22)
334
+ + freshnessOf(record, need),
335
+ );
336
+ }
337
+ printProviderIndexes(record);
338
+ if (unresolvable.length > 0) {
339
+ console.log('─'.repeat(78));
340
+ console.log(`Declared but unresolvable on this machine: ${unresolvable.join(', ')}`);
341
+ console.log(' No provider can answer these; they are recorded, not dropped, and will report as UNREACHED.');
342
+ }
343
+ console.log('─'.repeat(78));
344
+ console.log(`Declared ${needs.length} need(s) to ${file}`);
345
+ console.log('═'.repeat(78) + '\n');
346
+ return finishRuntime(0);
347
+ }
348
+
349
+ /**
350
+ * `--action report` — emit every declared item with what it returned.
351
+ *
352
+ * How an item earns each result:
353
+ * - **RETRIEVED** — the ledger holds evidence for this task from the need's provider or
354
+ * capability. What it yielded is linked by id.
355
+ * - **EMPTY** / **UNVERIFIED** — the caller named the intent in `--need`, stating it was asked,
356
+ * and the ledger holds nothing from it. Which of the two it becomes is design §4's table.
357
+ * - **UNREACHED** — everything else. A need nothing recorded and nobody said was asked was not
358
+ * asked, and the report will not upgrade that to a negative finding on its own.
359
+ *
360
+ * That is why `--need` is meaningful on report and not only on declare: only the caller knows
361
+ * whether a lookup ran and came back empty. Guessing would be the empty-versus-unreached
362
+ * conflation this whole module exists to prevent, committed by the reporter.
363
+ *
364
+ * Exits 1 when any declared item is UNREACHED or UNVERIFIED. Every declared need is required — the
365
+ * record carries no optionality, and inventing one here would be an escape hatch from the exit
366
+ * code rather than a fact about the plan.
367
+ *
368
+ * @returns {number} exit code
369
+ */
370
+ function reportPlan({ projectRoot, taskId, reached, json, slug }: any) {
371
+ let record;
372
+ try {
373
+ record = readPlan(projectRoot, taskId, slug);
374
+ } catch (error: any) {
375
+ return usageError('retrieval-plan', error.message, json, error);
376
+ }
377
+ if (!record) {
378
+ return usageError('retrieval-plan',
379
+ `no retrieval plan is declared for task '${taskId}' (looked in ${planPath(projectRoot, taskId, slug)}). `
380
+ + 'Declare one with --action declare --need <intent> before the retrieval runs; a report over no '
381
+ + 'plan would state that nothing was missed, which is not something an absent plan can establish', json);
382
+ }
383
+
384
+ const declared = new Set(record.needs.map((need: any) => need.intent));
385
+ const undeclared = [...reached].filter((intent) => !declared.has(intent)).sort();
386
+ if (undeclared.length > 0) {
387
+ return usageError('retrieval-plan',
388
+ `these intents are reported as reached but were never declared: ${undeclared.join(', ')}. `
389
+ + `Declared: ${[...declared].sort().join(', ')}`, json);
390
+ }
391
+
392
+ const ledger = new EvidenceLedger({ repoRoot: projectRoot, slug });
393
+ try {
394
+ ledger.load(taskId);
395
+ } catch (error: any) {
396
+ return usageError('retrieval-plan', error.message, json, error);
397
+ }
398
+ const items = ledger.queryEvidence({ taskId });
399
+
400
+ for (const need of record.needs) {
401
+ need.evidenceIds = evidenceIdsFor(need, items);
402
+ if (need.evidenceIds.length > 0) need.result = RETRIEVED;
403
+ else if (reached.has(need.intent)) need.result = resultForEmptyAnswer(freshnessOf(record, need));
404
+ else need.result = UNREACHED;
405
+ }
406
+
407
+ record.reportedAt = new Date().toISOString();
408
+ const file = writePlan(projectRoot, taskId, record, slug);
409
+
410
+ // Derived views, computed at emit and never stored on a need — a need holds a provider id and
411
+ // nothing about that provider's index (plan D7).
412
+ const byResult = (result: any) => record.needs.filter((need: any) => need.result === result).map((need: any) => need.intent);
413
+ const stale = record.needs.filter((need: any) => freshnessOf(record, need) === 'STALE').map((need: any) => need.intent);
414
+ const unreached = byResult(UNREACHED);
415
+ const unverified = byResult(UNVERIFIED);
416
+ const incomplete = unreached.length > 0 || unverified.length > 0;
417
+
418
+ if (json) {
419
+ console.log(JSON.stringify({
420
+ action: 'report',
421
+ ...record,
422
+ stateFile: file,
423
+ summary: {
424
+ declared: record.needs.length,
425
+ retrieved: byResult(RETRIEVED).length,
426
+ empty: byResult(EMPTY).length,
427
+ unreached: unreached.length,
428
+ unverified: unverified.length,
429
+ },
430
+ unreached,
431
+ unverified,
432
+ stale,
433
+ }, null, 2));
434
+ return finishRuntime(incomplete ? 1 : 0);
435
+ }
436
+
437
+ console.log(`\nDoFlow Retrieval Report [Task: ${taskId}]:`);
438
+ console.log('═'.repeat(78));
439
+ console.log(`Stage: ${record.stage || 'not stated'} Declared: ${record.declaredAt}`);
440
+ console.log('─'.repeat(78));
441
+ console.log('Intent'.padEnd(26) + 'Provider'.padEnd(22) + 'Index'.padEnd(16) + 'Result');
442
+ for (const need of record.needs) {
443
+ const freshness = freshnessOf(record, need);
444
+ console.log(
445
+ need.intent.padEnd(26)
446
+ + (need.provider || 'none').padEnd(22)
447
+ + freshness.padEnd(16)
448
+ + need.result + (freshness === 'STALE' ? ' (stale index)' : ''),
449
+ );
450
+ }
451
+ printProviderIndexes(record);
452
+ console.log('─'.repeat(78));
453
+ // NFR-004: the skip is stated as prominently as the failure, on its own line and named.
454
+ if (unreached.length > 0) {
455
+ console.log(`UNREACHED — declared and never asked: ${unreached.join(', ')}`);
456
+ }
457
+ if (unverified.length > 0) {
458
+ console.log(`UNVERIFIED — answered with nothing from an index that could not be located: ${unverified.join(', ')}`);
459
+ console.log(' This is not a negative finding. Rebuild the index above, or record what you found by other means.');
460
+ }
461
+ if (!incomplete) console.log('Every declared need was reached and its answer is grounded.');
462
+ console.log(`Reported ${record.needs.length} declared need(s) to ${file}`);
463
+ console.log('═'.repeat(78) + '\n');
464
+ return finishRuntime(incomplete ? 1 : 0);
465
+ }
466
+
467
+ /** The provider index block both actions print: state, what was located, and the remedy. */
468
+ function printProviderIndexes(record: any) {
469
+ const entries = Object.entries<any>(record.providers || {});
470
+ if (entries.length === 0) return;
471
+ console.log('─'.repeat(78));
472
+ console.log('Provider indexes (probed once each, at declare time):');
473
+ for (const [providerId, entry] of entries) {
474
+ console.log(` ${providerId} — ${entry.state}${entry.label ? ` (${entry.label})` : ''}`);
475
+ if (entry.artifact) console.log(` at ${entry.artifact}`);
476
+ if (entry.reason) console.log(` ${entry.reason}`);
477
+ if (entry.refresh && entry.state !== 'FRESH') console.log(` Refresh: ${entry.refresh}`);
478
+ }
479
+ }
480
+
481
+ // ── the verb ──────────────────────────────────────────────────────────────────────────────────
482
+
483
+ /** `--need a,b --need c` and `--need a --need b` are the same list. */
484
+ function parseIntents(raw: any) {
485
+ const list = Array.isArray(raw) ? raw : (raw === undefined || raw === null ? [] : [raw]);
486
+ const out: any = [];
487
+ for (const entry of list) {
488
+ for (const token of String(entry).split(',')) {
489
+ const trimmed = token.trim();
490
+ if (trimmed !== '' && !out.includes(trimmed)) out.push(trimmed);
491
+ }
492
+ }
493
+ return out;
494
+ }
495
+
496
+ /**
497
+ * Handles `doflow retrieval-plan` — declare the information needs a stage intends to resolve, and
498
+ * report every declared item afterwards.
499
+ *
500
+ * @param {Object} options
501
+ * @param {string} options.taskId
502
+ * @param {'declare'|'report'|'status'} [options.action='report']
503
+ * @param {Array<string>|string} [options.need] intents, comma-separated or repeated
504
+ * @param {string} [options.stage] the stage id declaring the plan
505
+ * @param {boolean} [options.json=false]
506
+ * @param {string} [options.repoRoot] the DoFlow package root, holding the capability registries
507
+ * @param {string} [options.stateRoot] the project the task belongs to; its state and its index
508
+ * @returns {number} exit code
509
+ */
510
+ function handleRetrievalPlanCommand(options: any = {}) {
511
+ const { taskId, action = 'report', need, stage, json = false, repoRoot, stateRoot, slug } = options;
512
+
513
+ // NFR-001, at the boundary where a caller's input first becomes a record. `bin/doflow.js` already
514
+ // refuses every score-shaped flag name on the command line; this is the same rule applied to the
515
+ // option object, so a second input surface cannot arrive later without inheriting the refusal.
516
+ try {
517
+ assertNoScoreFields(options, '');
518
+ } catch (error: any) {
519
+ return usageError('retrieval-plan', error.message, json, error);
520
+ }
521
+
522
+ // `'status'` is the CLI's shared `--action` default, owned by `tools`. Every read-action verb
523
+ // accepts it as an alias for its own default rather than reporting a usage error on an argument
524
+ // the caller never typed — `verify` and `claim` already do exactly this.
525
+ const resolvedAction = action === 'status' ? 'report' : action;
526
+ if (resolvedAction !== 'declare' && resolvedAction !== 'report') {
527
+ return usageError('retrieval-plan', `unknown --action '${action}'. Valid: declare, report (default)`, json);
528
+ }
529
+
530
+ // The project the task belongs to owns both the state and the index whose age is being measured.
531
+ // The package root owns the capability registries, and is a different directory in every install.
532
+ const projectRoot = stateRoot || process.cwd();
533
+ const packageRoot = repoRoot || REPO_ROOT;
534
+
535
+ try {
536
+ assertSafeTaskId(taskId);
537
+ } catch (error: any) {
538
+ return usageError('retrieval-plan', error.message, json, error);
539
+ }
540
+
541
+ const intents = parseIntents(need);
542
+
543
+ if (resolvedAction === 'declare') {
544
+ if (intents.length === 0) {
545
+ return usageError('retrieval-plan',
546
+ '--action declare needs at least one --need <intent>: a plan that declares nothing cannot '
547
+ + 'later report that anything went unreached, which is the only thing it is for', json);
548
+ }
549
+ if (stage !== undefined && stage !== null && String(stage).trim() === '') {
550
+ return usageError('retrieval-plan', '--stage requires a value', json);
551
+ }
552
+ let router;
553
+ try {
554
+ router = new CapabilityRouter({ repoRoot: packageRoot });
555
+ } catch (error: any) {
556
+ return usageError('retrieval-plan', error.message, json, error);
557
+ }
558
+ return declarePlan({ router, projectRoot, taskId, stage, intents, json, slug });
559
+ }
560
+
561
+ return reportPlan({ projectRoot, taskId, reached: new Set(intents), json, slug });
562
+ }
563
+
564
+ module.exports = {
565
+ handleRetrievalPlanCommand,
566
+ resultForEmptyAnswer,
567
+ freshnessEntry,
568
+ sourceModeLookup,
569
+ planPath,
570
+ RESULTS,
571
+ RETRIEVED,
572
+ EMPTY,
573
+ UNREACHED,
574
+ UNVERIFIED,
575
+ NOT_APPLICABLE,
576
+ FRESHNESS_STATES,
577
+ EMPTY_ANSWER_BY_FRESHNESS,
578
+ };