@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
@@ -8,52 +8,53 @@
8
8
  const fs = require('node:fs');
9
9
  const path = require('node:path');
10
10
  const crypto = require('node:crypto');
11
- const { harnessFor, selectAssets, selectMcpServers, mcpCapable } = require('../registry/index.ts');
12
- const { defaultLedger, ownershipKey, writeLedger, writeRecoveryRecord, upgradeLedger, LEDGER_VERSION } = require('../state/index.ts');
13
- const { resolveAdapter, projectAdapterInput } = require('../adapters/index.ts');
14
- const { pruneEmptyAncestors, discoverTree, sameFileIndex, sharedTreeDestDir, walkRelFiles } = require('../adapters/copy-tree.ts');
15
- const { renderPolicies } = require('./policies.ts');
16
- const { renderMcpIndex } = require('./mcp-index.ts');
17
- const { hasBashCapableShell } = require('./bash-availability.ts');
18
- const { hookTrustFor: geminiHookTrustFor } = require('../adapters/gemini/index.ts');
11
+ const { harnessFor, selectAssets, selectMcpServers, mcpCapable } = require('../registry/index.js');
12
+ const { defaultLedger, ownershipKey, writeLedger, writeRecoveryRecord, upgradeLedger, LEDGER_VERSION } = require('../state/index.js');
13
+ const { resolveAdapter, projectAdapterInput } = require('../adapters/index.js');
14
+ const { pruneEmptyAncestors, discoverTree, sameFileIndex, sharedTreeDestDir, walkRelFiles } = require('../adapters/copy-tree.js');
15
+ const { renderPolicies } = require('./policies.js');
16
+ const { renderMcpIndex } = require('./mcp-index.js');
17
+ const { hasBashCapableShell } = require('./bash-availability.js');
18
+ const { hookTrustFor: geminiHookTrustFor } = require('../adapters/gemini/index.js');
19
+ const { planHookCompanions } = require('./hook-step.js');
19
20
 
20
21
  const OPERATIONS = new Set(['create', 'merge', 'update', 'remove']);
21
22
  /** A harness's MCP selection meaning "the servers it owns now", read through its adapter's
22
23
  * `discover().mcpOwned`. */
23
24
  const MCP_KEEP = 'keep';
24
25
 
