@khoavu882/doflow 0.0.0-stage → 2.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (407) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -2
  3. package/bin/doflow.js +44 -0
  4. package/core/.antigravity-plugin/hooks.json +39 -0
  5. package/core/.antigravity-plugin/mcp_config.json +18 -0
  6. package/core/.antigravity-plugin/plugin.json +31 -0
  7. package/core/.claude-plugin/marketplace.json +20 -0
  8. package/core/.claude-plugin/plugin.json +21 -0
  9. package/core/.codex-plugin/marketplace.json +20 -0
  10. package/core/.codex-plugin/plugin.json +35 -0
  11. package/core/.plugin/plugin.json +19 -0
  12. package/core/harnesses/antigravity/hooks/pre-implementation-gate.sh +9 -0
  13. package/core/harnesses/antigravity/hooks/stop-check.sh +10 -0
  14. package/core/harnesses/claude/hooks/lib.sh +304 -0
  15. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +20 -0
  16. package/core/harnesses/claude/hooks/permission-audit.sh +47 -0
  17. package/core/harnesses/claude/hooks/post-compact.sh +74 -0
  18. package/core/harnesses/claude/hooks/post-edit-lint.sh +36 -0
  19. package/core/harnesses/claude/hooks/post-tool-failure.sh +47 -0
  20. package/core/harnesses/claude/hooks/pre-bash-guard.sh +20 -0
  21. package/core/harnesses/claude/hooks/pre-compact.sh +43 -0
  22. package/core/harnesses/claude/hooks/pre-implement-gate.sh +21 -0
  23. package/core/harnesses/claude/hooks/session-end.sh +89 -0
  24. package/core/harnesses/claude/hooks/session-start.sh +5 -0
  25. package/core/harnesses/claude/hooks/skill-config-audit.sh +39 -0
  26. package/core/harnesses/claude/hooks/stop-check.sh +5 -0
  27. package/core/harnesses/claude/hooks/subagent-audit.sh +7 -0
  28. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -0
  29. package/core/harnesses/claude/settings/keybindings.json +20 -0
  30. package/core/harnesses/claude/settings/settings.json +201 -0
  31. package/core/harnesses/claude/statusline/doflow-statusline.sh +49 -0
  32. package/core/harnesses/codex/agents/core-implementer.toml +7 -0
  33. package/core/harnesses/codex/agents/quality-guardian.toml +8 -0
  34. package/core/harnesses/codex/agents/research-writer.toml +8 -0
  35. package/core/harnesses/codex/agents/spec-analyst.toml +8 -0
  36. package/core/harnesses/codex/agents/system-architect.toml +9 -0
  37. package/core/harnesses/codex/config/config.toml +10 -0
  38. package/core/harnesses/codex/hooks/hooks.json +122 -0
  39. package/core/harnesses/codex/hooks/lib.sh +335 -0
  40. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +18 -0
  41. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +52 -0
  42. package/core/harnesses/codex/hooks/post-edit-lint.sh +6 -0
  43. package/core/harnesses/codex/hooks/pre-bash-guard.sh +18 -0
  44. package/core/harnesses/codex/hooks/pre-compact.impl.sh +46 -0
  45. package/core/harnesses/codex/hooks/pre-compact.sh +9 -0
  46. package/core/harnesses/codex/hooks/pre-implement-gate.sh +19 -0
  47. package/core/harnesses/codex/hooks/session-end.impl.sh +92 -0
  48. package/core/harnesses/codex/hooks/session-end.sh +5 -0
  49. package/core/harnesses/codex/hooks/session-start.sh +5 -0
  50. package/core/harnesses/codex/hooks/stop-check.sh +8 -0
  51. package/core/harnesses/codex/hooks/subagent-audit.sh +8 -0
  52. package/core/harnesses/codex/hooks/user-prompt-submit.sh +6 -0
  53. package/core/harnesses/gemini/hooks/hooks.json +77 -0
  54. package/core/harnesses/gemini/hooks/lib.sh +302 -0
  55. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +21 -0
  56. package/core/harnesses/gemini/hooks/post-edit-lint.sh +39 -0
  57. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +21 -0
  58. package/core/harnesses/gemini/hooks/pre-compact.sh +38 -0
  59. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +22 -0
  60. package/core/harnesses/gemini/hooks/session-end.impl.sh +89 -0
  61. package/core/harnesses/gemini/hooks/session-end.sh +5 -0
  62. package/core/harnesses/gemini/hooks/session-start.sh +5 -0
  63. package/core/harnesses/gemini/settings/adapter-defaults.json +4 -0
  64. package/core/harnesses/kiro/agents/doflow.json +58 -0
  65. package/core/harnesses/kiro/hooks/doflow.json +53 -0
  66. package/core/harnesses/kiro/hooks/lib.sh +324 -0
  67. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +9 -0
  68. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +9 -0
  69. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +9 -0
  70. package/core/harnesses/kiro/hooks/session-start.sh +9 -0
  71. package/core/harnesses/kiro/hooks/stop-check.sh +43 -0
  72. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +39 -0
  73. package/core/harnesses/shared/hooks/adapters/antigravity.js +111 -0
  74. package/core/harnesses/shared/hooks/adapters/arg-fields.js +19 -0
  75. package/core/harnesses/shared/hooks/adapters/gemini.js +114 -0
  76. package/core/harnesses/shared/hooks/hooks.json +47 -0
  77. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +91 -0
  78. package/core/harnesses/shared/hooks/policies/capture-failure.sh +97 -0
  79. package/core/harnesses/shared/hooks/policies/deny-json.sh +29 -0
  80. package/core/harnesses/shared/hooks/policies/lib.sh +501 -0
  81. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +21 -0
  82. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +91 -0
  83. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +328 -0
  84. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +496 -0
  85. package/core/harnesses/shared/hooks/policies/prompt-nudge.jq +131 -0
  86. package/core/harnesses/shared/hooks/policies/session-context.sh +141 -0
  87. package/core/harnesses/shared/hooks/policies/stop-check.sh +264 -0
  88. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +46 -0
  89. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +235 -0
  90. package/core/harnesses/shared/hooks/stream-hook-runner.js +590 -0
  91. package/core/harnesses/shared/locator/doflow-run +87 -0
  92. package/core/registry/advice.json +101 -0
  93. package/core/registry/assets.json +792 -0
  94. package/core/registry/capabilities.json +120 -0
  95. package/core/registry/contracts.json +189 -0
  96. package/core/registry/external-tools.json +191 -0
  97. package/core/registry/harnesses.json +2062 -0
  98. package/core/registry/lifecycle.json +231 -0
  99. package/core/registry/mcp.json +18 -0
  100. package/core/registry/models.json +79 -0
  101. package/core/registry/readiness-templates.json +118 -0
  102. package/core/registry/routes.json +47 -0
  103. package/core/registry/storage.json +1057 -0
  104. package/core/registry/verification.json +258 -0
  105. package/core/registry/workflows.json +790 -0
  106. package/core/shared/agent-specs/core-implementer.md +73 -0
  107. package/core/shared/agent-specs/quality-guardian.md +28 -0
  108. package/core/shared/agent-specs/research-writer.md +31 -0
  109. package/core/shared/agent-specs/spec-analyst.md +33 -0
  110. package/core/shared/agent-specs/system-architect.md +29 -0
  111. package/core/shared/guidance/DOFLOW_CORE.md +8 -0
  112. package/core/shared/guidance/FLAGS.md +25 -0
  113. package/core/shared/guidance/PRINCIPLES.md +61 -0
  114. package/core/shared/guidance/VERSION +1 -0
  115. package/core/shared/guidance/mcp/MCP_Context7.md +18 -0
  116. package/core/shared/guidance/mcp/MCP_Sequential.md +17 -0
  117. package/core/shared/guidance/modes/MODE_Brainstorming.md +46 -0
  118. package/core/shared/guidance/modes/MODE_DeepResearch.md +69 -0
  119. package/core/shared/guidance/modes/MODE_Introspection.md +33 -0
  120. package/core/shared/guidance/modes/MODE_Orchestration.md +57 -0
  121. package/core/shared/guidance/modes/MODE_Task_Management.md +74 -0
  122. package/core/shared/guidance/pointers/claude-gemini.md +2 -0
  123. package/core/shared/guidance/pointers/codex.md +6 -0
  124. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +373 -0
  125. package/core/shared/guidance/references/ARTIFACT_VOICE.md +104 -0
  126. package/core/shared/guidance/references/CONSTITUTION_BASE.md +47 -0
  127. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +161 -0
  128. package/core/shared/guidance/references/DOFLOW_CHAIN.md +87 -0
  129. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +93 -0
  130. package/core/shared/guidance/references/MODEL_SELECTION.md +74 -0
  131. package/core/shared/guidance/references/ORCHESTRATION.md +36 -0
  132. package/core/shared/guidance/references/RESEARCH_CONFIG.md +21 -0
  133. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +81 -0
  134. package/core/shared/guidance/rules/RULE_01_SAFETY.md +25 -0
  135. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +23 -0
  136. package/core/shared/guidance/rules/RULE_03_QUALITY.md +14 -0
  137. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +46 -0
  138. package/core/shared/scripts/doflow/assets/mermaid-theme.json +46 -0
  139. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +167 -0
  140. package/core/shared/scripts/doflow/bash/do-git-state.sh +546 -0
  141. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +208 -0
  142. package/core/shared/scripts/doflow/bash/do-paths.sh +347 -0
  143. package/core/shared/scripts/doflow/bash/do-prereqs.sh +66 -0
  144. package/core/shared/scripts/doflow/bash/do-review-package.sh +127 -0
  145. package/core/shared/scripts/doflow/bash/do-task-brief.sh +698 -0
  146. package/core/shared/scripts/doflow/bash/render-audit.sh +224 -0
  147. package/core/shared/scripts/doflow/bash/render-diagrams.sh +177 -0
  148. package/core/shared/scripts/doflow/bash/render-puml.sh +223 -0
  149. package/core/shared/scripts/doflow/bash/sync-context.sh +66 -0
  150. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +526 -0
  151. package/core/shared/scripts/doflow/bin/doflow-run +592 -0
  152. package/core/shared/skills/do/SKILL.md +128 -0
  153. package/core/shared/skills/do/references/estimation.md +13 -0
  154. package/core/shared/skills/do/references/pm_routing.md +25 -0
  155. package/core/shared/skills/do/references/tool_matrix.md +52 -0
  156. package/core/shared/skills/do-brainstorm/SKILL.md +252 -0
  157. package/core/shared/skills/do-code-review/README.md +49 -0
  158. package/core/shared/skills/do-code-review/SKILL.md +333 -0
  159. package/core/shared/skills/do-code-review/assets/sample_c_clean.c +71 -0
  160. package/core/shared/skills/do-code-review/assets/sample_c_smells.c +66 -0
  161. package/core/shared/skills/do-code-review/assets/sample_cpp_shift.cpp +19 -0
  162. package/core/shared/skills/do-code-review/assets/sample_csharp_clean.cs +100 -0
  163. package/core/shared/skills/do-code-review/assets/sample_csharp_smells.cs +78 -0
  164. package/core/shared/skills/do-code-review/assets/sample_java_clean.java +55 -0
  165. package/core/shared/skills/do-code-review/assets/sample_java_smells.java +56 -0
  166. package/core/shared/skills/do-code-review/assets/sample_json_clean.json +7 -0
  167. package/core/shared/skills/do-code-review/assets/sample_markdown_clean.md +30 -0
  168. package/core/shared/skills/do-code-review/assets/sample_markdown_smells.md +29 -0
  169. package/core/shared/skills/do-code-review/assets/sample_shell_heredoc.sh +38 -0
  170. package/core/shared/skills/do-code-review/assets/sample_shell_smells.sh +15 -0
  171. package/core/shared/skills/do-code-review/assets/sample_typescript_strings.ts +36 -0
  172. package/core/shared/skills/do-code-review/assets/sample_yaml_smells.yaml +6 -0
  173. package/core/shared/skills/do-code-review/content-types/config.md +83 -0
  174. package/core/shared/skills/do-code-review/content-types/markdown.md +132 -0
  175. package/core/shared/skills/do-code-review/expected_outputs/sample_c_clean_quality.json +79 -0
  176. package/core/shared/skills/do-code-review/expected_outputs/sample_c_smells_quality.json +169 -0
  177. package/core/shared/skills/do-code-review/expected_outputs/sample_cpp_shift_quality.json +49 -0
  178. package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_clean_quality.json +149 -0
  179. package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_smells_quality.json +157 -0
  180. package/core/shared/skills/do-code-review/expected_outputs/sample_java_clean_quality.json +67 -0
  181. package/core/shared/skills/do-code-review/expected_outputs/sample_java_smells_quality.json +97 -0
  182. package/core/shared/skills/do-code-review/expected_outputs/sample_json_clean_quality.json +33 -0
  183. package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_clean_quality.json +1 -0
  184. package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_smells_quality.json +16 -0
  185. package/core/shared/skills/do-code-review/expected_outputs/sample_shell_heredoc_quality.json +54 -0
  186. package/core/shared/skills/do-code-review/expected_outputs/sample_shell_smells_quality.json +91 -0
  187. package/core/shared/skills/do-code-review/expected_outputs/sample_typescript_strings_quality.json +60 -0
  188. package/core/shared/skills/do-code-review/expected_outputs/sample_yaml_smells_quality.json +46 -0
  189. package/core/shared/skills/do-code-review/languages/c.md +103 -0
  190. package/core/shared/skills/do-code-review/languages/cpp.md +103 -0
  191. package/core/shared/skills/do-code-review/languages/csharp.md +97 -0
  192. package/core/shared/skills/do-code-review/languages/dart.md +108 -0
  193. package/core/shared/skills/do-code-review/languages/go.md +92 -0
  194. package/core/shared/skills/do-code-review/languages/java.md +103 -0
  195. package/core/shared/skills/do-code-review/languages/kotlin.md +88 -0
  196. package/core/shared/skills/do-code-review/languages/php.md +109 -0
  197. package/core/shared/skills/do-code-review/languages/python.md +94 -0
  198. package/core/shared/skills/do-code-review/languages/ruby.md +105 -0
  199. package/core/shared/skills/do-code-review/languages/rust.md +99 -0
  200. package/core/shared/skills/do-code-review/languages/shell.md +90 -0
  201. package/core/shared/skills/do-code-review/languages/swift.md +88 -0
  202. package/core/shared/skills/do-code-review/languages/typescript.md +97 -0
  203. package/core/shared/skills/do-code-review/review-policy.json +47 -0
  204. package/core/shared/skills/do-code-review/rules/universal.md +49 -0
  205. package/core/shared/skills/do-code-review/scripts/code_quality_checker.py +1696 -0
  206. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +532 -0
  207. package/core/shared/skills/do-code-review/scripts/pr_analyzer.py +620 -0
  208. package/core/shared/skills/do-code-review/scripts/review_report_generator.py +505 -0
  209. package/core/shared/skills/do-constitution/SKILL.md +72 -0
  210. package/core/shared/skills/do-design/SKILL.md +212 -0
  211. package/core/shared/skills/do-diagnose/SKILL.md +156 -0
  212. package/core/shared/skills/do-diagnose/references/code_audit.md +15 -0
  213. package/core/shared/skills/do-diagnose/references/refactoring.md +12 -0
  214. package/core/shared/skills/do-diagnose/references/root_cause.md +17 -0
  215. package/core/shared/skills/do-document/SKILL.md +81 -0
  216. package/core/shared/skills/do-document/references/api-reference.md +42 -0
  217. package/core/shared/skills/do-document/references/deep_research.md +18 -0
  218. package/core/shared/skills/do-document/references/embedded-research.md +11 -0
  219. package/core/shared/skills/do-document/references/implementation-flow.md +105 -0
  220. package/core/shared/skills/do-document/references/index_generator.md +9 -0
  221. package/core/shared/skills/do-document/references/user-guide.md +22 -0
  222. package/core/shared/skills/do-execute-plan/SKILL.md +261 -0
  223. package/core/shared/skills/do-execute-plan/references/handoff_record.md +57 -0
  224. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +92 -0
  225. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +155 -0
  226. package/core/shared/skills/do-execute-plan/references/run_position.md +63 -0
  227. package/core/shared/skills/do-execute-plan/references/scaffold.md +441 -0
  228. package/core/shared/skills/do-execute-plan/references/task_attempts.md +41 -0
  229. package/core/shared/skills/do-flow/SKILL.md +186 -0
  230. package/core/shared/skills/do-flow/references/task_classes.md +134 -0
  231. package/core/shared/skills/do-git/SKILL.md +115 -0
  232. package/core/shared/skills/do-git/references/hotfix-ritual.md +44 -0
  233. package/core/shared/skills/do-git/references/lifecycle-policy.md +54 -0
  234. package/core/shared/skills/do-git/references/release-ritual.md +64 -0
  235. package/core/shared/skills/do-implement/SKILL.md +222 -0
  236. package/core/shared/skills/do-plan/SKILL.md +219 -0
  237. package/core/shared/skills/do-test/SKILL.md +113 -0
  238. package/core/shared/templates/doflow/constitution-template.md +28 -0
  239. package/core/shared/templates/doflow/data-model-template.md +169 -0
  240. package/core/shared/templates/doflow/design-template.md +193 -0
  241. package/core/shared/templates/doflow/external-contract-template.md +35 -0
  242. package/core/shared/templates/doflow/intent-template.md +45 -0
  243. package/core/shared/templates/doflow/plan-template.md +151 -0
  244. package/core/shared/templates/doflow/question-log-template.md +51 -0
  245. package/core/shared/templates/doflow/requirement-template.md +126 -0
  246. package/core/shared/templates/doflow/specs-template.md +55 -0
  247. package/core/shared/templates/doflow/state-template.md +59 -0
  248. package/core/shared/workflows/do-flow-chain.md +17 -0
  249. package/package.json +46 -4
  250. package/src/adapters/antigravity/index.ts +631 -0
  251. package/src/adapters/claude/index.ts +548 -0
  252. package/src/adapters/claude/mcp.ts +94 -0
  253. package/src/adapters/codex/agents.ts +208 -0
  254. package/src/adapters/codex/config.ts +272 -0
  255. package/src/adapters/codex/hooks.ts +203 -0
  256. package/src/adapters/codex/index.ts +707 -0
  257. package/src/adapters/codex/mcp.ts +271 -0
  258. package/src/adapters/codex/native-projection.ts +34 -0
  259. package/src/adapters/copilot/index.ts +413 -0
  260. package/src/adapters/copy-tree.ts +620 -0
  261. package/src/adapters/gemini/hooks.ts +210 -0
  262. package/src/adapters/gemini/index.ts +390 -0
  263. package/src/adapters/hook-commands.ts +60 -0
  264. package/src/adapters/index.ts +106 -0
  265. package/src/adapters/kiro/index.ts +310 -0
  266. package/src/adapters/mcp-entries.ts +331 -0
  267. package/src/adapters/opencode/index.ts +466 -0
  268. package/src/adapters/pi/index.ts +279 -0
  269. package/src/adapters/pi/json-members.ts +185 -0
  270. package/src/adapters/pi/mcp.ts +373 -0
  271. package/src/adapters/resolve-source.ts +19 -0
  272. package/src/cli/commands/install.ts +218 -0
  273. package/src/cli/commands/list-backups.ts +21 -0
  274. package/src/cli/commands/reconcile.ts +120 -0
  275. package/src/cli/commands/remove.ts +82 -0
  276. package/src/cli/commands/rollback.ts +172 -0
  277. package/src/cli/commands/self-update.ts +33 -0
  278. package/src/cli/commands/status.ts +138 -0
  279. package/src/cli/commands/tools.ts +92 -0
  280. package/src/cli/commands/update.ts +162 -0
  281. package/src/cli/index.ts +568 -0
  282. package/src/cli/runtime-commands.ts +281 -0
  283. package/src/cli/shared.ts +218 -0
  284. package/src/helper/bash-helper.ts +34 -0
  285. package/src/helper/git.ts +24 -0
  286. package/src/helper/guardrails.ts +49 -0
  287. package/src/helper/harness-paths.ts +140 -0
  288. package/src/helper/marker-merge.ts +116 -0
  289. package/src/helper/prompt.ts +61 -0
  290. package/src/helper/repo-root.ts +29 -0
  291. package/src/helper/settings-merge.ts +151 -0
  292. package/src/helper/settings-scope.ts +33 -0
  293. package/src/helper/toml.ts +244 -0
  294. package/src/install/backup-set.ts +149 -0
  295. package/src/install/backup.ts +724 -0
  296. package/src/install/context.ts +58 -0
  297. package/src/install/manifest.ts +145 -0
  298. package/src/install/mcp.ts +254 -0
  299. package/src/install/paths.ts +84 -0
  300. package/src/install/targets.ts +90 -0
  301. package/src/install/tool-lifecycle.ts +151 -0
  302. package/src/lifecycle/bash-availability.ts +25 -0
  303. package/src/lifecycle/index.ts +812 -0
  304. package/src/lifecycle/mcp-index.ts +28 -0
  305. package/src/lifecycle/policies.ts +73 -0
  306. package/src/lifecycle/view.ts +175 -0
  307. package/src/registry/index.ts +611 -0
  308. package/src/release/sync-plugin-versions.ts +66 -0
  309. package/src/runtime/advice/cli.ts +283 -0
  310. package/src/runtime/advice/client.ts +324 -0
  311. package/src/runtime/advice/config.ts +189 -0
  312. package/src/runtime/advice/doctor.ts +169 -0
  313. package/src/runtime/advice/endpoint-guard.ts +134 -0
  314. package/src/runtime/advice/kinds.ts +154 -0
  315. package/src/runtime/advice/providers/ollama-systemone.ts +209 -0
  316. package/src/runtime/advice/record.ts +166 -0
  317. package/src/runtime/advice/registry.ts +170 -0
  318. package/src/runtime/advice/strength.ts +66 -0
  319. package/src/runtime/brief-tools.ts +342 -0
  320. package/src/runtime/c4-project.ts +371 -0
  321. package/src/runtime/capability-router.ts +484 -0
  322. package/src/runtime/checkouts.ts +170 -0
  323. package/src/runtime/claims.ts +468 -0
  324. package/src/runtime/cli-result.ts +55 -0
  325. package/src/runtime/cli.ts +678 -0
  326. package/src/runtime/command-detect.ts +516 -0
  327. package/src/runtime/context-pack.ts +492 -0
  328. package/src/runtime/decision-register.ts +493 -0
  329. package/src/runtime/evidence-ledger.ts +338 -0
  330. package/src/runtime/failure/capture.ts +272 -0
  331. package/src/runtime/failure/classifier.ts +68 -0
  332. package/src/runtime/failure/cli.ts +178 -0
  333. package/src/runtime/failure/home.ts +64 -0
  334. package/src/runtime/failure/store.ts +226 -0
  335. package/src/runtime/feature-resolve.ts +79 -0
  336. package/src/runtime/freshness.ts +177 -0
  337. package/src/runtime/health.ts +863 -0
  338. package/src/runtime/history-compactor.ts +237 -0
  339. package/src/runtime/implementation-gate.ts +321 -0
  340. package/src/runtime/inventory/coverage.ts +218 -0
  341. package/src/runtime/inventory/identity.ts +65 -0
  342. package/src/runtime/inventory/index.ts +705 -0
  343. package/src/runtime/inventory/precedence.ts +134 -0
  344. package/src/runtime/inventory/read-scopes.ts +131 -0
  345. package/src/runtime/inventory/siblings.ts +192 -0
  346. package/src/runtime/knowledge/chunker.ts +124 -0
  347. package/src/runtime/knowledge/hybrid.ts +149 -0
  348. package/src/runtime/knowledge/index-store.ts +145 -0
  349. package/src/runtime/knowledge/retrieval.ts +194 -0
  350. package/src/runtime/leak-scan.ts +185 -0
  351. package/src/runtime/lifecycle/cli.ts +276 -0
  352. package/src/runtime/lifecycle/event-store.ts +340 -0
  353. package/src/runtime/lifecycle/fold.ts +353 -0
  354. package/src/runtime/lifecycle/followup.ts +432 -0
  355. package/src/runtime/lifecycle/goal.ts +249 -0
  356. package/src/runtime/lifecycle/intent-writer.ts +86 -0
  357. package/src/runtime/lifecycle/overview.ts +292 -0
  358. package/src/runtime/lifecycle/release.ts +241 -0
  359. package/src/runtime/lifecycle/report-store.ts +221 -0
  360. package/src/runtime/lifecycle/retention.ts +212 -0
  361. package/src/runtime/lifecycle/root.ts +71 -0
  362. package/src/runtime/lifecycle/status.ts +353 -0
  363. package/src/runtime/lifecycle/store-upkeep.ts +393 -0
  364. package/src/runtime/locator-resolve.ts +158 -0
  365. package/src/runtime/mask.ts +210 -0
  366. package/src/runtime/mcp-consultations.ts +412 -0
  367. package/src/runtime/mcp-reach.ts +157 -0
  368. package/src/runtime/model-router.ts +153 -0
  369. package/src/runtime/outcome.ts +467 -0
  370. package/src/runtime/reach.ts +148 -0
  371. package/src/runtime/readiness-record.ts +150 -0
  372. package/src/runtime/readiness.ts +435 -0
  373. package/src/runtime/recovery.ts +282 -0
  374. package/src/runtime/research-request.ts +231 -0
  375. package/src/runtime/retrieval-plan.ts +578 -0
  376. package/src/runtime/scaffold/artifacts.ts +217 -0
  377. package/src/runtime/scaffold/fingerprint.ts +56 -0
  378. package/src/runtime/scaffold/generate.ts +911 -0
  379. package/src/runtime/scaffold/languages.ts +245 -0
  380. package/src/runtime/storage/cli.ts +64 -0
  381. package/src/runtime/storage/engine.ts +249 -0
  382. package/src/runtime/storage/registry.ts +130 -0
  383. package/src/runtime/storage/roots.ts +63 -0
  384. package/src/runtime/storage/rules.ts +282 -0
  385. package/src/runtime/storage/upkeep.ts +243 -0
  386. package/src/runtime/storage/work-status.ts +326 -0
  387. package/src/runtime/task-attempts.ts +415 -0
  388. package/src/runtime/task-classifier.ts +550 -0
  389. package/src/runtime/task-scope.ts +130 -0
  390. package/src/runtime/task-state.ts +176 -0
  391. package/src/runtime/trace/indicators.ts +235 -0
  392. package/src/runtime/trace/ledger.ts +626 -0
  393. package/src/runtime/trace/render.ts +76 -0
  394. package/src/runtime/trace/views.ts +459 -0
  395. package/src/runtime/verification/contract-runner.ts +341 -0
  396. package/src/runtime/verification/engine.ts +1461 -0
  397. package/src/runtime/verification/registry.ts +130 -0
  398. package/src/runtime/verification/scope-bound.ts +418 -0
  399. package/src/runtime/verification/test-selection.ts +172 -0
  400. package/src/runtime/workflow-engine.ts +588 -0
  401. package/src/runtime/workflow-orchestrator.ts +953 -0
  402. package/src/runtime/worktree.ts +340 -0
  403. package/src/state/index.ts +193 -0
  404. package/src/state/lockfile.ts +136 -0
  405. package/src/state/run-lock.ts +408 -0
  406. package/types/node-commonjs.d.ts +68 -0
  407. package/types/runtime.d.ts +91 -0
