@open-agent-toolkit/cli 0.2.51 → 0.2.53

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 (218) hide show
  1. package/assets/bundle-metadata.json +1 -1
  2. package/assets/docs/cli-utilities/bootstrap.md +6 -2
  3. package/assets/docs/cli-utilities/tool-packs.md +87 -33
  4. package/assets/docs/provider-sync/commands.md +30 -2
  5. package/assets/docs/provider-sync/config.md +19 -0
  6. package/assets/docs/provider-sync/manifest-and-drift.md +37 -0
  7. package/assets/docs/provider-sync/providers.md +69 -2
  8. package/assets/docs/provider-sync/scope-and-surface.md +44 -0
  9. package/assets/docs/reference/cli-reference.md +1 -0
  10. package/assets/docs/reference/oat-directory-structure.md +11 -4
  11. package/assets/docs/workflows/projects/evidence-layers.md +110 -1
  12. package/assets/docs/workflows/projects/implementation-execution.md +13 -0
  13. package/assets/docs/workflows/projects/orchestration-model.md +18 -0
  14. package/assets/public-package-versions.json +4 -4
  15. package/assets/skills/oat-dispatch-subagents/SKILL.md +57 -1
  16. package/assets/skills/oat-dispatch-subagents/references/record-schema.md +77 -1
  17. package/assets/skills/oat-project-autonomous/references/gate-inventory.md +1 -1
  18. package/assets/skills/oat-project-dispatch-subagents/SKILL.md +24 -1
  19. package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +1 -1
  20. package/assets/skills/oat-project-implement/references/dispatch-and-dry-run.md +11 -0
  21. package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +1 -1
  22. package/assets/skills/oat-project-plan-writing/SKILL.md +12 -1
  23. package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +1 -1
  24. package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +1 -1
  25. package/assets/skills/oat-project-review-provide/SKILL.md +12 -1
  26. package/assets/skills/oat-project-review-provide-remote/SKILL.md +12 -1
  27. package/dist/agents/canonical/index.d.ts +2 -0
  28. package/dist/agents/canonical/index.d.ts.map +1 -1
  29. package/dist/agents/canonical/index.js +1 -0
  30. package/dist/agents/canonical/resolve.d.ts +45 -0
  31. package/dist/agents/canonical/resolve.d.ts.map +1 -0
  32. package/dist/agents/canonical/resolve.js +175 -0
  33. package/dist/commands/decision/agents-guidance.d.ts +2 -2
  34. package/dist/commands/decision/agents-guidance.d.ts.map +1 -1
  35. package/dist/commands/decision/agents-guidance.js +1 -4
  36. package/dist/commands/decision/index.d.ts.map +1 -1
  37. package/dist/commands/decision/index.js +25 -6
  38. package/dist/commands/docs/init/index.d.ts +7 -1
  39. package/dist/commands/docs/init/index.d.ts.map +1 -1
  40. package/dist/commands/docs/init/index.js +53 -20
  41. package/dist/commands/doctor/index.d.ts +8 -2
  42. package/dist/commands/doctor/index.d.ts.map +1 -1
  43. package/dist/commands/doctor/index.js +157 -46
  44. package/dist/commands/init/index.d.ts +8 -3
  45. package/dist/commands/init/index.d.ts.map +1 -1
  46. package/dist/commands/init/index.js +66 -28
  47. package/dist/commands/init/tools/index.d.ts +12 -12
  48. package/dist/commands/init/tools/index.d.ts.map +1 -1
  49. package/dist/commands/init/tools/index.js +430 -99
  50. package/dist/commands/init/tools/install-state.d.ts +2 -0
  51. package/dist/commands/init/tools/install-state.d.ts.map +1 -1
  52. package/dist/commands/init/tools/install-state.js +11 -0
  53. package/dist/commands/init/tools/project-guidance.d.ts +49 -0
  54. package/dist/commands/init/tools/project-guidance.d.ts.map +1 -0
  55. package/dist/commands/init/tools/project-guidance.js +144 -0
  56. package/dist/commands/init/tools/workflows/index.d.ts +7 -0
  57. package/dist/commands/init/tools/workflows/index.d.ts.map +1 -1
  58. package/dist/commands/init/tools/workflows/index.js +160 -7
  59. package/dist/commands/pjm/index.d.ts.map +1 -1
  60. package/dist/commands/pjm/index.js +28 -3
  61. package/dist/commands/pjm/init.d.ts +5 -0
  62. package/dist/commands/pjm/init.d.ts.map +1 -1
  63. package/dist/commands/pjm/init.js +19 -3
  64. package/dist/commands/project/dispatch/index.d.ts +10 -0
  65. package/dist/commands/project/dispatch/index.d.ts.map +1 -0
  66. package/dist/commands/project/dispatch/index.js +137 -0
  67. package/dist/commands/project/dispatch/record.d.ts +91 -0
  68. package/dist/commands/project/dispatch/record.d.ts.map +1 -0
  69. package/dist/commands/project/dispatch/record.js +402 -0
  70. package/dist/commands/project/index.d.ts.map +1 -1
  71. package/dist/commands/project/index.js +2 -0
  72. package/dist/commands/providers/inspect/inspect.d.ts +8 -1
  73. package/dist/commands/providers/inspect/inspect.d.ts.map +1 -1
  74. package/dist/commands/providers/inspect/inspect.js +146 -26
  75. package/dist/commands/providers/list/list.d.ts +8 -1
  76. package/dist/commands/providers/list/list.d.ts.map +1 -1
  77. package/dist/commands/providers/list/list.js +139 -23
  78. package/dist/commands/providers/providers.types.d.ts +25 -1
  79. package/dist/commands/providers/providers.types.d.ts.map +1 -1
  80. package/dist/commands/providers/set/index.d.ts +9 -1
  81. package/dist/commands/providers/set/index.d.ts.map +1 -1
  82. package/dist/commands/providers/set/index.js +43 -33
  83. package/dist/commands/remove/skill/remove-skill.d.ts.map +1 -1
  84. package/dist/commands/remove/skill/remove-skill.js +11 -0
  85. package/dist/commands/shared/agents-md.d.ts +42 -14
  86. package/dist/commands/shared/agents-md.d.ts.map +1 -1
  87. package/dist/commands/shared/agents-md.js +364 -70
  88. package/dist/commands/status/index.d.ts +8 -3
  89. package/dist/commands/status/index.d.ts.map +1 -1
  90. package/dist/commands/status/index.js +149 -37
  91. package/dist/commands/sync/apply.d.ts.map +1 -1
  92. package/dist/commands/sync/apply.js +220 -29
  93. package/dist/commands/sync/dry-run.d.ts.map +1 -1
  94. package/dist/commands/sync/dry-run.js +30 -6
  95. package/dist/commands/sync/index.d.ts +17 -2
  96. package/dist/commands/sync/index.d.ts.map +1 -1
  97. package/dist/commands/sync/index.js +108 -43
  98. package/dist/commands/sync/sync.types.d.ts +39 -2
  99. package/dist/commands/sync/sync.types.d.ts.map +1 -1
  100. package/dist/commands/sync/sync.utils.d.ts +5 -1
  101. package/dist/commands/sync/sync.utils.d.ts.map +1 -1
  102. package/dist/commands/sync/sync.utils.js +73 -0
  103. package/dist/commands/tools/info/info-tool.d.ts +2 -0
  104. package/dist/commands/tools/info/info-tool.d.ts.map +1 -1
  105. package/dist/commands/tools/info/info-tool.js +34 -12
  106. package/dist/commands/tools/install/index.d.ts.map +1 -1
  107. package/dist/commands/tools/install/index.js +19 -3
  108. package/dist/commands/tools/list/list-tools.d.ts +2 -0
  109. package/dist/commands/tools/list/list-tools.d.ts.map +1 -1
  110. package/dist/commands/tools/list/list-tools.js +36 -7
  111. package/dist/commands/tools/remove/index.d.ts.map +1 -1
  112. package/dist/commands/tools/remove/index.js +150 -21
  113. package/dist/commands/tools/remove/remove-tools.d.ts +20 -0
  114. package/dist/commands/tools/remove/remove-tools.d.ts.map +1 -1
  115. package/dist/commands/tools/remove/remove-tools.js +122 -1
  116. package/dist/commands/tools/shared/format-pack-inventory.d.ts +19 -0
  117. package/dist/commands/tools/shared/format-pack-inventory.d.ts.map +1 -1
  118. package/dist/commands/tools/shared/format-pack-inventory.js +109 -0
  119. package/dist/commands/tools/shared/pack-evidence.d.ts +66 -0
  120. package/dist/commands/tools/shared/pack-evidence.d.ts.map +1 -0
  121. package/dist/commands/tools/shared/pack-evidence.js +132 -0
  122. package/dist/commands/tools/shared/pack-inventory.d.ts +1 -0
  123. package/dist/commands/tools/shared/pack-inventory.d.ts.map +1 -1
  124. package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts +44 -0
  125. package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts.map +1 -0
  126. package/dist/commands/tools/shared/pack-lifecycle-outcome.js +91 -0
  127. package/dist/commands/tools/update/index.d.ts.map +1 -1
  128. package/dist/commands/tools/update/index.js +74 -4
  129. package/dist/commands/tools/update/update-tools.d.ts +5 -0
  130. package/dist/commands/tools/update/update-tools.d.ts.map +1 -1
  131. package/dist/commands/tools/update/update-tools.js +64 -2
  132. package/dist/config/oat-config.d.ts +5 -0
  133. package/dist/config/oat-config.d.ts.map +1 -1
  134. package/dist/config/oat-config.js +17 -5
  135. package/dist/config/user-sync-config.d.ts +1 -0
  136. package/dist/config/user-sync-config.d.ts.map +1 -1
  137. package/dist/config/user-sync-config.js +8 -0
  138. package/dist/drift/detector.d.ts +2 -2
  139. package/dist/drift/detector.d.ts.map +1 -1
  140. package/dist/drift/detector.js +18 -0
  141. package/dist/drift/strays.d.ts.map +1 -1
  142. package/dist/drift/strays.js +5 -0
  143. package/dist/engine/collection-sync.d.ts +14 -0
  144. package/dist/engine/collection-sync.d.ts.map +1 -0
  145. package/dist/engine/collection-sync.js +282 -0
  146. package/dist/engine/compute-plan.d.ts +8 -1
  147. package/dist/engine/compute-plan.d.ts.map +1 -1
  148. package/dist/engine/compute-plan.js +303 -12
  149. package/dist/engine/engine.types.d.ts +61 -0
  150. package/dist/engine/engine.types.d.ts.map +1 -1
  151. package/dist/engine/execute-plan.d.ts +22 -2
  152. package/dist/engine/execute-plan.d.ts.map +1 -1
  153. package/dist/engine/execute-plan.js +459 -27
  154. package/dist/engine/index.d.ts +1 -1
  155. package/dist/engine/index.d.ts.map +1 -1
  156. package/dist/engine/index.js +1 -1
  157. package/dist/engine/provider-path-safety.d.ts +1 -0
  158. package/dist/engine/provider-path-safety.d.ts.map +1 -1
  159. package/dist/engine/provider-path-safety.js +15 -0
  160. package/dist/engine/scanner.d.ts +9 -2
  161. package/dist/engine/scanner.d.ts.map +1 -1
  162. package/dist/engine/scanner.js +58 -4
  163. package/dist/fs/io.d.ts +67 -0
  164. package/dist/fs/io.d.ts.map +1 -1
  165. package/dist/fs/io.js +536 -3
  166. package/dist/manifest/index.d.ts +2 -2
  167. package/dist/manifest/index.d.ts.map +1 -1
  168. package/dist/manifest/index.js +1 -1
  169. package/dist/manifest/manager.d.ts +2 -2
  170. package/dist/manifest/manager.d.ts.map +1 -1
  171. package/dist/manifest/manager.js +20 -3
  172. package/dist/manifest/manifest.types.d.ts +704 -13
  173. package/dist/manifest/manifest.types.d.ts.map +1 -1
  174. package/dist/manifest/manifest.types.js +131 -15
  175. package/dist/providers/codex/codec/sync-extension.d.ts +1 -1
  176. package/dist/providers/codex/codec/sync-extension.d.ts.map +1 -1
  177. package/dist/providers/codex/codec/sync-extension.js +48 -17
  178. package/dist/providers/cursor/codec/sync-extension.d.ts +1 -1
  179. package/dist/providers/cursor/codec/sync-extension.d.ts.map +1 -1
  180. package/dist/providers/cursor/codec/sync-extension.js +52 -23
  181. package/dist/providers/identity/absolute-paths.d.ts +68 -0
  182. package/dist/providers/identity/absolute-paths.d.ts.map +1 -0
  183. package/dist/providers/identity/absolute-paths.js +144 -0
  184. package/dist/providers/identity/claude-runtime-observation.d.ts +105 -0
  185. package/dist/providers/identity/claude-runtime-observation.d.ts.map +1 -0
  186. package/dist/providers/identity/claude-runtime-observation.fixtures.d.ts +40 -0
  187. package/dist/providers/identity/claude-runtime-observation.fixtures.d.ts.map +1 -0
  188. package/dist/providers/identity/claude-runtime-observation.fixtures.js +146 -0
  189. package/dist/providers/identity/claude-runtime-observation.js +229 -0
  190. package/dist/providers/identity/codex-runtime-observation.d.ts +75 -0
  191. package/dist/providers/identity/codex-runtime-observation.d.ts.map +1 -0
  192. package/dist/providers/identity/codex-runtime-observation.fixtures.d.ts +37 -0
  193. package/dist/providers/identity/codex-runtime-observation.fixtures.d.ts.map +1 -0
  194. package/dist/providers/identity/codex-runtime-observation.fixtures.js +539 -0
  195. package/dist/providers/identity/codex-runtime-observation.js +255 -0
  196. package/dist/providers/identity/generic-dispatch-record.d.ts +313 -0
  197. package/dist/providers/identity/generic-dispatch-record.d.ts.map +1 -0
  198. package/dist/providers/identity/generic-dispatch-record.js +539 -0
  199. package/dist/providers/identity/oat-dispatch-record.d.ts +1964 -0
  200. package/dist/providers/identity/oat-dispatch-record.d.ts.map +1 -0
  201. package/dist/providers/identity/oat-dispatch-record.js +691 -0
  202. package/dist/providers/identity/runtime-observation.d.ts +100 -0
  203. package/dist/providers/identity/runtime-observation.d.ts.map +1 -0
  204. package/dist/providers/identity/runtime-observation.js +169 -0
  205. package/dist/providers/shared/index.d.ts +2 -0
  206. package/dist/providers/shared/index.d.ts.map +1 -1
  207. package/dist/providers/shared/index.js +1 -0
  208. package/dist/providers/shared/materialization-extension.d.ts +12 -0
  209. package/dist/providers/shared/materialization-extension.d.ts.map +1 -1
  210. package/dist/providers/shared/registry.d.ts +69 -0
  211. package/dist/providers/shared/registry.d.ts.map +1 -0
  212. package/dist/providers/shared/registry.js +252 -0
  213. package/dist/providers/shared/restart-adviser.d.ts +26 -0
  214. package/dist/providers/shared/restart-adviser.d.ts.map +1 -0
  215. package/dist/providers/shared/restart-adviser.js +96 -0
  216. package/dist/shared/types.d.ts +4 -0
  217. package/dist/shared/types.d.ts.map +1 -1
  218. package/package.json +2 -2
