@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,130 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * The registry-driven layer's own registry: loads and validates `core/registry/verification.json`
7
+ * (plan task C.2, design C7) into the shape `VerificationEngine` (in `verification.js`) compiles
8
+ * contracts against.
9
+ *
10
+ * Validation is deliberately strict and throws rather than degrading: a registry that half-loads
11
+ * produces a gate that verifies half of what it claims, which is the failure mode the whole
12
+ * verification module exists to prevent.
13
+ */
14
+
15
+ const path = require('node:path');
16
+ const nodeFs = require('node:fs');
17
+ const { parseYamlFile } = require('../capability-router.js');
18
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
19
+
20
+ const REGISTRY_FILENAME = 'verification.json';
21
+
22
+ /** Tier-level outcomes. `UNRESOLVED` is the load-bearing one: it says "this tier was required and
23
+ * nothing was evaluated for it", and it is what keeps a report off PASS. */
24
+ const TIER_RESOLUTIONS = Object.freeze([
25
+ 'RESOLVED',
26
+ 'UNRESOLVED',
27
+ 'SUBSUMED',
28
+ 'NOT_APPLICABLE',
29
+ 'SKIPPED',
30
+ ]);
31
+
32
+ const TIER_STATUSES = Object.freeze([
33
+ 'PASS',
34
+ 'FAIL',
35
+ 'UNRESOLVED',
36
+ 'SUBSUMED',
37
+ 'NOT_APPLICABLE',
38
+ 'SKIPPED',
39
+ 'NOT_RUN',
40
+ ]);
41
+
42
+ /** Outcomes of a bounded recovery loop. */
43
+ const RECOVERY_OUTCOMES = Object.freeze([
44
+ 'PASS',
45
+ 'ABORTED',
46
+ 'NO_CHANGE',
47
+ 'NO_PROGRESS',
48
+ 'NO_REMEDIATION',
49
+ ]);
50
+
51
+ function assert(condition , message ) {
52
+ if (!condition) throw new Error(`verification.json is invalid: ${message}`);
53
+ }
54
+
55
+ /**
56
+ * Loads and validates the verification registry.
57
+ *
58
+ * Validation is deliberately strict and throws rather than degrading: a registry that half-loads
59
+ * produces a gate that verifies half of what it claims, which is the failure mode this whole module
60
+ * exists to prevent.
61
+ *
62
+ * @param {Object} [options]
63
+ * @param {string} [options.repoRoot]
64
+ * @param {string} [options.registryDir]
65
+ * @param {string} [options.registryPath]
66
+ * @param {Object} [options.registry] pre-parsed registry, bypassing the filesystem (test seam)
67
+ * @param {Object} [options.fsImpl]
68
+ * @returns {Object} the validated registry
69
+ */
70
+ function loadVerificationRegistry(options = {}) {
71
+ let data = options.registry;
72
+ if (!data) {
73
+ const repoRoot = options.repoRoot || REPO_ROOT;
74
+ const registryDir = options.registryDir || path.join(repoRoot, 'core', 'registry');
75
+ const registryPath = options.registryPath || path.join(registryDir, REGISTRY_FILENAME);
76
+ data = parseYamlFile(registryPath, options.fsImpl || nodeFs);
77
+ }
78
+
79
+ assert(data && typeof data === 'object', 'root must be an object');
80
+ assert(Array.isArray(data.tiers) && data.tiers.length > 0, 'tiers must be a non-empty array');
81
+ assert(data.riskLevels && typeof data.riskLevels === 'object', 'riskLevels must be an object');
82
+
83
+ const policy = data.policy || {};
84
+ // FR-009 states this as a MUST, so it is enforced at load rather than trusted at call sites.
85
+ assert(policy.modelReviewIsPrimaryProof === false, 'policy.modelReviewIsPrimaryProof must be false');
86
+
87
+ const seen = new Set ();
88
+ let previousOrder = -Infinity;
89
+ let lastNonModelOrder = -Infinity;
90
+ let firstModelOrder = Infinity;
91
+
92
+ for (const tier of data.tiers) {
93
+ assert(tier && typeof tier.id === 'string' && tier.id !== '', 'every tier needs an id');
94
+ assert(!seen.has(tier.id), `duplicate tier id '${tier.id}'`);
95
+ seen.add(tier.id);
96
+ assert(Number.isFinite(tier.order), `tier '${tier.id}' needs a numeric order`);
97
+ assert(tier.order > previousOrder, `tier '${tier.id}' is declared out of order; tiers must be listed cheapest-first`);
98
+ previousOrder = tier.order;
99
+ if (tier.modelBased) {
100
+ firstModelOrder = Math.min(firstModelOrder, tier.order);
101
+ assert(tier.canEstablishPass === false, `model-based tier '${tier.id}' must declare canEstablishPass: false`);
102
+ } else {
103
+ lastNonModelOrder = Math.max(lastNonModelOrder, tier.order);
104
+ }
105
+ }
106
+ // "Deterministic checks MUST run before any model-based review" (FR-009) is a structural property
107
+ // of the registry, so it is checkable here instead of being re-asserted in prose.
108
+ assert(firstModelOrder > lastNonModelOrder, 'every model-based tier must be ordered after every deterministic tier');
109
+
110
+ for (const [level, def] of Object.entries(data.riskLevels) ) {
111
+ assert(def && Array.isArray(def.requiredTiers), `risk level '${level}' needs requiredTiers`);
112
+ for (const id of def.requiredTiers.concat(def.advisoryTiers || [])) {
113
+ assert(seen.has(id), `risk level '${level}' references unknown tier '${id}'`);
114
+ }
115
+ assert(
116
+ Number.isInteger(def.maxRecoveryIterations) && def.maxRecoveryIterations > 0,
117
+ `risk level '${level}' needs a positive integer maxRecoveryIterations`,
118
+ );
119
+ }
120
+
121
+ return data;
122
+ }
123
+
124
+ module.exports = {
125
+ loadVerificationRegistry,
126
+ TIER_RESOLUTIONS,
127
+ TIER_STATUSES,
128
+ RECOVERY_OUTCOMES,
129
+ REGISTRY_FILENAME,
130
+ };
@@ -0,0 +1,418 @@
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 { spawnSync } = require('node:child_process');
8
+ const { resolveActiveFeature } = require('../feature-resolve.js');
9
+ const { resolveBashHelper } = require('../../helper/bash-helper.js');
10
+ const { listCheckouts } = require('../checkouts.js');
11
+
12
+ // The change-scope bound of a planned feature, and the baseline a change is measured from
13
+ // (feature 045, IC-003 / IC-004).
14
+ //
15
+ // The change-scope tier compares what a change touched against a bound declared before
16
+ // implementation. Nothing declared one, so the tier read UNRESOLVED for every feature. The plan
17
+ // already names the files each task will touch, so the bound is derived from it; there is no
18
+ // extra step an agent has to remember.
19
+
20
+ /** `- [ ] A.1 ...` / `- [x] B.2 ...`: a checklist task line, as plan.md writes them. */
21
+ const TASK_LINE = /^\s*-\s*\[[ xX]\]\s+[A-Za-z]+\.\d+\b/;
22
+
23
+ /** `files:` (any case) as a field of its own: at the start of the line, after whitespace or after a `;`. A word
24
+ * that merely ends in it (`profiles:`) is not the field. */
25
+ const FILES_FIELD = /(?:^|[;\s])files:/gi;
26
+
27
+ /** A `files:` value that says the task touches no file: `none`, alone or followed by an aside. */
28
+ const NONE_FIELD = /^none(?=$|[\s(;,`])/i;
29
+
30
+ /**
31
+ * Paths a plan's task lines name after `files:`, in order, without duplicates.
32
+ *
33
+ * The field is the last `files:` on the task line (a description may mention the word earlier). It
34
+ * runs to the next `;` or the end of the line; a list that ends with `,` continues on the next
35
+ * indented line, which is how a long list is wrapped.
36
+ * @param {string} planText
37
+ * @returns {Array<string>}
38
+ */
39
+ function taskFilesFromPlan(planText ) {
40
+ const lines = String(planText || '').split('\n');
41
+ const paths = [];
42
+ for (let i = 0; i < lines.length; i += 1) {
43
+ if (!TASK_LINE.test(lines[i])) continue;
44
+ let at = -1;
45
+ for (const m of lines[i].matchAll(FILES_FIELD)) at = (m.index ) + m[0].length;
46
+ if (at === -1) continue;
47
+ let rest = lines[i].slice(at);
48
+ // `files: none (...)` states that a task changes no file; its words are not paths.
49
+ if (NONE_FIELD.test(rest.trim().replace(/^`+/, ''))) continue;
50
+ for (;;) {
51
+ const semicolon = rest.indexOf(';');
52
+ const field = semicolon === -1 ? rest : rest.slice(0, semicolon);
53
+ for (const raw of field.split(',')) {
54
+ // A leading `./` is the same path: changed files are reported without it.
55
+ const entry = raw.trim().replace(/^`+|`+$/g, '').trim().replace(/^(\.\/)+/, '');
56
+ if (entry) paths.push(entry);
57
+ }
58
+ const wraps = semicolon === -1 && field.trimEnd().endsWith(',')
59
+ && i + 1 < lines.length && /^\s+\S/.test(lines[i + 1]) && !TASK_LINE.test(lines[i + 1]);
60
+ if (!wraps) break;
61
+ i += 1;
62
+ rest = lines[i];
63
+ }
64
+ }
65
+ return [...new Set(paths)];
66
+ }
67
+
68
+ const folderOf = (slug ) => (slug ? `agent-docs/doflow/${slug}/` : null);
69
+
70
+ function isFile(file ) {
71
+ try {
72
+ return fs.statSync(file).isFile();
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ function isDirectory(dir ) {
79
+ try {
80
+ return fs.statSync(dir).isDirectory();
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+
86
+ /** Why no other checkout was consulted, in the words a reason text lists. */
87
+ function noMainCheckout(checkouts ) {
88
+ if (!checkouts.ok) {
89
+ if (checkouts.reason === 'not-a-git-repository') return 'not a git repository';
90
+ if (checkouts.reason === 'git-unavailable') return 'git is not available';
91
+ return `git worktree list failed: ${checkouts.detail || 'no detail'}`;
92
+ }
93
+ if (checkouts.sandbox) return 'a DoFlow sandbox sees no other checkout';
94
+ if (checkouts.mainReason === 'bare') return 'the repository is bare';
95
+ if (checkouts.mainReason === 'missing') return 'the main checkout no longer exists';
96
+ return 'this directory is the main checkout';
97
+ }
98
+
99
+ /**
100
+ * The plan that bounds a change, taken from stated sources in a stated order: `--plan-path`, else
101
+ * the feature folder in this checkout, else that folder in the main checkout when this is a linked
102
+ * worktree. The first folder that exists wins, an explicit slug is never replaced by the branch's,
103
+ * and a named plan that does not exist stops the search rather than falling through to a guess.
104
+ * Every source consulted is listed in `searched`. Reads files and copies nothing.
105
+ * @param {Object} options
106
+ * @param {string} options.projectRoot
107
+ * @param {string|null} [options.slug]
108
+ * @param {string|null} [options.planPath] as the caller wrote it, relative to projectRoot
109
+ * @param {Function} [options.exec] spawnSync-compatible, for the one `git worktree list`
110
+ * @returns {{plan: string|null, planRel: string|null, origin: string|null, root: string|null, slug: string|null,
111
+ * folderRel: string|null, hasRegister: boolean, searched: Array<{place: string, result: string}>, reason: string|null}}
112
+ */
113
+ function resolvePlanSource({ projectRoot, slug = null, planPath = null, exec = spawnSync }
114
+
115
+
116
+
117
+
118
+ ) {
119
+ const searched = [];
120
+ const result = (fields ) => ({
121
+ plan: null, planRel: null, origin: null, root: null, slug: null, folderRel: null, hasRegister: false, searched, reason: null, ...fields,
122
+ });
123
+ const resolve = (s ) => {
124
+ try {
125
+ return resolveActiveFeature({ projectRoot, slug: s });
126
+ } catch (error ) {
127
+ return { error: 'resolver-failed', message: `could not run the feature resolver: ${error.message}` };
128
+ }
129
+ };
130
+
131
+ if (planPath) {
132
+ const abs = path.resolve(projectRoot, planPath);
133
+ const exists = isFile(abs);
134
+ searched.push({ place: `--plan-path ${planPath}`, result: exists ? 'found' : 'does not exist' });
135
+ if (!exists) return result({ planRel: planPath, origin: 'plan-path', reason: 'plan-path-missing' });
136
+ // The flag is its own opt-in: no register is needed for a plan the caller named.
137
+ let s = slug;
138
+ if (!s) {
139
+ const branch = resolve(null);
140
+ if (!branch.error && branch.paths && branch.paths.feature_slug) s = branch.paths.feature_slug;
141
+ }
142
+ const parent = path.dirname(abs);
143
+ if (!s && path.dirname(parent).split(path.sep).slice(-2).join('/') === 'agent-docs/doflow') s = path.basename(parent);
144
+ return result({ plan: abs, planRel: planPath, origin: 'plan-path', slug: s || null, folderRel: folderOf(s), hasRegister: true });
145
+ }
146
+
147
+ const found = resolve(slug);
148
+ if (found.error || !found.paths || !found.paths.feature_slug) {
149
+ searched.push({ place: 'this checkout', result: found.message || 'the feature resolver named no feature' });
150
+ return result({ reason: 'no-feature' });
151
+ }
152
+ const s = found.paths.feature_slug;
153
+ const folderRel = folderOf(s);
154
+ const withFolder = (fields ) => {
155
+ const plan = path.resolve(fields.root, fields.planRel);
156
+ let reason = null;
157
+ if (!isFile(plan)) reason = 'no-plan';
158
+ else if (!fields.hasRegister) reason = 'no-register';
159
+ return result({ ...fields, plan, slug: s, folderRel, reason });
160
+ };
161
+
162
+ const here = isDirectory(found.featureDir);
163
+ searched.push({ place: `this checkout ${found.repoRoot}`, result: here ? `${folderRel} found` : `no ${folderRel}` });
164
+ if (here) {
165
+ return withFolder({
166
+ origin: 'current-checkout', root: found.repoRoot, planRel: found.paths.plan || `${folderRel}plan.md`, hasRegister: found.paths.has_decisions === true,
167
+ });
168
+ }
169
+
170
+ const checkouts = listCheckouts({ cwd: projectRoot, exec });
171
+ if (!checkouts.isLinked) {
172
+ searched.push({ place: 'main checkout', result: noMainCheckout(checkouts) });
173
+ return result({ slug: s, folderRel, reason: 'none' });
174
+ }
175
+ const folder = path.join(checkouts.main, 'agent-docs', 'doflow', s);
176
+ const there = isDirectory(folder);
177
+ searched.push({ place: `main checkout ${checkouts.main}`, result: there ? `${folderRel} found` : `no ${folderRel}` });
178
+ if (!there) return result({ slug: s, folderRel, reason: 'none' });
179
+ return withFolder({
180
+ origin: 'main-checkout', root: checkouts.main, planRel: `${folderRel}plan.md`, hasRegister: isFile(path.join(folder, 'decisions', 'register.json')),
181
+ });
182
+ }
183
+
184
+ /** Why a declared-scope token is refused, or null when it is a repository-relative path. */
185
+ function refusedToken(token ) {
186
+ if (token === '') return 'it is empty';
187
+ if (/[\s\\\0]/.test(token)) return 'it contains whitespace';
188
+ if (token.startsWith('/') || token.startsWith('~') || /^[A-Za-z]:/.test(token)) return 'it is absolute';
189
+ if (token === '.') return 'it names the whole repository';
190
+ const segments = token.split('/');
191
+ if (segments[segments.length - 1] === '') segments.pop();
192
+ if (segments.some((seg) => seg === '.' || seg === '..')) return 'it climbs out with ..';
193
+ return null;
194
+ }
195
+
196
+ /**
197
+ * A declared scope: one comma-separated list of repository-relative paths, a trailing `/` meaning
198
+ * a directory. One refused token refuses the whole text, so a typo never shrinks the bound
199
+ * silently. Duplicates are dropped and order is kept.
200
+ * @param {string} text
201
+ * @returns {{paths: Array<string>, reason: null}|{paths: null, reason: string}}
202
+ */
203
+ function parseDeclaredScope(text ) {
204
+ const paths = [];
205
+ for (const raw of String(text).trim().split(',')) {
206
+ const written = raw.trim();
207
+ const token = written.replace(/^(\.\/)+/, '');
208
+ // `./` alone strips to nothing, but what it names is the repository root, not an empty entry.
209
+ const why = written !== '' && token === '' ? 'it names the whole repository' : refusedToken(token);
210
+ if (why) return { paths: null, reason: `'${written}' is not a repository-relative path: ${why}` };
211
+ if (!paths.includes(token)) paths.push(token);
212
+ }
213
+ return { paths, reason: null };
214
+ }
215
+
216
+ /**
217
+ * The change-scope bound: the plan's task files plus its feature folder, unioned with a declared
218
+ * scope, plan entries first. Neither narrows the other, and nothing is derived from the changed
219
+ * files. With neither, `bound` is null and `reason` is the first source that failed.
220
+ * @param {Object} options
221
+ * @param {string} options.projectRoot
222
+ * @param {string|null} [options.slug]
223
+ * @param {string|null} [options.planPath]
224
+ * @param {{paths: Array<string>, origin: 'verify-flag'|'readiness-record', record?: string}|null} [options.declared]
225
+ * @param {Function} [options.exec]
226
+ * @returns {{bound: Object|null, reason: string|null, searched: Array<Object>, planSource: Object}}
227
+ */
228
+ function resolveScopeBound({ projectRoot, slug = null, planPath = null, declared = null, baselineRef = null, exec = spawnSync }
229
+
230
+
231
+
232
+
233
+
234
+
235
+ ) {
236
+ const planSource = resolvePlanSource({ projectRoot, slug, planPath, exec });
237
+ let reason = planSource.reason;
238
+ const allowed = [];
239
+ const sources = [];
240
+ if (reason === null) {
241
+ let text = null;
242
+ try {
243
+ text = fs.readFileSync(planSource.plan, 'utf8');
244
+ } catch {
245
+ reason = 'no-plan';
246
+ }
247
+ const files = text === null ? [] : taskFilesFromPlan(text);
248
+ if (text !== null && files.length === 0) reason = 'no-task-files';
249
+ if (files.length > 0) {
250
+ allowed.push(...files);
251
+ if (planSource.folderRel) allowed.push(planSource.folderRel);
252
+ sources.push({ kind: 'plan', path: planSource.planRel, origin: planSource.origin, root: planSource.root });
253
+ }
254
+ }
255
+ if (declared && Array.isArray(declared.paths) && declared.paths.length > 0) {
256
+ allowed.push(...declared.paths);
257
+ sources.push({ kind: 'declared', paths: [...declared.paths], origin: declared.origin, ...(declared.record ? { record: declared.record } : {}) });
258
+ }
259
+
260
+ let baseline = null;
261
+ if (baselineRef !== null && baselineRef !== undefined) {
262
+ const res = resolveExplicitBaseline({ cwd: projectRoot, ref: baselineRef, exec });
263
+ if (!res.ok) {
264
+ return {
265
+ bound: null,
266
+ reason: 'baseline-invalid',
267
+ unresolvedReason: `change-scope: ${res.reason}. Nothing was changed.`,
268
+ searched: planSource ? planSource.searched : [],
269
+ planSource,
270
+ };
271
+ }
272
+ baseline = res;
273
+ }
274
+
275
+ if (allowed.length === 0) return { bound: null, reason, searched: planSource.searched, planSource };
276
+ const source = sources[0].kind === 'plan' ? planSource.planRel : (declared.record || '--scope');
277
+ return {
278
+ bound: {
279
+ allowedPaths: [...new Set(allowed)],
280
+ source,
281
+ sources,
282
+ ...(baseline ? { baseline: 'explicit', baselineRef: baseline.ref, baselineSha: baseline.sha } : { baseline: 'integration' }),
283
+ },
284
+ reason: null,
285
+ searched: planSource.searched,
286
+ planSource,
287
+ };
288
+ }
289
+
290
+ /**
291
+ * The bound a plan declares, as 1.21.0 returned it: `{allowedPaths, source}`, or null.
292
+ * @param {Object} options
293
+ * @param {string} options.projectRoot
294
+ * @param {string|null} [options.slug]
295
+ * @returns {{allowedPaths: Array<string>, source: string}|null}
296
+ */
297
+ function buildScopeBound({ projectRoot, slug = null } ) {
298
+ const { bound } = resolveScopeBound({ projectRoot, slug });
299
+ return bound ? { allowedPaths: bound.allowedPaths, source: bound.source } : null;
300
+ }
301
+
302
+ function originText(origin , root ) {
303
+ if (origin === 'main-checkout') return `main checkout ${root}`;
304
+ if (origin === 'plan-path') return '--plan-path';
305
+ return 'current checkout';
306
+ }
307
+
308
+ /**
309
+ * The change-scope tier's reason when nothing bounds the change: where was looked and the command
310
+ * that bounds it.
311
+ * @param {Object} options
312
+ * @param {string} options.reason a `resolveScopeBound` reason
313
+ * @param {string} options.taskId
314
+ * @param {Object} options.planSource the `resolvePlanSource` result
315
+ * @returns {string}
316
+ */
317
+ function scopeReasonText({ reason, taskId, planSource } ) {
318
+ const places = planSource.searched.map((s ) => `${s.place}: ${s.result}`).join('; ');
319
+ const where = `${planSource.folderRel} (${originText(planSource.origin, planSource.root)})`;
320
+ switch (reason) {
321
+ case 'no-plan':
322
+ return `change-scope: the feature folder ${where} has no plan.md, so nothing bounds this change. Looked in: ${places}. Pass --scope <path>[,<path>...] or --plan-path <plan.md>. Nothing was changed.`;
323
+ case 'no-register':
324
+ return `change-scope: the feature folder ${where} has no decisions/register.json, so its plan bounds nothing (features from before the register keep that behaviour). Pass --scope <path>[,<path>...] or --plan-path <plan.md> to bound this change. Nothing was changed.`;
325
+ case 'no-task-files':
326
+ return `change-scope: ${planSource.planRel} names no files: in any task, so it bounds nothing. Add files: to its tasks or pass --scope <path>[,<path>...]. Nothing was changed.`;
327
+ case 'plan-path-missing':
328
+ return `change-scope: --plan-path ${planSource.planRel} does not exist; no other source is consulted when a plan is named. Pass an existing plan.md or drop --plan-path. Nothing was changed.`;
329
+ default:
330
+ return `change-scope: no plan or declared scope bounds this change. Looked in: ${places}. A change with no plan needs a declared scope: doflow-run verify --task-id ${taskId} --scope <path>[,<path>...] (a trailing / is a directory); to use a plan, pass --plan-path <plan.md> or --slug <feature>. Nothing was changed.`;
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Where a resolved bound came from, as the `bound:` line prints it.
336
+ * @param {Array<Object>} sources a bound's `sources`
337
+ * @returns {string}
338
+ */
339
+ function boundSourcesText(sources ) {
340
+ return sources.map((s ) => (s.kind === 'plan'
341
+ ? `plan ${s.path} (${originText(s.origin, s.root)})`
342
+ : `declared ${s.paths.join(',')} (${s.origin === 'readiness-record' ? `readiness record ${s.record}` : '--scope'})`)).join(' + ');
343
+ }
344
+
345
+ /**
346
+ * The commit a change is measured from: the merge base of HEAD and the integration ref that
347
+ * `do-git-state.sh` reports. `{ref, mergeBase}` when it resolves, else `{reason, note?}` saying why not;
348
+ * `note` is the tier detail when the reason has wording of its own (a missing integration branch).
349
+ * @param {Object} options
350
+ * @param {string} options.cwd
351
+ * @param {Function} [options.exec] spawnSync-compatible
352
+ * @returns {{ref: string, mergeBase: string}|{reason: string}}
353
+ */
354
+ function resolveIntegrationBase({ cwd, exec = spawnSync } ) {
355
+ const run = (cmd , args ) => {
356
+ try {
357
+ return exec(cmd, args, { cwd, encoding: 'utf8', timeout: 30000 });
358
+ } catch (error ) {
359
+ return { error };
360
+ }
361
+ };
362
+ const gitState = resolveBashHelper('do-git-state.sh');
363
+ const state = gitState ? run('bash', [gitState, '--json']) : null;
364
+ let ref = null;
365
+ if (state && !state.error && state.status === 0) {
366
+ try { ref = JSON.parse(state.stdout).integration_ref || null; } catch { /* reported below */ }
367
+ }
368
+ if (!ref) return { reason: gitState ? 'no integration ref could be resolved' : 'the DoFlow helper scripts are missing from this install (do-git-state.sh)' };
369
+ const exists = run('git', ['rev-parse', '--verify', '--quiet', ref]);
370
+ if (!exists || exists.error || exists.status !== 0) {
371
+ return { reason: `integration branch '${ref}' not found`, note: `integration branch '${ref}' not found; working tree only` };
372
+ }
373
+ const base = run('git', ['merge-base', ref, 'HEAD']);
374
+ const mergeBase = base && !base.error && base.status === 0 ? String(base.stdout || '').trim() : '';
375
+ if (!mergeBase) return { reason: `no merge base with '${ref}'` };
376
+ return { ref, mergeBase };
377
+ }
378
+
379
+ /**
380
+ * An explicit `--baseline-ref` (IC-022): resolved once to a commit SHA, and required to be an ancestor of
381
+ * HEAD so the measured diff is the whole of what happened since it. Anything else is unresolved with its
382
+ * reason; there is no fallback to the working tree, which would report committed work as a clean scope.
383
+ * @param {Object} options
384
+ * @param {string} options.cwd
385
+ * @param {string} options.ref commit or ref the caller pinned
386
+ * @param {Function} [options.exec] spawnSync-compatible
387
+ * @returns {{ok: true, ref: string, sha: string}|{ok: false, ref: string, reason: string}}
388
+ */
389
+ function resolveExplicitBaseline({ cwd, ref, exec = spawnSync } ) {
390
+ const fail = (reason ) => ({ ok: false, ref, reason });
391
+ if (typeof ref !== 'string' || ref.trim() === '' || ref.startsWith('-')) {
392
+ return fail(`baseline ref '${ref}' is not a usable commit or ref`);
393
+ }
394
+ const run = (args ) => {
395
+ try { return exec('git', args, { cwd, encoding: 'utf8', timeout: 30000 }); } catch (error ) { return { error }; }
396
+ };
397
+ const parsed = run(['rev-parse', '--verify', '--quiet', '--end-of-options', `${ref}^{commit}`]);
398
+ const sha = parsed && !parsed.error && parsed.status === 0 ? String(parsed.stdout || '').trim() : '';
399
+ if (!/^[0-9a-f]{40,64}$/.test(sha)) return fail(`baseline ref '${ref}' does not name a commit`);
400
+ const ancestry = run(['merge-base', '--is-ancestor', sha, 'HEAD']);
401
+ if (!ancestry || ancestry.error || (ancestry.status !== 0 && ancestry.status !== 1)) {
402
+ return fail(`could not check whether ${sha} is an ancestor of HEAD`);
403
+ }
404
+ if (ancestry.status === 1) return fail(`baseline ${sha} is not an ancestor of HEAD, so the change since it cannot be measured`);
405
+ return { ok: true, ref, sha };
406
+ }
407
+
408
+ module.exports = {
409
+ buildScopeBound,
410
+ taskFilesFromPlan,
411
+ resolveIntegrationBase,
412
+ resolveExplicitBaseline,
413
+ resolvePlanSource,
414
+ parseDeclaredScope,
415
+ resolveScopeBound,
416
+ scopeReasonText,
417
+ boundSourcesText,
418
+ };