@@ -0,0 +1,707 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ // Codex adapter boundary. Native parsing and mutation remain in the existing
6
+ // codex-* modules; this module composes their non-mutating plans into the
7
+ // common adapter shape without changing legacy CLI ownership.
8
+ const fs = require('node:fs');
9
+ const os = require('node:os');
10
+ const path = require('node:path');
11
+ const crypto = require('node:crypto');
12
+ const { configPath, fingerprint: configFingerprint, parseToml, planCodexConfig, applyCodexConfig } = require('./config.ts');
13
+ const { renderServer, planCodexMcp, applyCodexMcp, ownedCodexMcpIds, userDefinedCodexMcpIds, serverTableText } = require('./mcp.ts');
14
+ const { agentDirectory, discoverCodexAgents, planCodexAgents, applyCodexAgents } = require('./agents.ts');
15
+ const { planCodexHooks, deployCodexHooks, removeCodexHookScripts } = require('./hooks.ts');
16
+ const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices } = require('../copy-tree.ts');
17
+ const { mergeMarkedSection, removeMarkedSection, MARKER_START, MARKER_END } = require('../../helper/marker-merge.ts');
18
+ const { nativeMcpCatalog } = require('../../registry/index.ts');
19
+ const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.ts');
20
+
21
+ const HARNESS = 'codex';
22
+
23
+ /** Every server a capability provider names as its MCP server. */
24
+ function providerMcpServers(registry: any) {
25
+ return Object.values<any>(registry?.capabilities || {}).flatMap((capability) => (capability.providers || []).map((provider: any) => provider.mcp?.server)).filter(Boolean);
26
+ }
27
+
28
+ /** The servers config.toml defines among `names`, whoever wrote them: the tables DoFlow owns plus the
29
+ * user's own. `null` while the file is absent or cannot be parsed. */
30
+ function configuredCodexMcpIds({ config, scope, fsImpl, mcpRecords, names }: any) {
31
+ try {
32
+ if (!fsImpl.existsSync(config)) return null;
33
+ parseToml(fsImpl.readFileSync(config, 'utf8'));
34
+ } catch { return null; }
35
+ const owned = ownedCodexMcpIds({ file: config, scope, fsImpl, managedResources: mcpRecords });
36
+ const own = userDefinedCodexMcpIds({ file: config, scope, fsImpl, managedResources: mcpRecords, names });
37
+ return [...new Set([...owned, ...own])].sort();
38
+ }
39
+
40
+ /**
41
+ * Native path facts live in core/registry/harnesses.json under this harness's "paths" section and
42
+ * resolve through the shared harness-paths resolver. createCodexAdapter({ declaredPaths }) is the
43
+ * injection point buildAdapterRegistry() uses; module-level exports delegate to a default-
44
+ * configured instance so direct callers keep their historical shape.
45
+ */
46
+ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } = {}) {
47
+ function normalizeContext({ scope, scopeRoot, projectRoot, codexDir, homeDir }: any = {}) {
48
+ if (!['project', 'global', 'user'].includes(scope)) throw new Error(`Unsupported Codex scope '${scope}'`);
49
+ if (scope === 'project') {
50
+ // Logic residue (input normalization, not a path fact): scopeRoot is accepted as the selected
51
+ // repository root for generic lifecycle callers. A workspace container is never substituted
52
+ // here: the caller must pass that container as projectRoot only when it is itself the
53
+ // repository being configured.
54
+ const root = projectRoot ?? scopeRoot;
55
+ if (!root) throw new Error('Codex project scope requires the selected repository projectRoot');
56
+ const paths = resolveHarnessPaths(declaredPaths, { scope: 'project', scopeRoot: root });
57
+ return { scope: 'project', projectRoot: path.resolve(root), paths };
58
+ }
59
+ // Logic residue (input normalization): the generic lifecycle may pass either global or user;
60
+ // both target the actual user configuration root.
61
+ const home = homeDir ?? os.homedir();
62
+ const paths = resolveHarnessPaths(declaredPaths, { scope: 'user', scopeRoot: home, homeDir: home });
63
+ return { scope: 'global', codexDir: codexDir ? path.resolve(codexDir) : paths.configDir, paths };
64
+ }
65
+
66
+ const nativeContext = normalizeContext;
67
+
68
+ function discover(options: any, { fsImpl = fs }: any = {}) {
69
+ const context = nativeContext(options);
70
+ const paths = context.paths;
71
+ const config = paths.configFile;
72
+ const agents = paths.agentsDirectory;
73
+ const hooks = paths.hooksFile;
74
+ const mcpRecords = nativeManagedResources(options.managedResources || options.ledger?.resources || [], context, { kind: 'mcp-server', target: config });
75
+ const catalogNames = [...(options.registry?.mcp || []), ...(projectedNativeOptions(options).mcpCatalog || [])].map((server) => server.id);
76
+ return {
77
+ harness: HARNESS,
78
+ scope: options.scope,
79
+ config: { file: config, exists: fsImpl.existsSync(config) },
80
+ agents: { directory: agents, exists: fsImpl.existsSync(agents) },
81
+ hooks: { file: hooks, exists: fsImpl.existsSync(hooks) },
82
+ mcpOwned: ownedCodexMcpIds({ file: config, scope: context.scope, fsImpl, managedResources: mcpRecords }),
83
+ mcpUserDefined: userDefinedCodexMcpIds({ file: config, scope: context.scope, fsImpl, managedResources: mcpRecords, names: catalogNames }),
84
+ mcpConfigured: configuredCodexMcpIds({ config, scope: context.scope, fsImpl, mcpRecords, names: [...catalogNames, ...providerMcpServers(options.registry)] }),
85
+ };
86
+ }
87
+
88
+ function render({ registry, asset }: any) {
89
+ if (!registry || !asset) throw new Error('render requires registry and asset');
90
+ const harness = registry.harnesses.find((item: any) => item.id === HARNESS);
91
+ if (!harness) throw new Error('Registry does not define the Codex harness');
92
+ const projection = asset.projection?.[HARNESS];
93
+ if (!projection) return { harness: HARNESS, assetId: asset.id, status: 'not-applicable' };
94
+ const capability = harness.capabilities?.[projection.capability];
95
+ if (!capability) throw new Error(`Codex projection '${asset.id}' references unknown capability '${projection.capability}'`);
96
+ return {
97
+ harness: HARNESS,
98
+ assetId: asset.id,
99
+ renderer: projection.renderer,
100
+ capability: projection.capability,
101
+ capabilityStatus: capability.status,
102
+ target: harness.nativeTargets?.[projection.capability] ?? null,
103
+ status: capability.status === 'unavailable' ? 'unavailable' : 'renderable',
104
+ prerequisites: [...(capability.prerequisites || [])],
105
+ };
106
+ }
107
+
108
+ function assetIdFor(options: any, componentName: any) {
109
+ const explicit = options.projection?.assetIds?.[componentName];
110
+ if (explicit) return explicit;
111
+ const assets = options.assets || [];
112
+ if (componentName === 'agents') return assets.find((asset: any) => asset.id === 'agents.shared')?.id ?? assets[0]?.id;
113
+ return assets.find((asset: any) => asset.id === 'guidance.codex-pointer')?.id ?? assets[0]?.id ?? `codex.${componentName}`;
114
+ }
115
+
116
+ function ownershipKind(componentName: any) {
117
+ return ({ config: 'configuration-entry', mcp: 'mcp-server', agents: 'custom-agent', hooks: 'hooks-file' } as Record<string, string>)[componentName] ?? componentName;
118
+ }
119
+ function ownershipIdentity(componentName: any, identity: any) { return `doflow:codex:${ownershipKind(componentName)}:${identity}`; }
120
+
121
+ function addChanges(output: any, assetId: any, component: any, componentName: any) {
122
+ for (const change of component?.changes || []) output.push({
123
+ harness: HARNESS,
124
+ assetId,
125
+ nativeComponent: componentName,
126
+ nativePlan: component,
127
+ operation: change.operation ?? change.type,
128
+ target: change.target ?? change.file ?? component.file ?? component.destination ?? component.directory,
129
+ ownershipIdentity: change.ownershipIdentity ?? ownershipIdentity(componentName, change.identity ?? (componentName === 'hooks' ? 'hooks.json' : assetId)),
130
+ ...change,
131
+ // A release drops the ownership row and leaves the file alone, so the lifecycle never hands
132
+ // it to apply() or remove().
133
+ ...(change.release ? { retained: true, retainedFor: [] } : {}),
134
+ });
135
+ }
136
+
137
+ function sha256(text: any) { return `sha256:${crypto.createHash('sha256').update(text).digest('hex')}`; }
138
+ function lifecycleResource({ scope, assetId, kind, identity, target, fingerprint, sourceVersion, selection, recoveryRef, projection }: any) {
139
+ return { harness: HARNESS, scope, assetId, kind, identity, target, ownershipIdentity: `doflow:codex:${kind}:${identity}`,
140
+ fingerprint, sourceVersion: sourceVersion ?? 'unknown', selection: selection ?? true, recoveryRef: recoveryRef ?? null, projection };
141
+ }
142
+
143
+ /** Convert neutral ledger entries to the legacy planner's ownership record.
144
+ * An absolute target must match the exact native file this component will
145
+ * reconcile; a record from another Codex project cannot authorize this plan. */
146
+ function nativeManagedResources(resources: any, context: any, { kind, target, directory }: any = {}) {
147
+ return (resources || []).flatMap((resource: any) => {
148
+ if (!resource || resource.kind !== kind) return [];
149
+ // Retain the pre-registry native record shape for direct callers/tests.
150
+ if (!resource.harness && resource.target === HARNESS) {
151
+ return [{ ...resource, scope: context.scope, target: HARNESS }];
152
+ }
153
+ if (resource.harness !== HARNESS) return [];
154
+ const expected = directory && resource.identity?.startsWith('agent:')
155
+ ? path.join(directory, `${resource.identity.slice('agent:'.length)}.toml`) : target;
156
+ if (typeof resource.target !== 'string' || path.resolve(resource.target) !== path.resolve(expected)) return [];
157
+ return [{ target: HARNESS, scope: context.scope, kind, identity: resource.identity, fingerprint: resource.fingerprint,
158
+ sourceVersion: resource.sourceVersion, selection: resource.selection, recoveryPoint: resource.recoveryRef }];
159
+ });
160
+ }
161
+
162
+ /** Whether a row of the OTHER scope claims `target` (for a config or MCP entry: the same entry in
163
+ * that file). A project rooted at $HOME shares `~/.codex`, `~/.agents` and the one ledger with the
164
+ * global install, so its removal must leave whatever the global rows still own, and vice versa. */
165
+ function claimedByOtherScope(resources: any, context: any, target: any, identity?: any) {
166
+ if (typeof target !== 'string') return false;
167
+ return (resources || []).some((resource: any) => resource?.scope && resource.scope !== context.scope
168
+ && typeof resource.target === 'string' && path.resolve(resource.target) === path.resolve(target)
169
+ && (identity === undefined || resource.identity === identity));
170
+ }
171
+
172
+ function claimedRowElsewhere(resources: any, context: any, resource: any) {
173
+ return claimedByOtherScope(resources, context, resource.target,
174
+ ['configuration-entry', 'config-entry', 'mcp-server'].includes(resource.kind) ? resource.identity : undefined);
175
+ }
176
+
177
+ /** The rows a removal may act on: this scope's own, minus any path the other scope still claims. */
178
+ function removableRows(resources: any, context: any) {
179
+ return (resources || []).filter((resource: any) => (!resource?.scope || resource.scope === context.scope)
180
+ && !claimedRowElsewhere(resources, context, resource));
181
+ }
182
+
183
+ /** This scope's rows on a path the other scope still claims become retained removals: the
184
+ * lifecycle releases the row and never hands the change to remove(), so nothing is deleted. */
185
+ function retainedRemovals(resources: any, context: any) {
186
+ return (resources || []).filter((resource: any) => resource?.harness === HARNESS && resource.scope === context.scope
187
+ && claimedRowElsewhere(resources, context, resource))
188
+ .map((resource: any) => ({ harness: HARNESS, assetId: resource.assetId, target: resource.target, operation: 'remove',
189
+ ownershipIdentity: resource.ownershipIdentity, kind: resource.kind, identity: resource.identity,
190
+ retained: true, retainedFor: [`${HARNESS} (${context.scope === 'global' ? 'project' : 'global'} scope)`] }));
191
+ }
192
+
193
+ function ownedRemovalPlan(resources: any, context: any, kind: any, directory?: any) {
194
+ const entries = (resources || []).filter((resource: any) => resource?.harness === HARNESS && resource.kind === kind);
195
+ const changes = []; const conflicts = [];
196
+ for (const resource of entries) {
197
+ const target = kind === 'custom-agent'
198
+ ? path.join(directory, `${resource.identity.replace(/^agent:/, '')}.toml`)
199
+ : resource.target;
200
+ if (path.resolve(target) !== path.resolve(resource.target)) continue;
201
+ if (fs.existsSync(target) && sha256(fs.readFileSync(target, 'utf8')) !== resource.fingerprint) {
202
+ conflicts.push(`${kind} '${resource.identity}' was modified outside DoFlow`);
203
+ continue;
204
+ }
205
+ changes.push({ type: 'remove', identity: resource.identity, file: target, fingerprint: resource.fingerprint });
206
+ }
207
+ return { ok: conflicts.length === 0, status: conflicts.length ? 'conflict' : (changes.length ? 'change' : 'unchanged'), changes, conflicts, directRemove: true };
208
+ }
209
+
210
+ function configResourcesFrom(config: any, { scope, sourceVersion, recoveryRef }: any) {
211
+ return (config?.managedResources || [])
212
+ .filter((record: any) => record.kind === 'configuration-entry' || record.kind === 'config-entry')
213
+ .map((record: any) => lifecycleResource({ scope, assetId: 'guidance.codex-pointer', kind: 'configuration-entry', identity: record.identity,
214
+ target: config.file, fingerprint: record.fingerprint, sourceVersion: record.sourceVersion ?? sourceVersion, recoveryRef,
215
+ projection: { renderer: 'codex-config' } }));
216
+ }
217
+
218
+ function mcpResourcesFrom(mcp: any, { scope, sourceVersion, recoveryRef }: any) {
219
+ return (mcp?.managedResources || [])
220
+ .filter((record: any) => record.kind === 'mcp-server')
221
+ .map((record: any) => lifecycleResource({ scope, assetId: 'guidance.codex-pointer', kind: 'mcp-server', identity: record.identity,
222
+ target: mcp.file, fingerprint: record.fingerprint, sourceVersion: record.sourceVersion ?? sourceVersion, selection: record.selection, recoveryRef,
223
+ projection: { renderer: 'codex-mcp' } }));
224
+ }
225
+
226
+ function agentResourcesFrom(agents: any, { scope, sourceVersion, recoveryRef }: any) {
227
+ return (agents?.managedResources || [])
228
+ .filter((record: any) => record.kind === 'custom-agent')
229
+ .map((record: any) => lifecycleResource({ scope, assetId: 'agents.shared', kind: 'custom-agent', identity: record.identity,
230
+ target: path.join(agents.directory, `${record.identity.replace(/^agent:/, '')}.toml`), fingerprint: record.fingerprint,
231
+ sourceVersion: record.sourceVersion ?? sourceVersion, recoveryRef, projection: { renderer: 'codex-agent' } }));
232
+ }
233
+
234
+ function hooksResourceFrom(hooks: any, { scope, sourceVersion, recoveryRef }: any) {
235
+ if (!hooks?.applied || !hooks.destination) return [];
236
+ return [lifecycleResource({ scope, assetId: 'guidance.codex-pointer', kind: 'hooks-file', identity: 'hooks.json',
237
+ target: hooks.destination, fingerprint: sha256(fs.readFileSync(hooks.destination, 'utf8')), sourceVersion, recoveryRef,
238
+ projection: { renderer: 'codex-hooks' } })];
239
+ }
240
+
241
+ function resourcesFromApplied({ components, scope, sourceVersion, recoveryRef }: any) {
242
+ const shared = { scope, sourceVersion, recoveryRef };
243
+ return [
244
+ ...configResourcesFrom(components.config, shared),
245
+ ...mcpResourcesFrom(components.mcp, shared),
246
+ ...agentResourcesFrom(components.agents, shared),
247
+ ...hooksResourceFrom(components.hooks, shared),
248
+ ];
249
+ }
250
+
251
+ // ---- copy-tree assets (rules, skills, agents, templates, scripts, references) ----
252
+
253
+ function codexConfigDir(context: any) { return context.paths.configDir; }
254
+ function sourceDirFor(asset: any, repoRoot: any) {
255
+ if (!asset || typeof asset.source !== 'string') throw new Error('Codex asset requires a source path');
256
+ const root = repoRoot ? path.resolve(repoRoot) : process.cwd();
257
+ const source = path.resolve(root, asset.source);
258
+ if (!source.startsWith(`${root}${path.sep}`) || !fs.existsSync(source)) {
259
+ throw new Error(`Codex asset source is unavailable: ${asset.source}`);
260
+ }
261
+ return source;
262
+ }
263
+
264
+ // ---- guidance.codex-pointer instructions (AGENTS.md managed-section merge) ----
265
+ // Codex discovers AGENTS.md at the repository root and walks down toward the CWD, plus
266
+ // ~/.codex/AGENTS.md for global scope (learn.chatgpt.com/docs/agent-configuration/agents-md).
267
+ // A project-scoped file inside .codex/ is not in that discovery chain and would never load, so
268
+ // the project-scope managed section must land at the project root itself — both locations are
269
+ // declared in harnesses.json (paths.instructions). The registry declares this asset's codex
270
+ // projection as renderer "codex-agents" (see core/registry/assets.json).
271
+
272
+ function instructionsAsset(assets: any) { return (assets || []).find((asset: any) => asset?.renderer === 'codex-agents'); }
273
+ function instructionsPath(context: any) {
274
+ return context.paths.instructions;
275
+ }
276
+ function sha256Text(text: any) { return crypto.createHash('sha256').update(text).digest('hex'); }
277
+
278
+ function planInstructionsAsset({ assets, context, removing, repoRoot, claimedElsewhere = () => false }: any) {
279
+ const asset = instructionsAsset(assets);
280
+ const changes: any[] = [];
281
+ const conflicts: any[] = [];
282
+ if (!asset) return { changes, conflicts };
283
+ const target = instructionsPath(context);
284
+ // Broken markers are a plan conflict, as for Claude: found only while applying, they would stop
285
+ // the run after hooks, config and skills were already removed.
286
+ const existing = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : null;
287
+ if (existing !== null) {
288
+ const starts = existing.split(MARKER_START).length - 1;
289
+ if (starts !== existing.split(MARKER_END).length - 1 || starts > 1) {
290
+ conflicts.push(`Codex instructions contain malformed DoFlow markers: ${target}`);
291
+ return { changes, conflicts };
292
+ }
293
+ }
294
+ if (removing) {
295
+ if (existing === null || claimedElsewhere(target) || !existing.includes(MARKER_START)) return { changes, conflicts };
296
+ changes.push({ assetId: asset.id, target, operation: 'remove', ownershipIdentity: 'doflow:codex:instructions:managed-section',
297
+ kind: 'instructions-section', identity: 'AGENTS.md', projection: { renderer: 'codex-agents' } });
298
+ return { changes, conflicts };
299
+ }
300
+ const source = sourceDirFor(asset, repoRoot);
301
+ if (!mergeMarkedSection(source, target, { dryRun: true }).changed) return { changes, conflicts };
302
+ const content = fs.readFileSync(source, 'utf8').replace(/\s+$/, '');
303
+ changes.push({ assetId: asset.id, target, source, operation: fs.existsSync(target) ? 'update' : 'create',
304
+ ownershipIdentity: 'doflow:codex:instructions:managed-section', kind: 'instructions-section', identity: 'AGENTS.md',
305
+ afterFingerprint: sha256Text(content), sourceVersion: 'registry-v1', projection: { renderer: 'codex-agents' } });
306
+ return { changes, conflicts };
307
+ }
308
+
309
+ function applyInstructionsAsset(changes: any) {
310
+ for (const change of changes) {
311
+ if (change.projection?.renderer !== 'codex-agents' || change.operation === 'remove') continue;
312
+ mergeMarkedSection(change.source, change.target);
313
+ }
314
+ }
315
+
316
+ function removeInstructionsAsset(changes: any) {
317
+ for (const change of changes) {
318
+ if (change.projection?.renderer === 'codex-agents' && change.operation === 'remove') removeMarkedSection(change.target);
319
+ }
320
+ }
321
+
322
+ function verifyInstructionsAsset({ assets, context, repoRoot }: any) {
323
+ const asset = instructionsAsset(assets);
324
+ const statuses: any[] = []; const resources: any[] = [];
325
+ if (!asset) return { statuses, resources };
326
+ const target = instructionsPath(context);
327
+ const existing = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : null;
328
+ const managed = existing !== null && existing.includes(MARKER_START) && existing.includes(MARKER_END);
329
+ statuses.push({ assetId: asset.id, capability: asset.capability, status: managed ? 'managed' : 'absent', target });
330
+ if (managed) {
331
+ resources.push({ assetId: asset.id, target, ownershipIdentity: 'doflow:codex:instructions:managed-section',
332
+ kind: 'instructions-section', identity: 'AGENTS.md', fingerprint: sha256Text(existing), sourceVersion: 'registry-v1',
333
+ projection: { renderer: 'codex-agents' } });
334
+ }
335
+ return { statuses, resources };
336
+ }
337
+
338
+ function planCopyTreeAssets({ assets, context, neutralResources, removing, repoRoot, sourceVersion, force = false }: any) {
339
+ const changes = [];
340
+ const conflicts = [];
341
+ const treeResults = [];
342
+ const notices = [];
343
+ for (const asset of copyTreeAssets(assets)) {
344
+ const destDir = copyTreeDestDir(codexConfigDir(context), asset);
345
+ const sourceDir = sourceDirFor(asset, repoRoot);
346
+ const previousResources = ledgerFileResources(neutralResources, HARNESS, asset.id);
347
+ const result = planTree({ sourceDir, destDir, previousResources, siblingFingerprints: ledgerSiblingFingerprints(neutralResources, HARNESS), operation: removing ? 'remove' : 'apply', layout: asset.layout, keepModified: !removing,
348
+ // `force` is the CLI's --force reaching the one conflict class a plan can actually
349
+ // downgrade: a ledger-owned destination whose bytes were edited underneath us. With force,
350
+ // that is drift to heal rather than a refusal — exactly what `doflow reconcile` (always
351
+ // forced) and `update --force` need. Removal stays strict by design: a hand-edited file is
352
+ // never deleted, forced or not.
353
+ force: !removing && force === true });
354
+ treeResults.push(result);
355
+ conflicts.push(...result.conflicts.map((reason: any) => `${asset.id}: ${reason}`));
356
+ // A hand-edited file at a path DoFlow no longer writes is the user's: it stays, only its
357
+ // ownership row is released.
358
+ notices.push(...result.kept.map((item: any) => `kept hand-edited ${path.relative(context.paths.root, item.target)}; DoFlow no longer manages it`));
359
+ for (const change of result.changes) {
360
+ changes.push({
361
+ assetId: asset.id, target: change.target, source: change.source, operation: change.operation,
362
+ ownershipIdentity: `doflow:codex:copy-tree:${asset.id}:${change.relPath}`,
363
+ kind: 'copy-tree-file', identity: change.relPath,
364
+ afterFingerprint: change.fingerprint, fingerprint: change.fingerprint, sourceVersion: sourceVersion ?? 'unknown',
365
+ ...(change.kept ? { retained: true, retainedFor: [] } : {}),
366
+ projection: { renderer: 'copy-tree' },
367
+ });
368
+ }
369
+ }
370
+ return { changes, conflicts, notices: [...siblingReplacedNotices(treeResults), ...notices] };
371
+ }
372
+
373
+ function applyCopyTreeAssets(changes: any) {
374
+ const treeChanges = changes.filter((change: any) => change.projection?.renderer === 'copy-tree' && change.operation !== 'remove')
375
+ .map((change: any) => ({ relPath: change.identity, target: change.target, source: change.source, operation: change.operation, fingerprint: change.fingerprint }));
376
+ return applyTree({ changes: treeChanges }).applied;
377
+ }
378
+
379
+ function removeCopyTreeAssets(changes: any) {
380
+ const treeChanges = changes.filter((change: any) => change.projection?.renderer === 'copy-tree' && change.operation === 'remove' && !change.retained)
381
+ .map((change: any) => ({ relPath: change.identity, target: change.target, operation: 'remove', fingerprint: change.fingerprint, pruneGuard: change.pruneGuard }));
382
+ return removeTree({ changes: treeChanges }).removed;
383
+ }
384
+
385
+ function verifyCopyTreeAssets({ assets, context, repoRoot, sourceVersion, plannedWrites }: any) {
386
+ const statuses = [];
387
+ const resources = [];
388
+ const conflicts = [];
389
+ for (const asset of copyTreeAssets(assets)) {
390
+ const destDir = copyTreeDestDir(codexConfigDir(context), asset);
391
+ const sourceDir = sourceDirFor(asset, repoRoot);
392
+ const result = verifyTree({ sourceDir, destDir, layout: asset.layout, plannedWrites });
393
+ conflicts.push(...result.conflicts.map((reason: any) => `${asset.id}: ${reason}`));
394
+ for (const resource of result.resources) {
395
+ resources.push({
396
+ assetId: asset.id, target: resource.target, ownershipIdentity: `doflow:codex:copy-tree:${asset.id}:${resource.relPath}`,
397
+ kind: 'copy-tree-file', identity: resource.relPath,
398
+ fingerprint: resource.fingerprint, sourceVersion: sourceVersion ?? 'unknown',
399
+ projection: { renderer: 'copy-tree' },
400
+ });
401
+ }
402
+ statuses.push({ assetId: asset.id, capability: asset.capability, status: result.ok ? 'managed' : 'conflict', target: destDir });
403
+ }
404
+ return { statuses, resources, conflicts };
405
+ }
406
+
407
+ /** Resolve the explicit projected-native shape. During the migration, direct
408
+ * callers may still use legacy option names; lifecycle callers pass `assets`,
409
+ * `mcp`, `policies`, and optionally `projection` for source-bearing surfaces.
410
+ * Native source paths are never guessed from an asset's renderer. */
411
+ function projectedNativeOptions(options: any) {
412
+ const projection = options.projection || {};
413
+ const configResources = projection.configResources ?? options.configResources ?? options.context?.codexConfigResources;
414
+ const mcpCatalog = projection.mcp?.catalog ?? options.mcpCatalog ?? options.mcp ?? [];
415
+ const selectedMcp = projection.mcp?.selected ?? options.selectedMcp ?? (options.mcp ? options.mcp.map((server: any) => server.id) : undefined);
416
+ const agentsSourceDir = projection.agents?.sourceDir ?? options.agentsSourceDir ?? options.context?.codexAgentsSourceDir;
417
+ const hooksSourceFile = projection.hooks?.sourceFile ?? options.hooksSourceFile ?? options.context?.codexHooksSourceFile;
418
+ const hooksSourceDir = projection.hooks?.sourceHooksDir ?? options.hooksSourceDir ?? options.context?.codexHooksSourceDir;
419
+ const hooksConfig = projection.hooks?.config ?? options.hooksConfig;
420
+ const hooksTrusted = projection.hooks?.trusted ?? options.hooksTrusted;
421
+ return { configResources, mcpCatalog, selectedMcp, agentsSourceDir, hooksSourceFile, hooksSourceDir, hooksConfig, hooksTrusted };
422
+ }
423
+
424
+ function planConfigComponent({ native, neutralResources, context, configFile, removing, adopt = false }: any) {
425
+ if (native.configResources === undefined) return undefined;
426
+ const managedResources = nativeManagedResources(neutralResources, context, { kind: 'configuration-entry', target: configFile });
427
+ const component = planCodexConfig({ file: configFile, scope: context.scope, managedResources, desiredResources: removing ? [] : native.configResources, adopt: !removing && adopt });
428
+ component.scope = context.scope;
429
+ component.desiredResources = removing ? [] : native.configResources;
430
+ // The next ledger intentionally omits a removed record. Keep the pre-plan
431
+ // ownership proof solely for apply's immediate replan over the current file.
432
+ component.reconciliationManagedResources = managedResources;
433
+ return component;
434
+ }
435
+
436
+ function planMcpComponent({ native, neutralResources, context, configFile, removing, options }: any) {
437
+ if (native.selectedMcp === undefined) return undefined;
438
+ const catalog = nativeMcpCatalog(native.mcpCatalog);
439
+ const managedResources = nativeManagedResources(neutralResources, context, { kind: 'mcp-server', target: configFile });
440
+ return planCodexMcp({ file: configFile, scope: context.scope, managedResources, selected: removing ? [] : native.selectedMcp,
441
+ removing, scopeArg: context.scope === 'global' ? '-g' : context.projectRoot,
442
+ ...catalog, sourceVersion: options.sourceVersion ?? options.context?.sourceVersion, recoveryPoint: options.recoveryPoint });
443
+ }
444
+
445
+ /** Codex hooks.json is only replaced when the file on disk still matches the ledger's last-known
446
+ * fingerprint — a foreign edit (or a record the ledger never owned) downgrades an otherwise-clean
447
+ * plan to a conflict rather than silently overwriting it. */
448
+ function planHooksComponent({ native, neutralResources, context, adopt = false }: any) {
449
+ let hooks = planCodexHooks({ config: native.hooksConfig, sourceFile: native.hooksSourceFile,
450
+ sourceHooksDir: native.hooksSourceDir, trusted: native.hooksTrusted, destinationContext: context });
451
+ const hooksFile = hooks.destination;
452
+ if (hooks.ok && fs.existsSync(hooksFile)) {
453
+ const [record] = nativeManagedResources(neutralResources, context, { kind: 'hooks-file', target: hooksFile });
454
+ const current = sha256(fs.readFileSync(hooksFile, 'utf8'));
455
+ // `adopt` covers the no-record case only: the file is claimed into the ledger and planning
456
+ // proceeds. A record that disagrees with the bytes below is a different situation and stays a
457
+ // conflict — that one is `--force`'s business, not adoption's.
458
+ if (!record && !adopt) {
459
+ hooks = { ...hooks, ok: false, status: 'conflict', changes: [], errors: ['Codex hooks.json exists but is not owned by the neutral ledger'] };
460
+ } else if (record && record.fingerprint !== current) {
461
+ hooks = { ...hooks, ok: false, status: 'conflict', changes: [], errors: ['Codex hooks.json was modified outside DoFlow'] };
462
+ }
463
+ }
464
+ return hooks;
465
+ }
466
+
467
+ /** Agents/hooks share one removing-vs-planning fork: removal always owns both via the neutral
468
+ * ledger's own records, regardless of what native input the caller provided this run. */
469
+ function planAgentsAndHooksComponents({ native, neutralResources, context, agentsDirectory, removing, options, adopt = false }: any) {
470
+ if (removing) {
471
+ return {
472
+ agents: ownedRemovalPlan(neutralResources, context, 'custom-agent', agentsDirectory),
473
+ // The scripts deployed beside hooks.json have no ledger rows; remove() matches them by bytes.
474
+ hooks: { ...ownedRemovalPlan(neutralResources, context, 'hooks-file'),
475
+ scriptsDir: native.hooksSourceDir, destinationHooksDir: context.paths.hooksDirectory },
476
+ };
477
+ }
478
+ const components: Record<string, any> = {};
479
+ if (native.agentsSourceDir !== undefined) {
480
+ const managedResources = nativeManagedResources(neutralResources, context, { kind: 'custom-agent', directory: agentsDirectory });
481
+ // `...context` is the NARROW native context (scope/codexDir/paths) and carries no adopt, so
482
+ // it is passed explicitly. Reading it off that object is what silently did nothing.
483
+ components.agents = planCodexAgents({ ...context, managedResources, sourceDir: native.agentsSourceDir, sourceVersion: options.sourceVersion ?? options.context?.sourceVersion, adopt });
484
+ }
485
+ if (native.hooksSourceFile !== undefined || native.hooksConfig !== undefined) {
486
+ components.hooks = planHooksComponent({ native, neutralResources, context, adopt });
487
+ }
488
+ return components;
489
+ }
490
+
491
+ /**
492
+ * Compose native, non-mutating plans. Callers opt into a component by providing
493
+ * its desired input, which prevents a generic plan from accidentally removing
494
+ * an owned configuration entry or MCP server merely because it was omitted.
495
+ */
496
+ function plan(options: any) {
497
+ const context = nativeContext(options);
498
+ const removing = options.context?.operation === 'remove';
499
+ const ledgerRows = options.managedResources || options.ledger?.resources || [];
500
+ const neutralResources = removing ? removableRows(ledgerRows, context) : ledgerRows;
501
+ const native = projectedNativeOptions(options);
502
+ const configFile = context.paths.configFile;
503
+ const agentsDirectory = context.paths.agentsDirectory;
504
+ const components: Record<string, any> = {};
505
+ // `options.context` is the lifecycle context; `context` is this adapter's narrow native one.
506
+ // copyTree below already reads force off the former — adopt is read the same way.
507
+ const adopt = options.context?.adopt === true;
508
+ const configComponent = planConfigComponent({ native, neutralResources, context, configFile, removing, adopt });
509
+ if (configComponent) components.config = configComponent;
510
+ const mcpComponent = planMcpComponent({ native, neutralResources, context, configFile, removing, options });
511
+ if (mcpComponent) components.mcp = mcpComponent;
512
+ Object.assign(components, planAgentsAndHooksComponents({ native, neutralResources, context, agentsDirectory, removing, options, adopt }));
513
+ const copyTree = planCopyTreeAssets({ assets: options.assets, context, neutralResources, removing,
514
+ repoRoot: options.context?.repoRoot, sourceVersion: options.sourceVersion ?? options.context?.sourceVersion,
515
+ force: options.context?.force === true });
516
+ const instructions = planInstructionsAsset({ assets: options.assets, context, removing, repoRoot: options.context?.repoRoot,
517
+ claimedElsewhere: (target: any) => claimedByOtherScope(ledgerRows, context, target) });
518
+ // Two pseudo-components: their changes are already in final adapter shape (unlike the other
519
+ // components' native shape, which addChanges() below converts), so they're appended to `changes`
520
+ // directly rather than run through addChanges. Registering them in `components` still gets their
521
+ // conflicts picked up by both the existing `failures` reduction and the lifecycle's own
522
+ // adapterConflicts() component scan, with no special-casing needed in either.
523
+ components.copyTree = { ok: copyTree.conflicts.length === 0, conflicts: copyTree.conflicts };
524
+ components.instructions = { ok: instructions.conflicts.length === 0, conflicts: instructions.conflicts };
525
+ const failures = Object.entries(components).filter(([, component]: [string, any]) => !component.ok);
526
+ const changes: any[] = [];
527
+ addChanges(changes, assetIdFor(options, 'config'), components.config, 'config');
528
+ addChanges(changes, assetIdFor(options, 'mcp'), components.mcp, 'mcp');
529
+ addChanges(changes, assetIdFor(options, 'agents'), components.agents, 'agents');
530
+ addChanges(changes, assetIdFor(options, 'hooks'), components.hooks, 'hooks');
531
+ changes.push(...copyTree.changes, ...instructions.changes);
532
+ if (removing) changes.push(...retainedRemovals(ledgerRows, context));
533
+ const requiredNativeResources = [
534
+ ...Object.entries(components).flatMap(([component, result]: [string, any]) => (result.changes || []).map((change: any) => ({
535
+ harness: HARNESS, component, target: change.target ?? change.file ?? result.file ?? result.destination ?? result.directory,
536
+ operation: change.operation ?? change.type, identity: change.identity ?? null,
537
+ }))),
538
+ ...copyTree.changes.map((change) => ({ harness: HARNESS, component: 'copyTree', target: change.target, operation: change.operation, identity: change.identity })),
539
+ ...instructions.changes.map((change) => ({ harness: HARNESS, component: 'instructions', target: change.target, operation: change.operation, identity: change.identity })),
540
+ ];
541
+ return { harness: HARNESS, scope: options.scope, ok: failures.length === 0, safe: failures.length === 0, components, failures, changes, requiredNativeResources,
542
+ notices: [...copyTree.notices, ...(components.mcp?.notices || [])] };
543
+ }
544
+
545
+ /** Apply only native plans emitted by this adapter. MCP is first because it and
546
+ * config.toml share a file; config is then re-planned over the MCP result so it
547
+ * cannot overwrite an adjacent MCP table. */
548
+ function apply({ changes = [], dryRun = false, scope, sourceVersion, recoveryRef }: any = {}) {
549
+ const components = new Map();
550
+ for (const change of changes) if (change?.nativeComponent && change.nativePlan) components.set(change.nativeComponent, change.nativePlan);
551
+ let mcp = components.get('mcp');
552
+ if (mcp) mcp = applyCodexMcp(mcp, { dryRun });
553
+ let config = components.get('config');
554
+ if (config) {
555
+ const managedResources = mcp
556
+ ? [...(config.reconciliationManagedResources ?? config.managedResources ?? []), ...(mcp.managedResources ?? [])]
557
+ : null;
558
+ const replan = managedResources ? planCodexConfig({ file: config.file, scope: config.scope, managedResources,
559
+ desiredResources: config.desiredResources ?? [] }) : config;
560
+ config = applyCodexConfig(replan, { dryRun });
561
+ }
562
+ const agentsPlan = components.get('agents'); const hooksPlan = components.get('hooks');
563
+ const agents = agentsPlan && !agentsPlan.directRemove ? applyCodexAgents(agentsPlan, { dryRun }) : null;
564
+ const hooks = hooksPlan && !hooksPlan.directRemove ? deployCodexHooks(hooksPlan, { dryRun }) : null;
565
+ // applyCopyTreeAssets/applyInstructionsAsset already filter to non-remove operations, so calling
566
+ // them unconditionally here (including when apply() is invoked from remove(), below) is safe.
567
+ const copyTreeApplied = dryRun ? 0 : applyCopyTreeAssets(changes);
568
+ // Copy-tree removals run here too, for both verbs: an update that no longer ships a file
569
+ // carries its rows as removals inside the apply batch, and remove() delegates to this function.
570
+ if (!dryRun) { removeCopyTreeAssets(changes); applyInstructionsAsset(changes); }
571
+ const nativeComponents = { mcp, config, agents, hooks };
572
+ return { harness: HARNESS, applied: [mcp, config, agents, hooks].filter((result) => result?.applied).length + copyTreeApplied,
573
+ components: nativeComponents, resources: resourcesFromApplied({ components: nativeComponents, scope: scope ?? 'project', sourceVersion, recoveryRef }) };
574
+ }
575
+
576
+ /** Removal reuses the same fingerprint-checked native plans. Only config/MCP
577
+ * plans generated with context.operation=remove contain safe removals; hooks
578
+ * and agents are intentionally never blindly deleted. */
579
+ function remove({ changes = [], dryRun = false }: any = {}) {
580
+ const result = apply({ changes, dryRun });
581
+ const direct = new Map();
582
+ for (const change of changes) if (change?.nativePlan?.directRemove) direct.set(change.target, change);
583
+ for (const change of direct.values()) {
584
+ if (!fs.existsSync(change.target)) continue;
585
+ const current = sha256(fs.readFileSync(change.target, 'utf8'));
586
+ if (current !== change.fingerprint) throw new Error(`Refusing to remove modified Codex resource '${change.identity}'`);
587
+ if (!dryRun) fs.unlinkSync(change.target);
588
+ }
589
+ const hooksPlan = changes.find((change: any) => change?.nativeComponent === 'hooks' && change.nativePlan?.directRemove)?.nativePlan;
590
+ if (hooksPlan) removeCodexHookScripts(hooksPlan, { dryRun });
591
+ if (!dryRun) removeInstructionsAsset(changes);
592
+ return { ...result, removed: changes.filter((change: any) => (change.operation ?? change.type) === 'remove').length };
593
+ }
594
+
595
+ function verifyConfigResources({ native, parsed, context, options, sourceVersion, recoveryRef }: any) {
596
+ const statuses = []; const resources = [];
597
+ for (const resource of native.configResources || []) {
598
+ const entry = parsed?.entries.get(resource.identity);
599
+ const status = entry && configFingerprint(entry.value) === configFingerprint(resource.value) ? 'managed' : 'missing';
600
+ statuses.push({ assetId: assetIdFor(options, 'config'), capability: 'settings', status, identity: resource.identity,
601
+ ownershipIdentity: ownershipIdentity('config', resource.identity), target: context.paths.configFile });
602
+ if (status === 'managed') resources.push(lifecycleResource({ scope: options.scope, assetId: assetIdFor(options, 'config'), kind: 'configuration-entry', identity: resource.identity,
603
+ target: context.paths.configFile, fingerprint: configFingerprint(entry.value), sourceVersion, recoveryRef, projection: { renderer: 'codex-config' } }));
604
+ }
605
+ return { statuses, resources };
606
+ }
607
+
608
+ /** A server is recorded only when its table now holds DoFlow's rendering. One discovery found as
609
+ * the user's at plan time is reported `not-managed` and never recorded, even when its bytes match. */
610
+ function verifyMcpResources({ native, configText, context, options, sourceVersion, recoveryRef }: any) {
611
+ const statuses = []; const resources = [];
612
+ const catalog = nativeMcpCatalog(native.mcpCatalog);
613
+ const userDefined = new Set(options.discovery?.mcpUserDefined || []);
614
+ for (const id of native.selectedMcp || []) {
615
+ const definition = catalog.serverDefs[id];
616
+ const table = definition ? serverTableText(configText, id) : null;
617
+ const present = table !== null && configFingerprint(table) === configFingerprint(renderServer(id, definition));
618
+ const status = userDefined.has(id) ? 'not-managed' : (present ? 'managed' : 'missing');
619
+ statuses.push({ assetId: assetIdFor(options, 'mcp'), capability: 'mcp', status, identity: id,
620
+ ownershipIdentity: ownershipIdentity('mcp', id), target: context.paths.configFile });
621
+ if (status === 'managed') resources.push(lifecycleResource({ scope: options.scope, assetId: assetIdFor(options, 'mcp'), kind: 'mcp-server', identity: id,
622
+ target: context.paths.configFile, fingerprint: configFingerprint(renderServer(id, definition)), sourceVersion, recoveryRef,
623
+ projection: { renderer: 'codex-mcp' } }));
624
+ }
625
+ return { statuses, resources };
626
+ }
627
+
628
+ function verifyAgentResources({ native, context, options, sourceVersion, recoveryRef }: any) {
629
+ const statuses: any[] = []; const resources: any[] = [];
630
+ if (!native.agentsSourceDir) return { statuses, resources };
631
+ for (const agent of discoverCodexAgents(native.agentsSourceDir)) {
632
+ const target = path.join(agentDirectory(context), agent.fileName);
633
+ const present = fs.existsSync(target) && fs.readFileSync(target, 'utf8') === agent.source;
634
+ const identity = `agent:${agent.name}`;
635
+ statuses.push({ assetId: assetIdFor(options, 'agents'), capability: 'agents', status: present ? 'managed' : 'missing', identity,
636
+ ownershipIdentity: ownershipIdentity('agents', identity), target });
637
+ if (present) resources.push(lifecycleResource({ scope: options.scope, assetId: assetIdFor(options, 'agents'), kind: 'custom-agent', identity: `agent:${agent.name}`,
638
+ target, fingerprint: agent.fingerprint, sourceVersion, recoveryRef, projection: { renderer: 'codex-agent' } }));
639
+ }
640
+ return { statuses, resources };
641
+ }
642
+
643
+ function verifyHooksResource({ native, context, options, sourceVersion, recoveryRef }: any) {
644
+ const statuses: any[] = []; const resources: any[] = []; const conflicts: any[] = [];
645
+ if (!native.hooksSourceFile && !native.hooksConfig) return { statuses, resources, conflicts };
646
+ const hooks = planCodexHooks({ config: native.hooksConfig, sourceFile: native.hooksSourceFile, sourceHooksDir: native.hooksSourceDir,
647
+ trusted: native.hooksTrusted, destinationContext: context });
648
+ const status = hooks.ok && hooks.status === 'unchanged' ? 'managed' : (hooks.ok ? 'missing' : 'conflict');
649
+ statuses.push({ assetId: assetIdFor(options, 'hooks'), capability: 'hooks', status, identity: 'hooks.json',
650
+ ownershipIdentity: ownershipIdentity('hooks', 'hooks.json'), target: hooks.destination });
651
+ if (!hooks.ok) conflicts.push(...hooks.errors);
652
+ if (status === 'managed') resources.push(lifecycleResource({ scope: options.scope, assetId: assetIdFor(options, 'hooks'), kind: 'hooks-file', identity: 'hooks.json',
653
+ target: hooks.destination, fingerprint: sha256(fs.readFileSync(hooks.destination, 'utf8')), sourceVersion, recoveryRef, projection: { renderer: 'codex-hooks' } }));
654
+ return { statuses, resources, conflicts };
655
+ }
656
+
657
+ function verifyAssetRenderStatuses({ registry, assets }: any) {
658
+ return assets.map((asset: any) => render({ registry, asset })).filter((rendered: any) => rendered.status !== 'renderable');
659
+ }
660
+
661
+ function verify(options: any = {}) {
662
+ const { registry, assets = [], plan: proposed } = options;
663
+ const context = nativeContext(options);
664
+ const native = projectedNativeOptions(options);
665
+ const sourceVersion = options.sourceVersion ?? options.context?.sourceVersion;
666
+ const recoveryRef = options.recoveryRef;
667
+ let configText = '';
668
+ if (fs.existsSync(context.paths.configFile)) configText = fs.readFileSync(context.paths.configFile, 'utf8');
669
+ let parsed;
670
+ const conflicts = [];
671
+ try { parsed = parseToml(configText); } catch (error: any) { conflicts.push(`Malformed Codex config: ${error.message}`); }
672
+
673
+ const shared = { context, options, sourceVersion, recoveryRef };
674
+ const config = verifyConfigResources({ native, parsed, ...shared });
675
+ const mcp = verifyMcpResources({ native, configText, ...shared });
676
+ const agents = verifyAgentResources({ native, ...shared });
677
+ const hooks = verifyHooksResource({ native, ...shared });
678
+ const copyTree = verifyCopyTreeAssets({ assets, context, repoRoot: options.context?.repoRoot, sourceVersion, plannedWrites: options.context?.plannedWrites });
679
+ const instructions = verifyInstructionsAsset({ assets, context, repoRoot: options.context?.repoRoot });
680
+
681
+ const statuses = [
682
+ ...config.statuses, ...mcp.statuses, ...agents.statuses, ...hooks.statuses,
683
+ ...verifyAssetRenderStatuses({ registry, assets }),
684
+ ...copyTree.statuses, ...instructions.statuses,
685
+ ];
686
+ const resources = [...config.resources, ...mcp.resources, ...agents.resources, ...hooks.resources, ...copyTree.resources, ...instructions.resources];
687
+ conflicts.push(...hooks.conflicts, ...copyTree.conflicts);
688
+ const componentFailures = Object.values<any>(proposed?.components || {}).filter((component) => !component.ok);
689
+ for (const component of componentFailures) conflicts.push(...(component.conflicts || component.errors || []));
690
+
691
+ return { ok: conflicts.length === 0 && statuses.every((status) => status.status !== 'missing' && status.status !== 'conflict'), resources, statuses, conflicts };
692
+ }
693
+
694
+ return { normalizeContext, nativeContext, projectedNativeOptions, nativeManagedResources, ownershipIdentity, discover, render, plan, apply, remove, verify };
695
+ }
696
+
697
+ const singleton = createCodexAdapter();
698
+
699
+ module.exports = {
700
+ HARNESS,
701
+ normalizeContext: singleton.normalizeContext, nativeContext: singleton.nativeContext,
702
+ projectedNativeOptions: singleton.projectedNativeOptions, nativeManagedResources: singleton.nativeManagedResources,
703
+ ownershipIdentity: singleton.ownershipIdentity,
704
+ discover: singleton.discover, render: singleton.render, plan: singleton.plan, apply: singleton.apply,
705
+ remove: singleton.remove, verify: singleton.verify,
706
+ createCodexAdapter,
707
+ };