@@ -1,14 +1,60 @@
1
1
  import { mkdir, rm, writeFile } from 'node:fs/promises';
2
2
  import { dirname, join, relative, resolve } from 'node:path';
3
- import { copyDirectory, copySingleFile, createSymlink } from '../fs/io.js';
3
+ import { copyDirectory, copySingleFile, copySingleFileNoClobber, createCollectionSymlinkNoClobber, createSymlink, createSymlinkNoClobber, removeCollectionSymlinkIfUnchanged, writeFileNoClobber, } from '../fs/io.js';
4
4
  import { computeContentHash, computeStringHash } from '../manifest/hash.js';
5
- import { addEntry, findEntry, removeEntry, saveManifest, } from '../manifest/manager.js';
5
+ import { saveManifest as persistManifest } from '../manifest/manager.js';
6
+ import { collectionProofMatches, projectCollectionOwnership, proveCollectionIdentity, } from './collection-sync.js';
6
7
  import { insertMarker, writeDirectorySentinel } from './markers.js';
7
8
  import { assertSafeProviderMutationPath } from './provider-path-safety.js';
8
9
  const DEFAULT_EXECUTE_SYNC_PLAN_DEPENDENCIES = {};
9
10
  function mutatesProviderPath(entry) {
10
11
  return entry.operation !== 'skip' && entry.operation !== 'detach';
11
12
  }
