@khoavu882/doflow 2.0.0-beta.3 → 2.0.0-beta.5

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 (236) hide show
  1. package/bin/doflow.js +2 -2
  2. package/core/.antigravity-plugin/plugin.json +0 -1
  3. package/core/.claude-plugin/plugin.json +1 -1
  4. package/core/.codex-plugin/plugin.json +1 -1
  5. package/core/.plugin/plugin.json +1 -1
  6. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
  7. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  8. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  9. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  10. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  11. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  12. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  13. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  14. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  15. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  16. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  17. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  18. package/core/harnesses/codex/hooks/subagent-audit.sh +17 -6
  19. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  20. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +7 -18
  21. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +7 -18
  22. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +7 -19
  23. package/core/harnesses/gemini/hooks/session-start.sh +5 -3
  24. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +20 -5
  25. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +20 -5
  26. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +20 -5
  27. package/core/harnesses/kiro/hooks/session-start.sh +11 -5
  28. package/core/harnesses/kiro/hooks/stop-check.sh +21 -30
  29. package/core/harnesses/pi/extensions/doflow-guard.js +159 -0
  30. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -11
  31. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  32. package/core/harnesses/shared/hooks/bridge/bridge.sh +184 -0
  33. package/core/harnesses/shared/hooks/bridge/envelope.jq +60 -0
  34. package/core/harnesses/shared/hooks/bridge/respond.sh +62 -0
  35. package/core/harnesses/shared/hooks/policies/capture-failure.sh +4 -2
  36. package/core/harnesses/shared/hooks/policies/contract.sh +75 -0
  37. package/core/harnesses/shared/hooks/policies/deny-json.sh +39 -4
  38. package/core/harnesses/shared/hooks/policies/lib.sh +4 -2
  39. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +46 -34
  40. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +75 -25
  41. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +67 -73
  42. package/core/harnesses/shared/hooks/policies/session-context.sh +37 -7
  43. package/core/harnesses/shared/hooks/policies/stop-check.sh +42 -15
  44. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +35 -5
  45. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +40 -7
  46. package/core/harnesses/shared/hooks/stream-hook-runner.js +69 -181
  47. package/core/registry/assets.json +19 -0
  48. package/core/registry/contracts.json +2 -2
  49. package/core/registry/harnesses.json +196 -10
  50. package/core/registry/lifecycle.json +2626 -7
  51. package/core/registry/storage.json +110 -83
  52. package/core/registry/verification.json +1 -1
  53. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -2
  54. package/core/shared/scripts/doflow/bin/doflow-run +10 -6
  55. package/core/shared/skills/do-design/SKILL.md +6 -6
  56. package/core/shared/skills/do-execute-plan/SKILL.md +11 -10
  57. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +2 -0
  58. package/core/shared/skills/do-execute-plan/references/scaffold.md +2 -2
  59. package/core/shared/skills/do-implement/SKILL.md +3 -2
  60. package/core/shared/skills/do-plan/SKILL.md +7 -8
  61. package/package.json +4 -2
  62. package/src/adapters/antigravity/{index.ts → index.js} +141 -105
  63. package/src/adapters/claude/{index.ts → index.js} +63 -49
  64. package/src/adapters/claude/{mcp.ts → mcp.js} +16 -16
  65. package/src/adapters/codex/{agents.ts → agents.js} +24 -24
  66. package/src/adapters/codex/{config.ts → config.js} +49 -49
  67. package/src/adapters/codex/{hooks.ts → hooks.js} +62 -27
  68. package/src/adapters/codex/{index.ts → index.js} +148 -105
  69. package/src/adapters/codex/{mcp.ts → mcp.js} +38 -38
  70. package/src/adapters/codex/{native-projection.ts → native-projection.js} +5 -5
  71. package/src/adapters/copilot/{index.ts → index.js} +42 -42
  72. package/src/adapters/{copy-tree.ts → copy-tree.js} +74 -74
  73. package/src/adapters/gemini/{hooks.ts → hooks.js} +78 -37
  74. package/src/adapters/gemini/{index.ts → index.js} +96 -57
  75. package/src/adapters/{hook-commands.ts → hook-commands.js} +8 -8
  76. package/src/adapters/{index.ts → index.js} +13 -13
  77. package/src/adapters/kiro/{index.ts → index.js} +49 -35
  78. package/src/adapters/{mcp-entries.ts → mcp-entries.js} +55 -55
  79. package/src/adapters/opencode/{index.ts → index.js} +52 -52
  80. package/src/adapters/pi/hooks.js +119 -0
  81. package/src/adapters/pi/{index.ts → index.js} +64 -39
  82. package/src/adapters/pi/{json-members.ts → json-members.js} +23 -23
  83. package/src/adapters/pi/{mcp.ts → mcp.js} +70 -70
  84. package/src/adapters/{resolve-source.ts → resolve-source.js} +1 -1
  85. package/src/cli/commands/{install.ts → install.js} +25 -25
  86. package/src/cli/commands/{list-backups.ts → list-backups.js} +4 -4
  87. package/src/cli/commands/{reconcile.ts → reconcile.js} +19 -19
  88. package/src/cli/commands/{remove.ts → remove.js} +10 -10
  89. package/src/cli/commands/{rollback.ts → rollback.js} +17 -17
  90. package/src/cli/commands/{self-update.ts → self-update.js} +3 -3
  91. package/src/cli/commands/{status.ts → status.js} +24 -24
  92. package/src/cli/commands/{tools.ts → tools.js} +16 -16
  93. package/src/cli/commands/{update.ts → update.js} +20 -20
  94. package/src/cli/{index.ts → index.js} +43 -31
  95. package/src/cli/{runtime-commands.ts → runtime-commands.js} +97 -58
  96. package/src/cli/{shared.ts → shared.js} +40 -40
  97. package/src/helper/{bash-helper.ts → bash-helper.js} +7 -7
  98. package/src/helper/{git.ts → git.js} +2 -2
  99. package/src/helper/{guardrails.ts → guardrails.js} +10 -10
  100. package/src/helper/{harness-paths.ts → harness-paths.js} +24 -24
  101. package/src/helper/{marker-merge.ts → marker-merge.js} +4 -4
  102. package/src/helper/{prompt.ts → prompt.js} +5 -5
  103. package/src/helper/{repo-root.ts → repo-root.js} +2 -2
  104. package/src/helper/{settings-merge.ts → settings-merge.js} +27 -27
  105. package/src/helper/{settings-scope.ts → settings-scope.js} +3 -3
  106. package/src/helper/{toml.ts → toml.js} +48 -48
  107. package/src/install/{backup-set.ts → backup-set.js} +16 -16
  108. package/src/install/{backup.ts → backup.js} +65 -65
  109. package/src/install/{context.ts → context.js} +15 -15
  110. package/src/install/{manifest.ts → manifest.js} +27 -27
  111. package/src/install/{mcp.ts → mcp.js} +30 -30
  112. package/src/install/{paths.ts → paths.js} +15 -15
  113. package/src/install/{targets.ts → targets.js} +10 -10
  114. package/src/install/{tool-lifecycle.ts → tool-lifecycle.js} +20 -20
  115. package/src/lifecycle/{bash-availability.ts → bash-availability.js} +1 -1
  116. package/src/lifecycle/coverage.js +57 -0
  117. package/src/lifecycle/hook-step.js +61 -0
  118. package/src/lifecycle/{index.ts → index.js} +160 -146
  119. package/src/lifecycle/{mcp-index.ts → mcp-index.js} +1 -1
  120. package/src/lifecycle/{policies.ts → policies.js} +11 -11
  121. package/src/lifecycle/{view.ts → view.js} +28 -28
  122. package/src/registry/{index.ts → index.js} +195 -66
  123. package/src/release/build-package.js +91 -0
  124. package/src/release/changelog-section.js +70 -0
  125. package/src/release/{sync-plugin-versions.ts → sync-plugin-versions.js} +4 -4
  126. package/src/runtime/advice/{cli.ts → cli.js} +31 -31
  127. package/src/runtime/advice/{client.ts → client.js} +44 -44
  128. package/src/runtime/advice/{config.ts → config.js} +16 -16
  129. package/src/runtime/advice/{doctor.ts → doctor.js} +32 -32
  130. package/src/runtime/advice/{endpoint-guard.ts → endpoint-guard.js} +20 -20
  131. package/src/runtime/advice/{kinds.ts → kinds.js} +16 -16
  132. package/src/runtime/advice/providers/{ollama-systemone.ts → ollama-systemone.js} +21 -21
  133. package/src/runtime/advice/{record.ts → record.js} +19 -19
  134. package/src/runtime/advice/{registry.ts → registry.js} +16 -16
  135. package/src/runtime/advice/{strength.ts → strength.js} +7 -7
  136. package/src/runtime/{brief-tools.ts → brief-tools.js} +50 -50
  137. package/src/runtime/{c4-project.ts → c4-project.js} +27 -27
  138. package/src/runtime/candidate.js +505 -0
  139. package/src/runtime/{capability-router.ts → capability-router.js} +39 -39
  140. package/src/runtime/{checkouts.ts → checkouts.js} +21 -21
  141. package/src/runtime/{claims.ts → claims.js} +44 -35
  142. package/src/runtime/{cli-result.ts → cli-result.js} +15 -8
  143. package/src/runtime/{cli.ts → cli.js} +59 -292
  144. package/src/runtime/{command-detect.ts → command-detect.js} +47 -47
  145. package/src/runtime/context/budget.js +203 -0
  146. package/src/runtime/context/compose.js +1367 -0
  147. package/src/runtime/context/delivery-store.js +102 -0
  148. package/src/runtime/context/mandatory.js +594 -0
  149. package/src/runtime/context/pointer.js +142 -0
  150. package/src/runtime/context-pack.js +1277 -0
  151. package/src/runtime/{decision-register.ts → decision-register.js} +67 -67
  152. package/src/runtime/evidence-fields.js +39 -0
  153. package/src/runtime/{evidence-ledger.ts → evidence-ledger.js} +32 -25
  154. package/src/runtime/failure/{capture.ts → capture.js} +29 -29
  155. package/src/runtime/failure/{classifier.ts → classifier.js} +4 -4
  156. package/src/runtime/failure/{cli.ts → cli.js} +30 -30
  157. package/src/runtime/failure/{home.ts → home.js} +6 -6
  158. package/src/runtime/failure/{store.ts → store.js} +32 -32
  159. package/src/runtime/{feature-resolve.ts → feature-resolve.js} +6 -6
  160. package/src/runtime/{freshness.ts → freshness.js} +59 -22
  161. package/src/runtime/{health.ts → health.js} +72 -72
  162. package/src/runtime/{history-compactor.ts → history-compactor.js} +25 -25
  163. package/src/runtime/{implementation-gate.ts → implementation-gate.js} +30 -30
  164. package/src/runtime/inventory/{coverage.ts → coverage.js} +10 -10
  165. package/src/runtime/inventory/{identity.ts → identity.js} +2 -2
  166. package/src/runtime/inventory/{index.ts → index.js} +36 -36
  167. package/src/runtime/inventory/{precedence.ts → precedence.js} +2 -2
  168. package/src/runtime/inventory/{read-scopes.ts → read-scopes.js} +9 -9
  169. package/src/runtime/inventory/{siblings.ts → siblings.js} +15 -15
  170. package/src/runtime/knowledge/{chunker.ts → chunker.js} +12 -12
  171. package/src/runtime/knowledge/{hybrid.ts → hybrid.js} +17 -17
  172. package/src/runtime/knowledge/{index-store.ts → index-store.js} +23 -23
  173. package/src/runtime/knowledge/{retrieval.ts → retrieval.js} +27 -27
  174. package/src/runtime/{leak-scan.ts → leak-scan.js} +12 -12
  175. package/src/runtime/lifecycle/{cli.ts → cli.js} +32 -32
  176. package/src/runtime/lifecycle/{event-store.ts → event-store.js} +48 -48
  177. package/src/runtime/lifecycle/{fold.ts → fold.js} +41 -41
  178. package/src/runtime/lifecycle/{followup.ts → followup.js} +54 -54
  179. package/src/runtime/lifecycle/{goal.ts → goal.js} +50 -50
  180. package/src/runtime/lifecycle/{intent-writer.ts → intent-writer.js} +5 -5
  181. package/src/runtime/lifecycle/{overview.ts → overview.js} +47 -47
  182. package/src/runtime/lifecycle/{release.ts → release.js} +29 -29
  183. package/src/runtime/lifecycle/{report-store.ts → report-store.js} +25 -25
  184. package/src/runtime/lifecycle/{retention.ts → retention.js} +37 -37
  185. package/src/runtime/lifecycle/{root.ts → root.js} +9 -9
  186. package/src/runtime/lifecycle/{status.ts → status.js} +64 -64
  187. package/src/runtime/lifecycle/{store-upkeep.ts → store-upkeep.js} +46 -46
  188. package/src/runtime/{locator-resolve.ts → locator-resolve.js} +7 -7
  189. package/src/runtime/{mask.ts → mask.js} +22 -22
  190. package/src/runtime/{mcp-consultations.ts → mcp-consultations.js} +54 -54
  191. package/src/runtime/{mcp-reach.ts → mcp-reach.js} +24 -24
  192. package/src/runtime/{model-router.ts → model-router.js} +20 -20
  193. package/src/runtime/{outcome.ts → outcome.js} +27 -27
  194. package/src/runtime/{reach.ts → reach.js} +21 -21
  195. package/src/runtime/{readiness-record.ts → readiness-record.js} +12 -12
  196. package/src/runtime/{readiness.ts → readiness.js} +113 -43
  197. package/src/runtime/{recovery.ts → recovery.js} +15 -15
  198. package/src/runtime/{research-request.ts → research-request.js} +34 -34
  199. package/src/runtime/{retrieval-plan.ts → retrieval-plan.js} +453 -104
  200. package/src/runtime/retrieval-record.js +223 -0
  201. package/src/runtime/scaffold/{artifacts.ts → artifacts.js} +22 -19
  202. package/src/runtime/scaffold/{fingerprint.ts → fingerprint.js} +3 -3
  203. package/src/runtime/scaffold/{generate.ts → generate.js} +109 -109
  204. package/src/runtime/scaffold/{languages.ts → languages.js} +34 -34
  205. package/src/runtime/source-version.js +289 -0
  206. package/src/runtime/storage/{cli.ts → cli.js} +9 -9
  207. package/src/runtime/storage/{engine.ts → engine.js} +37 -37
  208. package/src/runtime/storage/{registry.ts → registry.js} +19 -19
  209. package/src/runtime/storage/{roots.ts → roots.js} +8 -8
  210. package/src/runtime/storage/{rules.ts → rules.js} +26 -26
  211. package/src/runtime/storage/{upkeep.ts → upkeep.js} +35 -35
  212. package/src/runtime/storage/{work-status.ts → work-status.js} +39 -39
  213. package/src/runtime/{task-attempts.ts → task-attempts.js} +62 -62
  214. package/src/runtime/{task-classifier.ts → task-classifier.js} +34 -34
  215. package/src/runtime/{task-scope.ts → task-scope.js} +40 -15
  216. package/src/runtime/{task-state.ts → task-state.js} +86 -21
  217. package/src/runtime/trace/{indicators.ts → indicators.js} +22 -22
  218. package/src/runtime/trace/{ledger.ts → ledger.js} +42 -42
  219. package/src/runtime/trace/{render.ts → render.js} +7 -7
  220. package/src/runtime/trace/{views.ts → views.js} +40 -40
  221. package/src/runtime/verification/{contract-runner.ts → contract-runner.js} +23 -23
  222. package/src/runtime/verification/{engine.ts → engine.js} +143 -143
  223. package/src/runtime/verification/{registry.ts → registry.js} +6 -6
  224. package/src/runtime/verification/{scope-bound.ts → scope-bound.js} +50 -50
  225. package/src/runtime/verification/{test-selection.ts → test-selection.js} +28 -28
  226. package/src/runtime/{workflow-engine.ts → workflow-engine.js} +50 -50
  227. package/src/runtime/{workflow-orchestrator.ts → workflow-orchestrator.js} +91 -91
  228. package/src/runtime/{worktree.ts → worktree.js} +31 -31
  229. package/src/state/{index.ts → index.js} +26 -26
  230. package/src/state/{lockfile.ts → lockfile.js} +20 -20
  231. package/src/state/{run-lock.ts → run-lock.js} +44 -44
  232. package/core/.antigravity-plugin/hooks.json +0 -39
  233. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  234. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  235. package/core/harnesses/shared/hooks/hooks.json +0 -47
  236. package/src/runtime/context-pack.ts +0 -492
