@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,516 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * Build/test/lint command detection from project manifests, with a per-feature `plan.md` override
7
+ * (plan task C.2, design component C7).
8
+ *
9
+ * Zero-config is the design target: a repository with a `package.json`, a `go.mod` or a `Cargo.toml`
10
+ * should need no configuration at all. The override exists because detection is frequently wrong in
11
+ * a monorepo, where the manifest at the repository root describes a workspace rather than the
12
+ * package the feature actually touches.
13
+ *
14
+ * The property that matters more than coverage: **a failed detection is visible.** Three outcomes
15
+ * are kept distinct, because collapsing them is how a verification gate ends up reporting PASS over
16
+ * a suite it never ran:
17
+ *
18
+ * - `commands[role]` — a command was found, and by what.
19
+ * - `absent` — a manifest was read and does not declare this role. For `build` or
20
+ * `lint` that is real evidence the project has no such step; for
21
+ * `test` it is not, and the caller must surface it (the tier registry
22
+ * decides which, via `absenceIsEvidence`).
23
+ * - `declaredNone` — a `plan.md` override set the role to `null`. A human stated that
24
+ * this project has no such command. That is a declaration, not a miss.
25
+ *
26
+ * And when nothing could be read at all, `manifestFound` is false and `commands` is empty — which
27
+ * the verification engine turns into UNRESOLVED tiers, never into passing ones.
28
+ */
29
+
30
+ const fs = require('node:fs');
31
+ const path = require('node:path');
32
+
33
+ /** The roles a tier in `verification.json` can ask for. Anything else in an override is an error,
34
+ * because a typo'd key that is silently ignored is a command the user believes is running. */
35
+ const COMMAND_ROLES = Object.freeze([
36
+ 'parse',
37
+ 'build',
38
+ 'lint',
39
+ 'typecheck',
40
+ 'test',
41
+ 'testTargeted',
42
+ ]);
43
+
44
+ /** Info string of the fenced block a feature's `plan.md` uses to override detection. JSON body,
45
+ * matching the plain-JSON registry files. */
46
+ const PLAN_OVERRIDE_BLOCK = 'doflow-verification';
47
+
48
+ /** Substituted by the verification engine when it has a target to scope tests to. */
49
+ const TARGET_PATTERN_TOKEN = '{pattern}';
50
+
51
+ /** A `package.json` test script that is `node --test` over files or directories, and nothing else:
52
+ * the one npm shape whose runner is known to take file arguments (feature 071, FR-002). */
53
+ const NODE_TEST_SCRIPT = /^node --test( \S+)+$/;
54
+ /** A character that makes a script argument a glob pattern rather than a literal path. */
55
+ const GLOB_CHARS = /[*?[{]/;
56
+
57
+ /**
58
+ * The directory roots a `node --test` script's arguments name. One pair of surrounding quotes is
59
+ * stripped; an argument holding a glob character reduces to the path segments before the first
60
+ * segment that has one (`test/**\/*.test.js` -> `test`, `**\/*.test.js` -> `.`); a plain file or
61
+ * directory is kept as written. Duplicates are dropped.
62
+ */
63
+ function testRootsFromArgs(args ) {
64
+ const roots = [];
65
+ for (const arg of args) {
66
+ const bare = arg.replace(/^(["'])(.*)\1$/, '$2');
67
+ let root = bare;
68
+ if (GLOB_CHARS.test(bare)) {
69
+ const segments = bare.split('/');
70
+ const firstGlob = segments.findIndex((segment ) => GLOB_CHARS.test(segment));
71
+ root = segments.slice(0, firstGlob).join('/') || (bare.startsWith('/') ? '/' : '.');
72
+ }
73
+ if (!roots.includes(root)) roots.push(root);
74
+ }
75
+ return roots;
76
+ }
77
+
78
+ /** A script chaining other commands is not only `node --test`: a targeted form would drop the rest. */
79
+ const SHELL_SYNTAX = /[&|;<>$`()\\]/;
80
+
81
+ /**
82
+ * Roles this module only ever synthesizes from another role. An override of the base role has to
83
+ * invalidate the derived one: `testTargeted` detected as `npm test -- {pattern}` is a lie the
84
+ * moment `plan.md` replaces the test command with something else, and declaring `"test": null`
85
+ * while leaving a targeted variant behind would report a tier as runnable that the plan just said
86
+ * does not exist.
87
+ */
88
+ const DERIVED_ROLES = Object.freeze({ testTargeted: 'test' });
89
+
90
+ /**
91
+ * How a command was arrived at. `declared` means a human or a manifest script named it exactly;
92
+ * `inferred` means this module constructed it from the toolchain's conventions and it may be wrong.
93
+ * Kept out of any numeric form on purpose — FR-008 forbids numeric confidence in output paths.
94
+ */
95
+ const DERIVATIONS = Object.freeze(['declared', 'inferred', 'override']);
96
+
97
+ function readFileIfPresent(filePath , fsImpl ) {
98
+ try {
99
+ return fsImpl.readFileSync(filePath, 'utf8');
100
+ } catch (error ) {
101
+ if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR' || error.code === 'EISDIR')) {
102
+ return null;
103
+ }
104
+ throw error;
105
+ }
106
+ }
107
+
108
+ function exists(filePath , fsImpl ) {
109
+ try {
110
+ fsImpl.statSync(filePath);
111
+ return true;
112
+ } catch {
113
+ return false;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Picks the run prefix from whichever lockfile is present. Running `npm run build` in a pnpm
119
+ * workspace usually works and occasionally installs a second, divergent `node_modules`.
120
+ * @returns {{ run: string, test: string, manager: string }}
121
+ */
122
+ function nodePackageManager(root , fsImpl ) {
123
+ if (exists(path.join(root, 'pnpm-lock.yaml'), fsImpl)) {
124
+ return { run: 'pnpm run', test: 'pnpm test', manager: 'pnpm' };
125
+ }
126
+ if (exists(path.join(root, 'yarn.lock'), fsImpl)) {
127
+ return { run: 'yarn', test: 'yarn test', manager: 'yarn' };
128
+ }
129
+ if (exists(path.join(root, 'bun.lockb'), fsImpl) || exists(path.join(root, 'bun.lock'), fsImpl)) {
130
+ return { run: 'bun run', test: 'bun test', manager: 'bun' };
131
+ }
132
+ return { run: 'npm run', test: 'npm test', manager: 'npm' };
133
+ }
134
+
135
+ /**
136
+ * @returns {{ id: string, roles: Object, absent: Array<string> }|null}
137
+ */
138
+ function detectNode(root , fsImpl ) {
139
+ const raw = readFileIfPresent(path.join(root, 'package.json'), fsImpl);
140
+ if (raw === null) return null;
141
+
142
+ let pkg ;
143
+ try {
144
+ pkg = JSON.parse(raw);
145
+ } catch (error ) {
146
+ // A malformed manifest is not an absent manifest. Reporting it as "no build script" would
147
+ // hand the caller a confident answer derived from a file it failed to read.
148
+ return { id: 'package.json', roles: {}, absent: [], error: `package.json is not valid JSON: ${error.message}` };
149
+ }
150
+
151
+ const scripts = (pkg && typeof pkg.scripts === 'object' && pkg.scripts) || {};
152
+ const pm = nodePackageManager(root, fsImpl);
153
+ const roles = {};
154
+ // Node has no project-wide parse step distinct from type-checking or running the code; `node
155
+ // --check` needs an explicit file list this module does not have. Reporting it absent (rather
156
+ // than leaving it undetected) lets the parse tier resolve NOT_APPLICABLE instead of stalling
157
+ // every contract in every plain-JavaScript repository on a step that does not exist.
158
+ const absent = ['parse'];
159
+
160
+ const script = (...names ) => names.find((n) => typeof scripts[n] === 'string' && scripts[n].trim() !== '');
161
+
162
+ const buildScript = script('build');
163
+ if (buildScript) roles.build = { command: `${pm.run} ${buildScript}`, derivation: 'declared' };
164
+ else absent.push('build');
165
+
166
+ const lintScript = script('lint');
167
+ if (lintScript) roles.lint = { command: `${pm.run} ${lintScript}`, derivation: 'declared' };
168
+ else absent.push('lint');
169
+
170
+ const typecheckScript = script('typecheck', 'type-check', 'tsc', 'types');
171
+ if (typecheckScript) roles.typecheck = { command: `${pm.run} ${typecheckScript}`, derivation: 'declared' };
172
+ else if (exists(path.join(root, 'tsconfig.json'), fsImpl)) {
173
+ // A tsconfig with no typecheck script still has a canonical way to be type-checked.
174
+ roles.typecheck = { command: 'npx --no-install tsc --noEmit', derivation: 'inferred' };
175
+ } else absent.push('typecheck');
176
+
177
+ const testScript = script('test');
178
+ if (testScript) {
179
+ roles.test = { command: pm.test, derivation: 'declared' };
180
+ const testText = scripts[testScript].trim();
181
+ const scriptArgs = testText.split(' ').slice(2);
182
+ // A flag (`--require ./setup.js`) would be dropped by the targeted form, so it is not declared.
183
+ if (NODE_TEST_SCRIPT.test(testText) && !SHELL_SYNTAX.test(testText) && !scriptArgs.some((arg ) => arg.startsWith('-'))) {
184
+ // The runner is named by the script itself, so the targeted form is declared, not guessed.
185
+ // `testDirs` are the roots the script's arguments name: where its test files live. A quoted
186
+ // argument loses its quotes and a glob keeps only the path before its first glob segment.
187
+ const testDirs = testRootsFromArgs(scriptArgs);
188
+ roles.testTargeted = { command: `node --test ${TARGET_PATTERN_TOKEN}`, derivation: 'declared', testDirs };
189
+ } else {
190
+ // Whether the underlying runner accepts a trailing path or pattern depends on the runner, not
191
+ // on npm, so this is inferred rather than declared and the engine falls back to the broad
192
+ // command when no pattern is supplied.
193
+ roles.testTargeted = { command: `${pm.test} -- ${TARGET_PATTERN_TOKEN}`, derivation: 'inferred' };
194
+ }
195
+ } else absent.push('test');
196
+
197
+ return { id: 'package.json', roles, absent };
198
+ }
199
+
200
+ function detectRust(root , fsImpl ) {
201
+ if (!exists(path.join(root, 'Cargo.toml'), fsImpl)) return null;
202
+ return {
203
+ id: 'Cargo.toml',
204
+ roles: {
205
+ parse: { command: 'cargo check --all-targets', derivation: 'inferred' },
206
+ build: { command: 'cargo build --all-targets', derivation: 'inferred' },
207
+ lint: { command: 'cargo clippy --all-targets -- -D warnings', derivation: 'inferred' },
208
+ test: { command: 'cargo test', derivation: 'inferred' },
209
+ testTargeted: { command: `cargo test ${TARGET_PATTERN_TOKEN}`, derivation: 'inferred' },
210
+ },
211
+ absent: ['typecheck'],
212
+ };
213
+ }
214
+
215
+ function detectGo(root , fsImpl ) {
216
+ if (!exists(path.join(root, 'go.mod'), fsImpl)) return null;
217
+ return {
218
+ id: 'go.mod',
219
+ roles: {
220
+ build: { command: 'go build ./...', derivation: 'inferred' },
221
+ lint: { command: 'go vet ./...', derivation: 'inferred' },
222
+ test: { command: 'go test ./...', derivation: 'inferred' },
223
+ testTargeted: { command: `go test ${TARGET_PATTERN_TOKEN}`, derivation: 'inferred' },
224
+ },
225
+ absent: ['parse', 'typecheck'],
226
+ };
227
+ }
228
+
229
+ /**
230
+ * `pyproject.toml` is read line-wise rather than parsed: Node ships no TOML parser and adding a
231
+ * dependency for tool-table presence detection is not worth it. Only the presence of a `[tool.X]`
232
+ * table is used, which is robust to the parts of TOML this does not understand.
233
+ */
234
+ function detectPython(root , fsImpl ) {
235
+ const raw = readFileIfPresent(path.join(root, 'pyproject.toml'), fsImpl);
236
+ if (raw === null) return null;
237
+
238
+ const hasTable = (name ) => new RegExp(`^\\s*\\[tool\\.${name}[\\].]`, 'm').test(raw);
239
+ const roles = {};
240
+ const absent = [];
241
+
242
+ if (hasTable('pytest') || exists(path.join(root, 'pytest.ini'), fsImpl) || exists(path.join(root, 'tests'), fsImpl)) {
243
+ roles.test = { command: 'pytest', derivation: 'inferred' };
244
+ roles.testTargeted = { command: `pytest ${TARGET_PATTERN_TOKEN}`, derivation: 'inferred' };
245
+ } else absent.push('test');
246
+
247
+ if (hasTable('ruff')) roles.lint = { command: 'ruff check .', derivation: 'inferred' };
248
+ else if (hasTable('flake8')) roles.lint = { command: 'flake8', derivation: 'inferred' };
249
+ else absent.push('lint');
250
+
251
+ if (hasTable('mypy')) roles.typecheck = { command: 'mypy .', derivation: 'inferred' };
252
+ else if (hasTable('pyright')) roles.typecheck = { command: 'pyright', derivation: 'inferred' };
253
+ else absent.push('typecheck');
254
+
255
+ // Python has no build step in the sense this tier means (packaging is not verification), and no
256
+ // parse step that is not just importing the code, which the tests already do.
257
+ absent.push('build', 'parse');
258
+ return { id: 'pyproject.toml', roles, absent };
259
+ }
260
+
261
+ /** Makefile target name → the role it serves. Only unambiguous names are mapped. */
262
+ const MAKE_TARGET_ROLES = Object.freeze({
263
+ build: 'build',
264
+ compile: 'build',
265
+ test: 'test',
266
+ tests: 'test',
267
+ lint: 'lint',
268
+ vet: 'lint',
269
+ typecheck: 'typecheck',
270
+ 'type-check': 'typecheck',
271
+ check: 'parse',
272
+ });
273
+
274
+ function detectMake(root , fsImpl ) {
275
+ const raw = readFileIfPresent(path.join(root, 'Makefile'), fsImpl) ??
276
+ readFileIfPresent(path.join(root, 'makefile'), fsImpl);
277
+ if (raw === null) return null;
278
+
279
+ const roles = {};
280
+ const absent = [];
281
+ const targets = new Set ();
282
+ // Target lines only: a leading tab means a recipe line, and `:=` is a variable assignment.
283
+ for (const line of raw.split('\n')) {
284
+ const m = /^([A-Za-z0-9_.\-/]+)\s*:(?!=)/.exec(line);
285
+ if (m) targets.add(m[1]);
286
+ }
287
+ for (const [target, role] of Object.entries(MAKE_TARGET_ROLES)) {
288
+ if (targets.has(target) && !roles[role]) {
289
+ roles[role] = { command: `make ${target}`, derivation: 'declared' };
290
+ }
291
+ }
292
+ for (const role of COMMAND_ROLES) {
293
+ if (!roles[role] && role !== 'testTargeted') absent.push(role);
294
+ }
295
+ return { id: 'Makefile', roles, absent, targets: [...targets] };
296
+ }
297
+
298
+ /**
299
+ * Detector order. The first manifest to supply a role wins; later manifests contribute only roles
300
+ * still unfilled, and their alternative is recorded rather than discarded. `Makefile` is last on
301
+ * purpose: it is usually a convenience wrapper over the language toolchain already detected above,
302
+ * and a repository where it is the real entrypoint is precisely the case the `plan.md` override
303
+ * exists for.
304
+ */
305
+ const DETECTORS = Object.freeze([detectNode, detectRust, detectGo, detectPython, detectMake]);
306
+
307
+ /**
308
+ * Extracts the override object from a feature's `plan.md`.
309
+ *
310
+ * A malformed block is an error, never a silent skip: the author believed they were overriding
311
+ * detection, and quietly running the detected commands instead runs something they did not ask for.
312
+ * @param {string} planText
313
+ * @returns {{ override: Object|null, errors: Array<string> }}
314
+ */
315
+ function parsePlanOverride(planText ) {
316
+ if (typeof planText !== 'string' || planText === '') return { override: null, errors: [] };
317
+
318
+ const fence = new RegExp(`^[ \\t]*\`\`\`${PLAN_OVERRIDE_BLOCK}[ \\t]*\\n([\\s\\S]*?)^[ \\t]*\`\`\``, 'm');
319
+ const match = fence.exec(planText);
320
+ if (!match) return { override: null, errors: [] };
321
+
322
+ let parsed ;
323
+ try {
324
+ parsed = JSON.parse(match[1]);
325
+ } catch (error ) {
326
+ return { override: null, errors: [`plan.md '${PLAN_OVERRIDE_BLOCK}' block is not valid JSON: ${error.message}`] };
327
+ }
328
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
329
+ return { override: null, errors: [`plan.md '${PLAN_OVERRIDE_BLOCK}' block must be a JSON object mapping command roles to commands`] };
330
+ }
331
+
332
+ const errors = [];
333
+ for (const [key, value] of Object.entries(parsed) ) {
334
+ if (!COMMAND_ROLES.includes(key)) {
335
+ errors.push(`plan.md override declares unknown command role '${key}'; known roles: ${COMMAND_ROLES.join(', ')}`);
336
+ continue;
337
+ }
338
+ if (value !== null && (typeof value !== 'string' || value.trim() === '')) {
339
+ errors.push(`plan.md override for '${key}' must be a non-empty command string or null (meaning: this project has none)`);
340
+ }
341
+ }
342
+ return { override: parsed, errors };
343
+ }
344
+
345
+ /**
346
+ * Detects the verification commands for a project.
347
+ *
348
+ * @param {Object} [options]
349
+ * @param {string} [options.projectRoot=process.cwd()]
350
+ * @param {string} [options.planPath] path to a feature's `plan.md`, whose override block wins
351
+ * @param {string} [options.planText] override text supplied directly, bypassing `planPath`
352
+ * @param {Object} [options.fsImpl] injection seam for tests
353
+ * @returns {{
354
+ * projectRoot: string,
355
+ * usable: boolean,
356
+ * manifestFound: boolean,
357
+ * manifests: Array<string>,
358
+ * commands: Object<string, {role: string, command: string, source: string, derivation: string}>,
359
+ * alternatives: Array<Object>,
360
+ * absent: Array<string>,
361
+ * declaredNone: Array<string>,
362
+ * errors: Array<string>
363
+ * }}
364
+ */
365
+ function detectCommands(options = {}) {
366
+ const fsImpl = options.fsImpl || fs;
367
+ const projectRoot = options.projectRoot || process.cwd();
368
+
369
+ const manifests = [];
370
+ const commands = {};
371
+ const alternatives = [];
372
+ const absentByRole = new Map ();
373
+ const errors = [];
374
+
375
+ for (const detector of DETECTORS) {
376
+ let found ;
377
+ try {
378
+ found = detector(projectRoot, fsImpl);
379
+ } catch (error ) {
380
+ errors.push(`Detector failed while reading ${projectRoot}: ${error.message}`);
381
+ continue;
382
+ }
383
+ if (!found) continue;
384
+ manifests.push(found.id);
385
+ if (found.error) {
386
+ errors.push(found.error);
387
+ continue;
388
+ }
389
+ for (const [role, entry] of Object.entries(found.roles) ) {
390
+ if (commands[role]) {
391
+ alternatives.push({ role, command: entry.command, source: found.id, derivation: entry.derivation });
392
+ } else {
393
+ commands[role] = { role, command: entry.command, source: found.id, derivation: entry.derivation, ...(entry.testDirs ? { testDirs: entry.testDirs } : {}) };
394
+ }
395
+ }
396
+ for (const role of found.absent || []) {
397
+ if (!absentByRole.has(role)) absentByRole.set(role, found.id);
398
+ }
399
+ }
400
+
401
+ // A role another manifest supplied is not absent, whatever the manifest that lacked it said.
402
+ for (const role of Object.keys(commands)) absentByRole.delete(role);
403
+
404
+ let planText = options.planText;
405
+ if (typeof planText !== 'string' && options.planPath) {
406
+ planText = readFileIfPresent(options.planPath, fsImpl);
407
+ if (planText === null) {
408
+ errors.push(`plan.md override path does not exist: ${options.planPath}`);
409
+ planText = undefined;
410
+ }
411
+ }
412
+
413
+ const declaredNone = [];
414
+ const { override, errors: overrideErrors } = parsePlanOverride(planText);
415
+ errors.push(...overrideErrors);
416
+
417
+ if (override && overrideErrors.length === 0) {
418
+ for (const [role, value] of Object.entries(override) ) {
419
+ if (value === null) {
420
+ delete commands[role];
421
+ absentByRole.delete(role);
422
+ declaredNone.push(role);
423
+ } else {
424
+ if (commands[role]) {
425
+ alternatives.push({ ...commands[role], supersededBy: 'plan.md' });
426
+ }
427
+ commands[role] = { role, command: value.trim(), source: 'plan.md', derivation: 'override' };
428
+ absentByRole.delete(role);
429
+ }
430
+ }
431
+
432
+ // Propagate to roles synthesized from an overridden base — see DERIVED_ROLES.
433
+ for (const [derived, base] of Object.entries(DERIVED_ROLES)) {
434
+ if (!(base in override) || derived in override) continue;
435
+ if (commands[derived]) {
436
+ alternatives.push({ ...commands[derived], supersededBy: `plan.md override of '${base}'` });
437
+ delete commands[derived];
438
+ }
439
+ if (override[base] === null) declaredNone.push(derived);
440
+ else absentByRole.delete(derived);
441
+ }
442
+ }
443
+
444
+ // An override this module could not trust invalidates the *whole* detection, not just its own
445
+ // block. Falling back to the detected commands would run something the plan explicitly replaced —
446
+ // the same failure family as a check with no command reporting PASS.
447
+ const usable = errors.length === 0;
448
+ if (!usable) {
449
+ return {
450
+ projectRoot,
451
+ usable: false,
452
+ manifestFound: manifests.length > 0,
453
+ manifests,
454
+ commands: {},
455
+ alternatives: [],
456
+ absent: [],
457
+ declaredNone: [],
458
+ errors,
459
+ };
460
+ }
461
+
462
+ // DERIVATIONS was declared and exported while nothing consulted it, and the value it names is
463
+ // written as a bare literal at twenty-three separate sites across the per-language detectors. That
464
+ // is precisely the shape where a typo survives: `derivation` is carried into the verification
465
+ // report and read there to say whether a command came from the project or was inferred on its
466
+ // behalf, so a misspelled one degrades into "not declared" silently rather than failing. Checking
467
+ // once here covers every producer, including the override branch above, without asking each of the
468
+ // twenty-three to remember.
469
+ for (const entry of Object.values(commands)) {
470
+ if (!DERIVATIONS.includes(entry.derivation)) {
471
+ throw new Error(
472
+ `Command role '${entry.role}' has derivation '${entry.derivation}', which is not one of: `
473
+ + `${DERIVATIONS.join(', ')}`,
474
+ );
475
+ }
476
+ }
477
+
478
+ return {
479
+ projectRoot,
480
+ usable: true,
481
+ manifestFound: manifests.length > 0,
482
+ manifests,
483
+ commands,
484
+ alternatives,
485
+ absent: [...absentByRole.keys()].sort(),
486
+ declaredNone: declaredNone.sort(),
487
+ errors: [],
488
+ };
489
+ }
490
+
491
+ /**
492
+ * Substitutes a target pattern into a detected command template.
493
+ * @param {string} command
494
+ * @param {string} [pattern]
495
+ * @returns {{ command: string, substituted: boolean }}
496
+ */
497
+ function applyTargetPattern(command , pattern ) {
498
+ if (typeof command !== 'string' || !command.includes(TARGET_PATTERN_TOKEN)) {
499
+ return { command, substituted: false };
500
+ }
501
+ if (typeof pattern !== 'string' || pattern.trim() === '') {
502
+ return { command: null, substituted: false };
503
+ }
504
+ return { command: command.split(TARGET_PATTERN_TOKEN).join(pattern.trim()), substituted: true };
505
+ }
506
+
507
+ module.exports = {
508
+ detectCommands,
509
+ parsePlanOverride,
510
+ applyTargetPattern,
511
+ COMMAND_ROLES,
512
+ DERIVED_ROLES,
513
+ PLAN_OVERRIDE_BLOCK,
514
+ TARGET_PATTERN_TOKEN,
515
+ DERIVATIONS,
516
+ };