13
+ function operationEvidence(scope, entry, status, failure) {
14
+ return {
15
+ scope,
16
+ provider: entry.provider,
17
+ contentKind: entry.canonical.type,
18
+ asset: entry.canonical.name,
19
+ action: entry.operation,
20
+ status,
21
+ ...(failure ? { failure } : {}),
22
+ };
23
+ }
24
+ function classifyFailure(error, destinationPolicy) {
25
+ if (hasErrorCode(error, 'E_DEFERRED_DIRECTORY_TRANSITION_UNSAFE')) {
26
+ return {
27
+ status: 'failed',
28
+ failure: 'Manual recovery required: deferred collection-directory transitions are unavailable on this runtime. Disable the provider and run sync to detach OAT ownership, then verify and remove the preserved alias before managing the provider directory externally. Automatic publication requires an identity-bound, non-following primitive.',
29
+ };
30
+ }
31
+ if (destinationPolicy === 'preserve-existing' &&
32
+ hasErrorCode(error, 'EEXIST')) {
33
+ return {
34
+ status: 'failed',
35
+ failure: 'Destination appeared after collection clearance; preserved unmanaged content. Resolve the destination and retry sync.',
36
+ };
37
+ }
38
+ if (typeof error === 'object' &&
39
+ error !== null &&
40
+ 'code' in error &&
41
+ error.code === 'ENOENT') {
42
+ return {
43
+ status: 'missing',
44
+ failure: 'Canonical or provider input was missing; restore it and retry sync.',
45
+ };
46
+ }
47
+ return {
48
+ status: 'failed',
49
+ failure: 'Operation failed; inspect local verbose diagnostics and retry sync.',
50
+ };
51
+ }
52
+ function hasErrorCode(error, code) {
53
+ return (typeof error === 'object' &&
54
+ error !== null &&
55
+ 'code' in error &&
56
+ error.code === code);
57
+ }
12
58
  async function assertSafeEntryProviderPath(entry) {
13
59
  const scopeRoot = inferScopeRoot(resolve(entry.canonical.canonicalPath));
14
60
  await assertSafeProviderMutationPath(scopeRoot, entry.providerPath);
@@ -71,19 +117,41 @@ async function applyCopyMarker(entry) {
71
117
  throw error;
72
118
  }
73
119
  }