@@ -9,25 +9,25 @@ const fs = require('node:fs');
9
9
  const os = require('node:os');
10
10
  const path = require('node:path');
11
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');
12
+ const { configPath, fingerprint: configFingerprint, parseToml, planCodexConfig, applyCodexConfig } = require('./config.js');
13
+ const { renderServer, planCodexMcp, applyCodexMcp, ownedCodexMcpIds, userDefinedCodexMcpIds, serverTableText } = require('./mcp.js');
14
+ const { agentDirectory, discoverCodexAgents, planCodexAgents, applyCodexAgents } = require('./agents.js');
15
+ const { planCodexHooks, deployCodexHooks, removeCodexHookScripts, scriptFingerprint } = require('./hooks.js');
16
+ const { planTree, applyTree, removeTree, verifyTree, copyTreeAssets, copyTreeDestDir, ledgerFileResources, ledgerSiblingFingerprints, siblingReplacedNotices } = require('../copy-tree.js');
17
+ const { mergeMarkedSection, removeMarkedSection, MARKER_START, MARKER_END } = require('../../helper/marker-merge.js');
18
+ const { nativeMcpCatalog } = require('../../registry/index.js');
19
+ const { declaredHarnessPaths, resolveHarnessPaths } = require('../../helper/harness-paths.js');
20
20
 
