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

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