74
- async function applyEntry(planEntry, manifest) {
120
+ async function applyEntry(planEntry, manifest, destinationPolicy, copyDirectoryImpl, createSymlinkNoClobberImpl) {
75
121
  switch (planEntry.operation) {
76
122
  case 'create_symlink':
77
123
  case 'update_symlink': {
124
+ if (destinationPolicy === 'preserve-existing') {
125
+ if (!planEntry.canonical.isFile) {
126
+ throw Object.assign(new Error('Deferred collection-directory publication is unavailable on this runtime.'), { code: 'E_DEFERRED_DIRECTORY_TRANSITION_UNSAFE' });
127
+ }
128
+ await createSymlinkNoClobberImpl(planEntry.canonical.canonicalPath, planEntry.providerPath, planEntry.canonical.isFile);
129
+ const manifestEntry = await toManifestEntry(planEntry, 'symlink');
130
+ return addManifestEntry(manifest, manifestEntry);
131
+ }
78
132
  if (planEntry.operation === 'update_symlink') {
79
133
  await rm(planEntry.providerPath, { recursive: true, force: true });
80
134
  }
81
135
  const strategyUsed = await createSymlink(planEntry.canonical.canonicalPath, planEntry.providerPath, undefined, planEntry.canonical.isFile);
82
136
  const manifestEntry = await toManifestEntry(planEntry, strategyUsed);
83
- return addEntry(manifest, manifestEntry);
137
+ return addManifestEntry(manifest, manifestEntry);
84
138
  }
85
139
  case 'create_copy':
86
140
  case 'update_copy': {
141
+ if (destinationPolicy === 'preserve-existing') {
142
+ if (planEntry.canonical.isFile &&
143
+ planEntry.renderedContent !== undefined) {
144
+ await writeFileNoClobber(planEntry.providerPath, planEntry.renderedContent);
145
+ }
146
+ else if (planEntry.canonical.isFile) {
147
+ await copySingleFileNoClobber(planEntry.canonical.canonicalPath, planEntry.providerPath);
148
+ }
149
+ else {
150
+ throw Object.assign(new Error('Deferred collection-directory publication is unavailable on this runtime.'), { code: 'E_DEFERRED_DIRECTORY_TRANSITION_UNSAFE' });
151
+ }
152
+ const manifestEntry = await toManifestEntry(planEntry, 'copy');
153
+ return addManifestEntry(manifest, manifestEntry);
154
+ }
87
155
  if (planEntry.operation === 'update_copy') {
88
156
  await rm(planEntry.providerPath, { recursive: true, force: true });
89
157
  }
@@ -96,20 +164,20 @@ async function applyEntry(planEntry, manifest) {
96
164
  await copySingleFile(planEntry.canonical.canonicalPath, planEntry.providerPath);
97
165
  }
98
166
  else {
99
- await copyDirectory(planEntry.canonical.canonicalPath, planEntry.providerPath);
167
+ await copyDirectoryImpl(planEntry.canonical.canonicalPath, planEntry.providerPath);
100
168
  await applyCopyMarker(planEntry);
101
169
  }
102
170
  const manifestEntry = await toManifestEntry(planEntry, 'copy');
103
- return addEntry(manifest, manifestEntry);
171
+ return addManifestEntry(manifest, manifestEntry);
104
172
  }
105
173
  case 'remove': {
106
174
  await rm(planEntry.providerPath, { recursive: true, force: true });
107
175
  const { canonicalPath } = resolveManifestPaths(planEntry);
108
- return removeEntry(manifest, canonicalPath, planEntry.provider);
176
+ return removeManifestEntry(manifest, canonicalPath, planEntry.provider);
109
177
  }
110
178
  case 'detach': {
111
179
  const { canonicalPath } = resolveManifestPaths(planEntry);
112
- return removeEntry(manifest, canonicalPath, planEntry.provider);
180
+ return removeManifestEntry(manifest, canonicalPath, planEntry.provider);
113
181
  }
114
182
  case 'skip': {
115
183
  return manifest;
@@ -118,50 +186,414 @@ async function applyEntry(planEntry, manifest) {
118
186
  return manifest;
119
187
  }
120
188
  }
189
+ function addManifestEntry(manifest, entry) {
190
+ return {
191
+ ...manifest,
192
+ entries: [
193
+ ...manifest.entries.filter((candidate) => !(candidate.canonicalPath === entry.canonicalPath &&
194
+ candidate.provider === entry.provider)),
195
+ entry,
196
+ ],
197
+ lastUpdated: new Date().toISOString(),
198
+ };
199
+ }
200
+ function removeManifestEntry(manifest, canonicalPath, provider) {
201
+ const entries = manifest.entries.filter((entry) => !(entry.canonicalPath === canonicalPath && entry.provider === provider));
202
+ return entries.length === manifest.entries.length
203
+ ? manifest
204
+ : { ...manifest, entries, lastUpdated: new Date().toISOString() };
205
+ }
121
206
  async function ensureSkipEntryManaged(planEntry, manifest) {
122
207
  const { canonicalPath } = resolveManifestPaths(planEntry);
123
- const existing = findEntry(manifest, canonicalPath, planEntry.provider);
208
+ const existing = manifest.entries.find((entry) => entry.canonicalPath === canonicalPath &&
209
+ entry.provider === planEntry.provider);
124
210
  if (existing) {
125
211
  return manifest;
126
212
  }
127
213
  const manifestEntry = await toManifestEntry(planEntry, planEntry.strategy);
128
- return addEntry(manifest, manifestEntry);
214
+ return addManifestEntry(manifest, manifestEntry);
215
+ }
216
+ function detachCollectionOwnership(manifest, plan) {
217
+ const scopeRoot = inferScopeRoot(plan.canonicalDir);
218
+ const canonicalDir = relative(scopeRoot, plan.canonicalDir).replaceAll('\\', '/');
219
+ const providerDir = relative(scopeRoot, plan.providerDir).replaceAll('\\', '/');
220
+ const detachedIds = new Set(manifest.collections
221
+ .filter((collection) => collection.provider === plan.provider &&
222
+ collection.contentType === plan.contentType &&
223
+ collection.canonicalDir === canonicalDir &&
224
+ collection.providerDir === providerDir)
225
+ .map(({ id }) => id));
226
+ if (detachedIds.size === 0) {
227
+ return manifest;
228
+ }
229
+ return {
230
+ ...manifest,
231
+ collections: manifest.collections.filter(({ id }) => !detachedIds.has(id)),
232
+ entries: manifest.entries.filter((entry) => entry.strategy !== 'collection' ||
233
+ entry.collectionId === undefined ||
234
+ !detachedIds.has(entry.collectionId)),
235
+ lastUpdated: new Date().toISOString(),
236
+ };
237
+ }
238
+ async function executeCollectionTransaction(plans, manifest, blockedDeferredDirectoryTransitions, dependencies, manifestPath, beforeMutation) {
239
+ const passiveResults = plans
240
+ .filter(({ action }) => action === 'fallback-per-entry' || action === 'reject-collection')
241
+ .map((plan) => ({
242
+ provider: plan.provider,
243
+ contentType: plan.contentType,
244
+ action: plan.action,
245
+ ownership: plan.ownership,
246
+ status: plan.action === 'fallback-per-entry' ? 'fallback' : 'rejected',
247
+ reason: plan.reason,
248
+ }));
249
+ const actionable = plans.filter(({ action }) => action === 'create-collection-link' ||
250
+ action === 'adopt-collection-link' ||
251
+ action === 'inherit-collection');
252
+ const detachments = plans.filter(({ action }) => action === 'detach-collection');
253
+ if (actionable.length === 0 && detachments.length === 0) {
254
+ return {
255
+ manifest,
256
+ results: passiveResults,
257
+ attempted: false,
258
+ persisted: false,
259
+ };
260
+ }
261
+ const verified = new Map();
262
+ const createdCollections = [];
263
+ const removedTransitionPlans = new Set();
264
+ const absentTransitionPlans = new Set();
265
+ const blockedTransitionResults = new Map();
266
+ let nextManifest = manifest;
267
+ try {
268
+ await beforeMutation();
269
+ for (const plan of actionable) {
270
+ const current = await dependencies.proveCollectionIdentity({
271
+ root: inferScopeRoot(plan.canonicalDir),
272
+ canonicalDir: plan.canonicalDir,
273
+ providerDir: plan.providerDir,
274
+ });
275
+ if (!collectionProofMatches(plan.proof, current)) {
276
+ throw new Error('collection identity changed after planning; preserved destination');
277
+ }
278
+ verified.set(plan, current);
279
+ if (plan.action === 'create-collection-link') {
280
+ if (current.status !== 'absent') {
281
+ throw new Error('collection destination is no longer absent');
282
+ }
283
+ const created = await dependencies.createCollectionSymlinkNoClobber(plan.canonicalDir, plan.providerDir, {
284
+ scopeRoot: inferScopeRoot(plan.canonicalDir),
285
+ expectedParent: current.providerParent,
286
+ });
287
+ createdCollections.push({ plan, created });
288
+ const rescanned = await dependencies.proveCollectionIdentity({
289
+ root: inferScopeRoot(plan.canonicalDir),
290
+ canonicalDir: plan.canonicalDir,
291
+ providerDir: plan.providerDir,
292
+ });
293
+ if (rescanned.status !== 'exact-link') {
294
+ throw new Error('created collection alias did not verify as exact');
295
+ }
296
+ verified.set(plan, rescanned);
297
+ }
298
+ const proof = verified.get(plan);
299
+ if (proof?.status !== 'exact-link') {
300
+ throw new Error('collection alias exact identity is unavailable');
301
+ }
302
+ nextManifest = await projectCollectionOwnership(nextManifest, plan, proof);
303
+ }
304
+ for (const plan of detachments) {
305
+ if (!plan.transitionToPerEntry) {
306
+ nextManifest = detachCollectionOwnership(nextManifest, plan);
307
+ continue;
308
+ }
309
+ const blockTransition = (status, reason) => {
310
+ blockedTransitionResults.set(plan, {
311
+ provider: plan.provider,
312
+ contentType: plan.contentType,
313
+ action: plan.action,
314
+ ownership: plan.ownership,
315
+ status,
316
+ reason,
317
+ });
318
+ };
319
+ if (blockedDeferredDirectoryTransitions.has(`${plan.provider}::${plan.contentType}`)) {
320
+ blockTransition('rejected', `Manual recovery required: deferred collection-directory transitions are unavailable on this runtime, so collection ownership was preserved. Run "oat providers set --scope ${plan.scope} --disabled ${plan.provider}" and then "oat sync --scope ${plan.scope}" to detach OAT ownership. Then verify and remove the preserved alias before managing the provider directory externally. Automatic publication requires an identity-bound, non-following primitive.`);
321
+ continue;
322
+ }
323
+ if (plan.proof.status === 'absent') {
324
+ try {
325
+ const current = await dependencies.proveCollectionIdentity({
326
+ root: inferScopeRoot(plan.canonicalDir),
327
+ canonicalDir: plan.canonicalDir,
328
+ providerDir: plan.providerDir,
329
+ });
330
+ if (current.status !== 'absent' ||
331
+ !collectionProofMatches(plan.proof, current)) {
332
+ blockTransition('rejected', 'collection transition blocked; the manually cleared destination changed after planning, so ownership and child operations were preserved');
333
+ continue;
334
+ }
335
+ absentTransitionPlans.add(plan);
336
+ nextManifest = detachCollectionOwnership(nextManifest, plan);
337
+ }
338
+ catch {
339
+ blockTransition('rejected', 'collection transition blocked; destination absence could not be safely re-proven, so ownership and child operations were preserved');
340
+ }
341
+ continue;
342
+ }
343
+ if (plan.ownership !== 'oat-created' ||
344
+ plan.proof.status !== 'exact-link' ||
345
+ plan.createdLink === undefined) {
346
+ blockTransition('rejected', 'collection transition blocked; exact durable creation identity is unavailable, so ownership and the alias were preserved');
347
+ continue;
348
+ }
349
+ try {
350
+ const current = await dependencies.proveCollectionIdentity({
351
+ root: inferScopeRoot(plan.canonicalDir),
352
+ canonicalDir: plan.canonicalDir,
353
+ providerDir: plan.providerDir,
354
+ });
355
+ if (current.status !== 'exact-link' ||
356
+ !collectionProofMatches(plan.proof, current) ||
357
+ current.providerLink.device !== plan.createdLink.device ||
358
+ current.providerLink.inode !== plan.createdLink.inode ||
359
+ current.linkText !== plan.createdLink.linkText) {
360
+ blockTransition('rejected', 'collection transition blocked; alias identity changed after planning, so ownership and the replacement path were preserved');
361
+ continue;
362
+ }
363
+ const removed = await dependencies.removeCollectionSymlinkIfUnchanged(plan.providerDir, plan.createdLink);
364
+ if (!removed) {
365
+ blockTransition('partial', 'collection transition blocked; this runtime cannot safely unlink the exact alias, so ownership and child operations were preserved; remove the alias manually, then replan and retry');
366
+ continue;
367
+ }
368
+ removedTransitionPlans.add(plan);
369
+ nextManifest = detachCollectionOwnership(nextManifest, plan);
370
+ }
371
+ catch {
372
+ blockTransition('rejected', 'collection transition blocked; alias identity could not be safely re-proven, so ownership and the provider path were preserved');
373
+ }
374
+ }
375
+ const hasManifestMutation = actionable.length > 0 ||
376
+ detachments.some((plan) => !plan.transitionToPerEntry) ||
377
+ removedTransitionPlans.size > 0 ||
378
+ absentTransitionPlans.size > 0;
379
+ if (hasManifestMutation) {
380
+ await dependencies.saveManifest(manifestPath, nextManifest);
381
+ }
382
+ const detachmentResults = [];
383
+ for (const plan of detachments) {
384
+ const blocked = blockedTransitionResults.get(plan);
385
+ if (blocked) {
386
+ detachmentResults.push(blocked);
387
+ continue;
388
+ }
389
+ if (plan.transitionToPerEntry) {
390
+ detachmentResults.push({
391
+ provider: plan.provider,
392
+ contentType: plan.contentType,
393
+ action: plan.action,
394
+ ownership: plan.ownership,
395
+ status: 'changed',
396
+ reason: absentTransitionPlans.has(plan)
397
+ ? 'manually cleared collection destination remained absent through apply; ownership detached and per-entry reconciliation may proceed'
398
+ : 'exact durably identified collection alias was removed before ownership detachment; per-entry reconciliation may proceed',
399
+ });
400
+ continue;
401
+ }
402
+ let reason = plan.reason;
403
+ let status = 'changed';
404
+ if (plan.ownership === 'oat-created' &&
405
+ plan.proof.status === 'exact-link' &&
406
+ plan.createdLink !== undefined) {
407
+ try {
408
+ const current = await dependencies.proveCollectionIdentity({
409
+ root: inferScopeRoot(plan.canonicalDir),
410
+ canonicalDir: plan.canonicalDir,
411
+ providerDir: plan.providerDir,
412
+ });
413
+ if (current.status === 'exact-link' &&
414
+ collectionProofMatches(plan.proof, current) &&
415
+ current.providerLink.device === plan.createdLink.device &&
416
+ current.providerLink.inode === plan.createdLink.inode &&
417
+ current.linkText === plan.createdLink.linkText) {
418
+ const removed = await dependencies.removeCollectionSymlinkIfUnchanged(plan.providerDir, plan.createdLink);
419
+ if (!removed) {
420
+ status = 'partial';
421
+ reason =
422
+ 'collection ownership detached, but this runtime cannot safely unlink the exact alias; the alias was preserved for manual removal';
423
+ }
424
+ }
425
+ else {
426
+ reason =
427
+ 'collection ownership detached; alias identity changed after planning and was preserved';
428
+ }
429
+ }
430
+ catch {
431
+ status = 'partial';
432
+ reason =
433
+ 'collection ownership detached; alias removal could not be proven safe and was preserved';
434
+ }
435
+ }
436
+ else if (plan.ownership === 'oat-created') {
437
+ reason =
438
+ 'collection ownership detached; durable creation identity was unavailable and the alias was preserved';
439
+ }
440
+ detachmentResults.push({
441
+ provider: plan.provider,
442
+ contentType: plan.contentType,
443
+ action: plan.action,
444
+ ownership: plan.ownership,
445
+ status,
446
+ reason,
447
+ });
448
+ }
449
+ return {
450
+ manifest: nextManifest,
451
+ results: [
452
+ ...passiveResults,
453
+ ...actionable.map((plan) => ({
454
+ provider: plan.provider,
455
+ contentType: plan.contentType,
456
+ action: plan.action,
457
+ ownership: plan.ownership,
458
+ status: plan.action === 'inherit-collection'
459
+ ? 'current'
460
+ : 'changed',
461
+ reason: plan.reason,
462
+ })),
463
+ ...detachmentResults,
464
+ ],
465
+ attempted: true,
466
+ persisted: hasManifestMutation,
467
+ };
468
+ }
469
+ catch (error) {
470
+ const rollback = await Promise.all([...createdCollections]
471
+ .reverse()
472
+ .map(({ plan, created }) => dependencies.removeCollectionSymlinkIfUnchanged(plan.providerDir, created)));
473
+ const partial = rollback.some((removed) => !removed) || removedTransitionPlans.size > 0;
474
+ const guardedCreationUnavailable = hasErrorCode(error, 'E_COLLECTION_LINK_UNSAFE');
475
+ return {
476
+ manifest,
477
+ results: [
478
+ ...passiveResults,
479
+ ...actionable.map((plan) => ({
480
+ provider: plan.provider,
481
+ contentType: plan.contentType,
482
+ action: plan.action,
483
+ ownership: plan.ownership,
484
+ status: partial ? 'partial' : 'failed',
485
+ reason: guardedCreationUnavailable
486
+ ? 'collection creation is disabled because this runtime cannot create a link relative to a securely guarded parent; configure explicit symlink/copy per-entry sync or create the exact collection alias manually and retry for adoption'
487
+ : partial
488
+ ? 'collection transaction failed; automatic rollback unlink is unavailable, so any created alias was preserved for manual recovery'
489
+ : 'collection transaction failed; newly created links were rolled back',
490
+ })),
491
+ ...detachments.map((plan) => {
492
+ const blocked = blockedTransitionResults.get(plan);
493
+ if (blocked)
494
+ return blocked;
495
+ return {
496
+ provider: plan.provider,
497
+ contentType: plan.contentType,
498
+ action: plan.action,
499
+ ownership: plan.ownership,
500
+ status: removedTransitionPlans.has(plan)
501
+ ? 'partial'
502
+ : 'failed',
503
+ reason: removedTransitionPlans.has(plan)
504
+ ? 'exact alias was safely removed, but manifest ownership could not be detached; no per-entry child operations were attempted'
505
+ : 'collection transaction failed before ownership detachment',
506
+ };
507
+ }),
508
+ ],
509
+ attempted: true,
510
+ persisted: false,
511
+ };
512
+ }
129
513
  }
130
514
  export async function executeSyncPlan(plan, manifest, manifestPath, dependencies = DEFAULT_EXECUTE_SYNC_PLAN_DEPENDENCIES) {
515
+ const saveManifest = dependencies.saveManifest ?? persistManifest;
516
+ const collectionDependencies = {
517
+ saveManifest,
518
+ createCollectionSymlinkNoClobber: dependencies.createCollectionSymlinkNoClobber ??
519
+ createCollectionSymlinkNoClobber,
520
+ removeCollectionSymlinkIfUnchanged: dependencies.removeCollectionSymlinkIfUnchanged ??
521
+ removeCollectionSymlinkIfUnchanged,
522
+ proveCollectionIdentity: dependencies.proveCollectionIdentity ?? proveCollectionIdentity,
523
+ };
131
524
  let nextManifest = manifest;
132
525
  let beforeFirstMutationCalled = false;
133
- const result = {
134
- applied: 0,
135
- failed: 0,
136
- skipped: 0,
137
- };
526
+ const operationResults = [];
138
527
  const operations = [...plan.entries, ...plan.removals];
528
+ const beforeMutation = async () => {
529
+ if (!beforeFirstMutationCalled) {
530
+ beforeFirstMutationCalled = true;
531
+ await dependencies.beforeFirstMutation?.();
532
+ }
533
+ };
139
534
  for (const operation of operations) {
140
- if (mutatesProviderPath(operation)) {
535
+ if (mutatesProviderPath(operation) &&
536
+ !operation.deferredUntilCollectionDetached) {
141
537
  await assertSafeEntryProviderPath(operation);
142
538
  }
143
539
  }
540
+ const blockedDeferredDirectoryTransitions = new Set(operations
541
+ .filter((operation) => (operation.operation === 'create_copy' ||
542
+ operation.operation === 'create_symlink') &&
543
+ operation.deferredUntilCollectionDetached &&
544
+ !operation.canonical.isFile)
545
+ .map((operation) => `${operation.provider}::${operation.canonical.type}`));
546
+ const collectionTransaction = await executeCollectionTransaction(plan.collections ?? [], nextManifest, blockedDeferredDirectoryTransitions, collectionDependencies, manifestPath, beforeMutation);
547
+ nextManifest = collectionTransaction.manifest;
548
+ const blockedTransitions = new Map(collectionTransaction.results
549
+ .filter(({ action, status }) => action === 'detach-collection' && status !== 'changed')
550
+ .map((result) => [
551
+ `${result.provider}::${result.contentType}`,
552
+ result.reason,
553
+ ]));
144
554
  for (const operation of operations) {
555
+ if (operation.deferredUntilCollectionDetached &&
556
+ blockedTransitions.has(`${operation.provider}::${operation.canonical.type}`)) {
557
+ operationResults.push(operationEvidence(plan.scope, operation, 'failed', blockedTransitions.get(`${operation.provider}::${operation.canonical.type}`) ??
558
+ 'Collection alias could not be safely cleared; resolve the reported collection conflict and retry.'));
559
+ continue;
560
+ }
145
561
  if (operation.operation === 'skip') {
146
562
  nextManifest = await ensureSkipEntryManaged(operation, nextManifest);
147
- result.skipped += 1;
563
+ operationResults.push(operationEvidence(plan.scope, operation, 'current'));
148
564
  continue;
149
565
  }
566
+ const destinationPolicy = operation.deferredUntilCollectionDetached
567
+ ? 'preserve-existing'
568
+ : 'ordinary';
150
569
  try {
151
570
  if (mutatesProviderPath(operation)) {
152
- if (!beforeFirstMutationCalled) {
153
- beforeFirstMutationCalled = true;
154
- await dependencies.beforeFirstMutation?.();
155
- }
571
+ await beforeMutation();
156
572
  await assertSafeEntryProviderPath(operation);
157
573
  }
158
- nextManifest = await applyEntry(operation, nextManifest);
159
- result.applied += 1;
574
+ nextManifest = await applyEntry(operation, nextManifest, destinationPolicy, dependencies.copyDirectory ?? copyDirectory, dependencies.createSymlinkNoClobber ?? createSymlinkNoClobber);
575
+ operationResults.push(operationEvidence(plan.scope, operation, 'changed'));
160
576
  }
161
- catch {
162
- result.failed += 1;
577
+ catch (error) {
578
+ const failure = classifyFailure(error, destinationPolicy);
579
+ operationResults.push(operationEvidence(plan.scope, operation, failure.status, failure.failure));
163
580
  }
164
581
  }
165
- await saveManifest(manifestPath, nextManifest);
166
- return result;
582
+ const entryApplySucceeded = operationResults.some(({ status }) => status === 'changed' || status === 'current');
583
+ const successfulNoOperationApply = operations.length === 0 && !collectionTransaction.attempted;
584
+ if (entryApplySucceeded || successfulNoOperationApply) {
585
+ await saveManifest(manifestPath, nextManifest);
586
+ }
587
+ const collectionApplied = collectionTransaction.results.filter(({ status }) => status === 'changed').length;
588
+ const collectionFailed = collectionTransaction.results.filter(({ status }) => status === 'failed' || status === 'partial' || status === 'rejected').length;
589
+ const collectionSkipped = collectionTransaction.results.filter(({ status }) => status === 'current' || status === 'fallback').length;
590
+ return {
591
+ applied: operationResults.filter(({ status }) => status === 'changed').length +
592
+ collectionApplied,
593
+ failed: operationResults.filter(({ status }) => status === 'failed' || status === 'missing').length + collectionFailed,
594
+ skipped: operationResults.filter(({ status }) => status === 'current').length +
595
+ collectionSkipped,
596
+ operations: operationResults,
597
+ collectionResults: collectionTransaction.results,
598
+ };
167
599
  }
@@ -6,5 +6,5 @@ export type { HookInstallInfo, HookStatus } from './hook.js';
6
6
  export { hasMarker, insertMarker, OAT_DIRECTORY_SENTINEL, OAT_MARKER_PREFIX, writeDirectorySentinel, } from './markers.js';
7
7
  export { assertSafeProviderMutationPath } from './provider-path-safety.js';
8
8
  export type { CanonicalEntry } from './scanner.js';
9
- export { scanBundledManagedAgents, scanBundledManagedCodexAgents, scanCanonical, } from './scanner.js';
9
+ export { materializationCanonicalPathAllowed, mergeUserScopeMaterializationEntries, scanBundledManagedAgents, scanBundledManagedCodexAgents, scanCanonical, } from './scanner.js';
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/engine/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iCAAiC,EACjC,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,oBAAoB,EACzB,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,YAAY,EACZ,aAAa,GACd,MAAM,QAAQ,CAAC;AAChB,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EACL,SAAS,EACT,YAAY,EACZ,sBAAsB,EACtB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AACxE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EACL,wBAAwB,EACxB,6BAA6B,EAC7B,aAAa,GACd,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/engine/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iCAAiC,EACjC,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,oBAAoB,EACzB,oBAAoB,EACpB,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,YAAY,EACZ,aAAa,GACd,MAAM,QAAQ,CAAC;AAChB,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EACL,SAAS,EACT,YAAY,EACZ,sBAAsB,EACtB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AACxE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EACL,mCAAmC,EACnC,oCAAoC,EACpC,wBAAwB,EACxB,6BAA6B,EAC7B,aAAa,GACd,MAAM,WAAW,CAAC"}
@@ -4,4 +4,4 @@ export { executeSyncPlan } from './execute-plan.js';
4
4
  export { configureLocalHooksPath, getHookInstallInfo, HOOK_DRIFT_WARNING, HOOK_MARKER_END, HOOK_MARKER_START, HOOK_STRAY_INFO, installHook, isHookInstalled, REPO_GITHOOKS_PATH, runHookCheck, uninstallHook, } from './hook.js';
5
5
  export { hasMarker, insertMarker, OAT_DIRECTORY_SENTINEL, OAT_MARKER_PREFIX, writeDirectorySentinel, } from './markers.js';
6
6
  export { assertSafeProviderMutationPath } from './provider-path-safety.js';
7
- export { scanBundledManagedAgents, scanBundledManagedCodexAgents, scanCanonical, } from './scanner.js';
7
+ export { materializationCanonicalPathAllowed, mergeUserScopeMaterializationEntries, scanBundledManagedAgents, scanBundledManagedCodexAgents, scanCanonical, } from './scanner.js';
@@ -1,2 +1,3 @@
1
1
  export declare function assertSafeProviderMutationPath(scopeRoot: string, providerPath: string): Promise<void>;
2
+ export declare function assertSafeProviderCollectionPath(scopeRoot: string, canonicalDir: string, providerDir: string): Promise<void>;
2
3
  //# sourceMappingURL=provider-path-safety.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"provider-path-safety.d.ts","sourceRoot":"","sources":["../../src/engine/provider-path-safety.ts"],"names":[],"mappings":"AAiBA,wBAAsB,8BAA8B,CAClD,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAuDf"}
1
+ {"version":3,"file":"provider-path-safety.d.ts","sourceRoot":"","sources":["../../src/engine/provider-path-safety.ts"],"names":[],"mappings":"AAiBA,wBAAsB,8BAA8B,CAClD,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAuDf;AAYD,wBAAsB,gCAAgC,CACpD,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAYf"}
@@ -52,3 +52,18 @@ export async function assertSafeProviderMutationPath(scopeRoot, providerPath) {
52
52
  }
53
53
  }
54
54
  }
55
+ function isNestedPath(parent, candidate) {
56
+ const relativePath = relative(resolve(parent), resolve(candidate));
57
+ return (relativePath === '' ||
58
+ (!isAbsolute(relativePath) &&
59
+ relativePath !== '..' &&
60
+ !relativePath.startsWith(`..${sep}`)));
61
+ }
62
+ export async function assertSafeProviderCollectionPath(scopeRoot, canonicalDir, providerDir) {
63
+ if (isNestedPath(canonicalDir, providerDir) ||
64
+ isNestedPath(providerDir, canonicalDir)) {
65
+ throw new Error('Canonical and provider collection paths must not be nested.');
66
+ }
67
+ await assertSafeProviderMutationPath(scopeRoot, canonicalDir);
68
+ await assertSafeProviderMutationPath(scopeRoot, providerDir);
69
+ }
@@ -1,4 +1,4 @@
1
- import { type Scope } from '../shared/types.js';
1
+ import { type CanonicalScanTarget, type Scope } from '../shared/types.js';
2
2
  type ConcreteScope = Exclude<Scope, 'all'>;
3
3
  export interface CanonicalEntry {
4
4
  name: string;
@@ -7,8 +7,15 @@ export interface CanonicalEntry {
7
7
  isFile: boolean;
8
8
  }
9
9
  export declare function scanBundledManagedAgents(): Promise<CanonicalEntry[]>;
10
+ /**
11
+ * User-scope Codex/Cursor materialization reads the bundled managed role files,
12
+ * not the installed copies under `~/.agents/agents/`. Concatenating both lists
13
+ * produces the same Cursor/Codex role name twice and aborts the whole sync.
14
+ */
15
+ export declare function mergeUserScopeMaterializationEntries(canonicalEntries: CanonicalEntry[], bundledManagedAgents: CanonicalEntry[]): CanonicalEntry[];
16
+ export declare function materializationCanonicalPathAllowed(scopeRoot: string, canonicalEntry: CanonicalEntry, allowedCanonicalPaths?: string[]): boolean;
10
17
  /** @deprecated Use scanBundledManagedAgents for provider-neutral materialization. */
11
18
  export declare const scanBundledManagedCodexAgents: typeof scanBundledManagedAgents;
12
- export declare function scanCanonical(basePath: string, scope: ConcreteScope): Promise<CanonicalEntry[]>;
19
+ export declare function scanCanonical(basePath: string, scope: ConcreteScope, targets?: readonly CanonicalScanTarget[]): Promise<CanonicalEntry[]>;
13
20
  export {};
14
21
  //# sourceMappingURL=scanner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/engine/scanner.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,KAAK,EACX,MAAM,eAAe,CAAC;AAEvB,KAAK,aAAa,GAAG,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAE3C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;CACjB;AA0DD,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAsB1E;AAED,qFAAqF;AACrF,eAAO,MAAM,6BAA6B,iCAA2B,CAAC;AAEtE,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC,cAAc,EAAE,CAAC,CA2B3B"}
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/engine/scanner.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,KAAK,EACX,MAAM,eAAe,CAAC;AAEvB,KAAK,aAAa,GAAG,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAE3C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;CACjB;AA0DD,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAsB1E;AAED;;;;GAIG;AACH,wBAAgB,oCAAoC,CAClD,gBAAgB,EAAE,cAAc,EAAE,EAClC,oBAAoB,EAAE,cAAc,EAAE,GACrC,cAAc,EAAE,CAqBlB;AAED,wBAAgB,mCAAmC,CACjD,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,cAAc,EAC9B,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAC/B,OAAO,CAqBT;AAED,qFAAqF;AACrF,eAAO,MAAM,6BAA6B,iCAA2B,CAAC;AAEtE,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE,SAAS,mBAAmB,EAAE,GACvC,OAAO,CAAC,cAAc,EAAE,CAAC,CA6C3B"}