@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,548 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ // Claude native adapter. It owns only Claude path/format decisions; selection and
6
+ // ownership journalling remain lifecycle concerns. Its MCP entries live in ./mcp.ts.
7
+ const crypto = require('node:crypto');
8
+ const fs = require('node:fs');
9
+ const path = require('node:path');
10
+ const { mergeMarkedSection, removeMarkedSection, MARKER_START, MARKER_END } = require('../../helper/marker-merge.js');
11
+ const { selectMcpServers } = require('../../registry/index.js');
12
+ const { GLOBAL_HOOK_PREFIX, PROJECT_HOOK_PREFIX } = require('../../helper/settings-scope.js');
13
+ const { mergeSettings, settingsContains, settingsContainsAny, stripManagedSettings } = require('../../helper/settings-merge.js');
14
+ const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices, resolveTransform } = require('../copy-tree.js');
15
+ const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
16
+ const { discoverMcp, planMcp, writeMcp, verifyMcp } = require('./mcp.js');
17
+
18
+ const INSTRUCTION_RENDERER = 'claude-instructions';
19
+ const SETTINGS_RENDERER = 'claude-settings';
20
+ /**
21
+ * Logic residue (content substitution + source anchoring, not install destinations):
22
+ * - the second settings file this asset projects; its destination IS declared (paths.keybindings),
23
+ * what stays here is the per-file dispatch between the two declared surfaces;
24
+ * - the hook-command prefixes rewritten INTO the authored settings content — `~/.claude/hooks/`
25
+ * becomes Claude Code's documented ${CLAUDE_PROJECT_DIR}/.claude/hooks/ placeholder at project
26
+ * scope (code.claude.com/docs/en/hooks). That placeholder is Claude-native payload syntax, not a
27
+ * filesystem join, so it cannot be declared;
28
+ * - `doflow-statusline.sh` names the projected script for the SOURCE read and the ledger identity;
29
+ * its install destination is declared (paths.statuslineScript).
30
+ */
31
+ const SETTINGS_FILES = ['settings.json', 'keybindings.json'];
32
+
33
+ function sha256(value ) {
34
+ return crypto.createHash('sha256').update(value).digest('hex');
35
+ }
36
+
37
+ function sourcePath(asset , context = {}) {
38
+ if (!asset || typeof asset.source !== 'string') throw new Error('Claude asset requires a source path');
39
+ const repoRoot = context.repoRoot ? path.resolve(context.repoRoot) : process.cwd();
40
+ const source = path.resolve(repoRoot, asset.source);
41
+ if (!source.startsWith(`${repoRoot}${path.sep}`) || !fs.existsSync(source)) {
42
+ throw new Error(`Claude asset source is unavailable: ${asset.source}`);
43
+ }
44
+ return source;
45
+ }
46
+
47
+ function markerState(file ) {
48
+ if (!fs.existsSync(file)) return { exists: false, managed: false, malformed: false };
49
+ const text = fs.readFileSync(file, 'utf8');
50
+ const starts = text.split(MARKER_START).length - 1;
51
+ const ends = text.split(MARKER_END).length - 1;
52
+ return { exists: true, managed: starts === 1 && ends === 1, malformed: starts !== ends || starts > 1 };
53
+ }
54
+
55
+ /** Single serialization form for every settings write, so repeated runs are byte-stable. */
56
+ const serializeSettings = (value ) => `${JSON.stringify(value, null, 2)}\n`;
57
+ const settingsChangeExtras = (fileName ) => ({ kind: 'settings-file', identity: fileName });
58
+
59
+ /**
60
+ * Native path facts live in core/registry/harnesses.json under this harness's "paths" section and
61
+ * resolve through the shared harness-paths resolver; nothing below hardcodes them.
62
+ * createClaudeAdapter({ declaredPaths }) is the injection point buildAdapterRegistry() uses;
63
+ * module-level exports delegate to a default-configured instance so direct callers keep their
64
+ * historical shape.
65
+ */
66
+ function createClaudeAdapter({ declaredPaths = declaredHarnessPaths().claude } = {}) {
67
+ // Parity note: Claude's historical nativePaths() resolved BOTH scopes' root from scopeRoot (the
68
+ // CLI passes $HOME there for -g installs) and never consulted homeDir — so the resolver is fed
69
+ // no homeDir and "root" keeps resolving exactly as before.
70
+ function nativePaths({ scope, scopeRoot } = {}) {
71
+ return resolveHarnessPaths(declaredPaths, { scope, scopeRoot });
72
+ }
73
+
74
+ function discover({ scope, scopeRoot, mcp = [], mcpAdoptable = [], ledger, registry, homeDir, context = {} } ) {
75
+ const paths = nativePaths({ scope, scopeRoot });
76
+ return {
77
+ paths,
78
+ instructions: markerState(paths.instructions),
79
+ settingsExists: fs.existsSync(paths.settings),
80
+ mcpExists: fs.existsSync(paths.mcp),
81
+ knownMcpServers: registry ? selectMcpServers(registry).map((server ) => server.id) : [],
82
+ ...discoverMcp({ paths, mcp, mcpAdoptable, ledger, scope, scopeRoot, homeDir: context.homeDir || homeDir || undefined }),
83
+ };
84
+ }
85
+
86
+ // ---- copy-tree assets (rules, skills, agents, templates, scripts, modes, references, hooks) ----
87
+
88
+ /** Every asset materialised through the copy-tree engine, whatever its declared renderer:
89
+ * verbatim mirrors declare 'copy-tree', transformed projections (output styles) declare their
90
+ * own name so the registry can tell the shapes apart. Routing is on `kind`, which both share. */
91
+ function claudeTreeAssets(assets ) {
92
+ return [...copyTreeAssets(assets), ...(assets || []).filter((asset ) => asset.renderer === 'claude-output-styles')];
93
+ }
94
+
95
+ function planCopyTreeAssets({ assets, scope, scopeRoot, context, ledger, removing } ) {
96
+ const paths = nativePaths({ scope, scopeRoot });
97
+ const changes = [];
98
+ const conflicts = [];
99
+ const treeResults = [];
100
+ const notices = [];
101
+ for (const asset of claudeTreeAssets(assets)) {
102
+ const destDir = copyTreeDestDir(paths.configDir, asset);
103
+ const sourceDir = sourcePath(asset, context);
104
+ const previousResources = ledgerFileResources(ledger?.resources, 'claude', asset.id);
105
+ const result = planTree({ sourceDir, destDir, previousResources, siblingFingerprints: ledgerSiblingFingerprints(ledger?.resources, 'claude'), operation: removing ? 'remove' : 'apply', layout: asset.layout, transform: asset.transform, keepModified: !removing,
106
+ // Forwarded so the CLI's --force reaches planTree's conflict check; omitting it let
107
+ // planTree's own `force = false` default stand in silently. Gated on `!removing` for the
108
+ // reason codex/index.ts states in full: force heals drift on apply, but a hand-edited file
109
+ // is never deleted on removal, forced or not.
110
+ force: !removing && context?.force === true, });
111
+ treeResults.push(result);
112
+ conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
113
+ // A hand-edited file at a path DoFlow no longer writes is the user's: it stays, only its
114
+ // ownership row is released.
115
+ notices.push(...result.kept.map((item ) => `kept hand-edited ${path.relative(paths.root, item.target)}; DoFlow no longer manages it`));
116
+ for (const change of result.changes) {
117
+ changes.push({
118
+ assetId: asset.id, target: change.target, source: change.source, operation: change.operation,
119
+ ownershipIdentity: `doflow:claude:copy-tree:${asset.id}:${change.relPath}`,
120
+ kind: 'copy-tree-file', identity: change.relPath,
121
+ afterFingerprint: change.fingerprint, fingerprint: change.fingerprint, sourceVersion: 'registry-v1',
122
+ transformName: asset.transform || null,
123
+ ...(change.kept ? { retained: true, retainedFor: [] } : {}),
124
+ projection: { renderer: 'copy-tree' },
125
+ });
126
+ }
127
+ }
128
+ return { changes, conflicts, notices: [...siblingReplacedNotices(treeResults), ...notices] };
129
+ }
130
+
131
+ function applyCopyTreeAssets(changes ) {
132
+ let applied = 0;
133
+ const grouped = new Map();
134
+ for (const change of changes) {
135
+ if (change.kind !== 'copy-tree-file' || change.operation === 'remove') continue;
136
+ const key = change.transformName || null;
137
+ if (!grouped.has(key)) grouped.set(key, []);
138
+ grouped.get(key).push({ relPath: change.identity, target: change.target, source: change.source, operation: change.operation, fingerprint: change.fingerprint });
139
+ }
140
+ for (const [transformName, treeChanges] of grouped) {
141
+ applied += applyTree({ changes: treeChanges, transform: resolveTransform(transformName) }).applied;
142
+ }
143
+ return applied;
144
+ }
145
+
146
+ function removeCopyTreeAssets(changes ) {
147
+ const treeChanges = changes.filter((change ) => change.kind === 'copy-tree-file' && change.operation === 'remove' && !change.retained)
148
+ .map((change ) => ({ relPath: change.identity, target: change.target, operation: 'remove', fingerprint: change.fingerprint, pruneGuard: change.pruneGuard }));
149
+ return removeTree({ changes: treeChanges }).removed;
150
+ }
151
+
152
+ function verifyCopyTreeAssets({ assets, scope, scopeRoot, context } ) {
153
+ const paths = nativePaths({ scope, scopeRoot });
154
+ const statuses = [];
155
+ const resources = [];
156
+ const conflicts = [];
157
+ for (const asset of claudeTreeAssets(assets)) {
158
+ const destDir = copyTreeDestDir(paths.configDir, asset);
159
+ const sourceDir = sourcePath(asset, context);
160
+ const result = verifyTree({ sourceDir, destDir, layout: asset.layout, transform: asset.transform, plannedWrites: context?.plannedWrites });
161
+ conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
162
+ for (const resource of result.resources) {
163
+ resources.push({
164
+ assetId: asset.id, target: resource.target, ownershipIdentity: `doflow:claude:copy-tree:${asset.id}:${resource.relPath}`,
165
+ kind: 'copy-tree-file', identity: resource.relPath,
166
+ fingerprint: resource.fingerprint, sourceVersion: 'registry-v1',
167
+ projection: { renderer: 'copy-tree' },
168
+ });
169
+ }
170
+ statuses.push({ assetId: asset.id, capability: asset.capability, status: result.ok ? 'managed' : 'conflict', target: destDir });
171
+ }
172
+ return { statuses, resources, conflicts };
173
+ }
174
+
175
+ // ---- claude-settings asset (settings.json + keybindings.json) ----
176
+
177
+ function settingsAsset(assets ) { return (assets || []).find((asset ) => asset?.renderer === SETTINGS_RENDERER); }
178
+ function settingsTarget(fileName , paths ) {
179
+ // Per-file dispatch across the two DECLARED settings surfaces (paths.settings / paths.keybindings):
180
+ // residue because the authored file list is content-shaped, not a single declarable destination.
181
+ return fileName === 'settings.json' ? paths.settings : paths.keybindings;
182
+ }
183
+ function settingsContent(fileName , raw , scope ) {
184
+ return fileName === 'settings.json' && scope !== 'global' ? raw.split(GLOBAL_HOOK_PREFIX).join(PROJECT_HOOK_PREFIX) : raw;
185
+ }
186
+ function settingsLedgerResources(ledger , assetId ) {
187
+ const byFile = new Map();
188
+ for (const resource of ledger?.resources || []) {
189
+ if (resource.harness === 'claude' && resource.assetId === assetId && resource.kind === 'settings-file') byFile.set(resource.identity, resource);
190
+ }
191
+ return byFile;
192
+ }
193
+ const statuslineScriptName = 'doflow-statusline.sh';
194
+
195
+ /** Inject (or leave untouched) the statusLine key pointing at the projected script. Refuses to
196
+ * overwrite a user's existing custom status line unless forced: their script is theirs. */
197
+ function withStatusLineKey(rawSettingsJson , scriptPath , force ) {
198
+ let parsed;
199
+ try {
200
+ parsed = JSON.parse(rawSettingsJson);
201
+ } catch {
202
+ return rawSettingsJson; // malformed authored settings are another guard's problem
203
+ }
204
+ const existing = parsed.statusLine?.command;
205
+ if (existing && existing !== scriptPath && !force) return rawSettingsJson;
206
+ parsed.statusLine = { type: 'command', command: scriptPath };
207
+ return serializeSettings(parsed);
208
+ }
209
+
210
+ function planSettingsAsset({ assets, scope, scopeRoot, context, ledger, removing } ) {
211
+ const asset = settingsAsset(assets);
212
+ const changes = [];
213
+ const conflicts = [];
214
+ if (!asset) return { changes, conflicts };
215
+ const paths = nativePaths({ scope, scopeRoot });
216
+ const sourceDir = sourcePath(asset, context);
217
+ const previous = settingsLedgerResources(ledger, asset.id);
218
+ if (removing) {
219
+ for (const fileName of SETTINGS_FILES) {
220
+ const prev = previous.get(fileName);
221
+ if (!prev) continue;
222
+ const target = settingsTarget(fileName, paths);
223
+ if (!fs.existsSync(target)) continue;
224
+ const sourceAbs = path.join(sourceDir, fileName);
225
+ if (!fs.existsSync(sourceAbs)) continue;
226
+ // No fingerprint gate on removal any more: a shared file is *expected* to have drifted
227
+ // (the user edited it, another tool registered a hook). removeSettingsAsset subtracts only our own
228
+ // entries, so a drifted file is safe to process rather than a reason to refuse.
229
+ changes.push({ assetId: asset.id, target, operation: 'remove', ownershipIdentity: `doflow:claude:settings:${fileName}`,
230
+ ...settingsChangeExtras(fileName), fingerprint: prev.fingerprint,
231
+ managed: settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope),
232
+ projection: { renderer: SETTINGS_RENDERER } });
233
+ }
234
+ if (previous.get(statuslineScriptName)) {
235
+ changes.push({ assetId: asset.id, target: paths.statuslineScript,
236
+ operation: 'remove', ownershipIdentity: 'doflow:claude:statusline',
237
+ ...settingsChangeExtras(statuslineScriptName), fingerprint: previous.get(statuslineScriptName).fingerprint,
238
+ managed: null, projection: { renderer: SETTINGS_RENDERER } });
239
+ }
240
+ return { changes, conflicts };
241
+ }
242
+ for (const fileName of SETTINGS_FILES) {
243
+ const sourceAbs = path.join(sourceDir, fileName);
244
+ if (!fs.existsSync(sourceAbs)) continue;
245
+ let managedRaw = settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope);
246
+ if (fileName === 'settings.json' && context.statusline && !removing) {
247
+ // --statusline: point Claude Code's statusLine key at the projected script. It rides the
248
+ // same managed settings write as everything else this asset owns — one serialization, one
249
+ // fingerprint, one ownership row.
250
+ managedRaw = withStatusLineKey(managedRaw, paths.statuslineScript, Boolean(context.force));
251
+ }
252
+ const target = settingsTarget(fileName, paths);
253
+ const prev = previous.get(fileName);
254
+ // Both the fresh-install and merge paths emit re-serialized JSON so the on-disk bytes are
255
+ // identical either way. Writing raw source bytes on create and re-serialized bytes on every
256
+ // later run would make each update look like a change and break idempotency.
257
+ let content;
258
+ try { content = serializeSettings(JSON.parse(managedRaw)); }
259
+ catch { conflicts.push(`${fileName} source is not valid JSON`); continue; }
260
+ let operation = prev ? 'update' : 'create';
261
+
262
+ if (fs.existsSync(target)) {
263
+ // settings.json is shared with the user and with third-party installers, so "the file
264
+ // differs from our source" is the normal steady state, not drift. Merge our keys in and
265
+ // leave everything else alone; only genuinely unreadable JSON is a conflict now. The old
266
+ // byte-identity check made a first install impossible on any already-configured machine.
267
+ const currentRaw = fs.readFileSync(target, 'utf8');
268
+ let merged;
269
+ try {
270
+ merged = mergeSettings(JSON.parse(currentRaw), JSON.parse(managedRaw));
271
+ } catch {
272
+ conflicts.push(`${fileName} is not valid JSON and cannot be merged`);
273
+ continue;
274
+ }
275
+ content = serializeSettings(merged);
276
+ if (sha256(currentRaw) === sha256(content)) continue; // already contains our keys verbatim
277
+ // 'merge' is the same operation the marker-merged instructions file reports — it is the
278
+ // lifecycle's existing vocabulary for "fold our content into a file we do not own".
279
+ operation = 'merge';
280
+ }
281
+
282
+ const fingerprint = sha256(content);
283
+ changes.push({ assetId: asset.id, target, source: sourceAbs, content, operation,
284
+ ownershipIdentity: `doflow:claude:settings:${fileName}`, ...settingsChangeExtras(fileName),
285
+ afterFingerprint: fingerprint, fingerprint, sourceVersion: 'registry-v1',
286
+ projection: { renderer: SETTINGS_RENDERER } });
287
+ }
288
+
289
+ if (context.statusline) {
290
+ const scriptSource = path.join(path.dirname(sourceDir), 'statusline', statuslineScriptName);
291
+ if (fs.existsSync(scriptSource)) {
292
+ const target = paths.statuslineScript;
293
+ const content = fs.readFileSync(scriptSource, 'utf8');
294
+ const fingerprint = sha256(content);
295
+ const prev = previous.get(statuslineScriptName);
296
+ const operation = !prev ? 'create' : (fs.existsSync(target) && sha256(fs.readFileSync(target, 'utf8')) === fingerprint ? null : 'update');
297
+ // A byte-identical script is a no-op — but the settings key above still needed its merge
298
+ // pass, so only the file write is skipped, never the change record's bookkeeping.
299
+ if (operation) {
300
+ changes.push({ assetId: asset.id, target, source: scriptSource, content, operation,
301
+ ownershipIdentity: 'doflow:claude:statusline', ...settingsChangeExtras(statuslineScriptName),
302
+ afterFingerprint: fingerprint, fingerprint, sourceVersion: 'registry-v1',
303
+ projection: { renderer: SETTINGS_RENDERER } });
304
+ }
305
+ }
306
+ }
307
+ return { changes, conflicts };
308
+ }
309
+
310
+ // Sets the destination's mtime to match its source file's, not the write time, mirroring every
311
+ // other copy-tree/settings write in this codebase — planSettingsAsset's own change detection is
312
+ // fingerprint(sha256)-based, not mtime-based, so this is a consistency convention, not a
313
+ // correctness requirement.
314
+ function applySettingsAsset(changes ) {
315
+ let applied = 0;
316
+ for (const change of changes) {
317
+ if (change.projection?.renderer !== SETTINGS_RENDERER || change.operation === 'remove') continue;
318
+ fs.mkdirSync(path.dirname(change.target), { recursive: true });
319
+ fs.writeFileSync(change.target, change.content);
320
+ const sourceStat = fs.statSync(change.source);
321
+ fs.utimesSync(change.target, Math.floor(sourceStat.atimeMs / 1000), Math.floor(sourceStat.mtimeMs / 1000));
322
+ applied += 1;
323
+ }
324
+ return applied;
325
+ }
326
+
327
+ // Strips only DoFlow's own hook entries and leaves the rest of a shared file standing. The file
328
+ // is deleted outright only when nothing but schema pointers remains — i.e. DoFlow created it.
329
+ // Previously this rmSync'd unconditionally, which on a user-maintained settings.json would have
330
+ // destroyed their model, permissions, env, and every foreign tool's hooks along with ours.
331
+ function removeSettingsAsset(changes ) {
332
+ let removed = 0;
333
+ for (const change of changes) {
334
+ if (change.projection?.renderer !== SETTINGS_RENDERER || change.operation !== 'remove') continue;
335
+ if (!fs.existsSync(change.target)) continue;
336
+ let remaining = null;
337
+ if (change.managed) {
338
+ try {
339
+ remaining = stripManagedSettings(JSON.parse(fs.readFileSync(change.target, 'utf8')), JSON.parse(change.managed));
340
+ } catch { remaining = undefined; } // unreadable -> leave the file untouched
341
+ }
342
+ if (remaining === undefined) continue;
343
+ if (remaining === null) fs.rmSync(change.target, { force: true });
344
+ else fs.writeFileSync(change.target, serializeSettings(remaining));
345
+ removed += 1;
346
+ }
347
+ return removed;
348
+ }
349
+
350
+ function verifySettingsAsset({ assets, scope, scopeRoot, context } ) {
351
+ const asset = settingsAsset(assets);
352
+ const statuses = [];
353
+ const resources = [];
354
+ const conflicts = [];
355
+ if (!asset) return { statuses, resources, conflicts };
356
+ const paths = nativePaths({ scope, scopeRoot });
357
+ const sourceDir = sourcePath(asset, context);
358
+ for (const fileName of SETTINGS_FILES) {
359
+ const sourceAbs = path.join(sourceDir, fileName);
360
+ if (!fs.existsSync(sourceAbs)) continue;
361
+ const fingerprint = sha256(settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope));
362
+ const target = settingsTarget(fileName, paths);
363
+ // 'absent' (not 'missing') matches the same convention the instructions asset already uses
364
+ // for "no file here" — 'missing' is reserved by the lifecycle layer's removal reconciliation
365
+ // as a hard-failure status, which would make every successful remove look like a verification
366
+ // failure (nothing to overwrite it back to 'removed', since this is a per-file, not a
367
+ // Codex-style shared-container-with-ownershipIdentity-matched status).
368
+ if (!fs.existsSync(target)) { statuses.push({ assetId: asset.id, capability: 'settings', status: 'absent', target }); continue; }
369
+ // Containment, not equality: a shared file legitimately holds the user's own keys and other
370
+ // tools' hook entries. Verification asks only "are DoFlow's keys still here and intact?" —
371
+ // byte-equality would report every user edit as a conflict forever.
372
+ let current;
373
+ let managed;
374
+ try {
375
+ current = JSON.parse(fs.readFileSync(target, 'utf8'));
376
+ managed = JSON.parse(settingsContent(fileName, fs.readFileSync(sourceAbs, 'utf8'), scope));
377
+ } catch {
378
+ conflicts.push(`${fileName} is not valid JSON`);
379
+ statuses.push({ assetId: asset.id, capability: 'settings', status: 'conflict', target });
380
+ continue;
381
+ }
382
+ if (!settingsContains(current, managed)) {
383
+ // None of our hook entries left -> this is a removed/never-installed file that legitimately
384
+ // still holds the user's own content, not damage. Only a partial presence is a conflict.
385
+ if (!settingsContainsAny(current, managed)) {
386
+ statuses.push({ assetId: asset.id, capability: 'settings', status: 'absent', target });
387
+ continue;
388
+ }
389
+ conflicts.push(`${fileName} is missing DoFlow-managed settings`);
390
+ statuses.push({ assetId: asset.id, capability: 'settings', status: 'conflict', target });
391
+ continue;
392
+ }
393
+ statuses.push({ assetId: asset.id, capability: 'settings', status: 'managed', target });
394
+ resources.push({ assetId: asset.id, target, ownershipIdentity: `doflow:claude:settings:${fileName}`,
395
+ ...settingsChangeExtras(fileName), fingerprint, sourceVersion: 'registry-v1', projection: { renderer: SETTINGS_RENDERER } });
396
+ }
397
+ if (context.statusline) {
398
+ const target = paths.statuslineScript;
399
+ const scriptSource = path.join(path.dirname(sourceDir), 'statusline', statuslineScriptName);
400
+ if (!fs.existsSync(scriptSource)) {
401
+ conflicts.push('doflow-statusline.sh source is missing from this checkout');
402
+ statuses.push({ assetId: asset.id, capability: 'settings', status: 'conflict', target });
403
+ } else {
404
+ const expected = sha256(fs.readFileSync(scriptSource, 'utf8'));
405
+ const current = fs.existsSync(target) ? sha256(fs.readFileSync(target, 'utf8')) : null;
406
+ if (current === null || (current !== expected && !(context.operation === 'remove'))) {
407
+ statuses.push({ assetId: asset.id, capability: 'settings', status: current === null ? 'absent' : 'missing',
408
+ ownershipIdentity: 'doflow:claude:statusline', target });
409
+ } else {
410
+ statuses.push({ assetId: asset.id, capability: 'settings', status: current === expected ? 'managed' : 'absent',
411
+ ownershipIdentity: 'doflow:claude:statusline', target });
412
+ if (current === expected) {
413
+ resources.push({ assetId: asset.id, target, ownershipIdentity: 'doflow:claude:statusline',
414
+ ...settingsChangeExtras(statuslineScriptName), fingerprint: expected,
415
+ sourceVersion: 'registry-v1', projection: { renderer: SETTINGS_RENDERER } });
416
+ }
417
+ }
418
+ }
419
+ }
420
+ return { statuses, resources, conflicts };
421
+ }
422
+
423
+ // ---- shared adapter contract ----
424
+
425
+ /** Render native Claude input without writing it.
426
+ * `asset` is the lifecycle-projected shape (flat `renderer`/`capability` fields — see
427
+ * src/adapters/index.js#projectAdapterInput), not the raw registry asset's nested
428
+ * `asset.projection.claude`. Every adapter method below shares that same expectation. */
429
+ function render({ asset, scope, scopeRoot, context = {} } ) {
430
+ const paths = nativePaths({ scope, scopeRoot });
431
+ if (asset?.renderer === INSTRUCTION_RENDERER) {
432
+ const source = sourcePath(asset, context);
433
+ const content = fs.readFileSync(source, 'utf8');
434
+ return { type: 'managed-section', assetId: asset.id, source, target: paths.instructions, content, fingerprint: sha256(content) };
435
+ }
436
+ if (asset?.renderer === 'copy-tree') {
437
+ const sourceDir = sourcePath(asset, context);
438
+ return { type: 'copy-tree', assetId: asset.id, sourceDir, destDir: copyTreeDestDir(paths.configDir, asset) };
439
+ }
440
+ if (asset?.renderer === SETTINGS_RENDERER) {
441
+ const sourceDir = sourcePath(asset, context);
442
+ return { type: 'settings-tree', assetId: asset.id, sourceDir, files: SETTINGS_FILES, targets: SETTINGS_FILES.map((fileName) => settingsTarget(fileName, paths)) };
443
+ }
444
+ return { type: 'unsupported', assetId: asset?.id, renderer: asset?.renderer ?? null };
445
+ }
446
+
447
+ function plan({ assets, scope, scopeRoot, mcp = [], mcpAdoptable = [], discovery, context = {}, ledger } ) {
448
+ const changes = [];
449
+ const conflicts = [];
450
+ const paths = nativePaths({ scope, scopeRoot });
451
+ const removing = context.operation === 'remove';
452
+ for (const asset of assets) {
453
+ if (asset?.renderer !== INSTRUCTION_RENDERER) continue;
454
+ if (discovery?.instructions?.malformed) {
455
+ conflicts.push(`Claude instructions contain malformed DoFlow markers: ${paths.instructions}`);
456
+ continue;
457
+ }
458
+ const rendered = render({ asset, scope, scopeRoot, context });
459
+ // Outside of removal, only report a change when the merge would actually alter the file —
460
+ // otherwise every install/update run would report a pending Claude change even when nothing
461
+ // changed, breaking the idempotent "already up to date" convergence every other mapping gets.
462
+ if (!removing && !mergeMarkedSection(rendered.source, rendered.target, { dryRun: true }).changed) continue;
463
+ changes.push({
464
+ assetId: asset.id,
465
+ target: rendered.target,
466
+ source: rendered.source,
467
+ operation: removing ? 'remove' : (discovery?.instructions?.managed ? 'merge' : 'create'),
468
+ ownershipIdentity: 'doflow:claude:instructions:managed-section',
469
+ afterFingerprint: rendered.fingerprint,
470
+ sourceVersion: 'registry-v1',
471
+ projection: { renderer: INSTRUCTION_RENDERER, target: 'CLAUDE.md' },
472
+ });
473
+ }
474
+ const copyTree = planCopyTreeAssets({ assets, scope, scopeRoot, context, ledger, removing });
475
+ changes.push(...copyTree.changes); conflicts.push(...copyTree.conflicts);
476
+ const settings = planSettingsAsset({ assets, scope, scopeRoot, context, ledger, removing });
477
+ changes.push(...settings.changes); conflicts.push(...settings.conflicts);
478
+ const mcpPlan = planMcp({ paths, mcp, mcpAdoptable, ledger, snapshot: discovery?.mcpSnapshot, removing });
479
+ changes.push(...mcpPlan.changes); conflicts.push(...mcpPlan.conflicts);
480
+ return { changes, conflicts, prerequisites: [], notices: [...copyTree.notices, ...mcpPlan.notices] };
481
+ }
482
+
483
+ function apply({ changes, scope, scopeRoot, context = {} } ) {
484
+ for (const change of changes) {
485
+ if (change.projection?.renderer !== INSTRUCTION_RENDERER) continue;
486
+ // A plan records the resolved source. This keeps apply self-contained when
487
+ // the lifecycle supplies normalized changes rather than the original asset
488
+ // array, while still avoiding any implicit source discovery at write time.
489
+ const source = change.source ?? sourcePath((context.assets || []).find((item ) => item.id === change.assetId), context);
490
+ mergeMarkedSection(source, change.target ?? nativePaths({ scope, scopeRoot }).instructions);
491
+ }
492
+ applyCopyTreeAssets(changes);
493
+ // An update that no longer ships a copy-tree file carries its rows as removals inside the
494
+ // apply batch; the lifecycle calls remove() only for `doflow remove`.
495
+ removeCopyTreeAssets(changes);
496
+ applySettingsAsset(changes);
497
+ writeMcp(changes);
498
+ return { applied: changes.length };
499
+ }
500
+
501
+ function remove({ changes } ) {
502
+ let removed = 0;
503
+ for (const change of changes) {
504
+ if (change.projection?.renderer === INSTRUCTION_RENDERER && removeMarkedSection(change.target)) removed += 1;
505
+ }
506
+ removed += removeCopyTreeAssets(changes);
507
+ removed += removeSettingsAsset(changes);
508
+ removed += writeMcp(changes);
509
+ return { removed };
510
+ }
511
+
512
+ function verify({ assets, scope, scopeRoot, mcp = [], mcpAdoptable = [], discovery, ledger, operation, context = {} } ) {
513
+ const paths = nativePaths({ scope, scopeRoot });
514
+ const statuses = [];
515
+ const resources = [];
516
+ const instruction = assets.find((asset ) => asset?.renderer === INSTRUCTION_RENDERER);
517
+ const state = markerState(paths.instructions);
518
+ if (instruction) {
519
+ statuses.push({ assetId: instruction.id, status: state.managed ? 'managed' : 'absent', target: paths.instructions });
520
+ if (state.managed) {
521
+ const rendered = render({ asset: instruction, scope, scopeRoot, context });
522
+ resources.push({ assetId: instruction.id, target: paths.instructions, ownershipIdentity: 'doflow:claude:instructions:managed-section', fingerprint: rendered.fingerprint, sourceVersion: 'registry-v1', projection: { renderer: INSTRUCTION_RENDERER } });
523
+ }
524
+ }
525
+ const copyTree = verifyCopyTreeAssets({ assets, scope, scopeRoot, context });
526
+ statuses.push(...copyTree.statuses); resources.push(...copyTree.resources);
527
+ const settings = verifySettingsAsset({ assets, scope, scopeRoot, context });
528
+ statuses.push(...settings.statuses); resources.push(...settings.resources);
529
+ const mcpResult = verifyMcp({ paths, mcp, mcpAdoptable, ledger, snapshot: discovery?.mcpSnapshot,
530
+ removing: (operation ?? context.operation) === 'remove', sourceVersion: context.sourceVersion ?? 'unknown' });
531
+ statuses.push(...mcpResult.statuses); resources.push(...mcpResult.resources);
532
+ const conflicts = [
533
+ ...(state.malformed ? [`Claude instructions contain malformed DoFlow markers: ${paths.instructions}`] : []),
534
+ ...copyTree.conflicts, ...settings.conflicts, ...mcpResult.conflicts,
535
+ ];
536
+ return { ok: conflicts.length === 0, statuses, resources, conflicts };
537
+ }
538
+
539
+ return { nativePaths, discover, render, plan, apply, remove, verify };
540
+ }
541
+
542
+ const singleton = createClaudeAdapter();
543
+
544
+ module.exports = {
545
+ nativePaths: singleton.nativePaths, discover: singleton.discover, render: singleton.render,
546
+ plan: singleton.plan, apply: singleton.apply, remove: singleton.remove, verify: singleton.verify,
547
+ createClaudeAdapter,
548
+ };
@@ -0,0 +1,94 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ // Claude's MCP component: DoFlow's servers as entries of `mcpServers` in `~/.claude.json` (user
6
+ // scope) or `<project>/.mcp.json` (project scope), the locations Claude Code actually reads. Both
7
+ // files are shared — `~/.claude.json` also holds Claude Code's own state, and a project `.mcp.json`
8
+ // can be read and written by other tools — so DoFlow owns its servers one entry at a time through
9
+ // ../mcp-entries.ts and never rewrites another key or another entry's value.
10
+ const fs = require('node:fs');
11
+ const os = require('node:os');
12
+ const path = require('node:path');
13
+ const { nativeMcpCatalog } = require('../../registry/index.js');
14
+ const { readMcpFiles, planMcpEntries, verifyMcpEntries, ownedMcpIds, writeMcpEntries } = require('../mcp-entries.js');
15
+
16
+ const HARNESS = 'claude';
17
+ const MCP_CONTAINER = 'mcpServers';
18
+ const MCP_RENDERER = 'claude-mcp';
19
+ // Claude's MCP entries have no asset of their own; they ride the guidance asset every Claude
20
+ // install carries.
21
+ const MCP_ASSET_ID = 'guidance.core';
22
+
23
+ function ownershipIdentity(id ) { return `doflow:${HARNESS}:mcp-server:${id}`; }
24
+
25
+ /** Claude Code's own per-server shape, the one the registry's native catalog renders. */
26
+ function rendered(servers ) {
27
+ const { serverDefs } = nativeMcpCatalog(servers);
28
+ return servers.map((server ) => ({ id: server.id, entry: serverDefs[server.id] }));
29
+ }
30
+
31
+ /** Everything the shared entry-ownership rules need from this adapter. */
32
+ function mcpEntriesInput({ paths, mcp = [], mcpAdoptable = [], ledger } ) {
33
+ const rows = (ledger?.resources || []).filter((row ) => row.kind === 'mcp-server');
34
+ return {
35
+ file: paths.mcp, selected: rendered(mcp), adoptable: rendered(mcpAdoptable),
36
+ ownRows: rows.filter((row ) => row.harness === HARNESS).map((row ) => ({
37
+ identity: row.identity, target: row.target, fingerprint: row.fingerprint ?? null, ownershipIdentity: row.ownershipIdentity, legacy: false,
38
+ })),
39
+ foreignRows: rows.filter((row ) => row.harness !== HARNESS).map((row ) => ({ harness: row.harness, identity: row.identity, target: row.target })),
40
+ identityFor: ownershipIdentity, assetId: MCP_ASSET_ID, renderer: MCP_RENDERER, label: 'Claude MCP',
41
+ };
42
+ }
43
+
44
+ function readFiles(entries , fsImpl ) {
45
+ return readMcpFiles({ file: entries.file, ownRows: entries.ownRows, container: MCP_CONTAINER, fsImpl });
46
+ }
47
+
48
+ /** Server names Claude Code merges into a project from `~/.claude.json` beside the project's own
49
+ * `.mcp.json`: the local scope (`projects[<root>].mcpServers`) and the user scope (`mcpServers`).
50
+ * Read-only and for reach alone; a file that cannot be read contributes nothing (null). */
51
+ function claudeJsonNames({ scopeRoot, homeDir, fsImpl } ) {
52
+ let doc;
53
+ try { doc = JSON.parse(fsImpl.readFileSync(path.join(homeDir, '.claude.json'), 'utf8')); } catch { return null; }
54
+ if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return null;
55
+ const names = (servers ) => (servers && typeof servers === 'object' && !Array.isArray(servers) ? Object.keys(servers) : []);
56
+ const projects = doc.projects && typeof doc.projects === 'object' ? doc.projects : {};
57
+ let real = scopeRoot;
58
+ try { real = fsImpl.realpathSync(scopeRoot); } catch { /* the path as given */ }
59
+ return [...names(doc.mcpServers), ...[...new Set([scopeRoot, real])].flatMap((key) => names(projects[key]?.mcpServers))];
60
+ }
61
+
62
+ /** The MCP files as plan read them (`mcpSnapshot`, which verify compares against), the servers
63
+ * DoFlow owns in them now (`mcpOwned`) and every server name DoFlow can see for the scope
64
+ * (`mcpConfigured`): the scope's file, plus at project scope the local- and user-scope entries in
65
+ * `~/.claude.json`. Claude Code also loads plugin, managed and connector servers DoFlow does not
66
+ * read, so the view is never complete (`mcpComplete: false`) and absence is never proof. */
67
+ function discoverMcp({ paths, mcp, mcpAdoptable, ledger, scope, scopeRoot, homeDir = os.homedir(), fsImpl = fs } ) {
68
+ const entries = mcpEntriesInput({ paths, mcp, mcpAdoptable, ledger });
69
+ const mcpSnapshot = readFiles(entries, fsImpl);
70
+ // `null` while nothing could be read; every name otherwise, whoever wrote it.
71
+ const read = mcpSnapshot[entries.file];
72
+ const own = read?.ok && read.servers ? Object.keys(read.servers) : null;
73
+ const merged = scope === 'project' && scopeRoot ? claudeJsonNames({ scopeRoot, homeDir, fsImpl }) : null;
74
+ const mcpConfigured = own || merged ? [...new Set([...(own || []), ...(merged || [])])].sort() : null;
75
+ return { mcpSnapshot, mcpOwned: ownedMcpIds({ ...entries, files: mcpSnapshot }), mcpConfigured, mcpComplete: false };
76
+ }
77
+
78
+ function planMcp({ paths, mcp, mcpAdoptable, ledger, snapshot, removing, fsImpl = fs } ) {
79
+ const entries = mcpEntriesInput({ paths, mcp, mcpAdoptable, ledger });
80
+ return planMcpEntries({ ...entries, files: snapshot ?? readFiles(entries, fsImpl), removing });
81
+ }
82
+
83
+ /** An emptied `mcpServers` stays as `{}`, as Claude Code itself leaves it. */
84
+ function writeMcp(changes , { fsImpl = fs } = {}) {
85
+ return writeMcpEntries(changes, { container: MCP_CONTAINER, keepEmptyContainer: true, fsImpl });
86
+ }
87
+
88
+ function verifyMcp({ paths, mcp, mcpAdoptable, ledger, snapshot, removing, sourceVersion, fsImpl = fs } ) {
89
+ const entries = mcpEntriesInput({ paths, mcp, mcpAdoptable, ledger });
90
+ const files = readFiles(entries, fsImpl);
91
+ return verifyMcpEntries({ ...entries, files, snapshot: snapshot ?? files, removing, harness: HARNESS, sourceVersion });
92
+ }
93
+
94
+ module.exports = { discoverMcp, planMcp, writeMcp, verifyMcp };