@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,150 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { updateTaskState } = require('./task-state.ts');
8
+ const { resolveTaskScope } = require('./task-scope.ts');
9
+ const { findStateFile } = require('./checkouts.ts');
10
+
11
+ // The readiness record: the latest evaluation of one task, kept where `readiness` ran.
12
+ //
13
+ // An evaluation used to vanish once printed, so a later step could only evaluate again with
14
+ // whatever inputs it was handed at that moment. Recording it lets the handoff, `verify` and the
15
+ // edit hook ask one question — was this task READY before the work started — and read the answer
16
+ // from any checkout of the repository.
17
+
18
+ const RECORD_VERSION = 1;
19
+ const STORE = path.join('.doflow', 'state', 'readiness');
20
+
21
+ /**
22
+ * Where a task's record may be under one checkout, in the order every reader takes them: under the
23
+ * feature's namespace, then flat. The writer, `verify`, `orchestrate` and the edit hook all use this
24
+ * one list, so a record written anywhere is found by each of them from any checkout.
25
+ * @param {string} taskId
26
+ * @param {string|null} slug the task's feature slug, when one is known
27
+ * @returns {Array<string>} paths relative to a checkout root
28
+ */
29
+ function recordCandidates(taskId: string, slug: any) {
30
+ const flat = path.join(STORE, `${taskId}.json`);
31
+ return slug && slug !== taskId ? [path.join(STORE, slug, `${taskId}.json`), flat] : [flat];
32
+ }
33
+
34
+ /** A record version this runtime cannot read because a newer DoFlow wrote it, else null. */
35
+ function newerVersionOf(record: any) {
36
+ const v = record && typeof record === 'object' ? record.version : undefined;
37
+ return Number.isInteger(v) && v > RECORD_VERSION ? v : null;
38
+ }
39
+
40
+ /** The refusal for a record a newer DoFlow wrote: it names the file and is never overwritten. */
41
+ function newerRecordText(file: any, version: any) {
42
+ return `the readiness record ${file} was written by a newer DoFlow (record version ${version}; this runtime reads ${RECORD_VERSION}), `
43
+ + 'so it was left as it is. Run this command with the DoFlow that wrote the record, or upgrade DoFlow here.';
44
+ }
45
+
46
+ /**
47
+ * What is already at `file` before a write: nothing, a record to replace, a record a newer DoFlow
48
+ * wrote, or bytes no evaluation can be read from.
49
+ * @returns {{kind: 'none'|'record'|'newer'|'unreadable', version?: number}}
50
+ */
51
+ function priorRecord(file: any, fsImpl: any) {
52
+ let text;
53
+ try {
54
+ text = fsImpl.readFileSync(file, 'utf8');
55
+ } catch (error: any) {
56
+ if (error.code === 'ENOENT') return { kind: 'none' };
57
+ throw error;
58
+ }
59
+ let data;
60
+ try {
61
+ data = JSON.parse(text);
62
+ } catch {
63
+ return { kind: 'unreadable' };
64
+ }
65
+ const newer = newerVersionOf(data);
66
+ if (newer !== null) return { kind: 'newer', version: newer };
67
+ // The state-file reader refuses any other version it did not write, so such bytes are set aside too.
68
+ if (data && typeof data === 'object' && data.version !== undefined && data.version !== RECORD_VERSION) return { kind: 'unreadable' };
69
+ return { kind: 'record' };
70
+ }
71
+
72
+ /**
73
+ * Records one evaluation, replacing the previous one for the task. A previous file that cannot be
74
+ * read is moved to `<file>.unreadable` (replacing an earlier one there) so the refusal that sent the
75
+ * caller here can be cleared; one a newer DoFlow wrote is left alone and the write is refused.
76
+ * @param {Object} options
77
+ * @param {string} options.stateRoot
78
+ * @param {string} options.taskId
79
+ * @param {string|null} [options.slug]
80
+ * @param {Object} options.report an `evaluateTaskReadiness` result
81
+ * @param {Object} options.inputs only the caller-stated keys that were given
82
+ * @param {Array<string>|null} [options.declaredScope] the scope's paths when it parses as a path list
83
+ * @param {string} options.mode
84
+ * @param {Date} [options.now]
85
+ * @param {Object} [options.fsImpl]
86
+ * @returns {{file: string, record: Object, replacedUnreadable: string|null}} the `.unreadable` path when
87
+ * a previous file was set aside
88
+ */
89
+ function writeReadinessRecord({ stateRoot, taskId, slug = null, report, inputs, declaredScope = null, mode, now = new Date(), fsImpl = fs }: any) {
90
+ const scope = resolveTaskScope({ projectRoot: stateRoot, taskId, slug });
91
+ const recordSlug = scope.slug || slug || null;
92
+ // The record a reader would find in this checkout is the one replaced; with none, the record goes
93
+ // where the task's evidence goes (under the feature's namespace when it has a register).
94
+ const file = recordCandidates(taskId, recordSlug).map((rel) => path.join(stateRoot, rel)).find((f) => fsImpl.existsSync(f))
95
+ || path.join(stateRoot, STORE, ...(scope.namespace ? [scope.namespace] : []), `${taskId}.json`);
96
+ const record = {
97
+ version: RECORD_VERSION,
98
+ taskId,
99
+ slug: recordSlug,
100
+ taskClass: report.taskClass,
101
+ templateName: report.templateName,
102
+ state: report.state,
103
+ stageEntry: report.stageEntry ? report.stageEntry.decision : null,
104
+ executionMode: mode,
105
+ inputs: { ...inputs },
106
+ declaredScope: Array.isArray(declaredScope) ? [...declaredScope] : null,
107
+ unmet: (report.requirements || []).filter((r: any) => r.required && !r.satisfied).map((r: any) => r.id),
108
+ evidenceCount: Number.isInteger(report.evidenceCount) ? report.evidenceCount : 0,
109
+ evaluatedAt: now.toISOString(),
110
+ };
111
+ // Inspected and set aside under the write's own lock, so a concurrent writer can neither rename
112
+ // a record just written nor slip a newer one in between the check and the write.
113
+ let replacedUnreadable = null;
114
+ const prepare = () => {
115
+ const prior = priorRecord(file, fsImpl);
116
+ if (prior.kind === 'newer') throw new Error(newerRecordText(file, prior.version));
117
+ if (prior.kind === 'unreadable') {
118
+ replacedUnreadable = `${file}.unreadable`;
119
+ fsImpl.renameSync(file, replacedUnreadable);
120
+ }
121
+ };
122
+ updateTaskState({ fsImpl, file, prepare, build: () => record });
123
+ return { file, record, replacedUnreadable };
124
+ }
125
+
126
+ /**
127
+ * The task's record in this checkout, else in exactly one other checkout, at the first of
128
+ * `recordCandidates` that exists in each. No resolver is run: the candidates depend only on the
129
+ * task id and the slug the caller names.
130
+ * @param {Object} options
131
+ * @param {string} options.stateRoot
132
+ * @param {string} options.taskId
133
+ * @param {string|null} [options.slug]
134
+ * @param {Function} [options.exec]
135
+ * @param {Object} [options.fsImpl]
136
+ * @returns {{status: 'found'|'missing'|'ambiguous'|'unreadable', record: Object|null, file: string|null,
137
+ * origin: 'current'|'other'|null, candidates: Array<string>, detail: string|null}}
138
+ */
139
+ function readReadinessRecord({ stateRoot, taskId, slug = null, exec, fsImpl = fs }: any) {
140
+ const found = findStateFile({ stateRoot, relPath: recordCandidates(taskId, slug), ...(exec ? { exec } : {}), fsImpl });
141
+ const base = { record: null, file: found.file, origin: found.origin, candidates: found.candidates, detail: null };
142
+ if (found.status !== 'found') return { ...base, status: found.status };
143
+ try {
144
+ return { ...base, status: 'found', record: JSON.parse(fsImpl.readFileSync(found.file, 'utf8')) };
145
+ } catch (error: any) {
146
+ return { ...base, status: 'unreadable', detail: `unparsable JSON: ${error.message}` };
147
+ }
148
+ }
149
+
150
+ module.exports = { writeReadinessRecord, readReadinessRecord, recordCandidates, newerVersionOf, RECORD_VERSION };
@@ -0,0 +1,435 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { parseYamlFile } = require('./capability-router.ts');
8
+ const { resolveLocator, describeResolution } = require('./locator-resolve.ts');
9
+ const { REPO_ROOT } = require('../helper/repo-root.ts');
10
+
11
+ const READINESS_STATES = new Set([
12
+ 'READY',
13
+ 'NEEDS_EVIDENCE',
14
+ 'NEEDS_USER_DECISION',
15
+ 'BLOCKED',
16
+ ]);
17
+
18
+ // ── The one stage-entry policy ───────────────────────────────────────────────────────────────────
19
+ //
20
+ // Whether a readiness state permits editing used to live in prose, three times, disagreeing:
21
+ // do-implement allowed edits under NEEDS_EVIDENCE and NEEDS_USER_DECISION, do-flow stopped on
22
+ // anything but READY, and readiness_gate.md said gather-or-ask first — so entering the same work
23
+ // through different skills changed whether unresolved prerequisites blocked editing (review R3,
24
+ // P1). The policy now lives here, once; skills consume the decision instead of re-deriving it.
25
+ //
26
+ // The standalone exemption is an explicit execution mode, not an inference from a missing evidence
27
+ // record: a caller doing a deliberate one-off fix passes `--mode standalone`, and only the
28
+ // NEEDS_EVIDENCE row softens for it — an owed user decision or contradicting evidence stops edits
29
+ // in every mode, because neither is cured by the work being small.
30
+
31
+ const EXECUTION_MODES = new Set(['workflow', 'standalone']);
32
+ const STAGE_ENTRY_DECISIONS = new Set(['ENTER', 'GATHER_FIRST', 'ASK_USER', 'STOP']);
33
+
34
+ /**
35
+ * Maps a computed readiness state and a declared execution mode to the one stage-entry decision.
36
+ * @param {string} state one of READINESS_STATES
37
+ * @param {string} [mode='workflow'] 'workflow' | 'standalone'
38
+ * @returns {{decision: string, reason: string}}
39
+ */
40
+ function stageEntryFor(state: any, mode = 'workflow') {
41
+ if (!EXECUTION_MODES.has(mode)) {
42
+ throw new Error(`Unknown execution mode '${mode}'. Valid: ${[...EXECUTION_MODES].join(', ')} — the standalone exemption is declared, never inferred.`);
43
+ }
44
+ if (!READINESS_STATES.has(state)) {
45
+ throw new Error(`Unknown readiness state '${state}' has no stage-entry decision.`);
46
+ }
47
+ switch (state) {
48
+ case 'BLOCKED':
49
+ return { decision: 'STOP', reason: 'A claim on this task is conflicted — evidence disagrees with itself. No mode edits through that; resolve the contradiction first.' };
50
+ case 'NEEDS_USER_DECISION':
51
+ return { decision: 'ASK_USER', reason: 'A decision is owed by the user. Ask it and wait; editing first would decide it silently on their behalf.' };
52
+ case 'NEEDS_EVIDENCE':
53
+ return mode === 'standalone'
54
+ ? { decision: 'ENTER', reason: 'Standalone mode: the unmet contract is reported, not enforced — a declared one-off edit outside a workflow run. Relay the missing requirements in the result.' }
55
+ : { decision: 'GATHER_FIRST', reason: 'Workflow mode: gather the missing requirements before entering the stage; do not start editing on the assumption it will work out.' };
56
+ case 'READY':
57
+ default:
58
+ return { decision: 'ENTER', reason: 'Every mandatory prerequisite is verified by fresh evidence.' };
59
+ }
60
+ }
61
+
62
+ class ReadinessEngine {
63
+ declare fsImpl: any;
64
+ declare repoRoot: any;
65
+ declare projectRoot: any;
66
+ declare templatePath: any;
67
+ declare templates: any;
68
+ /**
69
+ * @param {Object} [options]
70
+ * @param {string} [options.repoRoot] the DoFlow install, where readiness-templates.json lives
71
+ * @param {string} [options.projectRoot] the repository under work, which evidence locators name.
72
+ * Distinct from repoRoot on purpose: when the CLI runs inside another project, the templates
73
+ * come from the install and the locators must be resolved against that project, not the install.
74
+ * @param {string} [options.templatePath]
75
+ * @param {Object} [options.templates]
76
+ * @param {Object} [options.fsImpl]
77
+ */
78
+ constructor(options: any = {}) {
79
+ this.fsImpl = options.fsImpl || fs;
80
+ this.repoRoot = options.repoRoot || REPO_ROOT;
81
+ this.projectRoot = options.projectRoot || process.cwd();
82
+ this.templatePath = options.templatePath || path.join(this.repoRoot, 'core', 'registry', 'readiness-templates.json');
83
+
84
+ if (options.templates) {
85
+ this.templates = options.templates;
86
+ } else {
87
+ this.loadTemplates();
88
+ }
89
+ }
90
+
91
+ loadTemplates() {
92
+ try {
93
+ const data = parseYamlFile(this.templatePath, this.fsImpl);
94
+ this.templates = data.templates || {};
95
+ } catch (error: any) {
96
+ throw new Error(`Failed to load readiness templates from '${this.templatePath}': ${error.message}`);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Suggests retrieval actions when an evidence requirement is missing.
102
+ * @param {string} reqId
103
+ * @param {Object} reqDef
104
+ * @param {Object} taskProfile
105
+ * @returns {Object}
106
+ */
107
+ suggestAction(reqId: any, reqDef: any, taskProfile: any) {
108
+ if (reqId === 'root_cause') {
109
+ return {
110
+ intent: 'trace-dependency',
111
+ capability: 'code.relationships',
112
+ action: 'Query structural call graphs or inspect git history to establish root cause evidence for the bug.',
113
+ };
114
+ }
115
+ if (reqId === 'affected_code' || reqId === 'target_identified') {
116
+ return {
117
+ intent: 'locate-known-symbol',
118
+ capability: 'code.exact-search',
119
+ action: 'Perform exact symbol or file search to record target code locators.',
120
+ };
121
+ }
122
+ if (reqId === 'blast_radius' || reqId === 'architecture_mapped') {
123
+ return {
124
+ intent: 'estimate-blast-radius',
125
+ capability: 'code.impact-analysis',
126
+ action: 'Query caller trees and dependent modules using Graphify or exact search.',
127
+ };
128
+ }
129
+ if (reqId === 'reproduction' || reqId === 'baseline_tests') {
130
+ return {
131
+ intent: 'verify-runtime-behavior',
132
+ capability: 'behavior.verify',
133
+ action: 'Execute automated tests or record runtime observation reproducing the behavior.',
134
+ };
135
+ }
136
+ return {
137
+ intent: 'locate-concept',
138
+ capability: 'code.semantic-search',
139
+ action: `Gather evidence for requirement '${reqId}' (${reqDef.description}).`,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Evaluates task readiness against a declarative template.
145
+ * @param {Object} taskProfile
146
+ * @param {string} taskProfile.taskId
147
+ * @param {'bug'|'feature'|'refactor'|'trivial-edit'|'dependency-change'} [taskProfile.taskClass='feature']
148
+ * @param {string} [taskProfile.description]
149
+ * @param {string} [taskProfile.verificationPlan]
150
+ * @param {boolean} [taskProfile.userDecisionPending=false]
151
+ * @param {Object} evidenceLedger
152
+ * @param {Object} claimsManager
153
+ * @returns {Object} ReadinessReport
154
+ */
155
+ evaluateReadiness(taskProfile: any, evidenceLedger: any, claimsManager: any) {
156
+ if (!taskProfile || typeof taskProfile !== 'object') {
157
+ throw new Error('taskProfile must be an object');
158
+ }
159
+
160
+ // Fail closed on identity, for the same reason the unrecognized-requirement branch below does.
161
+ // These previously defaulted to `'default'` and `'feature'`, which meant a caller passing `id`
162
+ // instead of `taskId` — an easy slip, and one that actually happened while writing
163
+ // test/runtime/runtime-claim-status.test.js — got a confident READY/NEEDS_EVIDENCE verdict computed
164
+ // from an unrelated task's evidence and claims. A gate must never answer about something it did
165
+ // not evaluate; reported independently by three separate reviews of this runtime before being
166
+ // fixed here.
167
+ if (typeof taskProfile.taskId !== 'string' || taskProfile.taskId.trim() === '') {
168
+ const nearMiss = ['id', 'task_id', 'taskID'].find((k) => taskProfile[k] !== undefined);
169
+ throw new Error(
170
+ `taskProfile.taskId is required to evaluate readiness${nearMiss ? ` (received '${nearMiss}' instead)` : ''}` +
171
+ ' — readiness is evaluated against one task\'s evidence and claims, so guessing the id would grade the wrong task.',
172
+ );
173
+ }
174
+ if (typeof taskProfile.taskClass !== 'string' || taskProfile.taskClass.trim() === '') {
175
+ throw new Error(
176
+ `taskProfile.taskClass is required to evaluate readiness. Available: ${Object.keys(this.templates).join(', ')}`,
177
+ );
178
+ }
179
+
180
+ const taskId = taskProfile.taskId;
181
+ const taskClass = taskProfile.taskClass;
182
+ const template = this.templates[taskClass];
183
+
184
+ if (!template) {
185
+ throw new Error(`Unknown task class '${taskClass}'. Available: ${Object.keys(this.templates).join(', ')}`);
186
+ }
187
+
188
+ const requirements = template.requirements || {};
189
+ const requirementResults = [];
190
+ let allSatisfied = true;
191
+ let hasBlocked = false;
192
+
193
+ // Check for user decision gate
194
+ if (taskProfile.userDecisionPending) {
195
+ return {
196
+ taskId,
197
+ taskClass,
198
+ state: 'NEEDS_USER_DECISION',
199
+ templateName: template.name,
200
+ summary: 'Task requires explicit user approval on an architectural or design decision.',
201
+ satisfiedRequirements: [],
202
+ missingRequirements: [],
203
+ claimsSummary: { supported: 0, hypotheses: 0, conflicts: 0 },
204
+ };
205
+ }
206
+
207
+ // Inspect claims
208
+ const taskClaims = claimsManager ? claimsManager.getClaims(taskId) : [];
209
+ const conflictedClaims = taskClaims.filter((c: any) => c.status === 'conflicted');
210
+ if (conflictedClaims.length > 0) {
211
+ hasBlocked = true;
212
+ }
213
+
214
+ const taskEvidence = evidenceLedger ? evidenceLedger.queryEvidence({ taskId, status: 'FRESH' }) : [];
215
+
216
+ // An item accepted before FR-004 existed, or one whose target file has since shrunk, can be
217
+ // FRESH and still point at nothing. *Stale* (the file changed at all) and *unresolvable* (the
218
+ // locator no longer names anything) are different verdicts and must not be collapsed: a stale
219
+ // item is still checkable by a human, an unresolvable one is not (FR-005).
220
+ // Stale and unresolvable are different failures and are reported apart: an unresolvable
221
+ // locator points at nothing, a stale one points at something that changed since it was read.
222
+ // A superseded item is excluded here, not from getAllEvidence(): superseding is a recorded
223
+ // human judgment that this specific item no longer needs re-checking (a fresher item already
224
+ // covers the same ground), the same way a retracted/superseded claim stops being read as live
225
+ // in evaluateClaim — it is not a claim that the file never changed. An item that genuinely went
226
+ // stale and was never superseded still forces NEEDS_EVIDENCE exactly as before.
227
+ const staleEvidence = (evidenceLedger ? evidenceLedger.getAllEvidence() : [])
228
+ .filter((item: any) => item.taskId === taskId && item.freshness?.status === 'STALE' && item.status !== 'superseded')
229
+ .map((item: any) => ({ evidenceId: item.id, locator: item.locator, reason: 'file-modified-since-recorded' }));
230
+
231
+ const unresolvableEvidence = [];
232
+ for (const item of taskEvidence) {
233
+ if (item.provenance !== 'extracted' || !item.locator || !item.locator.file) continue;
234
+ const resolution = resolveLocator({ locator: item.locator, repoRoot: this.projectRoot });
235
+ if (!resolution.resolved) {
236
+ unresolvableEvidence.push({
237
+ evidenceId: item.id,
238
+ locator: item.locator,
239
+ reason: resolution.reason,
240
+ detail: describeResolution(item.locator, resolution),
241
+ });
242
+ }
243
+ }
244
+
245
+ for (const [reqId, reqDef] of Object.entries<any>(requirements)) {
246
+ let isSatisfied = false;
247
+ let reason = null;
248
+ let matchedEvidenceIds = [];
249
+
250
+ // Check verification plan requirement
251
+ if (reqId === 'regression_verification' || reqId === 'verification_plan' || reqId === 'verification_command') {
252
+ if (taskProfile.verificationPlan || taskProfile.verificationCommand) {
253
+ isSatisfied = true;
254
+ } else {
255
+ reason = 'No verification command or test execution plan defined in task profile.';
256
+ }
257
+ }
258
+ // Check claim status requirement (e.g. root_cause). A template can additionally demand a
259
+ // claim *role*: `root_cause` is only established by a claim declared as the root-cause claim,
260
+ // not by any supported claim the task happens to hold (review R1 — a supported claim about an
261
+ // unrelated invariant satisfied the bug template's root-cause requirement).
262
+ else if (reqDef.requiresClaimStatus) {
263
+ let matchingClaims = taskClaims.filter((c: any) => c.status === reqDef.requiresClaimStatus);
264
+ if (reqDef.requiresClaimRole) {
265
+ const withRole = matchingClaims.filter((c: any) => c.role === reqDef.requiresClaimRole);
266
+ if (withRole.length === 0 && matchingClaims.length > 0) {
267
+ reason = `${matchingClaims.length} claim(s) have status '${reqDef.requiresClaimStatus}' but none `
268
+ + `declares role '${reqDef.requiresClaimRole}' — add the claim with --role ${reqDef.requiresClaimRole}, `
269
+ + 'or re-state which claim answers this requirement.';
270
+ }
271
+ matchingClaims = withRole;
272
+ }
273
+ if (matchingClaims.length > 0) {
274
+ isSatisfied = true;
275
+ matchedEvidenceIds = matchingClaims.flatMap((c: any) => c.supportingEvidence);
276
+ } else if (!reason) {
277
+ reason = `Requires at least one claim with status '${reqDef.requiresClaimStatus}'`
278
+ + `${reqDef.requiresClaimRole ? ` and role '${reqDef.requiresClaimRole}'` : ''}.`;
279
+ }
280
+ }
281
+ // Check evidence kind requirement. Kind alone is a category, not an outcome: to count, an
282
+ // item must also (a) be an extracted read rather than a model inference, (b) explicitly name
283
+ // this requirement in `establishes`, and (c) where the template demands an execution, carry a
284
+ // typed observation whose exit status matches. Before these checks, an inferred item reading
285
+ // "Baseline tests failed: 12 failures." satisfied `baseline_tests` by being kind test-result
286
+ // (review R1, P1 — reproduced through the public CLI).
287
+ else if (Array.isArray(reqDef.evidenceKinds) && reqDef.evidenceKinds.length > 0) {
288
+ const allowedKinds = new Set(reqDef.evidenceKinds);
289
+ const kindMatched = taskEvidence.filter((ev: any) => allowedKinds.has(ev.kind));
290
+ const extracted = kindMatched.filter((ev: any) => ev.provenance === 'extracted');
291
+ const bound = extracted.filter((ev: any) => Array.isArray(ev.establishes) && ev.establishes.includes(reqId));
292
+ let qualifying = bound;
293
+ if (reqDef.requiresObservation) {
294
+ qualifying = qualifying.filter((ev: any) => ev.observation && Number.isInteger(ev.observation.exitCode));
295
+ if (reqDef.expectedExit === 'zero') {
296
+ qualifying = qualifying.filter((ev: any) => ev.observation.exitCode === 0);
297
+ } else if (reqDef.expectedExit === 'nonzero') {
298
+ qualifying = qualifying.filter((ev: any) => ev.observation.exitCode !== 0);
299
+ }
300
+ }
301
+ if (qualifying.length > 0) {
302
+ isSatisfied = true;
303
+ matchedEvidenceIds = qualifying.map((ev: any) => ev.id);
304
+ } else if (kindMatched.length === 0) {
305
+ reason = `Missing fresh evidence of kind: ${reqDef.evidenceKinds.join(', ')}.`;
306
+ } else if (extracted.length === 0) {
307
+ reason = `${kindMatched.length} item(s) of the right kind exist but none is 'extracted' — an inferred `
308
+ + 'or asserted item is analysis, and analysis cannot establish a measured prerequisite.';
309
+ } else if (bound.length === 0) {
310
+ reason = `${extracted.length} extracted item(s) of the right kind exist but none declares it `
311
+ + `establishes '${reqId}' — record evidence with establishes: ['${reqId}'] so the gate grades `
312
+ + 'what the item was actually gathered to prove.';
313
+ } else if (reqDef.expectedExit === 'zero' && bound.some((ev: any) => ev.observation && ev.observation.exitCode !== 0)) {
314
+ const failed = bound.find((ev: any) => ev.observation && ev.observation.exitCode !== 0);
315
+ reason = `The bound observation ran '${failed.observation.command}' and exited ${failed.observation.exitCode}; `
316
+ + 'this requirement needs an exit status of 0. A failed run does not establish a passing baseline.';
317
+ } else {
318
+ reason = 'The bound evidence lacks a typed observation ({command, exitCode})'
319
+ + `${reqDef.expectedExit ? ` with a${reqDef.expectedExit === 'zero' ? ' passing (0)' : ' non-zero'} exit status` : ''} — `
320
+ + 'record the execution itself, not a description of one.';
321
+ }
322
+ }
323
+ // Scope verified / scope clear
324
+ else if (reqId === 'scope_clear' || reqId === 'scope_verified' || reqId === 'invariants_captured') {
325
+ if (taskProfile.scopeClear || taskProfile.description || taskProfile.invariants) {
326
+ isSatisfied = true;
327
+ } else {
328
+ reason = `Prerequisite '${reqId}' not documented.`;
329
+ }
330
+ } else {
331
+ // Fail closed. A gate whose unrecognized requirements default to satisfied reports READY
332
+ // for prerequisites it never actually evaluated — the one failure mode a readiness contract
333
+ // exists to prevent. No shipped template reaches here today; a future one adding a
334
+ // requirement without `evidenceKinds`/`requiresClaimStatus` should surface as unmet, not
335
+ // silently pass.
336
+ isSatisfied = false;
337
+ reason = `Requirement '${reqId}' has no evaluator; treating as unmet.`;
338
+ }
339
+
340
+ if (!isSatisfied && reqDef.required) {
341
+ allSatisfied = false;
342
+ }
343
+
344
+ requirementResults.push({
345
+ id: reqId,
346
+ description: reqDef.description,
347
+ required: Boolean(reqDef.required),
348
+ satisfied: isSatisfied,
349
+ reason,
350
+ evidenceIds: matchedEvidenceIds,
351
+ recommendedAction: isSatisfied ? null : this.suggestAction(reqId, reqDef, taskProfile),
352
+ });
353
+ }
354
+
355
+ let state = 'READY';
356
+ let summary = 'All mandatory readiness prerequisites are verified by fresh evidence.';
357
+
358
+ if (hasBlocked) {
359
+ state = 'BLOCKED';
360
+ summary = `Task is blocked by ${conflictedClaims.length} conflicted claim(s). Contradicting evidence must be resolved.`;
361
+ } else if (!allSatisfied) {
362
+ state = 'NEEDS_EVIDENCE';
363
+ const missingCount = requirementResults.filter((r) => r.required && !r.satisfied).length;
364
+ summary = `Task requires ${missingCount} additional evidence item(s) before implementation.`;
365
+ }
366
+
367
+ // FR-005. Applied after the base verdict so it can never be overwritten by it, and appended
368
+ // rather than replacing, so a task that is short of evidence *and* holding an unresolvable
369
+ // locator reports both. Not BLOCKED: nothing contradicts anything here — the record simply no
370
+ // longer points at the repository, which is a different failure from evidence that disagrees
371
+ // with itself. Naming the specific items is the requirement; a silent downgrade would leave the
372
+ // reader to hunt for which item moved.
373
+ if (staleEvidence.length > 0) {
374
+ if (state === 'READY') state = 'NEEDS_EVIDENCE';
375
+ summary += ` ${staleEvidence.length} evidence item(s) went stale: the files they name have `
376
+ + 'changed since they were recorded. Re-record them against the tree as it stands.';
377
+ }
378
+
379
+ if (unresolvableEvidence.length > 0) {
380
+ if (state === 'READY') state = 'NEEDS_EVIDENCE';
381
+ summary += ` ${unresolvableEvidence.length} evidence item(s) have a locator that no longer `
382
+ + `resolves: ${unresolvableEvidence.map((u) => u.detail).join('; ')}. `
383
+ + 'Re-record them against the tree as it stands.';
384
+ }
385
+
386
+ return {
387
+ taskId,
388
+ taskClass,
389
+ state,
390
+ templateName: template.name,
391
+ summary,
392
+ requirements: requirementResults,
393
+ claimsSummary: {
394
+ total: taskClaims.length,
395
+ supported: taskClaims.filter((c: any) => c.status === 'supported').length,
396
+ hypotheses: taskClaims.filter((c: any) => c.status === 'hypothesis').length,
397
+ conflicts: conflictedClaims.length,
398
+ },
399
+ evidenceCount: taskEvidence.length,
400
+ unresolvableEvidence,
401
+ staleEvidence,
402
+ };
403
+ }
404
+ }
405
+
406
+ /** One live snapshot for readiness inspection and orchestration transitions. Reads never persist
407
+ * derived freshness or claim status; every invocation evaluates the current project again.
408
+ * `mode` defaults to 'workflow' — fail closed: the standalone exemption must be declared. */
409
+ function evaluateTaskReadiness({ taskProfile, repoRoot = REPO_ROOT, projectRoot = process.cwd(), mode = 'workflow' }: any) {
410
+ // Validated before any ledger work, not just inside stageEntryFor at the end — an invalid mode
411
+ // used to pay a full freshness/claims evaluation before erroring (review suggestion, 026).
412
+ if (!EXECUTION_MODES.has(mode)) {
413
+ throw new Error(`Unknown execution mode '${mode}'. Valid: ${[...EXECUTION_MODES].join(', ')} — the standalone exemption is declared, never inferred.`);
414
+ }
415
+ const { EvidenceLedger } = require('./evidence-ledger.ts');
416
+ const { ClaimsManager } = require('./claims.ts');
417
+ const { FreshnessValidator } = require('./freshness.ts');
418
+ const ledger = new EvidenceLedger({ repoRoot: projectRoot });
419
+ ledger.load(taskProfile.taskId);
420
+ new FreshnessValidator({ repoRoot: projectRoot }).validateLedgerFreshness(ledger);
421
+ const claims = new ClaimsManager({ evidenceLedger: ledger, repoRoot: projectRoot });
422
+ claims.load(taskProfile.taskId);
423
+ claims.evaluateAll();
424
+ const report = new ReadinessEngine({ repoRoot, projectRoot }).evaluateReadiness(taskProfile, ledger, claims);
425
+ return { ...report, executionMode: mode, stageEntry: stageEntryFor(report.state, mode) };
426
+ }
427
+
428
+ module.exports = {
429
+ evaluateTaskReadiness,
430
+ stageEntryFor,
431
+ ReadinessEngine,
432
+ READINESS_STATES,
433
+ EXECUTION_MODES,
434
+ STAGE_ENTRY_DECISIONS,
435
+ };