25
- function registryScope(scope: any) { return scope === 'global' ? 'user' : scope; }
26
- function assertScope(scope: any) {
26
+ function registryScope(scope ) { return scope === 'global' ? 'user' : scope; }
27
+ function assertScope(scope ) {
27
28
  if (scope !== 'project' && scope !== 'global') throw new Error(`Lifecycle scope must be project or global, got '${scope}'`);
28
29
  }
29
30
 
30
- function normalizeTargets(registry: any, targets: any) {
31
- const wanted = targets && targets.length ? targets : registry.harnesses.map((harness: any) => harness.id);
31
+ function normalizeTargets(registry , targets ) {
32
+ const wanted = targets && targets.length ? targets : registry.harnesses.map((harness ) => harness.id);
32
33
  if (!Array.isArray(wanted)) throw new Error('Lifecycle targets must be an array');
33
34
  return [...new Set(wanted)].map((id) => harnessFor(registry, id));
34
35
  }
35
36
 
36
- function identityFor(change: any) { return change.ownershipIdentity ?? change.ownership; }
37
+ function identityFor(change ) { return change.ownershipIdentity ?? change.ownership; }
37
38
 
38
- function normalizeChange(change: any, { harness, assets }: any) {
39
+ function normalizeChange(change , { harness, assets } ) {
39
40
  if (!change || typeof change !== 'object') throw new Error(`Adapter '${harness.id}' returned a non-object change`);
40
41
  const ownershipIdentity = identityFor(change);
41
42
  if (change.harness !== undefined && change.harness !== harness.id) throw new Error(`Adapter '${harness.id}' returned a change for '${change.harness}'`);
42
- if (!assets.some((asset: any) => asset.id === change.assetId)) throw new Error(`Adapter '${harness.id}' returned unknown asset '${change.assetId}'`);
43
+ if (!assets.some((asset ) => asset.id === change.assetId)) throw new Error(`Adapter '${harness.id}' returned unknown asset '${change.assetId}'`);
43
44
  if (!OPERATIONS.has(change.operation)) throw new Error(`Adapter '${harness.id}' returned unsupported operation '${change.operation}'`);
44
45
  if (typeof change.target !== 'string' || !change.target) throw new Error(`Adapter '${harness.id}' change '${change.assetId}' requires target`);
45
46
  if (typeof ownershipIdentity !== 'string' || !ownershipIdentity) throw new Error(`Adapter '${harness.id}' change '${change.assetId}' requires ownershipIdentity`);
46
47
  return Object.freeze({ ...change, harness: harness.id, ownershipIdentity });
47
48
  }
48
49
 
49
- function normalizeVerification(result: any, harness: any) {
50
+ function normalizeVerification(result , harness ) {
50
51
  if (!result || typeof result !== 'object') throw new Error(`Adapter '${harness.id}' returned an invalid verification result`);
51
52
  const statuses = Array.isArray(result.statuses) ? result.statuses : [];
52
53
  const resources = Array.isArray(result.resources) ? result.resources : [];
53
54
  return { ...result, harness: harness.id, statuses, resources };
54
55
  }
55
56
 
56
- function matchesChange(resource: any, change: any) {
57
+ function matchesChange(resource , change ) {
57
58
  if (resource?.assetId !== change.assetId || resource?.target !== change.target) return false;
58
59
  const resourceOwnership = resource?.ownershipIdentity ?? resource?.ownership;
59
60
  const changeOwnership = change.ownershipIdentity ?? change.ownership;
@@ -68,9 +69,9 @@ function matchesChange(resource: any, change: any) {
68
69
  /** Whether one removed change is genuinely gone, and if so, how `statuses` should record that.
69
70
  * `statuses` is read live, not a snapshot — an earlier change in the same removal batch can add
70
71
  * or update an entry a later change's own lookup then finds. */
71
- function reconcileRemovedChange(verification: any, statuses: any, change: any) {
72
- const reported = verification.resources.some((resource: any) => matchesChange(resource, change));
73
- const statusIndex = statuses.findIndex((item: any) => matchesChange(item, change));
72
+ function reconcileRemovedChange(verification , statuses , change ) {
73
+ const reported = verification.resources.some((resource ) => matchesChange(resource, change));
74
+ const statusIndex = statuses.findIndex((item ) => matchesChange(item, change));
74
75
  const status = statusIndex >= 0 ? statuses[statusIndex] : null;
75
76
  // 'absent' belongs here alongside 'missing': a shared file (settings.json) survives removal
76
77
  // holding the user's own content, and its adapter reports 'absent' once none of DoFlow's
@@ -101,28 +102,28 @@ function reconcileRemovedChange(verification: any, statuses: any, change: any) {
101
102
  * 'missing' the ledger *does* claim stays a hard failure — that is a removal which genuinely did
102
103
  * not happen, and the existing "unrelated missing remains false" contract depends on it. Without a
103
104
  * ledger, non-ownership cannot be proven, so the strict reading is kept. */
104
- function markUnownedAbsences(statuses: any, ledger: any, harness: any) {
105
+ function markUnownedAbsences(statuses , ledger , harness ) {
105
106
  if (!ledger) return statuses;
106
107
  const owned = new Set((ledger.resources || [])
107
- .filter((resource: any) => resource.harness === harness)
108
- .map((resource: any) => resource.ownershipIdentity)
108
+ .filter((resource ) => resource.harness === harness)
109
+ .map((resource ) => resource.ownershipIdentity)
109
110
  .filter(Boolean));
110
- return statuses.map((status: any) => (
111
+ return statuses.map((status ) => (
111
112
  status?.status === 'missing' && status.ownershipIdentity && !owned.has(status.ownershipIdentity)
112
113
  ? { ...status, status: 'absent', expectedAbsence: true }
113
114
  : status
114
115
  ));
115
116
  }
116
117
 
117
- function normalizeRemovalVerification(verification: any, changes: any, ledger: any) {
118
- const removals = changes.filter((change: any) => change.operation === 'remove' && change.harness === verification.harness);
118
+ function normalizeRemovalVerification(verification , changes , ledger ) {
119
+ const removals = changes.filter((change ) => change.operation === 'remove' && change.harness === verification.harness);
119
120
  if (!removals.length) return verification;
120
121
  // A retained removal released this harness's ownership row without deleting the file, because
121
122
  // another harness still claims it (see markRetainedRemovals). The file is therefore *expected*
122
123
  // to still be on disk, so asking "is it gone?" of it would turn the correct outcome into a
123
124
  // verification conflict. Everything the plan actually deleted is still checked as strictly as
124
125
  // before.
125
- const removed = removals.filter((change: any) => !change.retained);
126
+ const removed = removals.filter((change ) => !change.retained);
126
127
  let statuses = [...verification.statuses];
127
128
  const conflicts = [...(verification.conflicts || [])];
128
129
  for (const change of removed) {
@@ -140,35 +141,35 @@ function normalizeRemovalVerification(verification: any, changes: any, ledger: a
140
141
  * that exact absence has been normalized (an `expectedRemoval` status), recompute rather than
141
142
  * carrying the stale false forward. A bare adapter failure, conflict, or any unrelated
142
143
  * missing/error status remains a hard verification failure. */
143
- function removalVerificationOk(ok: any, statuses: any, conflicts: any) {
144
- const unresolvedFailure = statuses.some((status: any) => ['missing', 'conflict', 'error', 'invalid'].includes(status?.status));
145
- const onlyExpectedRemovalFailure = ok === false && statuses.some((status: any) => status?.expectedRemoval || status?.expectedAbsence) && !unresolvedFailure && conflicts.length === 0;
144
+ function removalVerificationOk(ok , statuses , conflicts ) {
145
+ const unresolvedFailure = statuses.some((status ) => ['missing', 'conflict', 'error', 'invalid'].includes(status?.status));
146
+ const onlyExpectedRemovalFailure = ok === false && statuses.some((status ) => status?.expectedRemoval || status?.expectedAbsence) && !unresolvedFailure && conflicts.length === 0;
146
147
  return conflicts.length === 0 && !unresolvedFailure && (ok !== false || onlyExpectedRemovalFailure);
147
148
  }
148
149
 
149
150
  /** A `failures` entry comes in three shapes from different adapters: a [name, detail] tuple,
150
151
  * a bare string, or an object with its own conflicts/errors. */
151
- function failureReasons(failure: any) {
152
+ function failureReasons(failure ) {
152
153
  if (Array.isArray(failure)) {
153
154
  const [name, detail] = failure;
154
155
  const detailReasons = detail?.conflicts || detail?.errors || [];
155
- return detailReasons.map((reason: any) => `${name}: ${reason}`);
156
+ return detailReasons.map((reason ) => `${name}: ${reason}`);
156
157
  }
157
158
  if (typeof failure === 'string') return [failure];
158
159
  if (failure?.conflicts || failure?.errors) return failure.conflicts || failure.errors;
159
160
  return [];
160
161
  }
161
162
 
162
- function componentReasons(name: any, component: any) {
163
+ function componentReasons(name , component ) {
163
164
  if (component?.ok !== false) return [];
164
165
  const details = component.conflicts || component.errors || [];
165
- return details.length ? details.map((reason: any) => `${name}: ${reason}`) : [`${name}: native plan failed`];
166
+ return details.length ? details.map((reason ) => `${name}: ${reason}`) : [`${name}: native plan failed`];
166
167
  }
167
168
 
168
169
  /** Promote adapter-declared failed components into the common conflict channel.
169
170
  * A false `ok` is never advisory: otherwise a native adapter can decline a
170
171
  * dangerous reconciliation while the lifecycle still journals and writes state. */
171
- function adapterConflicts(result: any, harness: any) {
172
+ function adapterConflicts(result , harness ) {
172
173
  const reasons = [
173
174
  ...(result.conflicts || []),
174
175
  ...(result.errors || []),
@@ -182,7 +183,7 @@ function adapterConflicts(result: any, harness: any) {
182
183
  /** Every ownership row this plan is about to give up, keyed exactly as the neutral ledger keys it.
183
184
  * A removal change always releases its harness's claim; whether it also deletes the file is a
184
185
  * separate question, answered by markRetainedRemovals below. */
185
- function releasedOwnershipKeys(harnessPlans: any, scope: any) {
186
+ function releasedOwnershipKeys(harnessPlans , scope ) {
186
187
  const keys = new Set();
187
188
  for (const target of harnessPlans) {
188
189
  for (const change of target.changes || []) {
@@ -199,7 +200,7 @@ function releasedOwnershipKeys(harnessPlans: any, scope: any) {
199
200
  * on this file is not a claimant even if it is releasing nothing else, and a harness in the same
200
201
  * batch that keeps its row (an install that relocates one asset while a sibling's projection is
201
202
  * unchanged) still is. */
202
- function survivingClaimantsByTarget(ledger: any, releasedKeys: any) {
203
+ function survivingClaimantsByTarget(ledger , releasedKeys ) {
203
204
  const byTarget = new Map();
204
205
  for (const resource of ledger?.resources || []) {
205
206
  if (typeof resource?.target !== 'string' || typeof resource?.harness !== 'string') continue;
@@ -213,7 +214,7 @@ function survivingClaimantsByTarget(ledger: any, releasedKeys: any) {
213
214
 
214
215
  /** What one claim covers: a whole file, except an MCP entry, which is claimed on its own because
215
216
  * several harnesses keep their servers side by side in one MCP file. */
216
- function claimKey(record: any) {
217
+ function claimKey(record ) {
217
218
  return record.kind === 'mcp-server' ? `${record.target}\u0000${record.identity}` : record.target;
218
219
  }
219
220
 
@@ -232,13 +233,13 @@ function claimKey(record: any) {
232
233
  * stays, and the last claimant's removal is the one that reclaims it. The neutral ledger is the
233
234
  * only thing consulted — it is already the record of who owns what, and a second ownership
234
235
  * registry would be one more thing to keep in sync. */
235
- function markRetainedRemovals(harnessPlans: any, ledger: any, scope: any) {
236
+ function markRetainedRemovals(harnessPlans , ledger , scope ) {
236
237
  const claimants = survivingClaimantsByTarget(ledger, releasedOwnershipKeys(harnessPlans, scope));
237
238
  if (!claimants.size) return harnessPlans;
238
- return harnessPlans.map((target: any) => {
239
+ return harnessPlans.map((target ) => {
239
240
  if (target.skipped) return target;
240
241
  let retainedAny = false;
241
- const changes = target.changes.map((change: any) => {
242
+ const changes = target.changes.map((change ) => {
242
243
  if (change.operation !== 'remove') return change;
243
244
  const retainedFor = [...(claimants.get(claimKey(change)) ?? [])].filter((harness) => harness !== change.harness).sort();
244
245
  if (!retainedFor.length) return change;
@@ -255,15 +256,15 @@ function markRetainedRemovals(harnessPlans: any, ledger: any, scope: any) {
255
256
 
256
257
  /** Whether this plan leaves no ownership row standing anywhere — the only case in which a shared,
257
258
  * install-wide artifact that no single harness owns is genuinely unclaimed. */
258
- function removalIsTotal(plan: any) {
259
+ function removalIsTotal(plan ) {
259
260
  const released = releasedOwnershipKeys(plan.targets || [], plan.scope);
260
- return (plan.ledger?.resources || []).every((resource: any) => released.has(ownershipKey(resource)));
261
+ return (plan.ledger?.resources || []).every((resource ) => released.has(ownershipKey(resource)));
261
262
  }
262
263
 
263
264
  /** What a removal kept and why, ready to print. A command that says "removed" while silently
264
265
  * leaving files behind is answering confidently about something it did not do, so the retained
265
266
  * count and the harnesses responsible for it are reported, never inferred by the caller. */
266
- function retentionSummary(retained: any = []) {
267
+ function retentionSummary(retained = []) {
267
268
  const groups = new Map();
268
269
  for (const item of retained) {
269
270
  // No claimant: a release that is reported on its own line (a kept hand edit) or is silent
@@ -278,26 +279,26 @@ function retentionSummary(retained: any = []) {
278
279
  });
279
280
  }
280
281
 
281
- function retainedChanges(changes: any) {
282
- return changes.filter((change: any) => change.retained)
283
- .map((change: any) => ({ harness: change.harness, assetId: change.assetId, target: change.target, retainedFor: change.retainedFor }));
282
+ function retainedChanges(changes ) {
283
+ return changes.filter((change ) => change.retained)
284
+ .map((change ) => ({ harness: change.harness, assetId: change.assetId, target: change.target, retainedFor: change.retainedFor }));
284
285
  }
285
286
 
286
287
  /** An adapter's non-blocking notes: one line of at most 200 characters each. A malformed entry is
287
288
  * dropped rather than failing the plan, because a notice must never change whether an install runs. */
288
- function adapterNotices(result: any) {
289
+ function adapterNotices(result ) {
289
290
  return (Array.isArray(result.notices) ? result.notices : [])
290
- .filter((notice: any) => typeof notice === 'string' && notice.length <= 200 && !/[\u0000-\u001f\u007f]/.test(notice));
291
+ .filter((notice ) => typeof notice === 'string' && notice.length <= 200 && !/[\u0000-\u001f\u007f]/.test(notice));
291
292
  }
292
293
 
293
294
  /** The ids of `ids` the catalog declares, in registry order. */
294
- function catalogIds(registry: any, ids: any) {
295
- return registry.mcp.filter((server: any) => ids.includes(server.id)).map((server: any) => server.id);
295
+ function catalogIds(registry , ids ) {
296
+ return registry.mcp.filter((server ) => ids.includes(server.id)).map((server ) => server.id);
296
297
  }
297
298
 
298
299
  /** Each harness takes its own MCP selection: an id array, or MCP_KEEP for the servers it owns now.
299
300
  * A harness that takes no MCP servers reads `keep` and an absent entry as none and refuses any id. */
300
- function harnessMcpSelection(registry: any, harness: any, raw: any) {
301
+ function harnessMcpSelection(registry , harness , raw ) {
301
302
  const value = raw ?? [];
302
303
  if (value !== MCP_KEEP && !(Array.isArray(value) && value.every((id) => typeof id === 'string'))) {
303
304
  throw new Error(`MCP selection for '${harness.id}' must be an array of server ids or '${MCP_KEEP}'`);
@@ -315,22 +316,22 @@ function harnessMcpSelection(registry: any, harness: any, raw: any) {
315
316
  * right for them. */
316
317
  const RUNTIME_TREE_ASSETS = ['runtime.cli', 'runtime.lib', 'runtime.registry'];
317
318
 
318
- function sha256File(file: string, fsImpl: any): string {
319
+ function sha256File(file , fsImpl ) {
319
320
  return crypto.createHash('sha256').update(fsImpl.readFileSync(file)).digest('hex');
320
321
  }
321
322
 
322
- function isUnder(file: string, root: string): boolean {
323
+ function isUnder(file , root ) {
323
324
  return file.startsWith(`${root}${path.sep}`);
324
325
  }
325
326
 
326
327
  /** Replace some of a harness plan's changes and append others, keeping `requiredNativeResources`
327
328
  * in step: the same array when it is one (as markRetainedRemovals does), else with the added
328
329
  * changes listed, so a plan holding only those is still applicable (codex lists its own). */
329
- function withChanges(target: any, replaced: Map<any, any>, added: any[], notices: string[]) {
330
+ function withChanges(target , replaced , added , notices ) {
330
331
  if (!replaced.size && !added.length && !notices.length) return target;
331
- const changes = [...target.changes.map((change: any) => replaced.get(change) ?? change), ...added];
332
+ const changes = [...target.changes.map((change ) => replaced.get(change) ?? change), ...added];
332
333
  const requiredNativeResources = target.requiredNativeResources === target.changes ? changes
333
- : [...target.requiredNativeResources, ...added.map((change: any) => ({ harness: change.harness, component: 'runtimePrune', target: change.target, operation: change.operation, identity: change.identity }))];
334
+ : [...target.requiredNativeResources, ...added.map((change ) => ({ harness: change.harness, component: 'runtimePrune', target: change.target, operation: change.operation, identity: change.identity }))];
334
335
  return { ...target, changes, requiredNativeResources, notices: [...target.notices, ...notices] };
335
336
  }
336
337
 
@@ -353,92 +354,92 @@ function withChanges(target: any, replaced: Map<any, any>, added: any[], notices
353
354
  * removeTree re-hashes every file just before deleting it, as for any removal, and for the removals
354
355
  * this pass plans (`pruneGuard`) re-runs the containment and same-file checks there too.
355
356
  */
356
- function pruneRetiredRuntimeFiles(harnessPlans: any[], ledger: any, { scopeRoot, repoRoot, fsImpl = fs }: any) {
357
- const planned = harnessPlans.filter((target: any) => !target.skipped);
357
+ function pruneRetiredRuntimeFiles(harnessPlans , ledger , { scopeRoot, repoRoot, fsImpl = fs } ) {
358
+ const planned = harnessPlans.filter((target ) => !target.skipped);
358
359
  if (!planned.length || !repoRoot) return harnessPlans;
359
- const replaced = new Map<string, Map<any, any>>(planned.map((target: any) => [target.harness, new Map()]));
360
- const added = new Map<string, any[]>(planned.map((target: any) => [target.harness, []]));
361
- const notices = new Map<string, string[]>(planned.map((target: any) => [target.harness, []]));
362
- const rows = (ledger?.resources ?? []).filter((row: any) => typeof row?.target === 'string');
363
- const recorded = new Set(rows.map((row: any) => row.target));
364
- const shown = (file: string) => path.relative(scopeRoot, file);
360
+ const replaced = new Map (planned.map((target ) => [target.harness, new Map()]));
361
+ const added = new Map (planned.map((target ) => [target.harness, []]));
362
+ const notices = new Map (planned.map((target ) => [target.harness, []]));
363
+ const rows = (ledger?.resources ?? []).filter((row ) => typeof row?.target === 'string');
364
+ const recorded = new Set(rows.map((row ) => row.target));
365
+ const shown = (file ) => path.relative(scopeRoot, file);
365
366
 
366
367
  for (const assetId of RUNTIME_TREE_ASSETS) {
367
- const executor = planned.find((target: any) => target.assets.some((asset: any) => asset.id === assetId));
368
+ const executor = planned.find((target ) => target.assets.some((asset ) => asset.id === assetId));
368
369
  if (!executor) continue;
369
- const asset = executor.assets.find((item: any) => item.id === assetId);
370
+ const asset = executor.assets.find((item ) => item.id === assetId);
370
371
  const nativeDir = typeof asset.nativeDir === 'string' ? asset.nativeDir : asset.nativeDir?.[executor.harness];
371
372
  const destDir = sharedTreeDestDir(scopeRoot, nativeDir);
372
373
  const sourceDir = path.resolve(repoRoot, asset.source);
373
374
  if (!destDir || !fsImpl.existsSync(sourceDir) || !fsImpl.existsSync(destDir)) continue;
374
- const produced = discoverTree({ sourceDir, destDir, fsImpl }).files.map((file: any) => file.destAbs);
375
+ const produced = discoverTree({ sourceDir, destDir, fsImpl }).files.map((file ) => file.destAbs);
375
376
  const producedSet = new Set(produced);
376
377
  const sameFile = sameFileIndex(produced, fsImpl);
377
378
  const realRoot = fsImpl.realpathSync(destDir);
378
379
 
379
- const rowsAt = new Map<string, any[]>();
380
+ const rowsAt = new Map ();
380
381
  for (const row of rows) {
381
382
  if (!isUnder(row.target, destDir) || producedSet.has(row.target)) continue;
382
383
  if (!rowsAt.has(row.target)) rowsAt.set(row.target, []);
383
- rowsAt.get(row.target)!.push(row);
384
+ rowsAt.get(row.target) .push(row);
384
385
  }
385
- const removalsAt = new Map<string, any[]>();
386
+ const removalsAt = new Map ();
386
387
  for (const target of planned) {
387
388
  for (const change of target.changes) {
388
389
  if (change.operation !== 'remove' || change.assetId !== assetId || !isUnder(change.target, destDir) || producedSet.has(change.target)) continue;
389
390
  if (!removalsAt.has(change.target)) removalsAt.set(change.target, []);
390
- removalsAt.get(change.target)!.push(change);
391
+ removalsAt.get(change.target) .push(change);
391
392
  }
392
393
  }
393
394
  /** Turn every planned removal of `file` into a release that deletes nothing. */
394
- const keepFile = (file: string) => {
395
+ const keepFile = (file ) => {
395
396
  for (const change of removalsAt.get(file) ?? []) {
396
- if (!change.retained) replaced.get(change.harness)!.set(change, Object.freeze({ ...change, retained: true, retainedFor: [] }));
397
+ if (!change.retained) replaced.get(change.harness) .set(change, Object.freeze({ ...change, retained: true, retainedFor: [] }));
397
398
  }
398
399
  };
399
400
 
400
401
  for (const file of new Set([...rowsAt.keys(), ...removalsAt.keys()])) {
401
402
  const fileRows = rowsAt.get(file) ?? [];
402
403
  // Another asset's live row on this path is a claim this pass does not judge.
403
- if (fileRows.some((row: any) => row.assetId !== assetId)) continue;
404
+ if (fileRows.some((row ) => row.assetId !== assetId)) continue;
404
405
  const removals = removalsAt.get(file) ?? [];
405
406
  const identity = path.relative(destDir, file);
406
407
  // The rows no planned removal of this file releases on its own.
407
- const superseded = fileRows.filter((row: any) => !removals.some((change: any) => change.harness === row.harness)).map((row: any) => ownershipKey(row));
408
- const synthetic = (extra: any) => Object.freeze({
408
+ const superseded = fileRows.filter((row ) => !removals.some((change ) => change.harness === row.harness)).map((row ) => ownershipKey(row));
409
+ const synthetic = (extra ) => Object.freeze({
409
410
  harness: executor.harness, assetId, target: file, operation: 'remove', kind: 'copy-tree-file', identity,
410
411
  ownershipIdentity: `doflow:${executor.harness}:copy-tree:${assetId}:${identity}`, projection: { renderer: 'copy-tree' }, ...extra,
411
412
  });
412
413
 
413
414
  if (!fsImpl.existsSync(file)) {
414
415
  // Already gone (an interrupted run, a user's delete): the rows only point at nothing.
415
- if (fileRows.length) added.get(executor.harness)!.push(synthetic({ fingerprint: null, retained: true, retainedFor: [], supersedes: superseded }));
416
+ if (fileRows.length) added.get(executor.harness) .push(synthetic({ fingerprint: null, retained: true, retainedFor: [], supersedes: superseded }));
416
417
  continue;
417
418
  }
418
419
  const real = fsImpl.realpathSync(file);
419
420
  if (!isUnder(real, realRoot)) {
420
421
  keepFile(file);
421
- notices.get(executor.harness)!.push(`kept ${shown(file)}: no longer shipped, but it resolves outside ${shown(destDir)}`);
422
+ notices.get(executor.harness) .push(`kept ${shown(file)}: no longer shipped, but it resolves outside ${shown(destDir)}`);
422
423
  continue;
423
424
  }
424
425
  if (sameFile(file)) { keepFile(file); continue; } // the produced file under an old spelling
425
426
  const current = sha256File(file, fsImpl);
426
- if (!fileRows.some((row: any) => row.fingerprint === current)) {
427
+ if (!fileRows.some((row ) => row.fingerprint === current)) {
427
428
  // A planned removal here was already kept by planTree, and its adapter named it.
428
- if (!removals.length) notices.get(executor.harness)!.push(`kept ${shown(file)}: no longer shipped, changed since DoFlow wrote it`);
429
+ if (!removals.length) notices.get(executor.harness) .push(`kept ${shown(file)}: no longer shipped, changed since DoFlow wrote it`);
429
430
  continue;
430
431
  }
431
- notices.get(executor.harness)!.push(`prune ${shown(file)}: no longer shipped, unchanged since DoFlow wrote it`);
432
+ notices.get(executor.harness) .push(`prune ${shown(file)}: no longer shipped, unchanged since DoFlow wrote it`);
432
433
  // removeTree re-runs containment and the same-file check from this right before the unlink.
433
434
  const pruneGuard = { root: destDir, source: sourceDir };
434
435
  if (!removals.length) {
435
- added.get(executor.harness)!.push(synthetic({ fingerprint: current, supersedes: superseded, pruneGuard }));
436
+ added.get(executor.harness) .push(synthetic({ fingerprint: current, supersedes: superseded, pruneGuard }));
436
437
  continue;
437
438
  }
438
- removals.forEach((change: any, index: number) => {
439
+ removals.forEach((change , index ) => {
439
440
  const { retained, retainedFor, ...rest } = change;
440
441
  void retained; void retainedFor;
441
- replaced.get(change.harness)!.set(change, Object.freeze({ ...rest, fingerprint: current, pruneGuard, ...(index === 0 ? { supersedes: superseded } : {}) }));
442
+ replaced.get(change.harness) .set(change, Object.freeze({ ...rest, fingerprint: current, pruneGuard, ...(index === 0 ? { supersedes: superseded } : {}) }));
442
443
  });
443
444
  }
444
445
 
@@ -446,14 +447,26 @@ function pruneRetiredRuntimeFiles(harnessPlans: any[], ledger: any, { scopeRoot,
446
447
  for (const rel of walkRelFiles(destDir, fsImpl)) {
447
448
  const file = path.join(destDir, rel);
448
449
  if (producedSet.has(file) || recorded.has(file) || sameFile(file)) continue;
449
- notices.get(executor.harness)!.push(`kept ${shown(file)}: not shipped by this version and not recorded as DoFlow's`);
450
+ notices.get(executor.harness) .push(`kept ${shown(file)}: not shipped by this version and not recorded as DoFlow's`);
450
451
  }
451
452
  }
452
- return harnessPlans.map((target: any) => (target.skipped ? target
453
- : withChanges(target, replaced.get(target.harness)!, added.get(target.harness)!, notices.get(target.harness)!)));
453
+ return harnessPlans.map((target ) => (target.skipped ? target
454
+ : withChanges(target, replaced.get(target.harness) , added.get(target.harness) , notices.get(target.harness) )));
454
455
  }
455
456
 
456
- function planLifecycle({ registry, adapters, scope, scopeRoot, targets, mcpSelections = {}, mcpAdoptable = {}, retainedMcpIds = [], ledger, context = {} }: any) {
457
+ /** One harness's plan entry, from what its adapter returned: the changes checked against the
458
+ * harness's assets, and the conflicts, prerequisites and notices in the lifecycle's common shape. */
459
+ function targetFromResult({ harness, assets, mcp, mcpSelected, policies, adapterInput, discovery, result } ) {
460
+ if (!result || typeof result !== 'object') throw new Error(`Adapter '${harness.id}' returned an invalid plan`);
461
+ const changes = (result.changes || []).map((change ) => normalizeChange(change, { harness, assets }));
462
+ const conflicts = adapterConflicts(result, harness);
463
+ const prerequisites = [...(result.prerequisites || []), ...changes.map((change ) => change.prerequisite).filter(Boolean)];
464
+ const requiredNativeResources = result.requiredNativeResources ?? changes;
465
+ if (!Array.isArray(requiredNativeResources)) throw new Error(`Adapter '${harness.id}' returned invalid requiredNativeResources`);
466
+ return { harness: harness.id, adapter: harness.adapter, assets, mcp, mcpSelected, policies, adapterInput, discovery, changes, requiredNativeResources, conflicts, prerequisites, notices: adapterNotices(result), skipped: false };
467
+ }
468
+
469
+ function planLifecycle({ registry, adapters, scope, scopeRoot, targets, mcpSelections = {}, mcpAdoptable = {}, retainedMcpIds = [], ledger, context = {} } ) {
457
470
  assertScope(scope);
458
471
  if (!registry) throw new Error('registry is required');
459
472
  const baseLedger = ledger ?? defaultLedger({ scope, scopeRoot });
@@ -467,7 +480,7 @@ function planLifecycle({ registry, adapters, scope, scopeRoot, targets, mcpSelec
467
480
  const policies = renderPolicies(registry, { harness: harness.id });
468
481
  const adoptable = capable ? selectMcpServers(registry, catalogIds(registry, mcpAdoptable[harness.id] ?? [])) : [];
469
482
  const adapter = resolveAdapter(adapters, harness);
470
- const project = (mcp: any) => projectAdapterInput({ registry, harness, scope, scopeRoot, assets, mcp, mcpAdoptable: adoptable, policies, context });
483
+ const project = (mcp ) => projectAdapterInput({ registry, harness, scope, scopeRoot, assets, mcp, mcpAdoptable: adoptable, policies, context });
471
484
  // `keep` asks the adapter what it owns before the real projection: the probe carries no
472
485
  // selection, so discovery reads the files as they are.
473
486
  const ids = selection === MCP_KEEP
@@ -478,13 +491,7 @@ function planLifecycle({ registry, adapters, scope, scopeRoot, targets, mcpSelec
478
491
  const input = { ...adapterInput, registry, ledger: baseLedger };
479
492
  const discovery = adapter.discover(input);
480
493
  const result = adapter.plan({ ...input, discovery });
481
- if (!result || typeof result !== 'object') throw new Error(`Adapter '${harness.id}' returned an invalid plan`);
482
- const changes = (result.changes || []).map((change: any) => normalizeChange(change, { harness, assets }));
483
- const conflicts = adapterConflicts(result, harness);
484
- const prerequisites = [...(result.prerequisites || []), ...changes.map((change: any) => change.prerequisite).filter(Boolean)];
485
- const requiredNativeResources = result.requiredNativeResources ?? changes;
486
- if (!Array.isArray(requiredNativeResources)) throw new Error(`Adapter '${harness.id}' returned invalid requiredNativeResources`);
487
- return { harness: harness.id, adapter: harness.adapter, assets, mcp, mcpSelected: capable ? mcp.map((server: any) => server.id) : null, policies, adapterInput, discovery, changes, requiredNativeResources, conflicts, prerequisites, notices: adapterNotices(result), skipped: false };
494
+ return targetFromResult({ harness, assets, mcp, mcpSelected: capable ? mcp.map((server ) => server.id) : null, policies, adapterInput, discovery, result });
488
495
  });
489
496
  // The MCP index names every server a harness of this scope uses: the selections planned here and
490
497
  // the recorded selections of the harnesses this run does not touch.
@@ -495,18 +502,23 @@ function planLifecycle({ registry, adapters, scope, scopeRoot, targets, mcpSelec
495
502
  const retained = markRetainedRemovals(harnessPlans, baseLedger, scope);
496
503
  const annotated = context.operation === 'remove' ? retained
497
504
  : pruneRetiredRuntimeFiles(retained, baseLedger, { scopeRoot, repoRoot: context.repoRoot });
498
- const changes = annotated.flatMap((item: any) => item.changes);
499
- const conflicts = annotated.flatMap((item: any) => item.conflicts.map((reason: any) => ({ harness: item.harness, reason })));
500
- const prerequisites = annotated.flatMap((item: any) => item.prerequisites.map((prerequisite: any) => ({ harness: item.harness, prerequisite })));
501
- const requiredNativeResources = annotated.flatMap((item: any) => item.requiredNativeResources || []);
502
- const notices = annotated.flatMap((item: any) => item.notices.map((notice: any) => ({ harness: item.harness, notice })));
503
- return Object.freeze({ scope, scopeRoot, mcp: selectedMcp, ledger: baseLedger, targets: annotated, changes, requiredNativeResources, conflicts, prerequisites, notices,
505
+ // The shared hooks tree may have moved under harnesses this run does not name (IC-037): their
506
+ // front doors come along, as targets of this same plan.
507
+ const companions = planHookCompanions({ registry, adapters, scope, harnessScope: registryScope(scope), scopeRoot, ledger: baseLedger, context,
508
+ targets: annotated, targetFromResult });
509
+ const planned = [...annotated, ...companions];
510
+ const changes = planned.flatMap((item ) => item.changes);
511
+ const conflicts = planned.flatMap((item ) => item.conflicts.map((reason ) => ({ harness: item.harness, reason })));
512
+ const prerequisites = planned.flatMap((item ) => item.prerequisites.map((prerequisite ) => ({ harness: item.harness, prerequisite })));
513
+ const requiredNativeResources = planned.flatMap((item ) => item.requiredNativeResources || []);
514
+ const notices = planned.flatMap((item ) => item.notices.map((notice ) => ({ harness: item.harness, notice })));
515
+ return Object.freeze({ scope, scopeRoot, mcp: selectedMcp, ledger: baseLedger, targets: planned, changes, requiredNativeResources, conflicts, prerequisites, notices,
504
516
  retained: retainedChanges(changes), safe: conflicts.length === 0 && prerequisites.length === 0 });
505
517
  }
506
518
 
507
- function verificationResources(verification: any, changes: any, scope: any, recoveryRef: any) {
508
- if (verification.resources.length) return verification.resources.map((resource: any) => ({ ...resource, harness: verification.harness, scope, recoveryRef: resource.recoveryRef ?? recoveryRef }));
509
- return changes.filter((change: any) => change.operation !== 'remove').map((change: any) => ({
519
+ function verificationResources(verification , changes , scope , recoveryRef ) {
520
+ if (verification.resources.length) return verification.resources.map((resource ) => ({ ...resource, harness: verification.harness, scope, recoveryRef: resource.recoveryRef ?? recoveryRef }));
521
+ return changes.filter((change ) => change.operation !== 'remove').map((change ) => ({
510
522
  harness: change.harness, scope, assetId: change.assetId, target: change.target,
511
523
  ownershipIdentity: change.ownershipIdentity, fingerprint: change.afterFingerprint ?? null,
512
524
  sourceVersion: change.sourceVersion ?? 'unknown', projection: change.projection ?? null, recoveryRef,
@@ -516,7 +528,7 @@ function verificationResources(verification: any, changes: any, scope: any, reco
516
528
  /** The guidance-content layer's own version marker (core/shared/guidance/VERSION, copy-treed
517
529
  * to <scopeRoot>/.doflow/guidance/VERSION) is independent of this package's version — read it
518
530
  * fresh on every ledger update so the ledger reflects whatever is actually on disk. */
519
- function readGuidanceVersion(scopeRoot: any, fsImpl = fs) {
531
+ function readGuidanceVersion(scopeRoot , fsImpl = fs) {
520
532
  const file = path.join(scopeRoot, '.doflow', 'guidance', 'VERSION');
521
533
  if (!fsImpl.existsSync(file)) return null;
522
534
  return fsImpl.readFileSync(file, 'utf8').trim();
@@ -532,7 +544,7 @@ function readGuidanceVersion(scopeRoot: any, fsImpl = fs) {
532
544
  * same anchor DOFLOW_CORE.md's own @-imports use. Writing this file into a subdirectory would
533
545
  * silently reinterpret every one of those paths against that subdirectory and break them all,
534
546
  * with no error at install time. test/mcp-index.test.js pins the anchor from both ends. */
535
- function mcpIndexPath(scopeRoot: any) {
547
+ function mcpIndexPath(scopeRoot ) {
536
548
  return path.join(scopeRoot, '.doflow', 'guidance', 'MCP_INDEX.md');
537
549
  }
538
550
 
@@ -547,7 +559,7 @@ function mcpIndexPath(scopeRoot: any) {
547
559
  * shared runtime, in the one asset that is generated rather than ledger-tracked and so cannot be
548
560
  * protected by ownership counting. The remaining harnesses' MCP selection has not changed, so the
549
561
  * file is left exactly as it is rather than re-rendered from this removal's empty selection. */
550
- function applyMcpIndex({ scopeRoot, selectedMcp, mode, retain = false, fsImpl = fs }: any) {
562
+ function applyMcpIndex({ scopeRoot, selectedMcp, mode, retain = false, fsImpl = fs } ) {
551
563
  const file = mcpIndexPath(scopeRoot);
552
564
  const content = mode === 'remove' ? null : renderMcpIndex(selectedMcp);
553
565
  if (content === null) {
@@ -559,7 +571,7 @@ function applyMcpIndex({ scopeRoot, selectedMcp, mode, retain = false, fsImpl =
559
571
  fsImpl.writeFileSync(file, content);
560
572
  }
561
573
 
562
- function updateLedger({ ledger, scope, scopeRoot, verifications, changes, recoveryRef, fsImpl = fs }: any) {
574
+ function updateLedger({ ledger, scope, scopeRoot, verifications, changes, recoveryRef, fsImpl = fs } ) {
563
575
  // upgradeLedger makes every written ledger v2-shaped (tombstone log present) while v1 inputs
564
576
  // stay readable — migration happens on write, never as a separate user-facing step.
565
577
  const next = JSON.parse(JSON.stringify(upgradeLedger(ledger ?? defaultLedger({ scope, scopeRoot }))));
@@ -571,13 +583,13 @@ function updateLedger({ ledger, scope, scopeRoot, verifications, changes, recove
571
583
  priorByIdentity.set(`${resource.harness}\u0000${resource.assetId}\u0000${resource.identity ?? ''}`, resource);
572
584
  }
573
585
  for (const verification of verifications) {
574
- const related = changes.filter((change: any) => change.harness === verification.harness);
575
- const removed = related.filter((change: any) => change.operation === 'remove');
576
- const removeKeys = new Set(removed.map((change: any) => ownershipKey({ harness: change.harness, scope, assetId: change.assetId, target: change.target, ownershipIdentity: change.ownershipIdentity })));
586
+ const related = changes.filter((change ) => change.harness === verification.harness);
587
+ const removed = related.filter((change ) => change.operation === 'remove');
588
+ const removeKeys = new Set(removed.map((change ) => ownershipKey({ harness: change.harness, scope, assetId: change.assetId, target: change.target, ownershipIdentity: change.ownershipIdentity })));
577
589
  // A runtime prune also gives up the rows other harnesses hold on the file it deletes
578
590
  // (pruneRetiredRuntimeFiles): released here, in the same write, so no row outlives its file.
579
591
  for (const change of removed) for (const key of change.supersedes ?? []) removeKeys.add(key);
580
- next.resources = next.resources.filter((resource: any) => !removeKeys.has(ownershipKey(resource)));
592
+ next.resources = next.resources.filter((resource ) => !removeKeys.has(ownershipKey(resource)));
581
593
  for (const resource of verificationResources(verification, related, scope, recoveryRef)) {
582
594
  const key = ownershipKey(resource);
583
595
  // A verifier enumerates what is on disk, and a released-but-retained file is deliberately
@@ -590,9 +602,9 @@ function updateLedger({ ledger, scope, scopeRoot, verifications, changes, recove
590
602
  const prior = priorByIdentity.get(`${resource.harness}\u0000${resource.assetId}\u0000${resource.identity ?? ''}`);
591
603
  if (prior && prior.target !== resource.target) {
592
604
  const supersededKey = ownershipKey(prior);
593
- next.resources = next.resources.filter((item: any) => ownershipKey(item) !== supersededKey);
605
+ next.resources = next.resources.filter((item ) => ownershipKey(item) !== supersededKey);
594
606
  }
595
- const index = next.resources.findIndex((item: any) => ownershipKey(item) === key);
607
+ const index = next.resources.findIndex((item ) => ownershipKey(item) === key);
596
608
  if (index >= 0) next.resources[index] = resource;
597
609
  else next.resources.push(resource);
598
610
  }
@@ -605,7 +617,7 @@ function updateLedger({ ledger, scope, scopeRoot, verifications, changes, recove
605
617
  for (const resource of next.resources) {
606
618
  const prior = priorByIdentity.get(`${resource.harness}\u0000${resource.assetId}\u0000${resource.identity ?? ''}`);
607
619
  if (!prior || prior.target === resource.target) continue;
608
- if (!next.tombstones.some((entry: any) => entry.harness === resource.harness
620
+ if (!next.tombstones.some((entry ) => entry.harness === resource.harness
609
621
  && entry.fromTarget === prior.target && entry.toTarget === resource.target)) {
610
622
  next.tombstones.push({
611
623
  harness: resource.harness, assetId: resource.assetId,
@@ -614,7 +626,7 @@ function updateLedger({ ledger, scope, scopeRoot, verifications, changes, recove
614
626
  });
615
627
  }
616
628
  }
617
- const claims = new Set(next.resources.map((resource: any) => resource.target));
629
+ const claims = new Set(next.resources.map((resource ) => resource.target));
618
630
  for (const entry of next.tombstones) {
619
631
  if (entry.sweptAt || !fsImpl.existsSync(entry.fromTarget) || claims.has(entry.fromTarget)) continue;
620
632
  try {
@@ -632,7 +644,7 @@ function updateLedger({ ledger, scope, scopeRoot, verifications, changes, recove
632
644
  return next;
633
645
  }
634
646
 
635
- function verifyLifecycle({ plan, adapters, context = {} }: any) {
647
+ function verifyLifecycle({ plan, adapters, context = {} } ) {
636
648
  const verifications = [];
637
649
  for (const target of plan.targets) {
638
650
  if (target.skipped) continue;
@@ -642,19 +654,21 @@ function verifyLifecycle({ plan, adapters, context = {} }: any) {
642
654
  // After an apply, the files this harness just wrote: a copy-tree verifier reports one that is
643
655
  // missing instead of skipping it like a file the user deleted.
644
656
  const plannedWrites = context.checkPlannedWrites
645
- ? target.changes.filter((change: any) => !change.retained && (change.operation === 'create' || change.operation === 'update')).map((change: any) => change.target)
657
+ ? target.changes.filter((change ) => !change.retained && (change.operation === 'create' || change.operation === 'update')).map((change ) => change.target)
646
658
  : undefined;
647
- let verification = normalizeVerification(adapter.verify({ ...target.adapterInput,
659
+ // A hook-front-door companion (IC-037) is verified for its doors alone: the rest of that
660
+ // harness is not part of this run and may legitimately trail the current source.
661
+ let verification = normalizeVerification(adapter[target.hookDoorsOnly ? 'verifyHookDoors' : 'verify']({ ...target.adapterInput,
648
662
  registry: context.registry ?? plan.registry, discovery: target.discovery, ledger: plan.ledger,
649
663
  operation, context: { ...target.adapterInput.context, operation, ...(plannedWrites ? { plannedWrites } : {}) } }), harness);
650
664
  if (operation === 'remove') verification = normalizeRemovalVerification(verification, target.changes, plan.ledger);
651
665
  verifications.push({ ...verification, hookWiring: hookWiringStatus(harness, target, verification) });
652
666
  }
653
- const conflicts = verifications.flatMap((result) => (result.conflicts || []).map((reason: any) => ({ harness: result.harness, reason })));
667
+ const conflicts = verifications.flatMap((result) => (result.conflicts || []).map((reason ) => ({ harness: result.harness, reason })));
654
668
  return { verifications, conflicts, ok: conflicts.length === 0 && verifications.every((result) => result.ok !== false) };
655
669
  }
656
670
 
657
- function assertPlanApplicable(plan: any, stateRoot: any, acceptPrerequisites: any) {
671
+ function assertPlanApplicable(plan , stateRoot , acceptPrerequisites ) {
658
672
  if (!plan.safe && !(acceptPrerequisites && plan.conflicts.length === 0)) throw new Error('Refusing to apply an unsafe lifecycle plan');
659
673
  if (!stateRoot) throw new Error('stateRoot is required before lifecycle mutation');
660
674
  if (!plan.requiredNativeResources?.length || !plan.changes.length) throw new Error('Refusing to apply a lifecycle plan with no required native resources');
@@ -664,35 +678,35 @@ function assertPlanApplicable(plan: any, stateRoot: any, acceptPrerequisites: an
664
678
  * plan selected — currently only claude.hooks-scripts and kiro.hooks-scripts. Codex and Gemini
665
679
  * deploy hooks via their own bespoke code paths (see targetNeedsHooks below) rather than a
666
680
  * registered asset, so they never appear here. */
667
- function hookAssetIds(target: any) {
668
- return new Set((target.assets || []).filter((asset: any) => asset.kind === 'hooks').map((asset: any) => asset.id));
681
+ function hookAssetIds(target ) {
682
+ return new Set((target.assets || []).filter((asset ) => asset.kind === 'hooks').map((asset ) => asset.id));
669
683
  }
670
684
 
671
685
  /** Whether this harness's plan includes at least one hooks-bearing change. Claude's hooks are a
672
686
  * registry-declared asset (`kind: "hooks"` in core/registry/assets.json); Codex and Gemini deploy
673
687
  * hooks via their own bespoke code paths and tag the resulting change with `nativeComponent:
674
- * 'hooks'` instead (see src/adapters/codex/index.ts and src/adapters/gemini/index.ts). Either
688
+ * 'hooks'` instead (see src/adapters/codex/index.js and src/adapters/gemini/index.js). Either
675
689
  * signal is sufficient — this stays harness-agnostic on purpose. */
676
- function targetNeedsHooks(target: any) {
690
+ function targetNeedsHooks(target ) {
677
691
  const hookIds = hookAssetIds(target);
678
- return (target.changes || []).some((change: any) => hookIds.has(change.assetId) || change.nativeComponent === 'hooks');
692
+ return (target.changes || []).some((change ) => hookIds.has(change.assetId) || change.nativeComponent === 'hooks');
679
693
  }
680
694
 
681
695
  /** Whether a verified resource is the hooks resource this harness owns. A registry-declared hooks
682
696
  * asset (Claude, Kiro) is matched by assetId; Codex's bespoke hooks-file resource is matched by
683
697
  * `kind`; Gemini's bespoke hooks resource (a key inside settings.json, not a standalone file) is
684
- * matched by its `projection.renderer` tag — see src/adapters/codex/index.ts's
685
- * `lifecycleResource(..., kind: 'hooks-file', ...)` and src/adapters/gemini/index.ts's verify(),
698
+ * matched by its `projection.renderer` tag — see src/adapters/codex/index.js's
699
+ * `lifecycleResource(..., kind: 'hooks-file', ...)` and src/adapters/gemini/index.js's verify(),
686
700
  * which tags its hooks resource `projection: { renderer: 'gemini-hooks' }`. */
687
- function verificationOwnsHooks(verification: any, hookIds: any) {
688
- return (verification?.resources || []).some((resource: any) =>
701
+ function verificationOwnsHooks(verification , hookIds ) {
702
+ return (verification?.resources || []).some((resource ) =>
689
703
  hookIds.has(resource.assetId) || resource.kind === 'hooks-file' || resource.projection?.renderer === 'gemini-hooks');
690
704
  }
691
705
 
692
706
  /** Gemini's hooks trust, observed through the adapter's own API. How trust is computed — the
693
707
  * fingerprint semantics, which context fields matter — lives in src/adapters/gemini (review A3:
694
708
  * this file used to import planGeminiHooks and re-derive the plan itself). */
695
- function geminiHookTrust(target: any) {
709
+ function geminiHookTrust(target ) {
696
710
  return geminiHookTrustFor(target);
697
711
  }
698
712
 
@@ -706,7 +720,7 @@ function geminiHookTrust(target: any) {
706
720
  * trust computation via geminiHookTrust above.
707
721
  * - 'active': installed with no unmet prerequisite (Claude and Kiro today, since neither declares
708
722
  * any `capabilities.hooks.prerequisites` and Kiro hooks activate automatically per kiro.dev). */
709
- function hookWiringStatus(harness: any, target: any, verification: any) {
723
+ function hookWiringStatus(harness , target , verification ) {
710
724
  if (!target || target.skipped) return { status: 'absent', prerequisites: [] };
711
725
  if (!verificationOwnsHooks(verification, hookAssetIds(target))) return { status: 'absent', prerequisites: [] };
712
726
  if (harness.id === 'gemini') {
@@ -725,15 +739,15 @@ function hookWiringStatus(harness: any, target: any, verification: any) {
725
739
  * silently at runtime. Only harnesses whose selected changes actually include hooks trigger
726
740
  * this — a plan touching only guidance/skills assets never invokes the check. Removal is
727
741
  * exempt: deleting hook files does not require running them. */
728
- function assertBashAvailableForHooks(plan: any, mode: any, hasBashCapableShellFn: any) {
742
+ function assertBashAvailableForHooks(plan , mode , hasBashCapableShellFn ) {
729
743
  if (mode === 'remove') return;
730
- const hookHarnesses = plan.targets.filter((target: any) => !target.skipped && targetNeedsHooks(target)).map((target: any) => target.harness);
744
+ const hookHarnesses = plan.targets.filter((target ) => !target.skipped && targetNeedsHooks(target)).map((target ) => target.harness);
731
745
  if (hookHarnesses.length === 0) return;
732
746
  if (hasBashCapableShellFn()) return;
733
747
  throw new Error(`No bash-capable shell detected (checked: bash --version). Install Git Bash for Windows, or run inside WSL, before installing hooks for ${hookHarnesses.join(', ')}.`);
734
748
  }
735
749
 
736
- function applyLifecycle({ plan, registry, adapters, stateRoot, ledger = plan.ledger, context = {}, mode = 'apply', acceptPrerequisites = false, writeLedgerFn = writeLedger, writeRecoveryRecordFn = writeRecoveryRecord, hasBashCapableShellFn = hasBashCapableShell }: any) {
750
+ function applyLifecycle({ plan, registry, adapters, stateRoot, ledger = plan.ledger, context = {}, mode = 'apply', acceptPrerequisites = false, writeLedgerFn = writeLedger, writeRecoveryRecordFn = writeRecoveryRecord, hasBashCapableShellFn = hasBashCapableShell } ) {
737
751
  assertPlanApplicable(plan, stateRoot, acceptPrerequisites);
738
752
  assertBashAvailableForHooks(plan, mode, hasBashCapableShellFn);
739
753
  const recovery = writeRecoveryRecordFn(stateRoot, { status: 'pending', changes: plan.changes });
@@ -745,14 +759,14 @@ function applyLifecycle({ plan, registry, adapters, stateRoot, ledger = plan.led
745
759
  // it, because every adapter's job with a removal change is to delete the thing. Filtered
746
760
  // here rather than in planLifecycle so the change still reaches updateLedger, which is what
747
761
  // actually drops this harness's ownership row.
748
- const applicable = target.changes.filter((change: any) => !change.retained);
762
+ const applicable = target.changes.filter((change ) => !change.retained);
749
763
  if (!applicable.length) continue;
750
764
  const harness = harnessFor(registry, target.harness);
751
765
  const adapter = resolveAdapter(adapters, harness);
752
766
  adapter[mode === 'remove' ? 'remove' : 'apply']({ ...target.adapterInput, registry, changes: applicable, ledger, recoveryRef: recovery.id });
753
767
  completedHarnesses.push(target.harness);
754
768
  }
755
- } catch (error: any) {
769
+ } catch (error ) {
756
770
  // A mid-loop throw (fs error, TOCTOU ownership mismatch) leaves some harnesses' native writes
757
771
  // already on disk. Recording exactly which ones completed — rather than leaving the record
758
772
  // stuck at 'pending' forever — is the difference between a diagnosable partial failure and a
@@ -768,7 +782,7 @@ function applyLifecycle({ plan, registry, adapters, stateRoot, ledger = plan.led
768
782
  }
769
783
  try {
770
784
  applyMcpIndex({ scopeRoot: plan.scopeRoot, selectedMcp: plan.mcp, mode, retain: !removalIsTotal(plan) });
771
- } catch (error: any) {
785
+ } catch (error ) {
772
786
  // Same reasoning as the harness-loop catch above: every native resource already applied and
773
787
  // verified successfully by this point, so leaving the record at 'pending' on a failure here
774
788
  // would hide that success and make a retry indistinguishable from a from-scratch install.
@@ -789,14 +803,14 @@ function applyLifecycle({ plan, registry, adapters, stateRoot, ledger = plan.led
789
803
  * selects. Verify only reads; this writes the ledger and nothing native. `dryRun` reports without
790
804
  * writing. Returns the ledger and the server ids recorded per harness.
791
805
  */
792
- function recordMcpOwnership({ plan, registry, adapters, stateRoot, ledger = plan.ledger, dryRun = false, writeLedgerFn = writeLedger }: any) {
806
+ function recordMcpOwnership({ plan, registry, adapters, stateRoot, ledger = plan.ledger, dryRun = false, writeLedgerFn = writeLedger } ) {
793
807
  const { verifications } = verifyLifecycle({ plan: { ...plan, registry }, adapters, context: { registry, operation: 'apply' } });
794
- const held = new Set((ledger.resources || []).map((resource: any) => ownershipKey(resource)));
808
+ const held = new Set((ledger.resources || []).map((resource ) => ownershipKey(resource)));
795
809
  const added = verifications.flatMap((verification) => (verification.resources || [])
796
- .filter((resource: any) => resource.kind === 'mcp-server')
797
- .map((resource: any) => ({ ...resource, harness: verification.harness, scope: plan.scope, recoveryRef: resource.recoveryRef ?? null })))
810
+ .filter((resource ) => resource.kind === 'mcp-server')
811
+ .map((resource ) => ({ ...resource, harness: verification.harness, scope: plan.scope, recoveryRef: resource.recoveryRef ?? null })))
798
812
  .filter((resource) => !held.has(ownershipKey(resource)));
799
- const recorded: Record<string, any[]> = {};
813
+ const recorded = {};
800
814
  for (const resource of added) (recorded[resource.harness] ??= []).push(resource.identity);
801
815
  if (!added.length || dryRun) return { ledger, recorded };
802
816
  const next = upgradeLedger({ ...ledger, resources: [...(ledger.resources || []), ...added] });
@@ -804,7 +818,7 @@ function recordMcpOwnership({ plan, registry, adapters, stateRoot, ledger = plan
804
818
  return { ledger: next, recorded };
805
819
  }
806
820
 
807
- function removeLifecycle(options: any) {
821
+ function removeLifecycle(options ) {
808
822
  const plan = planLifecycle({ ...options, context: { ...(options.context || {}), operation: 'remove' } });
809
823
  return applyLifecycle({ ...options, plan, mode: 'remove', acceptPrerequisites: options.acceptPrerequisites });
810
824
  }