21
21
  const HARNESS = 'codex';
22
22
 
23
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);
24
+ function providerMcpServers(registry ) {
25
+ return Object.values (registry?.capabilities || {}).flatMap((capability) => (capability.providers || []).map((provider ) => provider.mcp?.server)).filter(Boolean);
26
26
  }
27
27
 
28
28
  /** The servers config.toml defines among `names`, whoever wrote them: the tables DoFlow owns plus the
29
29
  * user's own. `null` while the file is absent or cannot be parsed. */
30
- function configuredCodexMcpIds({ config, scope, fsImpl, mcpRecords, names }: any) {
30
+ function configuredCodexMcpIds({ config, scope, fsImpl, mcpRecords, names } ) {
31
31
  try {
32
32
  if (!fsImpl.existsSync(config)) return null;
33
33
  parseToml(fsImpl.readFileSync(config, 'utf8'));
@@ -44,7 +44,7 @@ function configuredCodexMcpIds({ config, scope, fsImpl, mcpRecords, names }: any
44
44
  * configured instance so direct callers keep their historical shape.
45
45
  */
46
46
  function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] } = {}) {
47
- function normalizeContext({ scope, scopeRoot, projectRoot, codexDir, homeDir }: any = {}) {
47
+ function normalizeContext({ scope, scopeRoot, projectRoot, codexDir, homeDir } = {}) {
48
48
  if (!['project', 'global', 'user'].includes(scope)) throw new Error(`Unsupported Codex scope '${scope}'`);
49
49
  if (scope === 'project') {
50
50
  // Logic residue (input normalization, not a path fact): scopeRoot is accepted as the selected
@@ -65,7 +65,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
65
65
 
66
66
  const nativeContext = normalizeContext;
67
67
 
68
- function discover(options: any, { fsImpl = fs }: any = {}) {
68
+ function discover(options , { fsImpl = fs } = {}) {
69
69
  const context = nativeContext(options);
70
70
  const paths = context.paths;
71
71
  const config = paths.configFile;
@@ -85,9 +85,9 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
85
85
  };
86
86
  }
87
87
 
88
- function render({ registry, asset }: any) {
88
+ function render({ registry, asset } ) {
89
89
  if (!registry || !asset) throw new Error('render requires registry and asset');
90
- const harness = registry.harnesses.find((item: any) => item.id === HARNESS);
90
+ const harness = registry.harnesses.find((item ) => item.id === HARNESS);
91
91
  if (!harness) throw new Error('Registry does not define the Codex harness');
92
92
  const projection = asset.projection?.[HARNESS];
93
93
  if (!projection) return { harness: HARNESS, assetId: asset.id, status: 'not-applicable' };
@@ -105,20 +105,20 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
105
105
  };
106
106
  }
107
107
 
108
- function assetIdFor(options: any, componentName: any) {
108
+ function assetIdFor(options , componentName ) {
109
109
  const explicit = options.projection?.assetIds?.[componentName];
110
110
  if (explicit) return explicit;
111
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}`;
112
+ if (componentName === 'agents') return assets.find((asset ) => asset.id === 'agents.shared')?.id ?? assets[0]?.id;
113
+ return assets.find((asset ) => asset.id === 'guidance.codex-pointer')?.id ?? assets[0]?.id ?? `codex.${componentName}`;
114
114
  }
115
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;
116
+ function ownershipKind(componentName ) {
117
+ return ({ config: 'configuration-entry', mcp: 'mcp-server', agents: 'custom-agent', hooks: 'hooks-file' } )[componentName] ?? componentName;
118
118
  }
119
- function ownershipIdentity(componentName: any, identity: any) { return `doflow:codex:${ownershipKind(componentName)}:${identity}`; }
119
+ function ownershipIdentity(componentName , identity ) { return `doflow:codex:${ownershipKind(componentName)}:${identity}`; }
120
120
 
121
- function addChanges(output: any, assetId: any, component: any, componentName: any) {
121
+ function addChanges(output , assetId , component , componentName ) {
122
122
  for (const change of component?.changes || []) output.push({
123
123
  harness: HARNESS,
124
124
  assetId,
@@ -134,8 +134,8 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
134
134
  });
135
135
  }
136
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) {
137
+ function sha256(text ) { return `sha256:${crypto.createHash('sha256').update(text).digest('hex')}`; }
138
+ function lifecycleResource({ scope, assetId, kind, identity, target, fingerprint, sourceVersion, selection, recoveryRef, projection } ) {
139
139
  return { harness: HARNESS, scope, assetId, kind, identity, target, ownershipIdentity: `doflow:codex:${kind}:${identity}`,
140
140
  fingerprint, sourceVersion: sourceVersion ?? 'unknown', selection: selection ?? true, recoveryRef: recoveryRef ?? null, projection };
141
141
  }
@@ -143,8 +143,8 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
143
143
  /** Convert neutral ledger entries to the legacy planner's ownership record.
144
144
  * An absolute target must match the exact native file this component will
145
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) => {
146
+ function nativeManagedResources(resources , context , { kind, target, directory } = {}) {
147
+ return (resources || []).flatMap((resource ) => {
148
148
  if (!resource || resource.kind !== kind) return [];
149
149
  // Retain the pre-registry native record shape for direct callers/tests.
150
150
  if (!resource.harness && resource.target === HARNESS) {
@@ -162,36 +162,36 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
162
162
  /** Whether a row of the OTHER scope claims `target` (for a config or MCP entry: the same entry in
163
163
  * that file). A project rooted at $HOME shares `~/.codex`, `~/.agents` and the one ledger with the
164
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) {
165
+ function claimedByOtherScope(resources , context , target , identity ) {
166
166
  if (typeof target !== 'string') return false;
167
- return (resources || []).some((resource: any) => resource?.scope && resource.scope !== context.scope
167
+ return (resources || []).some((resource ) => resource?.scope && resource.scope !== context.scope
168
168
  && typeof resource.target === 'string' && path.resolve(resource.target) === path.resolve(target)
169
169
  && (identity === undefined || resource.identity === identity));
170
170
  }
171
171
 
172
- function claimedRowElsewhere(resources: any, context: any, resource: any) {
172
+ function claimedRowElsewhere(resources , context , resource ) {
173
173
  return claimedByOtherScope(resources, context, resource.target,
174
174
  ['configuration-entry', 'config-entry', 'mcp-server'].includes(resource.kind) ? resource.identity : undefined);
175
175
  }
176
176
 
177
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)
178
+ function removableRows(resources , context ) {
179
+ return (resources || []).filter((resource ) => (!resource?.scope || resource.scope === context.scope)
180
180
  && !claimedRowElsewhere(resources, context, resource));
181
181
  }
182
182
 
183
183
  /** This scope's rows on a path the other scope still claims become retained removals: the
184
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
185
+ function retainedRemovals(resources , context ) {
186
+ return (resources || []).filter((resource ) => resource?.harness === HARNESS && resource.scope === context.scope
187
187
  && claimedRowElsewhere(resources, context, resource))
188
- .map((resource: any) => ({ harness: HARNESS, assetId: resource.assetId, target: resource.target, operation: 'remove',
188
+ .map((resource ) => ({ harness: HARNESS, assetId: resource.assetId, target: resource.target, operation: 'remove',
189
189
  ownershipIdentity: resource.ownershipIdentity, kind: resource.kind, identity: resource.identity,
190
190
  retained: true, retainedFor: [`${HARNESS} (${context.scope === 'global' ? 'project' : 'global'} scope)`] }));
191
191
  }
192
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);
193
+ function ownedRemovalPlan(resources , context , kind , directory ) {
194
+ const entries = (resources || []).filter((resource ) => resource?.harness === HARNESS && resource.kind === kind);
195
195
  const changes = []; const conflicts = [];
196
196
  for (const resource of entries) {
197
197
  const target = kind === 'custom-agent'
@@ -207,38 +207,38 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
207
207
  return { ok: conflicts.length === 0, status: conflicts.length ? 'conflict' : (changes.length ? 'change' : 'unchanged'), changes, conflicts, directRemove: true };
208
208
  }
209
209
 
210
- function configResourcesFrom(config: any, { scope, sourceVersion, recoveryRef }: any) {
210
+ function configResourcesFrom(config , { scope, sourceVersion, recoveryRef } ) {
211
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,
212
+ .filter((record ) => record.kind === 'configuration-entry' || record.kind === 'config-entry')
213
+ .map((record ) => lifecycleResource({ scope, assetId: 'guidance.codex-pointer', kind: 'configuration-entry', identity: record.identity,
214
214
  target: config.file, fingerprint: record.fingerprint, sourceVersion: record.sourceVersion ?? sourceVersion, recoveryRef,
215
215
  projection: { renderer: 'codex-config' } }));
216
216
  }
217
217
 
218
- function mcpResourcesFrom(mcp: any, { scope, sourceVersion, recoveryRef }: any) {
218
+ function mcpResourcesFrom(mcp , { scope, sourceVersion, recoveryRef } ) {
219
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,
220
+ .filter((record ) => record.kind === 'mcp-server')
221
+ .map((record ) => lifecycleResource({ scope, assetId: 'guidance.codex-pointer', kind: 'mcp-server', identity: record.identity,
222
222
  target: mcp.file, fingerprint: record.fingerprint, sourceVersion: record.sourceVersion ?? sourceVersion, selection: record.selection, recoveryRef,
223
223
  projection: { renderer: 'codex-mcp' } }));
224
224
  }
225
225
 
226
- function agentResourcesFrom(agents: any, { scope, sourceVersion, recoveryRef }: any) {
226
+ function agentResourcesFrom(agents , { scope, sourceVersion, recoveryRef } ) {
227
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,
228
+ .filter((record ) => record.kind === 'custom-agent')
229
+ .map((record ) => lifecycleResource({ scope, assetId: 'agents.shared', kind: 'custom-agent', identity: record.identity,
230
230
  target: path.join(agents.directory, `${record.identity.replace(/^agent:/, '')}.toml`), fingerprint: record.fingerprint,
231
231
  sourceVersion: record.sourceVersion ?? sourceVersion, recoveryRef, projection: { renderer: 'codex-agent' } }));
232
232
  }
233
233
 
234
- function hooksResourceFrom(hooks: any, { scope, sourceVersion, recoveryRef }: any) {
234
+ function hooksResourceFrom(hooks , { scope, sourceVersion, recoveryRef } ) {
235
235
  if (!hooks?.applied || !hooks.destination) return [];
236
236
  return [lifecycleResource({ scope, assetId: 'guidance.codex-pointer', kind: 'hooks-file', identity: 'hooks.json',
237
237
  target: hooks.destination, fingerprint: sha256(fs.readFileSync(hooks.destination, 'utf8')), sourceVersion, recoveryRef,
238
238
  projection: { renderer: 'codex-hooks' } })];
239
239
  }
240
240
 
241
- function resourcesFromApplied({ components, scope, sourceVersion, recoveryRef }: any) {
241
+ function resourcesFromApplied({ components, scope, sourceVersion, recoveryRef } ) {
242
242
  const shared = { scope, sourceVersion, recoveryRef };
243
243
  return [
244
244
  ...configResourcesFrom(components.config, shared),
@@ -250,8 +250,8 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
250
250
 
251
251
  // ---- copy-tree assets (rules, skills, agents, templates, scripts, references) ----
252
252
 
253
- function codexConfigDir(context: any) { return context.paths.configDir; }
254
- function sourceDirFor(asset: any, repoRoot: any) {
253
+ function codexConfigDir(context ) { return context.paths.configDir; }
254
+ function sourceDirFor(asset , repoRoot ) {
255
255
  if (!asset || typeof asset.source !== 'string') throw new Error('Codex asset requires a source path');
256
256
  const root = repoRoot ? path.resolve(repoRoot) : process.cwd();
257
257
  const source = path.resolve(root, asset.source);
@@ -269,16 +269,16 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
269
269
  // declared in harnesses.json (paths.instructions). The registry declares this asset's codex
270
270
  // projection as renderer "codex-agents" (see core/registry/assets.json).
271
271
 
272
- function instructionsAsset(assets: any) { return (assets || []).find((asset: any) => asset?.renderer === 'codex-agents'); }
273
- function instructionsPath(context: any) {
272
+ function instructionsAsset(assets ) { return (assets || []).find((asset ) => asset?.renderer === 'codex-agents'); }
273
+ function instructionsPath(context ) {
274
274
  return context.paths.instructions;
275
275
  }
276
- function sha256Text(text: any) { return crypto.createHash('sha256').update(text).digest('hex'); }
276
+ function sha256Text(text ) { return crypto.createHash('sha256').update(text).digest('hex'); }
277
277
 
278
- function planInstructionsAsset({ assets, context, removing, repoRoot, claimedElsewhere = () => false }: any) {
278
+ function planInstructionsAsset({ assets, context, removing, repoRoot, claimedElsewhere = () => false } ) {
279
279
  const asset = instructionsAsset(assets);
280
- const changes: any[] = [];
281
- const conflicts: any[] = [];
280
+ const changes = [];
281
+ const conflicts = [];
282
282
  if (!asset) return { changes, conflicts };
283
283
  const target = instructionsPath(context);
284
284
  // Broken markers are a plan conflict, as for Claude: found only while applying, they would stop
@@ -306,22 +306,22 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
306
306
  return { changes, conflicts };
307
307
  }
308
308
 
309
- function applyInstructionsAsset(changes: any) {
309
+ function applyInstructionsAsset(changes ) {
310
310
  for (const change of changes) {
311
311
  if (change.projection?.renderer !== 'codex-agents' || change.operation === 'remove') continue;
312
312
  mergeMarkedSection(change.source, change.target);
313
313
  }
314
314
  }
315
315
 
316
- function removeInstructionsAsset(changes: any) {
316
+ function removeInstructionsAsset(changes ) {
317
317
  for (const change of changes) {
318
318
  if (change.projection?.renderer === 'codex-agents' && change.operation === 'remove') removeMarkedSection(change.target);
319
319
  }
320
320
  }
321
321
 
322
- function verifyInstructionsAsset({ assets, context, repoRoot }: any) {
322
+ function verifyInstructionsAsset({ assets, context, repoRoot } ) {
323
323
  const asset = instructionsAsset(assets);
324
- const statuses: any[] = []; const resources: any[] = [];
324
+ const statuses = []; const resources = [];
325
325
  if (!asset) return { statuses, resources };
326
326
  const target = instructionsPath(context);
327
327
  const existing = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : null;
@@ -335,7 +335,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
335
335
  return { statuses, resources };
336
336
  }
337
337
 
338
- function planCopyTreeAssets({ assets, context, neutralResources, removing, repoRoot, sourceVersion, force = false }: any) {
338
+ function planCopyTreeAssets({ assets, context, neutralResources, removing, repoRoot, sourceVersion, force = false } ) {
339
339
  const changes = [];
340
340
  const conflicts = [];
341
341
  const treeResults = [];
@@ -352,10 +352,10 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
352
352
  // never deleted, forced or not.
353
353
  force: !removing && force === true });
354
354
  treeResults.push(result);
355
- conflicts.push(...result.conflicts.map((reason: any) => `${asset.id}: ${reason}`));
355
+ conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
356
356
  // A hand-edited file at a path DoFlow no longer writes is the user's: it stays, only its
357
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`));
358
+ notices.push(...result.kept.map((item ) => `kept hand-edited ${path.relative(context.paths.root, item.target)}; DoFlow no longer manages it`));
359
359
  for (const change of result.changes) {
360
360
  changes.push({
361
361
  assetId: asset.id, target: change.target, source: change.source, operation: change.operation,
@@ -370,19 +370,19 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
370
370
  return { changes, conflicts, notices: [...siblingReplacedNotices(treeResults), ...notices] };
371
371
  }
372
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 }));
373
+ function applyCopyTreeAssets(changes ) {
374
+ const treeChanges = changes.filter((change ) => change.projection?.renderer === 'copy-tree' && change.operation !== 'remove')
375
+ .map((change ) => ({ relPath: change.identity, target: change.target, source: change.source, operation: change.operation, fingerprint: change.fingerprint }));
376
376
  return applyTree({ changes: treeChanges }).applied;
377
377
  }
378
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 }));
379
+ function removeCopyTreeAssets(changes ) {
380
+ const treeChanges = changes.filter((change ) => change.projection?.renderer === 'copy-tree' && change.operation === 'remove' && !change.retained)
381
+ .map((change ) => ({ relPath: change.identity, target: change.target, operation: 'remove', fingerprint: change.fingerprint, pruneGuard: change.pruneGuard }));
382
382
  return removeTree({ changes: treeChanges }).removed;
383
383
  }
