@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,467 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * The task's terminal record (feature 011, plan task B.2, design component C2; FR-011).
7
+ *
8
+ * A task that ends leaves nothing behind today. Readiness answered at some point, verification
9
+ * answered at some point, evidence accumulated — and then the run stopped, and the next worker
10
+ * opening the same task finds four states scattered across three stores and no statement of how it
11
+ * ended. This module writes that statement: **one closed-vocabulary terminal state, with the basis
12
+ * it rests on recorded beside it**, so "this finished" and "this stopped" are never the same
13
+ * record.
14
+ *
15
+ * It mirrors `readiness.ts`'s four-state shape and reuses `verification`'s meaning of
16
+ * `INCONCLUSIVE` — a verdict over zero evidence is not a pass — and it owns no verdict of its own:
17
+ *
18
+ * - It **never re-evaluates readiness and never re-runs verification.** Re-running verification
19
+ * would execute the project's checks as a side effect of filing a record, and re-evaluating
20
+ * readiness here would grade the task under a profile this verb invented rather than the one
21
+ * the stage actually acted on. Both verdicts therefore arrive as the caller's own statement of
22
+ * what its run saw, validated against the vocabulary each owning module exports, and are
23
+ * labelled in the record as stated rather than measured (the `callerAsserted` precedent
24
+ * `readiness`'s own verb already sets). A basis nobody stated reads `NOT_RECORDED` — never a
25
+ * pass, never an empty string, never absent.
26
+ * - It **does not decide when a task ends.** The workflow's terminal stage decides that (design
27
+ * A1), so the writer is learned from `workflow-engine`'s `terminalStage` for the declared
28
+ * class. A caller naming a non-terminal `--stage` is refused with the stage that is terminal.
29
+ * - The two things it *does* measure it measures itself, at the write: how many records the
30
+ * evidence ledger holds for this task, and which declared retrieval needs the run never
31
+ * reached (read from `retrieval-plan`'s own record, by its own path function).
32
+ *
33
+ * `recordedAt` is stamped here and is never accepted from the caller, exactly as the evidence
34
+ * ledger stamps freshness at the write — a timestamp a caller supplies is a claim about when
35
+ * something happened, not a record of it.
36
+ *
37
+ * No state here is a number, a percentage or a confidence (NFR-001). `basis.evidenceCount` is the
38
+ * only integer the record carries and it is a **count of records**: how many things were filed, not
39
+ * how good they were. Score-shaped inputs are refused by name at this boundary through the same
40
+ * one definition `bin/doflow.js` and `retrieval-plan.ts` use.
41
+ */
42
+
43
+ const fs = require('node:fs');
44
+ const path = require('node:path');
45
+ const { EvidenceLedger, assertSafeTaskId } = require('./evidence-ledger.js');
46
+ const { taskStoreDir } = require('./task-scope.js');
47
+ // The two verdict vocabularies, imported from the modules that own them rather than respelled
48
+ // here. A second copy would let this file accept a readiness state readiness itself has retired.
49
+ const { READINESS_STATES } = require('./readiness.js');
50
+ const { VERIFICATION_STATUSES } = require('./verification/engine.js');
51
+ const { WorkflowEngine } = require('./workflow-engine.js');
52
+ // Read, not re-derived: the retrieval plan's location is `retrieval-plan.ts`'s to define, and the
53
+ // unreached items this record carries forward are the ones that record already holds.
54
+ const { planPath, UNREACHED } = require('./retrieval-plan.js');
55
+ // One definition of the score refusal, applied at a further boundary. See the note on
56
+ // EVIDENCE_SCORE_FIELDS in cli.js: a terminal state that carried a confidence would be exactly the
57
+ // number NFR-001 exists to keep out of the one record the next worker reads first.
58
+ const { assertNoScoreFields } = require('./cli.js');
59
+ const { finishRuntime, usageError } = require('./cli-result.js');
60
+ const { REPO_ROOT } = require('../helper/repo-root.js');
61
+
62
+ // ── the closed terminal vocabulary (design §4) ───────────────────────────────────────────────
63
+
64
+ /** The work was done and rests on something. Unavailable over an empty ledger — see `record`. */
65
+ const COMPLETED = 'COMPLETED';
66
+ /** The work stopped against an obstacle it could not pass. It is not finished and not abandoned. */
67
+ const BLOCKED = 'BLOCKED';
68
+ /** The work was deliberately dropped. A decision, and distinct from having been stopped. */
69
+ const ABANDONED = 'ABANDONED';
70
+ /**
71
+ * Verification's meaning, unchanged: a verdict over zero evidence is not a pass. This is what a
72
+ * task ending with nothing recorded is, and the reason `COMPLETED` cannot describe it.
73
+ */
74
+ const INCONCLUSIVE = 'INCONCLUSIVE';
75
+
76
+ const OUTCOME_STATES = Object.freeze([COMPLETED, BLOCKED, ABANDONED, INCONCLUSIVE]);
77
+
78
+ /**
79
+ * What a basis field says when nobody stated it.
80
+ *
81
+ * Deliberately a token and not `null`: an absent readiness state and a readiness state of `READY`
82
+ * must not be able to read the same way to anything scanning these records, and an empty field
83
+ * invites the reader to supply the missing half themselves.
84
+ */
85
+ const NOT_RECORDED = 'NOT_RECORDED';
86
+
87
+ // ── the record ────────────────────────────────────────────────────────────────────────────────
88
+
89
+ /**
90
+ * Where a task's outcome lives, under the invoking project's state root.
91
+ *
92
+ * The task id is validated by the evidence ledger's own rule, unchanged: it becomes a filename, so
93
+ * it must never be able to name a path.
94
+ * @param {string} projectRoot
95
+ * @param {string} taskId
96
+ * @param {string|null} [slug] the feature the task belongs to, when not the branch's (task-scope.js)
97
+ * @returns {string}
98
+ */
99
+ function outcomePath(projectRoot , taskId , slug = null) {
100
+ const dir = taskStoreDir({ projectRoot, store: 'outcome', taskId: assertSafeTaskId(taskId), slug });
101
+ return path.join(dir, `${taskId}.json`);
102
+ }
103
+
104
+ /**
105
+ * @param {string} projectRoot
106
+ * @param {string} taskId
107
+ * @returns {Object|null} the record, or null when no outcome was recorded
108
+ */
109
+ function readOutcome(projectRoot , taskId , slug = null) {
110
+ const file = outcomePath(projectRoot, taskId, slug);
111
+ if (!fs.existsSync(file)) return null;
112
+ try {
113
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
114
+ } catch (error ) {
115
+ throw new Error(`the outcome record at '${file}' is not readable: ${error.message}`);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * @param {string} projectRoot
121
+ * @param {string} taskId
122
+ * @param {Object} record
123
+ * @returns {string} the file written
124
+ */
125
+ function writeOutcome(projectRoot , taskId , record , slug = null) {
126
+ const file = outcomePath(projectRoot, taskId, slug);
127
+ fs.mkdirSync(path.dirname(file), { recursive: true });
128
+ fs.writeFileSync(file, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
129
+ return file;
130
+ }
131
+
132
+ // ── the basis, half measured and half stated ─────────────────────────────────────────────────
133
+
134
+ /**
135
+ * How many records the ledger holds for this task.
136
+ *
137
+ * A count of records and nothing more (NFR-001). It does not weigh them, does not judge whether
138
+ * they are the right ones — that is readiness's job, and readiness already did it — and it is the
139
+ * one number this whole record contains.
140
+ *
141
+ * @param {string} projectRoot
142
+ * @param {string} taskId
143
+ * @returns {number}
144
+ */
145
+ function countEvidence(projectRoot , taskId , slug = null) {
146
+ const ledger = new EvidenceLedger({ repoRoot: projectRoot, slug });
147
+ ledger.load(taskId);
148
+ return ledger.queryEvidence({ taskId }).length;
149
+ }
150
+
151
+ /**
152
+ * Declared items no run reached, carried forward from retrieval and from verification.
153
+ *
154
+ * Every entry names its source, because the two halves are not the same kind of gap: a retrieval
155
+ * need that was declared and never asked is a lookup that did not happen, while an unstated or
156
+ * inconclusive verification verdict is a check that did not establish anything. Collapsing them
157
+ * into one unlabelled list would leave a reader unable to tell which one they are looking at.
158
+ *
159
+ * The retrieval half is read from the plan record — its own `UNREACHED` result, computed by the
160
+ * module that owns that vocabulary, not recomputed here.
161
+ *
162
+ * @param {string} projectRoot
163
+ * @param {string} taskId
164
+ * @param {string} verification the stated verification verdict, or NOT_RECORDED
165
+ * @returns {Array<string>}
166
+ */
167
+ function unreachedItems(projectRoot , taskId , verification , slug = null) {
168
+ const items = [];
169
+ const file = planPath(projectRoot, taskId, slug);
170
+ if (fs.existsSync(file)) {
171
+ let plan;
172
+ try {
173
+ plan = JSON.parse(fs.readFileSync(file, 'utf8'));
174
+ } catch (error ) {
175
+ throw new Error(`the retrieval plan at '${file}' is not readable: ${error.message}`);
176
+ }
177
+ for (const need of plan.needs || []) {
178
+ if (need.result === UNREACHED) items.push(`retrieval:${need.intent}`);
179
+ }
180
+ }
181
+ if (verification === NOT_RECORDED) {
182
+ items.push('verification:NOT_RECORDED — no verification verdict was stated for this task');
183
+ } else if (verification === 'INCONCLUSIVE') {
184
+ items.push('verification:INCONCLUSIVE — the contract reached a verdict over zero evidence');
185
+ }
186
+ return items;
187
+ }
188
+
189
+ /**
190
+ * Validates one caller-stated basis verdict against the vocabulary its owning module exports.
191
+ *
192
+ * An omitted verdict is `NOT_RECORDED`. An unrecognised one is refused with the valid set rather
193
+ * than stored: a basis field holding a token nothing else in the runtime uses would look like a
194
+ * verdict while meaning nothing.
195
+ *
196
+ * @param {string|undefined} value
197
+ * @param {Iterable<string>} vocabulary
198
+ * @param {string} flag the flag name, for the refusal
199
+ * @returns {{value: string, stated: boolean}}
200
+ */
201
+ function statedVerdict(value , vocabulary , flag ) {
202
+ if (value === undefined || value === null || String(value).trim() === '') {
203
+ return { value: NOT_RECORDED, stated: false };
204
+ }
205
+ const token = String(value).trim();
206
+ const valid = [...vocabulary];
207
+ if (!valid.includes(token)) {
208
+ throw new Error(`unknown ${flag} '${token}'. Valid: ${valid.join(', ')}, or omit the flag to record ${NOT_RECORDED}`);
209
+ }
210
+ return { value: token, stated: true };
211
+ }
212
+
213
+ // ── actions ───────────────────────────────────────────────────────────────────────────────────
214
+
215
+ /**
216
+ * `--action record` — write the terminal state with its basis.
217
+ *
218
+ * One consistency rule is enforced here and it is the one design §4 states: **`COMPLETED` is not
219
+ * available over an empty ledger.** A task whose evidence ledger holds nothing has no basis for
220
+ * "the work was done and rests on something", and `INCONCLUSIVE` is the state that describes it —
221
+ * the same rule `verify` applies to a contract with no checks. It is a refusal rather than a silent
222
+ * downgrade, because rewriting the caller's word would hide the thing worth knowing.
223
+ *
224
+ * A record replaces any previous one for the task: the record is keyed by task id and stamps one
225
+ * `recordedAt`, so a second write is a second terminal statement, not an amendment to the first.
226
+ *
227
+ * @returns {number} exit code
228
+ */
229
+ function recordOutcome({ projectRoot, packageRoot, taskId, taskClass, stage, state, readiness, verification, json, slug } ) {
230
+ if (state === undefined || state === null || String(state).trim() === '') {
231
+ return usageError('outcome',
232
+ `--action record needs --state <state>. Valid: ${OUTCOME_STATES.join(', ')}`, json);
233
+ }
234
+ const proposed = String(state).trim();
235
+ if (!OUTCOME_STATES.includes(proposed)) {
236
+ return usageError('outcome',
237
+ `unknown --state '${proposed}'. Valid: ${OUTCOME_STATES.join(', ')}`, json);
238
+ }
239
+
240
+ // The terminal stage is learned, never decided here (design A1). That is why the class is
241
+ // required on a write: without it there is no workflow to ask which stage is the last one, and
242
+ // guessing would put this module in charge of when a task ends.
243
+ if (typeof taskClass !== 'string' || taskClass.trim() === '') {
244
+ return usageError('outcome',
245
+ '--task-class is required to record an outcome: the workflow\'s terminal stage is what writes '
246
+ + 'one (design A1), and the class is what names that stage. Run `doflow workflow --task-class <c>` '
247
+ + 'to see it', json);
248
+ }
249
+ let workflow;
250
+ try {
251
+ workflow = new WorkflowEngine({ repoRoot: packageRoot }).resolveWorkflow(taskClass);
252
+ } catch (error ) {
253
+ return usageError('outcome', error.message, json, error);
254
+ }
255
+ const terminalStage = workflow.terminalStage;
256
+ if (stage !== undefined && stage !== null && String(stage).trim() !== '' && String(stage).trim() !== terminalStage.id) {
257
+ return usageError('outcome',
258
+ `stage '${String(stage).trim()}' is not the terminal stage of task class '${taskClass}' — `
259
+ + `'${terminalStage.id}' is (${workflow.stageIds.join(' -> ')}). The outcome is written by the `
260
+ + 'stage the workflow ends on, so a record from an earlier stage would state a task had ended '
261
+ + 'while stages remained', json);
262
+ }
263
+
264
+ let basisReadiness;
265
+ let basisVerification;
266
+ try {
267
+ basisReadiness = statedVerdict(readiness, READINESS_STATES, '--readiness');
268
+ basisVerification = statedVerdict(verification, VERIFICATION_STATUSES, '--verification');
269
+ } catch (error ) {
270
+ return usageError('outcome', error.message, json, error);
271
+ }
272
+
273
+ let evidenceCount;
274
+ let unreached;
275
+ try {
276
+ evidenceCount = countEvidence(projectRoot, taskId, slug);
277
+ unreached = unreachedItems(projectRoot, taskId, basisVerification.value, slug);
278
+ } catch (error ) {
279
+ return usageError('outcome', error.message, json, error);
280
+ }
281
+
282
+ if (proposed === COMPLETED && evidenceCount === 0) {
283
+ return usageError('outcome',
284
+ `'${COMPLETED}' is not available for task '${taskId}': its evidence ledger holds no records, so `
285
+ + `there is nothing the completion rests on. ${INCONCLUSIVE} is the state that describes a verdict `
286
+ + 'over zero evidence. Record the evidence with `doflow evidence --action add`, or record '
287
+ + `--state ${INCONCLUSIVE}`, json);
288
+ }
289
+
290
+ const statedByCaller = [
291
+ ...(basisReadiness.stated ? ['readiness'] : []),
292
+ ...(basisVerification.stated ? ['verification'] : []),
293
+ ];
294
+
295
+ const record = {
296
+ version: 1,
297
+ taskId,
298
+ state: proposed,
299
+ // Stamped here, never accepted from the caller: a caller-supplied timestamp is a claim about
300
+ // when a task ended, and this record's whole value is that it was written when it happened.
301
+ recordedAt: new Date().toISOString(),
302
+ taskClass,
303
+ writtenByStage: terminalStage.id,
304
+ basis: {
305
+ readiness: basisReadiness.value,
306
+ verification: basisVerification.value,
307
+ evidenceCount,
308
+ unreached,
309
+ },
310
+ // Which half of the basis rests on someone saying so. A record that mixes measured and stated
311
+ // inputs must say which is which, or the next reader treats both as measurements.
312
+ statedByCaller,
313
+ };
314
+ const file = writeOutcome(projectRoot, taskId, record, slug);
315
+
316
+ if (json) {
317
+ console.log(JSON.stringify({ action: 'record', ...record, stateFile: file }, null, 2));
318
+ return finishRuntime(0);
319
+ }
320
+ printOutcome(record, file, 'recorded');
321
+ return finishRuntime(0);
322
+ }
323
+
324
+ /**
325
+ * `--action show` — emit the recorded outcome.
326
+ *
327
+ * Exits 1 when none exists, and that is the *only* thing exit 1 means here. A recorded `BLOCKED`
328
+ * exits 0, because the record answered: overloading the code with "the task did not complete"
329
+ * would make "no outcome was ever recorded" and "the outcome was recorded and it was not a
330
+ * completion" indistinguishable to every caller, which is the collapse this feature exists to undo.
331
+ *
332
+ * @returns {number} exit code
333
+ */
334
+ function showOutcome({ projectRoot, taskId, json, slug } ) {
335
+ let record;
336
+ try {
337
+ record = readOutcome(projectRoot, taskId, slug);
338
+ } catch (error ) {
339
+ return usageError('outcome', error.message, json, error);
340
+ }
341
+ if (!record) {
342
+ const file = outcomePath(projectRoot, taskId, slug);
343
+ if (json) {
344
+ console.log(JSON.stringify({
345
+ action: 'show', taskId, state: null, stateFile: file,
346
+ summary: `no outcome is recorded for task '${taskId}'`,
347
+ }, null, 2));
348
+ } else {
349
+ console.log(`\nDoFlow Task Outcome [Task: ${taskId}]:`);
350
+ console.log('═'.repeat(78));
351
+ console.log(`No outcome is recorded for this task (looked in ${file}).`);
352
+ console.log('An unrecorded outcome is not a completed task and not a failed one — it is a task');
353
+ console.log('whose end nobody stated. The terminal stage records it with --action record.');
354
+ console.log('═'.repeat(78) + '\n');
355
+ }
356
+ return finishRuntime(1);
357
+ }
358
+
359
+ if (json) {
360
+ console.log(JSON.stringify({ action: 'show', ...record, stateFile: outcomePath(projectRoot, taskId, slug) }, null, 2));
361
+ return finishRuntime(0);
362
+ }
363
+ printOutcome(record, outcomePath(projectRoot, taskId, slug), 'recorded at');
364
+ return finishRuntime(0);
365
+ }
366
+
367
+ /** The one text rendering both actions print, so a shown record reads exactly as the written one. */
368
+ function printOutcome(record , file , verb ) {
369
+ const basis = record.basis || {};
370
+ console.log(`\nDoFlow Task Outcome [Task: ${record.taskId}] — ${record.state}:`);
371
+ console.log('═'.repeat(78));
372
+ console.log(`Task class: ${record.taskClass || 'not stated'}`);
373
+ console.log(`Written by: ${record.writtenByStage || 'not stated'} (the workflow's terminal stage)`);
374
+ console.log(`Recorded at: ${record.recordedAt}`);
375
+ console.log('─'.repeat(78));
376
+ console.log('Basis:');
377
+ console.log(` Readiness ${basis.readiness}`);
378
+ console.log(` Verification ${basis.verification}`);
379
+ console.log(` Evidence ${basis.evidenceCount} record(s) — a count of records, not a score`);
380
+ const stated = record.statedByCaller || [];
381
+ if (stated.length > 0) {
382
+ // Named, not hidden, exactly as the readiness verb names its own caller-asserted inputs.
383
+ console.log(` Caller-stated: ${stated.join(', ')} (stated by the run that recorded this, not measured here)`);
384
+ }
385
+ // NFR-004: what nothing reached is stated as prominently as what was decided.
386
+ const unreached = basis.unreached || [];
387
+ if (unreached.length > 0) {
388
+ console.log('─'.repeat(78));
389
+ console.log('Declared and never reached:');
390
+ for (const item of unreached) console.log(` ${item}`);
391
+ }
392
+ console.log('─'.repeat(78));
393
+ console.log(`Outcome ${verb} ${file}`);
394
+ console.log('═'.repeat(78) + '\n');
395
+ }
396
+
397
+ // ── the verb ──────────────────────────────────────────────────────────────────────────────────
398
+
399
+ /**
400
+ * Handles `doflow outcome` — record a task's terminal state with its basis, or show the one
401
+ * already recorded.
402
+ *
403
+ * @param {Object} options
404
+ * @param {string} options.taskId
405
+ * @param {'record'|'show'|'status'} [options.action='show']
406
+ * @param {string} [options.state] the terminal state, on `record`
407
+ * @param {string} [options.taskClass] the workflow whose terminal stage is writing, on `record`
408
+ * @param {string} [options.stage] the writing stage, refused when it is not the terminal one
409
+ * @param {string} [options.readiness] the readiness state the run saw
410
+ * @param {string} [options.verification] the verification verdict the run saw
411
+ * @param {boolean} [options.json=false]
412
+ * @param {string} [options.repoRoot] the DoFlow package root, holding the workflow registry
413
+ * @param {string} [options.stateRoot] the project the task belongs to, holding its state
414
+ * @returns {number} exit code
415
+ */
416
+ function handleOutcomeCommand(options = {}) {
417
+ const {
418
+ taskId, action = 'show', state, taskClass, stage, readiness, verification,
419
+ json = false, repoRoot, stateRoot, slug,
420
+ } = options;
421
+
422
+ // NFR-001, at the boundary where a caller's input first becomes a record. `bin/doflow.js` already
423
+ // refuses every score-shaped flag name on the command line; this is the same rule applied to the
424
+ // option object, so a second input surface cannot arrive later without inheriting the refusal.
425
+ try {
426
+ assertNoScoreFields(options, '');
427
+ } catch (error ) {
428
+ return usageError('outcome', error.message, json, error);
429
+ }
430
+
431
+ // `'status'` is the CLI's shared `--action` default, owned by `tools`. Every read-action verb
432
+ // accepts it as an alias for its own default rather than reporting a usage error on an argument
433
+ // the caller never typed — `verify`, `claim` and `retrieval-plan` already do exactly this.
434
+ const resolvedAction = action === 'status' ? 'show' : action;
435
+ if (resolvedAction !== 'record' && resolvedAction !== 'show') {
436
+ return usageError('outcome', `unknown --action '${action}'. Valid: record, show (default)`, json);
437
+ }
438
+
439
+ // The project the task belongs to owns the state; the package root owns the workflow registry,
440
+ // and is a different directory in every install.
441
+ const projectRoot = stateRoot || process.cwd();
442
+ const packageRoot = repoRoot || REPO_ROOT;
443
+
444
+ try {
445
+ assertSafeTaskId(taskId);
446
+ } catch (error ) {
447
+ return usageError('outcome', error.message, json, error);
448
+ }
449
+
450
+ if (resolvedAction === 'record') {
451
+ return recordOutcome({
452
+ projectRoot, packageRoot, taskId, taskClass, stage, state, readiness, verification, json, slug,
453
+ });
454
+ }
455
+ return showOutcome({ projectRoot, taskId, json, slug });
456
+ }
457
+
458
+ module.exports = {
459
+ handleOutcomeCommand,
460
+ outcomePath,
461
+ OUTCOME_STATES,
462
+ COMPLETED,
463
+ BLOCKED,
464
+ ABANDONED,
465
+ INCONCLUSIVE,
466
+ NOT_RECORDED,
467
+ };
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * Static reach: for each harness the ledger says is installed, can the skills it received find a
7
+ * dispatcher and a runtime? The answer mirrors the lookup order of the skill resolver and of
8
+ * `doflow-run` using stats only — no process is spawned and nothing touches the network — so
9
+ * `doctor` can say "installed, but a skill's first runtime call would stop" before a skill runs.
10
+ *
11
+ * Reach belongs to the `.doflow` root that holds the dispatcher, not to a harness: a harness is
12
+ * reached through a tree another harness installed in the same root.
13
+ */
14
+
15
+ const fs = require('node:fs');
16
+ const os = require('node:os');
17
+ const path = require('node:path');
18
+ const { readLedger, stateRoot } = require('../state/index.js');
19
+
20
+ /** Where the dispatcher sits under a `.doflow` root; the skill resolver and the locator shim spell the same path. */
21
+ const REACH_DISPATCHER_REL = 'scripts/doflow/bin/doflow-run';
22
+ /** Where the Node runtime sits under a `.doflow` root; `doflow-run` execs it from there. */
23
+ const REACH_RUNTIME_REL = 'runtime/bin/doflow.js';
24
+
25
+ /** `dir`, then each parent, ending when the parent is the directory itself (`/`, `C:\`). */
26
+ function* ancestors(dir ) {
27
+ for (let current = dir; ; current = path.dirname(current)) {
28
+ yield current;
29
+ if (path.dirname(current) === current) return;
30
+ }
31
+ }
32
+
33
+ function statIs(fsImpl , target , kind ) {
34
+ try { return fsImpl.statSync(target)[kind](); } catch { return false; }
35
+ }
36
+
37
+ function isExecutableFile(fsImpl , target ) {
38
+ if (!statIs(fsImpl, target, 'isFile')) return false;
39
+ try { fsImpl.accessSync(target, fs.constants.X_OK); return true; } catch { return false; }
40
+ }
41
+
42
+ /**
43
+ * @param {Object} options
44
+ * @param {Object} options.registry loaded registry; its harness order orders the rows
45
+ * @param {string} options.projectRoot directory the project walk starts from
46
+ * @param {string} [options.homeDir]
47
+ * @param {Object} [options.fsImpl]
48
+ * @returns {{rows: Array<{harness: string, scope: 'project'|'global', installRoot: string, state: 'REACHED'|'NO-REACH'|'N/A', root?: string, reason?: string, fix?: string}>, unreadable: Array<{scope: string, ledger: string, detail: string}>}}
49
+ */
50
+ function evaluateReach({ registry, projectRoot, homeDir = os.homedir(), fsImpl = fs } ) {
51
+ const real = (target ) => { try { return fsImpl.realpathSync(target); } catch { return path.resolve(target); } };
52
+ const start = real(projectRoot);
53
+ const home = real(homeDir);
54
+ const homeDoflow = path.join(home, '.doflow');
55
+ const homeDispatcher = path.join(homeDoflow, REACH_DISPATCHER_REL);
56
+
57
+ // Every ledger on the walk is read, not only the nearest: a removed harness's residue in a
58
+ // subdirectory must not hide the install that encloses it. The walk stops at the home directory,
59
+ // whose own ledger is the global scope; nothing above it is read.
60
+ const scopes = [];
61
+ if (start !== home) {
62
+ const walk = [];
63
+ for (const dir of ancestors(start)) {
64
+ if (dir === home) break;
65
+ walk.push(dir);
66
+ }
67
+ for (const installRoot of walk.filter((dir ) => statIs(fsImpl, path.join(dir, '.doflow', 'state'), 'isDirectory'))) {
68
+ scopes.push({
69
+ scope: 'project',
70
+ installRoot,
71
+ stateDir: path.join(installRoot, '.doflow', 'state'),
72
+ from: start,
73
+ searched: `${path.join(start, '.doflow', REACH_DISPATCHER_REL)} and each directory above it, then ${homeDispatcher}`,
74
+ doflowDirs: [...ancestors(start)].map((dir) => path.join(dir, '.doflow')).concat(homeDoflow),
75
+ fix: (harness ) => `npx @khoavu882/doflow install ${installRoot} -t ${harness}`,
76
+ });
77
+ }
78
+ }
79
+ scopes.push({
80
+ scope: 'global',
81
+ installRoot: home,
82
+ stateDir: stateRoot({ scope: 'global', homeDir: home }),
83
+ from: home,
84
+ searched: homeDispatcher,
85
+ doflowDirs: [homeDoflow],
86
+ fix: (harness ) => `npx @khoavu882/doflow install -g -t ${harness}`,
87
+ });
88
+
89
+ const harnessIds = registry.harnesses.map((harness ) => harness.id);
90
+ const rows = [];
91
+ const unreadable = [];
92
+ const seen = new Set ();
93
+ const addRow = (row ) => {
94
+ // A stale outer ledger that says what a nearer one already said adds nothing.
95
+ const key = JSON.stringify([row.harness, row.scope, row.state, row.root, row.reason]);
96
+ if (seen.has(key)) return;
97
+ seen.add(key);
98
+ rows.push(row);
99
+ };
100
+
101
+ for (const { scope, installRoot, stateDir, from, searched, doflowDirs, fix } of scopes) {
102
+ let ledger ;
103
+ try {
104
+ ledger = readLedger(stateDir, { fsImpl });
105
+ } catch (error ) {
106
+ unreadable.push({ scope, ledger: stateDir, detail: error.message });
107
+ continue;
108
+ }
109
+ if (!ledger) continue;
110
+
111
+ const resources = ledger.resources || [];
112
+ const installed = harnessIds.filter((id ) => resources.some((resource ) => resource.harness === id));
113
+ if (!installed.length) continue;
114
+
115
+ const root = doflowDirs.find((dir ) => isExecutableFile(fsImpl, path.join(dir, REACH_DISPATCHER_REL)));
116
+ const dispatcher = root && path.join(root, REACH_DISPATCHER_REL);
117
+ const configDir = [...ancestors(from)].map((dir) => path.join(dir, '.doflow')).find((dir) => statIs(fsImpl, dir, 'isDirectory'));
118
+ const runtime = [configDir, homeDoflow].filter(Boolean).some((dir) => statIs(fsImpl, path.join(dir, REACH_RUNTIME_REL), 'isFile'));
119
+ // The runtime is looked up in the nearest `.doflow` only, so a nearer one than this ledger's
120
+ // install hides that install, and installing there would not change what the skills find.
121
+ const hiddenBy = scope === 'project' && configDir && configDir !== path.join(installRoot, '.doflow') ? configDir : null;
122
+
123
+ for (const harness of installed) {
124
+ if (!resources.some((resource ) => resource.harness === harness && resource.assetId === 'skills.doflow')) {
125
+ addRow({ harness, scope, installRoot, state: 'N/A', reason: `no skills at ${scope} scope` });
126
+ } else if (dispatcher && runtime) {
127
+ addRow({ harness, scope, installRoot, state: 'REACHED', root });
128
+ } else {
129
+ addRow({
130
+ harness,
131
+ scope,
132
+ installRoot,
133
+ state: 'NO-REACH',
134
+ reason: dispatcher
135
+ ? `dispatcher at ${dispatcher} but no ${REACH_RUNTIME_REL}`
136
+ : `no executable dispatcher at ${searched}`,
137
+ fix: hiddenBy
138
+ ? `${hiddenBy} hides the install at ${installRoot}; remove it if it is stale, or run: npx @khoavu882/doflow install ${path.dirname(hiddenBy)} -t ${harness}`
139
+ : fix(harness),
140
+ });
141
+ }
142
+ }
143
+ }
144
+
145
+ return { rows, unreadable };
146
+ }
147
+
148
+ module.exports = { REACH_DISPATCHER_REL, REACH_RUNTIME_REL, evaluateReach };