@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,466 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ // OpenCode adapter.
6
+ //
7
+ // OpenCode gets its own copy of DoFlow's skills tree, the same as Claude, Codex, and Gemini: it
8
+ // natively discovers skills under `.opencode/skills/` (project) and `~/.config/opencode/skills/`
9
+ // (global) without any settings entry, so the shared copy-tree engine in ../copy-tree.ts
10
+ // materialises `skills.doflow` there directly. What remains registration, rather than
11
+ // materialisation, is `opencode.json`'s `instructions[]` array (OpenCode needs `AGENTS.md` named
12
+ // explicitly to read it) and its `mcp` object — neither has a native discovery path the way skills
13
+ // now do. DoFlow's servers in `mcp` are owned one entry at a time through ../mcp-entries.ts.
14
+ //
15
+ // Evidence: https://opencode.ai/docs/skills, https://opencode.ai/docs/config,
16
+ // https://opencode.ai/docs/rules
17
+ const fs = require('node:fs');
18
+ const path = require('node:path');
19
+ const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, sharedTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, fingerprint, readJson, sourceDirFor, resolveTransform } = require('../copy-tree.js');
20
+ const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.js');
21
+
22
+ // Only the marker constants: marker-merge.js reads and writes files itself, which cannot be used
23
+ // from plan(), whose contract is to compute changes without touching disk. The gemini adapter
24
+ // solves this the same way, with a pure managedInstruction() over strings.
25
+ const { MARKER_START, MARKER_END } = require('../../helper/marker-merge.js');
26
+ const { destructiveCommandGlobs } = require('../../helper/guardrails.js');
27
+ const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
28
+
29
+ const HARNESS = 'opencode';
30
+ /**
31
+ * Payload literal written INTO opencode.json's instructions[] — it names the instruction file the
32
+ * declared `instruction` path installs (harnesses.json "paths".instruction's basename). It is a
33
+ * content string inside a foreign config file, not an install destination, so it stays here;
34
+ * test/guards/harness-paths.test.js cross-checks it against the declaration via a probe install.
35
+ */
36
+ const INSTRUCTION_FILE = 'AGENTS.md';
37
+ const PERMISSIONS_OWNER = 'doflow:guardrails';
38
+ const MCP_CONTAINER = 'mcp';
39
+ const MCP_RENDERER = 'opencode-mcp';
40
+
41
+ /**
42
+ * Native path facts live in core/registry/harnesses.json under this harness's "paths" section and
43
+ * resolve through the shared harness-paths resolver. Global config lives under `~/.config/opencode/`,
44
+ * NOT `~/.opencode/` — the latter is a plausible guess that OpenCode does not use, and DoFlow's own
45
+ * docs asserted it for several releases.
46
+ * createOpenCodeAdapter({ declaredPaths }) is the injection point buildAdapterRegistry() uses;
47
+ * module-level exports delegate to a default-configured instance so direct callers keep their
48
+ * historical shape.
49
+ */
50
+ function createOpenCodeAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } = {}) {
51
+ function nativePaths({ scope, scopeRoot, homeDir } ) {
52
+ return resolveHarnessPaths(declaredPaths, { scope, scopeRoot, homeDir });
53
+ }
54
+
55
+ /** The directory a copy-tree asset (skills) materialises under. Distinct from `configDir` at
56
+ * project scope: `configDir` there is the project root itself, because `AGENTS.md` and
57
+ * `opencode.json` are read from the repo root, but the skills tree is namespaced under
58
+ * `.opencode/` per design.md's native-surfaces table. Global scope has no such split —
59
+ * `~/.config/opencode` already IS the skills root. Declared as `treeRoot` in harnesses.json. */
60
+ function copyTreeConfigDir(paths ) {
61
+ return paths.treeRoot;
62
+ }
63
+
64
+ /** Agents live under `.opencode/agents/` at project scope and `~/.config/opencode/agents/`
65
+ * globally — a different root from the skills tree in both scopes, so they get their own
66
+ * destination resolution rather than reusing copyTreeConfigDir. Declared as `agentsDirectory`. */
67
+ function agentsDestDir(paths ) {
68
+ return paths.agentsDirectory;
69
+ }
70
+
71
+ /** Discovery keeps opencode.json as plan read it (`mcpSnapshot`), which verify compares against,
72
+ * and reports the servers DoFlow owns in its `mcp` object now (`mcpOwned`). */
73
+ function discover({ scope, scopeRoot, mcp = [], mcpAdoptable = [], ledger, context = {}, fsImpl = fs } ) {
74
+ const paths = nativePaths({ scope, scopeRoot, homeDir: context.homeDir });
75
+ const instruction = fsImpl.existsSync(paths.instruction) ? fsImpl.readFileSync(paths.instruction, 'utf8') : null;
76
+ const entries = mcpEntriesInput({ paths, mcp, mcpAdoptable, ledger });
77
+ const mcpSnapshot = readMcpFiles({ file: entries.file, ownRows: entries.ownRows, container: MCP_CONTAINER, fsImpl });
78
+ const read = mcpSnapshot[entries.file];
79
+ // `null` while the file is absent, unreadable or has no server map; every name in it otherwise, whoever wrote it.
80
+ const mcpConfigured = read?.ok && read.servers ? Object.keys(read.servers).sort() : null;
81
+ return { paths, instruction, config: readJson(paths.config, { fsImpl }), mcpSnapshot, mcpOwned: ownedMcpIds({ ...entries, files: mcpSnapshot }), mcpConfigured };
82
+ }
83
+
84
+ function render({ content = '' } = {}) {
85
+ return `${MARKER_START}\n${String(content).trimEnd()}\n${MARKER_END}\n`;
86
+ }
87
+
88
+ /** Register DoFlow's instructions in opencode.json without disturbing anything else in it.
89
+ * `instructions` is unioned rather than replaced because OpenCode treats it as additive across
90
+ * every config it discovers — replacing would silently drop a user's own entries. Skills are
91
+ * deliberately absent here: they are a materialised directory now, discovered natively, not a
92
+ * registered path. The `mcp` object is left to the entry changes. */
93
+ function mergeConfig(existing ) {
94
+ const next = { ...(existing || {}) };
95
+ const union = (current , add ) => [...new Set([...(Array.isArray(current) ? current : []), ...add])];
96
+ next.instructions = union(next.instructions, [INSTRUCTION_FILE]);
97
+ return next;
98
+ }
99
+
100
+ /** Remove only what mergeConfig added, leaving every user key and every unrelated array entry. */
101
+ function unmergeConfig(existing ) {
102
+ const next = { ...(existing || {}) };
103
+ const without = (current , drop ) => (Array.isArray(current) ? current.filter((v) => !drop.includes(v)) : []);
104
+ next.instructions = without(next.instructions, [INSTRUCTION_FILE]);
105
+ if (!next.instructions.length) delete next.instructions;
106
+ return next;
107
+ }
108
+
109
+ /** Replace only the span between DoFlow's markers, leaving everything else byte-identical.
110
+ * An AGENTS.md that exists without a DoFlow section is refused rather than appended to — the same
111
+ * policy the gemini adapter applies to GEMINI.md, and for the same reason: AGENTS.md is a shared,
112
+ * cross-tool file, so where DoFlow's content belongs inside it is a decision for its owner, not a
113
+ * guess for the installer. Add an empty marker pair to opt in. */
114
+ /** Opt-in (--permissions) guardrail projection: DoFlow's destructive-command blocklist lands as
115
+ * deny rules under permission.bash. Only keys this feature owns (each carries a matching
116
+ * comment-free glob we can re-derive) are touched; a user's own bash rules survive untouched.
117
+ * Entries are namespaced by value, not by key, so ownership is decided by re-deriving the list
118
+ * from the single authored source rather than by trusting whatever happens to be present. */
119
+ function guardrailBashRules(context ) {
120
+ const globs = destructiveCommandGlobs({ repoRoot: context.repoRoot });
121
+ return Object.fromEntries(globs.map((glob ) => [glob, 'deny']));
122
+ }
123
+
124
+ function mergeGuardrails(existing , context ) {
125
+ const next = { ...(existing || {}) };
126
+ const bash = { ...guardrailBashRules(context), ...((next.permission?.bash ?? {})) };
127
+ // Keep user rules, but make sure ours win for identical globs (they are the stricter value).
128
+ next.permission = { ...(next.permission ?? {}), bash };
129
+ return next;
130
+ }
131
+
132
+ function stripGuardrails(existing , context ) {
133
+ if (!existing?.permission?.bash) return existing;
134
+ const ours = new Set(Object.keys(guardrailBashRules(context)));
135
+ const bash = Object.fromEntries(Object.entries(existing.permission.bash).filter(([glob]) => !ours.has(glob)));
136
+ const permission = { ...existing.permission };
137
+ if (Object.keys(bash).length) permission.bash = bash; else delete permission.bash;
138
+ if (Object.keys(permission).length) return { ...existing, permission };
139
+ const { permission: _dropped, ...rest } = existing;
140
+ void _dropped;
141
+ return rest;
142
+ }
143
+
144
+ function managedInstruction(existing , rendered ) {
145
+ if (existing === null) return { ok: true, operation: 'create', content: rendered };
146
+ const start = existing.indexOf(MARKER_START);
147
+ const end = existing.indexOf(MARKER_END);
148
+ if (start === -1 && end === -1) return { ok: false, conflict: `${INSTRUCTION_FILE} exists without a DoFlow managed section` };
149
+ if (start === -1 || end === -1 || end < start) return { ok: false, conflict: `${INSTRUCTION_FILE} has malformed DoFlow managed-section markers` };
150
+ const content = `${existing.slice(0, start)}${rendered}${existing.slice(end + MARKER_END.length).replace(/^\n?/, '')}`;
151
+ return { ok: true, operation: content === existing ? 'none' : 'merge', content };
152
+ }
153
+
154
+ /** Strip DoFlow's span, leaving the rest of a shared AGENTS.md intact. Returns null when there is
155
+ * nothing to do, so remove() can skip the write entirely. */
156
+ function strippedInstruction(existing ) {
157
+ if (typeof existing !== 'string') return null;
158
+ const start = existing.indexOf(MARKER_START);
159
+ const end = existing.indexOf(MARKER_END);
160
+ if (start === -1 || end === -1 || end < start) return null;
161
+ let after = end + MARKER_END.length;
162
+ if (existing[after] === '\n') after += 1;
163
+ return `${existing.slice(0, start)}${existing.slice(after)}`;
164
+ }
165
+
166
+ // ---- copy-tree assets (skills) ----
167
+
168
+ /** `agents.shared` projects onto OpenCode through its own renderer name (its frontmatter is
169
+ * transformed into OpenCode's markdown-agent vocabulary) but materialises with exactly the same
170
+ * engine and change records as every other copy-tree asset. */
171
+ function opencodeTreeAssets(assets ) {
172
+ return [...copyTreeAssets(assets), ...(assets || []).filter((asset ) => asset.renderer === 'opencode-agents')];
173
+ }
174
+
175
+ function planCopyTreeAssets({ assets, scope, scopeRoot, context, ledger, removing, fsImpl = fs } ) {
176
+ const paths = nativePaths({ scope, scopeRoot, homeDir: context.homeDir });
177
+ const treeConfigDir = copyTreeConfigDir(paths);
178
+ const changes = [];
179
+ const conflicts = [];
180
+ const treeResults = [];
181
+ const notices = [];
182
+ for (const asset of opencodeTreeAssets(assets)) {
183
+ const destDir = asset.renderer === 'opencode-agents' ? agentsDestDir(paths) : sharedTreeDestDir(paths.root, asset.nativeDir) ?? copyTreeDestDir(treeConfigDir, asset);
184
+ const sourceDir = sourceDirFor(asset, context, fsImpl, 'OpenCode');
185
+ const previousResources = ledgerFileResources(ledger?.resources, HARNESS, asset.id);
186
+ const result = planTree({ sourceDir, destDir, previousResources, siblingFingerprints: ledgerSiblingFingerprints(ledger?.resources, HARNESS), operation: removing ? 'remove' : 'apply', fsImpl, layout: asset.layout, transform: asset.transform, keepModified: !removing,
187
+ // Forwarded so the CLI's --force reaches planTree's conflict check; omitting it let
188
+ // planTree's own `force = false` default stand in silently. Gated on `!removing` for the
189
+ // reason codex/index.ts states in full: force heals drift on apply, but a hand-edited file
190
+ // is never deleted on removal, forced or not.
191
+ force: !removing && context?.force === true, });
192
+ treeResults.push(result);
193
+ conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
194
+ // A hand-edited file at a path DoFlow no longer writes is the user's: it stays, only its
195
+ // ownership row is released.
196
+ notices.push(...result.kept.map((item ) => `kept hand-edited ${path.relative(paths.root, item.target)}; DoFlow no longer manages it`));
197
+ for (const change of result.changes) {
198
+ changes.push({
199
+ assetId: asset.id, target: change.target, source: change.source, operation: change.operation,
200
+ ownershipIdentity: `doflow:${HARNESS}:copy-tree:${asset.id}:${change.relPath}`,
201
+ kind: 'copy-tree-file', identity: change.relPath,
202
+ afterFingerprint: change.fingerprint, fingerprint: change.fingerprint, sourceVersion: 'registry-v1',
203
+ transformName: asset.transform || null,
204
+ ...(change.kept ? { retained: true, retainedFor: [] } : {}),
205
+ projection: { renderer: asset.renderer },
206
+ });
207
+ }
208
+ }
209
+ return { changes, conflicts, notices: [...siblingReplacedNotices(treeResults), ...notices] };
210
+ }
211
+
212
+ function applyCopyTreeAssets(changes , { fsImpl = fs } = {}) {
213
+ let applied = 0;
214
+ const grouped = new Map();
215
+ for (const change of changes) {
216
+ if (change.kind !== 'copy-tree-file' || change.operation === 'remove') continue;
217
+ const key = change.transformName || null;
218
+ if (!grouped.has(key)) grouped.set(key, []);
219
+ grouped.get(key).push({ relPath: change.identity, target: change.target, source: change.source, operation: change.operation, fingerprint: change.fingerprint });
220
+ }
221
+ for (const [transformName, treeChanges] of grouped) {
222
+ applied += applyTree({ changes: treeChanges, fsImpl, transform: resolveTransform(transformName) }).applied;
223
+ }
224
+ return applied;
225
+ }
226
+
227
+ function removeCopyTreeAssets(changes , { fsImpl = fs } = {}) {
228
+ const treeChanges = changes.filter((change ) => change.kind === 'copy-tree-file' && change.operation === 'remove' && !change.retained)
229
+ .map((change ) => ({ relPath: change.identity, target: change.target, operation: 'remove', fingerprint: change.fingerprint, pruneGuard: change.pruneGuard }));
230
+ return removeTree({ changes: treeChanges, fsImpl }).removed;
231
+ }
232
+
233
+ function verifyCopyTreeAssets({ assets, scope, scopeRoot, context, fsImpl = fs } ) {
234
+ const paths = nativePaths({ scope, scopeRoot, homeDir: context.homeDir });
235
+ const treeConfigDir = copyTreeConfigDir(paths);
236
+ const statuses = [];
237
+ const resources = [];
238
+ const conflicts = [];
239
+ for (const asset of opencodeTreeAssets(assets)) {
240
+ const destDir = asset.renderer === 'opencode-agents' ? agentsDestDir(paths) : sharedTreeDestDir(paths.root, asset.nativeDir) ?? copyTreeDestDir(treeConfigDir, asset);
241
+ const sourceDir = sourceDirFor(asset, context, fsImpl, 'OpenCode');
242
+ const result = verifyTree({ sourceDir, destDir, fsImpl, layout: asset.layout, transform: asset.transform, plannedWrites: context?.plannedWrites });
243
+ conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
244
+ for (const resource of result.resources) {
245
+ resources.push({
246
+ assetId: asset.id, target: resource.target, ownershipIdentity: `doflow:${HARNESS}:copy-tree:${asset.id}:${resource.relPath}`,
247
+ kind: 'copy-tree-file', identity: resource.relPath,
248
+ fingerprint: resource.fingerprint, sourceVersion: 'registry-v1',
249
+ projection: { renderer: asset.renderer },
250
+ });
251
+ }
252
+ statuses.push({ harness: HARNESS, assetId: asset.id, capability: asset.capability, status: result.ok ? 'managed' : 'conflict', target: destDir });
253
+ }
254
+ return { statuses, resources, conflicts };
255
+ }
256
+
257
+ // ---- shared adapter contract ----
258
+
259
+ /** OpenCode's settings-merge change has no asset of its own in core/registry/assets.json (it's a
260
+ * registration side-effect of the instructions/mcp assets, not a projected asset), so it piggybacks
261
+ * on a real asset id already routed to this harness — the same "pseudo-component" technique
262
+ * src/adapters/gemini/index.js#hooksAssetId and src/adapters/kiro/index.js#pseudoAssetId use. The
263
+ * lifecycle layer validates every change's assetId against the harness's actual asset list, so an
264
+ * invented id like the literal string 'opencode.config' is rejected as unknown. */
265
+ function pseudoAssetId(assets ) {
266
+ return assets.find((asset ) => asset.capability === 'instructions')?.id ?? assets[0]?.id;
267
+ }
268
+
269
+ // ---- MCP entries (opencode.json `mcp`) ----
270
+
271
+ /** OpenCode's own server shape: a local server whose command and arguments form one array. */
272
+ function buildServerEntry(server ) {
273
+ return { type: 'local', command: [server.command, ...(server.args || [])], enabled: true };
274
+ }
275
+
276
+ function ownershipIdentity(id ) { return `doflow:${HARNESS}:mcp-server:${id}`; }
277
+
278
+ /** Everything the shared entry-ownership rules (../mcp-entries.ts) need from this adapter. */
279
+ function mcpEntriesInput({ paths, mcp = [], mcpAdoptable = [], ledger, assets = [] } ) {
280
+ const rendered = (servers ) => servers.map((server ) => ({ id: server.id, entry: buildServerEntry(server) }));
281
+ const rows = (ledger?.resources || []).filter((row ) => row.kind === 'mcp-server');
282
+ return {
283
+ file: paths.config, selected: rendered(mcp), adoptable: rendered(mcpAdoptable),
284
+ ownRows: rows.filter((row ) => row.harness === HARNESS).map((row ) => ({
285
+ identity: row.identity, target: row.target, fingerprint: row.fingerprint ?? null, ownershipIdentity: row.ownershipIdentity, legacy: false,
286
+ })),
287
+ foreignRows: rows.filter((row ) => row.harness !== HARNESS).map((row ) => ({ harness: row.harness, identity: row.identity, target: row.target })),
288
+ identityFor: ownershipIdentity, assetId: pseudoAssetId(assets), renderer: MCP_RENDERER, label: 'OpenCode MCP',
289
+ };
290
+ }
291
+
292
+ /** opencode.json cannot be edited safely while removing: its registration row is released with
293
+ * the entry rows, so the removal completes and leaves the file as it is. */
294
+ function releasedConfigRow({ assets, paths, ledger } ) {
295
+ const owned = (ledger?.resources || []).some((row ) => row.harness === HARNESS && row.ownershipIdentity === `${HARNESS}:config:registration` && row.target === paths.config);
296
+ return owned ? [{ assetId: pseudoAssetId(assets), target: paths.config, operation: 'remove', release: true,
297
+ ownershipIdentity: `${HARNESS}:config:registration`, harness: HARNESS, projection: { renderer: 'opencode-config' } }] : [];
298
+ }
299
+
300
+ function plan({ scope, scopeRoot, assets = [], mcp = [], mcpAdoptable = [], discovery, context = {}, ledger, fsImpl = fs } ) {
301
+ const found = discovery || discover({ scope, scopeRoot, mcp, mcpAdoptable, ledger, context, fsImpl });
302
+ const changes = [];
303
+ const conflicts = [];
304
+ const removing = context.operation === 'remove';
305
+ // One read decides whether opencode.json can be edited: its refusal comes from the entry plan
306
+ // below, under a fixed reason that never quotes the file.
307
+ const configReadable = found.mcpSnapshot[found.paths.config].ok;
308
+
309
+ const guidance = assets.find((asset ) => asset.capability === 'instructions');
310
+ if (guidance) {
311
+ const rendered = render({ content: fsImpl.readFileSync(path.resolve(context.repoRoot, guidance.source), 'utf8') });
312
+ const outcome = removing
313
+ ? { ok: true, operation: 'remove', content: strippedInstruction(found.instruction) }
314
+ : managedInstruction(found.instruction, rendered);
315
+ if (!outcome.ok) conflicts.push(outcome.conflict);
316
+ else if (outcome.operation !== 'none' && outcome.content !== null) {
317
+ changes.push({ assetId: guidance.id, target: found.paths.instruction, operation: outcome.operation,
318
+ content: outcome.content, ownershipIdentity: `${HARNESS}:instructions:managed-section`,
319
+ fingerprint: fingerprint(outcome.content), harness: HARNESS, projection: { renderer: 'opencode-instructions' } });
320
+ }
321
+ }
322
+
323
+ if (configReadable) {
324
+ const current = found.config.value || {};
325
+ let next = removing ? unmergeConfig(current) : mergeConfig(current);
326
+ // Opt-in (--permissions): fold the destructive-command deny list into the same single write
327
+ // so a run never produces two competing rewrites of one JSON file.
328
+ if (context.permissions) {
329
+ next = removing ? stripGuardrails(next, context) : mergeGuardrails(next, context);
330
+ }
331
+ if (JSON.stringify(next) !== JSON.stringify(current)) {
332
+ changes.push({ assetId: pseudoAssetId(assets), target: found.paths.config,
333
+ // Tagged 'remove' during a remove operation (rather than always 'update'/'create') so this
334
+ // adapter's own apply()/remove() dispatch — which routes strictly on `operation` — writes
335
+ // the unmerged file during removal instead of silently skipping it (the same fix
336
+ // src/adapters/copilot/index.js's analogous mcp-merge change already carries).
337
+ operation: removing ? 'remove' : (found.config.exists ? 'update' : 'create'), content: `${JSON.stringify(next, null, 2)}\n`,
338
+ ownershipIdentity: `${HARNESS}:config:registration`, fingerprint: fingerprint(next),
339
+ harness: HARNESS, projection: { renderer: 'opencode-config' } });
340
+ }
341
+ } else if (removing) {
342
+ changes.push(...releasedConfigRow({ assets, paths: found.paths, ledger }));
343
+ }
344
+
345
+ const mcpPlan = planMcpEntries({
346
+ ...mcpEntriesInput({ paths: found.paths, mcp, mcpAdoptable, ledger, assets }), files: found.mcpSnapshot, removing,
347
+ });
348
+ changes.push(...mcpPlan.changes);
349
+ conflicts.push(...mcpPlan.conflicts);
350
+
351
+ const copyTree = planCopyTreeAssets({ assets, scope, scopeRoot, context, ledger, removing, fsImpl });
352
+ changes.push(...copyTree.changes);
353
+ conflicts.push(...copyTree.conflicts);
354
+
355
+ return { changes, conflicts, notices: [...copyTree.notices, ...mcpPlan.notices], paths: found.paths };
356
+ }
357
+
358
+ function writeChange(change , fsImpl ) {
359
+ fsImpl.mkdirSync(path.dirname(change.target), { recursive: true });
360
+ fsImpl.writeFileSync(change.target, change.content, 'utf8');
361
+ }
362
+
363
+ // The config change is written first; the entry changes then go to a fresh read of the same file,
364
+ // so they apply on top of it.
365
+ function apply({ changes = [], fsImpl = fs } ) {
366
+ let applied = 0;
367
+ for (const change of changes) {
368
+ if (change.operation === 'remove') continue;
369
+ if (change.kind === 'copy-tree-file' || change.kind === 'mcp-server') continue; // routed below
370
+ writeChange(change, fsImpl);
371
+ applied += 1;
372
+ }
373
+ applied += writeMcpEntries(changes, { container: MCP_CONTAINER, fsImpl });
374
+ applied += applyCopyTreeAssets(changes, { fsImpl });
375
+ // An update that no longer ships a copy-tree file carries its rows as removals inside the
376
+ // apply batch; the lifecycle calls remove() only for `doflow remove`.
377
+ removeCopyTreeAssets(changes, { fsImpl });
378
+ return { applied };
379
+ }
380
+
381
+ function remove({ changes = [], fsImpl = fs } ) {
382
+ let removed = 0;
383
+ for (const change of changes) {
384
+ if (change.operation !== 'remove') continue;
385
+ if (change.kind === 'copy-tree-file' || change.kind === 'mcp-server') continue; // routed below
386
+ if (typeof change.content !== 'string') continue;
387
+ writeChange(change, fsImpl);
388
+ removed += 1;
389
+ }
390
+ removed += writeMcpEntries(changes, { container: MCP_CONTAINER, fsImpl });
391
+ removed += removeCopyTreeAssets(changes, { fsImpl });
392
+ return { removed };
393
+ }
394
+
395
+ function verify({ scope, scopeRoot, assets = [], mcp = [], mcpAdoptable = [], discovery, ledger, operation, context = {}, fsImpl = fs } ) {
396
+ const found = discover({ scope, scopeRoot, mcp, mcpAdoptable, ledger, context, fsImpl });
397
+ const removing = (operation ?? context.operation) === 'remove';
398
+ const statuses = [];
399
+ const resources = [];
400
+ const conflicts = [];
401
+ const instructionsAssetId = assets.find((asset ) => asset.capability === 'instructions')?.id ?? pseudoAssetId(assets);
402
+
403
+ const hasSection = typeof found.instruction === 'string'
404
+ && found.instruction.includes(MARKER_START) && found.instruction.includes(MARKER_END);
405
+ statuses.push({ harness: HARNESS, assetId: instructionsAssetId, capability: 'instructions',
406
+ status: hasSection ? 'managed' : 'absent', target: found.paths.instruction,
407
+ ownershipIdentity: `${HARNESS}:instructions:managed-section` });
408
+ if (hasSection) {
409
+ resources.push({ assetId: instructionsAssetId, target: found.paths.instruction,
410
+ ownershipIdentity: `${HARNESS}:instructions:managed-section`, fingerprint: fingerprint(found.instruction),
411
+ sourceVersion: context.sourceVersion ?? 'unknown', projection: { renderer: 'opencode-instructions' } });
412
+ }
413
+
414
+ if (!found.mcpSnapshot[found.paths.config].ok) {
415
+ // The refusal itself comes from the entry verify below; a removal released the row instead.
416
+ statuses.push({ harness: HARNESS, assetId: pseudoAssetId(assets), capability: 'settings', status: removing ? 'not-managed' : 'invalid',
417
+ target: found.paths.config, ownershipIdentity: `${HARNESS}:config:registration` });
418
+ } else {
419
+ const value = found.config.value || {};
420
+ const registered = Array.isArray(value.instructions) && value.instructions.includes(INSTRUCTION_FILE);
421
+ statuses.push({ harness: HARNESS, assetId: pseudoAssetId(assets), capability: 'settings',
422
+ status: registered ? 'managed' : 'absent', target: found.paths.config,
423
+ ownershipIdentity: `${HARNESS}:config:registration` });
424
+ if (registered) {
425
+ resources.push({ assetId: pseudoAssetId(assets), target: found.paths.config,
426
+ ownershipIdentity: `${HARNESS}:config:registration`, fingerprint: fingerprint(value),
427
+ sourceVersion: context.sourceVersion ?? 'unknown', projection: { renderer: 'opencode-config' } });
428
+ }
429
+ if (context.permissions) {
430
+ const ours = guardrailBashRules(context);
431
+ const projected = Object.entries(ours).every(([glob, effect]) => value.permission?.bash?.[glob] === effect);
432
+ statuses.push({ harness: HARNESS, assetId: pseudoAssetId(assets), capability: 'settings',
433
+ status: context.operation === 'remove' ? (projected ? 'retained' : 'absent') : (projected ? 'managed' : 'missing'),
434
+ ownershipIdentity: `${HARNESS}:guardrails:permissions`, target: found.paths.config });
435
+ }
436
+ }
437
+
438
+ const mcpResult = verifyMcpEntries({
439
+ ...mcpEntriesInput({ paths: found.paths, mcp, mcpAdoptable, ledger, assets }), files: found.mcpSnapshot,
440
+ snapshot: discovery?.mcpSnapshot ?? found.mcpSnapshot, removing, harness: HARNESS, sourceVersion: context.sourceVersion ?? 'unknown',
441
+ });
442
+ resources.push(...mcpResult.resources);
443
+ statuses.push(...mcpResult.statuses);
444
+ conflicts.push(...mcpResult.conflicts);
445
+
446
+ const copyTree = verifyCopyTreeAssets({ assets, scope, scopeRoot, context, fsImpl });
447
+ resources.push(...copyTree.resources);
448
+ statuses.push(...copyTree.statuses.map((status ) => ({ harness: HARNESS, ...status })));
449
+ conflicts.push(...copyTree.conflicts);
450
+
451
+ return { ok: conflicts.length === 0 && !statuses.some((s) => s.status === 'invalid' || s.status === 'conflict'), resources, statuses, conflicts };
452
+ }
453
+
454
+ return { nativePaths, discover, render, plan, apply, remove, verify, managedInstruction, strippedInstruction, mergeConfig, unmergeConfig };
455
+ }
456
+
457
+ const singleton = createOpenCodeAdapter();
458
+
459
+ module.exports = {
460
+ HARNESS, MARKER_START, MARKER_END,
461
+ nativePaths: singleton.nativePaths, discover: singleton.discover, render: singleton.render,
462
+ plan: singleton.plan, apply: singleton.apply, remove: singleton.remove, verify: singleton.verify,
463
+ managedInstruction: singleton.managedInstruction, strippedInstruction: singleton.strippedInstruction,
464
+ mergeConfig: singleton.mergeConfig, unmergeConfig: singleton.unmergeConfig,
465
+ createOpenCodeAdapter,
466
+ };