384
384
 
385
- function verifyCopyTreeAssets({ assets, context, repoRoot, sourceVersion, plannedWrites }: any) {
385
+ function verifyCopyTreeAssets({ assets, context, repoRoot, sourceVersion, plannedWrites } ) {
386
386
  const statuses = [];
387
387
  const resources = [];
388
388
  const conflicts = [];
@@ -390,7 +390,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
390
390
  const destDir = copyTreeDestDir(codexConfigDir(context), asset);
391
391
  const sourceDir = sourceDirFor(asset, repoRoot);
392
392
  const result = verifyTree({ sourceDir, destDir, layout: asset.layout, plannedWrites });
393
- conflicts.push(...result.conflicts.map((reason: any) => `${asset.id}: ${reason}`));
393
+ conflicts.push(...result.conflicts.map((reason ) => `${asset.id}: ${reason}`));
394
394
  for (const resource of result.resources) {
395
395
  resources.push({
396
396
  assetId: asset.id, target: resource.target, ownershipIdentity: `doflow:codex:copy-tree:${asset.id}:${resource.relPath}`,
@@ -408,11 +408,11 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
408
408
  * callers may still use legacy option names; lifecycle callers pass `assets`,
409
409
  * `mcp`, `policies`, and optionally `projection` for source-bearing surfaces.
410
410
  * Native source paths are never guessed from an asset's renderer. */
411
- function projectedNativeOptions(options: any) {
411
+ function projectedNativeOptions(options ) {
412
412
  const projection = options.projection || {};
413
413
  const configResources = projection.configResources ?? options.configResources ?? options.context?.codexConfigResources;
414
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);
415
+ const selectedMcp = projection.mcp?.selected ?? options.selectedMcp ?? (options.mcp ? options.mcp.map((server ) => server.id) : undefined);
416
416
  const agentsSourceDir = projection.agents?.sourceDir ?? options.agentsSourceDir ?? options.context?.codexAgentsSourceDir;
417
417
  const hooksSourceFile = projection.hooks?.sourceFile ?? options.hooksSourceFile ?? options.context?.codexHooksSourceFile;
418
418
  const hooksSourceDir = projection.hooks?.sourceHooksDir ?? options.hooksSourceDir ?? options.context?.codexHooksSourceDir;
@@ -421,7 +421,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
421
421
  return { configResources, mcpCatalog, selectedMcp, agentsSourceDir, hooksSourceFile, hooksSourceDir, hooksConfig, hooksTrusted };
422
422
  }
423
423
 
424
- function planConfigComponent({ native, neutralResources, context, configFile, removing, adopt = false }: any) {
424
+ function planConfigComponent({ native, neutralResources, context, configFile, removing, adopt = false } ) {
425
425
  if (native.configResources === undefined) return undefined;
426
426
  const managedResources = nativeManagedResources(neutralResources, context, { kind: 'configuration-entry', target: configFile });
427
427
  const component = planCodexConfig({ file: configFile, scope: context.scope, managedResources, desiredResources: removing ? [] : native.configResources, adopt: !removing && adopt });
@@ -433,7 +433,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
433
433
  return component;
434
434
  }
435
435
 
436
- function planMcpComponent({ native, neutralResources, context, configFile, removing, options }: any) {
436
+ function planMcpComponent({ native, neutralResources, context, configFile, removing, options } ) {
437
437
  if (native.selectedMcp === undefined) return undefined;
438
438
  const catalog = nativeMcpCatalog(native.mcpCatalog);
439
439
  const managedResources = nativeManagedResources(neutralResources, context, { kind: 'mcp-server', target: configFile });
@@ -445,9 +445,16 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
445
445
  /** Codex hooks.json is only replaced when the file on disk still matches the ledger's last-known
446
446
  * fingerprint — a foreign edit (or a record the ledger never owned) downgrades an otherwise-clean
447
447
  * plan to a conflict rather than silently overwriting it. */
448
- function planHooksComponent({ native, neutralResources, context, adopt = false }: any) {
448
+ /** This scope's ledger rows for the hook scripts deployed beside hooks.json. */
449
+ function scriptRows(neutralResources , context ) {
450
+ return (neutralResources || []).filter((resource ) => resource?.harness === HARNESS && resource.kind === 'hooks-script'
451
+ && (!resource.scope || resource.scope === context.scope));
452
+ }
453
+
454
+ function planHooksComponent({ native, neutralResources, context, adopt = false, force = false } ) {
449
455
  let hooks = planCodexHooks({ config: native.hooksConfig, sourceFile: native.hooksSourceFile,
450
- sourceHooksDir: native.hooksSourceDir, trusted: native.hooksTrusted, destinationContext: context });
456
+ sourceHooksDir: native.hooksSourceDir, trusted: native.hooksTrusted, destinationContext: context,
457
+ scriptRows: scriptRows(neutralResources, context), force });
451
458
  const hooksFile = hooks.destination;
452
459
  if (hooks.ok && fs.existsSync(hooksFile)) {
453
460
  const [record] = nativeManagedResources(neutralResources, context, { kind: 'hooks-file', target: hooksFile });
@@ -457,7 +464,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
457
464
  // conflict — that one is `--force`'s business, not adoption's.
458
465
  if (!record && !adopt) {
459
466
  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) {
467
+ } else if (record && record.fingerprint !== current && !force) {
461
468
  hooks = { ...hooks, ok: false, status: 'conflict', changes: [], errors: ['Codex hooks.json was modified outside DoFlow'] };
462
469
  }
463
470
  }
@@ -466,16 +473,25 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
466
473
 
467
474
  /** Agents/hooks share one removing-vs-planning fork: removal always owns both via the neutral
468
475
  * 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) {
476
+ /** A removal drops the rows of the hook scripts too. The scripts themselves go by their bytes
477
+ * (removeCodexHookScripts), so each row is only released here. */
478
+ function withScriptReleases(plan , neutralResources , context , extra ) {
479
+ const releases = scriptRows(neutralResources, context).map((row ) => ({ type: 'remove', assetId: row.assetId, identity: row.identity,
480
+ target: row.target, ownershipIdentity: row.ownershipIdentity, kind: row.kind, release: true }));
481
+ return { ...plan, ...extra, changes: [...plan.changes, ...releases],
482
+ status: plan.status === 'unchanged' && releases.length ? 'change' : plan.status };
483
+ }
484
+
485
+ function planAgentsAndHooksComponents({ native, neutralResources, context, agentsDirectory, removing, options, adopt = false } ) {
470
486
  if (removing) {
471
487
  return {
472
488
  agents: ownedRemovalPlan(neutralResources, context, 'custom-agent', agentsDirectory),
473
489
  // 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 },
490
+ hooks: withScriptReleases(ownedRemovalPlan(neutralResources, context, 'hooks-file'), neutralResources, context,
491
+ { scriptsDir: native.hooksSourceDir, destinationHooksDir: context.paths.hooksDirectory }),
476
492
  };
477
493
  }
478
- const components: Record<string, any> = {};
494
+ const components = {};
479
495
  if (native.agentsSourceDir !== undefined) {
480
496
  const managedResources = nativeManagedResources(neutralResources, context, { kind: 'custom-agent', directory: agentsDirectory });
481
497
  // `...context` is the NARROW native context (scope/codexDir/paths) and carries no adopt, so
@@ -483,7 +499,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
483
499
  components.agents = planCodexAgents({ ...context, managedResources, sourceDir: native.agentsSourceDir, sourceVersion: options.sourceVersion ?? options.context?.sourceVersion, adopt });
484
500
  }
485
501
  if (native.hooksSourceFile !== undefined || native.hooksConfig !== undefined) {
486
- components.hooks = planHooksComponent({ native, neutralResources, context, adopt });
502
+ components.hooks = planHooksComponent({ native, neutralResources, context, adopt, force: options.context?.force === true });
487
503
  }
488
504
  return components;
489
505
  }
@@ -493,7 +509,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
493
509
  * its desired input, which prevents a generic plan from accidentally removing
494
510
  * an owned configuration entry or MCP server merely because it was omitted.
495
511
  */
496
- function plan(options: any) {
512
+ function plan(options ) {
497
513
  const context = nativeContext(options);
498
514
  const removing = options.context?.operation === 'remove';
499
515
  const ledgerRows = options.managedResources || options.ledger?.resources || [];
@@ -501,7 +517,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
501
517
  const native = projectedNativeOptions(options);
502
518
  const configFile = context.paths.configFile;
503
519
  const agentsDirectory = context.paths.agentsDirectory;
504
- const components: Record<string, any> = {};
520
+ const components = {};
505
521
  // `options.context` is the lifecycle context; `context` is this adapter's narrow native one.
506
522
  // copyTree below already reads force off the former — adopt is read the same way.
507
523
  const adopt = options.context?.adopt === true;
@@ -514,7 +530,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
514
530
  repoRoot: options.context?.repoRoot, sourceVersion: options.sourceVersion ?? options.context?.sourceVersion,
515
531
  force: options.context?.force === true });
516
532
  const instructions = planInstructionsAsset({ assets: options.assets, context, removing, repoRoot: options.context?.repoRoot,
517
- claimedElsewhere: (target: any) => claimedByOtherScope(ledgerRows, context, target) });
533
+ claimedElsewhere: (target ) => claimedByOtherScope(ledgerRows, context, target) });
518
534
  // Two pseudo-components: their changes are already in final adapter shape (unlike the other
519
535
  // components' native shape, which addChanges() below converts), so they're appended to `changes`
520
536
  // directly rather than run through addChanges. Registering them in `components` still gets their
@@ -522,8 +538,8 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
522
538
  // adapterConflicts() component scan, with no special-casing needed in either.
523
539
  components.copyTree = { ok: copyTree.conflicts.length === 0, conflicts: copyTree.conflicts };
524
540
  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[] = [];
541
+ const failures = Object.entries(components).filter(([, component] ) => !component.ok);
542
+ const changes = [];
527
543
  addChanges(changes, assetIdFor(options, 'config'), components.config, 'config');
528
544
  addChanges(changes, assetIdFor(options, 'mcp'), components.mcp, 'mcp');
529
545
  addChanges(changes, assetIdFor(options, 'agents'), components.agents, 'agents');
@@ -531,7 +547,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
531
547
  changes.push(...copyTree.changes, ...instructions.changes);
532
548
  if (removing) changes.push(...retainedRemovals(ledgerRows, context));
533
549
  const requiredNativeResources = [
534
- ...Object.entries(components).flatMap(([component, result]: [string, any]) => (result.changes || []).map((change: any) => ({
550
+ ...Object.entries(components).flatMap(([component, result] ) => (result.changes || []).map((change ) => ({
535
551
  harness: HARNESS, component, target: change.target ?? change.file ?? result.file ?? result.destination ?? result.directory,
536
552
  operation: change.operation ?? change.type, identity: change.identity ?? null,
537
553
  }))),
@@ -542,10 +558,29 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
542
558
  notices: [...copyTree.notices, ...(components.mcp?.notices || [])] };
543
559
  }
544
560
 
561
+ /** The hook front doors (IC-037): hooks.json and the scripts deployed beside it, planned and
562
+ * verified alone. A conflict reaches the lifecycle through `components`, as in plan(). */
563
+ function planHookDoors(options ) {
564
+ const native = projectedNativeOptions(options);
565
+ const hooks = native.hooksSourceFile === undefined && native.hooksConfig === undefined ? null
566
+ : planHooksComponent({ native, neutralResources: options.managedResources || options.ledger?.resources || [], context: nativeContext(options),
567
+ adopt: options.context?.adopt === true, force: options.context?.force === true });
568
+ const changes = [];
569
+ if (hooks) addChanges(changes, assetIdFor(options, 'hooks'), hooks, 'hooks');
570
+ return { changes, components: hooks ? { hooks } : {}, prerequisites: [], notices: [] };
571
+ }
572
+
573
+ function verifyHookDoors(options = {}) {
574
+ const hooks = verifyHooksResource({ native: projectedNativeOptions(options), context: nativeContext(options), options,
575
+ sourceVersion: options.sourceVersion ?? options.context?.sourceVersion, recoveryRef: options.recoveryRef });
576
+ return { ok: hooks.conflicts.length === 0 && hooks.statuses.every((status ) => status.status !== 'missing' && status.status !== 'conflict'),
577
+ resources: hooks.resources, statuses: hooks.statuses, conflicts: hooks.conflicts };
578
+ }
579
+
545
580
  /** Apply only native plans emitted by this adapter. MCP is first because it and
546
581
  * config.toml share a file; config is then re-planned over the MCP result so it
547
582
  * cannot overwrite an adjacent MCP table. */
548
- function apply({ changes = [], dryRun = false, scope, sourceVersion, recoveryRef }: any = {}) {
583
+ function apply({ changes = [], dryRun = false, scope, sourceVersion, recoveryRef } = {}) {
549
584
  const components = new Map();
550
585
  for (const change of changes) if (change?.nativeComponent && change.nativePlan) components.set(change.nativeComponent, change.nativePlan);
551
586
  let mcp = components.get('mcp');
@@ -576,23 +611,23 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
576
611
  /** Removal reuses the same fingerprint-checked native plans. Only config/MCP
577
612
  * plans generated with context.operation=remove contain safe removals; hooks
578
613
  * and agents are intentionally never blindly deleted. */
579
- function remove({ changes = [], dryRun = false }: any = {}) {
614
+ function remove({ changes = [], dryRun = false } = {}) {
580
615
  const result = apply({ changes, dryRun });
581
616
  const direct = new Map();
582
- for (const change of changes) if (change?.nativePlan?.directRemove) direct.set(change.target, change);
617
+ for (const change of changes) if (change?.nativePlan?.directRemove && !change.retained) direct.set(change.target, change);
583
618
  for (const change of direct.values()) {
584
619
  if (!fs.existsSync(change.target)) continue;
585
620
  const current = sha256(fs.readFileSync(change.target, 'utf8'));
586
621
  if (current !== change.fingerprint) throw new Error(`Refusing to remove modified Codex resource '${change.identity}'`);
587
622
  if (!dryRun) fs.unlinkSync(change.target);
588
623
  }
589
- const hooksPlan = changes.find((change: any) => change?.nativeComponent === 'hooks' && change.nativePlan?.directRemove)?.nativePlan;
624
+ const hooksPlan = changes.find((change ) => change?.nativeComponent === 'hooks' && change.nativePlan?.directRemove)?.nativePlan;
590
625
  if (hooksPlan) removeCodexHookScripts(hooksPlan, { dryRun });
591
626
  if (!dryRun) removeInstructionsAsset(changes);
592
- return { ...result, removed: changes.filter((change: any) => (change.operation ?? change.type) === 'remove').length };
627
+ return { ...result, removed: changes.filter((change ) => (change.operation ?? change.type) === 'remove').length };
593
628
  }
594
629
 
595
- function verifyConfigResources({ native, parsed, context, options, sourceVersion, recoveryRef }: any) {
630
+ function verifyConfigResources({ native, parsed, context, options, sourceVersion, recoveryRef } ) {
596
631
  const statuses = []; const resources = [];
597
632
  for (const resource of native.configResources || []) {
598
633
  const entry = parsed?.entries.get(resource.identity);
@@ -607,7 +642,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
607
642
 
608
643
  /** A server is recorded only when its table now holds DoFlow's rendering. One discovery found as
609
644
  * 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) {
645
+ function verifyMcpResources({ native, configText, context, options, sourceVersion, recoveryRef } ) {
611
646
  const statuses = []; const resources = [];
612
647
  const catalog = nativeMcpCatalog(native.mcpCatalog);
613
648
  const userDefined = new Set(options.discovery?.mcpUserDefined || []);
@@ -625,8 +660,8 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
625
660
  return { statuses, resources };
626
661
  }
627
662
 
628
- function verifyAgentResources({ native, context, options, sourceVersion, recoveryRef }: any) {
629
- const statuses: any[] = []; const resources: any[] = [];
663
+ function verifyAgentResources({ native, context, options, sourceVersion, recoveryRef } ) {
664
+ const statuses = []; const resources = [];
630
665
  if (!native.agentsSourceDir) return { statuses, resources };
631
666
  for (const agent of discoverCodexAgents(native.agentsSourceDir)) {
632
667
  const target = path.join(agentDirectory(context), agent.fileName);
@@ -640,8 +675,8 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
640
675
  return { statuses, resources };
641
676
  }
642
677
 
643
- function verifyHooksResource({ native, context, options, sourceVersion, recoveryRef }: any) {
644
- const statuses: any[] = []; const resources: any[] = []; const conflicts: any[] = [];
678
+ function verifyHooksResource({ native, context, options, sourceVersion, recoveryRef } ) {
679
+ const statuses = []; const resources = []; const conflicts = [];
645
680
  if (!native.hooksSourceFile && !native.hooksConfig) return { statuses, resources, conflicts };
646
681
  const hooks = planCodexHooks({ config: native.hooksConfig, sourceFile: native.hooksSourceFile, sourceHooksDir: native.hooksSourceDir,
647
682
  trusted: native.hooksTrusted, destinationContext: context });
@@ -651,14 +686,21 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
651
686
  if (!hooks.ok) conflicts.push(...hooks.errors);
652
687
  if (status === 'managed') resources.push(lifecycleResource({ scope: options.scope, assetId: assetIdFor(options, 'hooks'), kind: 'hooks-file', identity: 'hooks.json',
653
688
  target: hooks.destination, fingerprint: sha256(fs.readFileSync(hooks.destination, 'utf8')), sourceVersion, recoveryRef, projection: { renderer: 'codex-hooks' } }));
689
+ if (status === 'managed') {
690
+ for (const name of hooks.scriptNames) {
691
+ resources.push(lifecycleResource({ scope: options.scope, assetId: assetIdFor(options, 'hooks'), kind: 'hooks-script', identity: name,
692
+ target: path.join(hooks.destinationHooksDir, name), fingerprint: scriptFingerprint(fs.readFileSync(path.join(hooks.destinationHooksDir, name))),
693
+ sourceVersion, recoveryRef, projection: { renderer: 'codex-hooks' } }));
694
+ }
695
+ }
654
696
  return { statuses, resources, conflicts };
655
697
  }
656
698
 
657
- function verifyAssetRenderStatuses({ registry, assets }: any) {
658
- return assets.map((asset: any) => render({ registry, asset })).filter((rendered: any) => rendered.status !== 'renderable');
699
+ function verifyAssetRenderStatuses({ registry, assets } ) {
700
+ return assets.map((asset ) => render({ registry, asset })).filter((rendered ) => rendered.status !== 'renderable');
659
701
  }
660
702
 
661
- function verify(options: any = {}) {
703
+ function verify(options = {}) {
662
704
  const { registry, assets = [], plan: proposed } = options;
663
705
  const context = nativeContext(options);
664
706
  const native = projectedNativeOptions(options);
@@ -668,7 +710,7 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
668
710
  if (fs.existsSync(context.paths.configFile)) configText = fs.readFileSync(context.paths.configFile, 'utf8');
669
711
  let parsed;
670
712
  const conflicts = [];
671
- try { parsed = parseToml(configText); } catch (error: any) { conflicts.push(`Malformed Codex config: ${error.message}`); }
713
+ try { parsed = parseToml(configText); } catch (error ) { conflicts.push(`Malformed Codex config: ${error.message}`); }
672
714
 
673
715
  const shared = { context, options, sourceVersion, recoveryRef };
674
716
  const config = verifyConfigResources({ native, parsed, ...shared });
@@ -685,13 +727,13 @@ function createCodexAdapter({ declaredPaths = declaredHarnessPaths()[HARNESS] }
685
727
  ];
686
728
  const resources = [...config.resources, ...mcp.resources, ...agents.resources, ...hooks.resources, ...copyTree.resources, ...instructions.resources];
687
729
  conflicts.push(...hooks.conflicts, ...copyTree.conflicts);
688
- const componentFailures = Object.values<any>(proposed?.components || {}).filter((component) => !component.ok);
730
+ const componentFailures = Object.values (proposed?.components || {}).filter((component) => !component.ok);
689
731
  for (const component of componentFailures) conflicts.push(...(component.conflicts || component.errors || []));
690
732
 
691
733
  return { ok: conflicts.length === 0 && statuses.every((status) => status.status !== 'missing' && status.status !== 'conflict'), resources, statuses, conflicts };
692
734
  }
693
735
 
694
- return { normalizeContext, nativeContext, projectedNativeOptions, nativeManagedResources, ownershipIdentity, discover, render, plan, apply, remove, verify };
736
+ return { normalizeContext, nativeContext, projectedNativeOptions, nativeManagedResources, ownershipIdentity, discover, render, plan, apply, remove, verify, planHookDoors, verifyHookDoors };
695
737
  }
696
738
 
697
739
  const singleton = createCodexAdapter();
@@ -703,5 +745,6 @@ module.exports = {
703
745
  ownershipIdentity: singleton.ownershipIdentity,
704
746
  discover: singleton.discover, render: singleton.render, plan: singleton.plan, apply: singleton.apply,
705
747
  remove: singleton.remove, verify: singleton.verify,
748
+ planHookDoors: singleton.planHookDoors, verifyHookDoors: singleton.verifyHookDoors,
706
749
  createCodexAdapter,
707
750
  };