@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
@@ -7,26 +7,24 @@ import { applyNativeSkillDisposition, getNativeSkillProviderDetails, isNativeSki
7
7
  import { withScopeOption } from '../shared/scope-option.js';
8
8
  import { confirmAction, selectManyWithAbort, selectWithAbort, } from '../shared/shared.prompts.js';
9
9
  import { readGlobalOptions, resolveConcreteScopes, } from '../shared/shared.utils.js';
10
+ import { packEvidenceBlock, projectRenderablePackEvidence, unavailablePackEvidence, } from '../tools/shared/format-pack-inventory.js';
10
11
  import { attributeSharedOwnerDiagnostics, hasScopedPackPlacementEvidence, inventoryPack, } from '../tools/shared/pack-inventory.js';
11
12
  import { PACK_NAMES } from '../tools/shared/pack-manifest.js';
12
13
  import { formatPackPath, formatPackPaths, updatePackRecovery, } from '../tools/shared/pack-paths.js';
13
14
  import { DEFAULT_SYNC_CONFIG, loadSyncConfig, } from '../../config/index.js';
14
15
  import { resolveUserSyncConfig } from '../../config/user-sync-config.js';
15
16
  import { detectDrift, detectStrays, filterKnownStrays, } from '../../drift/index.js';
16
- import { HOOK_DRIFT_WARNING, HOOK_STRAY_INFO, scanBundledManagedAgents, scanCanonical, } from '../../engine/index.js';
17
+ import { HOOK_DRIFT_WARNING, HOOK_STRAY_INFO, mergeUserScopeMaterializationEntries, scanBundledManagedAgents, scanCanonical, } from '../../engine/index.js';
17
18
  import { resolveAssetsRoot } from '../../fs/assets.js';
18
19
  import { normalizeToPosixPath, resolveProjectRoot, resolveScopeRoot, } from '../../fs/paths.js';
19
20
  import { loadManifest, saveManifest } from '../../manifest/manager.js';
20
- import { claudeAdapter } from '../../providers/claude/index.js';
21
- import { codexAdapter } from '../../providers/codex/index.js';
22
21
  import { applyCodexProjectExtensionPlan, computeCodexProjectExtensionPlan, } from '../../providers/codex/codec/sync-extension.js';
23
- import { copilotAdapter } from '../../providers/copilot/index.js';
24
- import { cursorAdapter } from '../../providers/cursor/index.js';
25
22
  import { applyCursorProjectExtensionPlan, computeCursorProjectExtensionPlan, } from '../../providers/cursor/codec/sync-extension.js';
26
- import { geminiAdapter } from '../../providers/gemini/index.js';
27
- import { getConfigAwareAdapters, getSyncMappings, } from '../../providers/shared/index.js';
23
+ import { getConfigAwareAdapters, getProviderRegistrations, getSyncMappings, resolveProviderScopeContext, } from '../../providers/shared/index.js';
28
24
  import { getAdoptionSources } from '../../providers/shared/adapter.utils.js';
29
- import { SCOPE_CONTENT_TYPES, } from '../../shared/types.js';
25
+ import { userAgentMaterializationCoverage, } from '../../providers/shared/registry.js';
26
+ import { adviseProviderRefresh, } from '../../providers/shared/restart-adviser.js';
27
+ import {} from '../../shared/types.js';
30
28
  import { formatStatusTable } from '../../ui/output.js';
31
29
  import { Command } from 'commander';
32
30
  const DEFAULT_REMEDIATION = 'Run "oat init" to adopt stray entries.';
@@ -61,8 +59,49 @@ function unavailablePackReport(error, roots, unavailableScopes) {
61
59
  states: [],
62
60
  unavailableScopes,
63
61
  pjm: null,
62
+ evidence: packEvidenceBlock(PACK_NAMES.map((pack) => unavailablePackEvidence({
63
+ pack,
64
+ scopes: Object.keys(roots).map((key) => key === 'projectRoot' ? 'project' : 'user'),
65
+ reason: detail,
66
+ roots,
67
+ }))),
64
68
  };
65
69
  }
70
+ function collectProviderRefreshAdvice(collections) {
71
+ return collections.flatMap((collection) => {
72
+ const providerContext = collection.providerContext;
73
+ if (!providerContext)
74
+ return [];
75
+ const active = new Set(providerContext.activeProviders);
76
+ return providerContext.registrations.flatMap((registration) => {
77
+ if (!active.has(registration.adapter.name))
78
+ return [];
79
+ return registration.capabilities
80
+ .filter(({ scope, support }) => scope === collection.scope && support === 'supported')
81
+ .map((capability) => ({
82
+ scope: collection.scope,
83
+ provider: registration.adapter.name,
84
+ contentKind: capability.contentKind,
85
+ visibility: adviseProviderRefresh({
86
+ policy: capability.catalogRefresh,
87
+ materialization: 'unknown',
88
+ observation: {
89
+ state: 'not-reported',
90
+ reference: 'oat status inspects managed files but does not query the active provider catalog',
91
+ },
92
+ }),
93
+ }));
94
+ });
95
+ });
96
+ }
97
+ function formatProviderRefreshAdvice(advice) {
98
+ if (advice.length === 0)
99
+ return null;
100
+ return [
101
+ 'Provider catalog visibility:',
102
+ ...advice.map(({ scope, provider, contentKind, visibility }) => ` [${scope}] ${provider}/${contentKind}: ${visibility.state} (${visibility.policy.state})`),
103
+ ].join('\n');
104
+ }
66
105
  const DEFAULT_DEPENDENCIES = {
67
106
  buildCommandContext,
68
107
  async resolveScopeRoot(scope, context) {
@@ -80,15 +119,10 @@ const DEFAULT_DEPENDENCIES = {
80
119
  scanCanonical,
81
120
  scanBundledManagedAgents,
82
121
  getAdapters() {
83
- return [
84
- claudeAdapter,
85
- cursorAdapter,
86
- codexAdapter,
87
- copilotAdapter,
88
- geminiAdapter,
89
- ];
122
+ return getProviderRegistrations().map(({ adapter }) => adapter);
90
123
  },
91
124
  getConfigAwareAdapters,
125
+ resolveProviderScopeContext,
92
126
  getSyncMappings,
93
127
  getAdoptionSources,
94
128
  detectDrift,
@@ -120,9 +154,6 @@ function canonicalInsideMapping(canonicalPath, mappingCanonicalDir) {
120
154
  return (normalizedCanonicalPath === normalizedCanonicalDir ||
121
155
  normalizedCanonicalPath.startsWith(`${normalizedCanonicalDir}/`));
122
156
  }
123
- function contentTypeAllowed(contentType, scope) {
124
- return SCOPE_CONTENT_TYPES[scope].includes(contentType);
125
- }
126
157
  function summarizeReports(reports) {
127
158
  const summary = {
128
159
  total: reports.length,
@@ -243,7 +274,7 @@ function shouldReportPjmAdoption(adoption, states) {
243
274
  const pjm = states.find(({ pack }) => pack === 'project-management');
244
275
  return (pjm?.scopes ?? []).some(({ scope }) => scope === 'project');
245
276
  }
246
- async function collectPackReport(scopeRoots, unavailableScopes, userManagedRoleMaterialization, dependencies) {
277
+ async function collectPackReport(scopeRoots, unavailableScopes, userAgentCoverage, dependencies) {
247
278
  const roots = {
248
279
  ...(scopeRoots.has('project')
249
280
  ? { projectRoot: scopeRoots.get('project') }
@@ -256,6 +287,7 @@ async function collectPackReport(scopeRoots, unavailableScopes, userManagedRoleM
256
287
  states: [],
257
288
  unavailableScopes,
258
289
  pjm: null,
290
+ evidence: packEvidenceBlock([]),
259
291
  };
260
292
  }
261
293
  let inventories;
@@ -266,9 +298,23 @@ async function collectPackReport(scopeRoots, unavailableScopes, userManagedRoleM
266
298
  assetsRoot,
267
299
  ...roots,
268
300
  ...(scopeRoots.has('user')
269
- ? { userManagedRoleMaterialization }
301
+ ? {
302
+ userManagedRoleMaterialization: userAgentCoverage !== 'none',
303
+ }
270
304
  : {}),
271
305
  }))));
306
+ if (userAgentCoverage === 'all') {
307
+ inventories = inventories.map((inventory) => ({
308
+ ...inventory,
309
+ scopes: inventory.scopes.map((scoped) => scoped.scope === 'user'
310
+ ? {
311
+ ...scoped,
312
+ diagnostics: scoped.diagnostics.filter(({ code }) => code !== 'user-agent-unmaterialized'),
313
+ }
314
+ : scoped),
315
+ diagnostics: inventory.diagnostics.filter(({ code }) => code !== 'user-agent-unmaterialized'),
316
+ }));
317
+ }
272
318
  }
273
319
  catch (error) {
274
320
  return unavailablePackReport(error, roots, unavailableScopes);
@@ -276,6 +322,7 @@ async function collectPackReport(scopeRoots, unavailableScopes, userManagedRoleM
276
322
  const states = inventories
277
323
  .map((inventory) => toStatusPackState(inventory, roots))
278
324
  .filter((state) => state !== null);
325
+ const evidence = packEvidenceBlock(inventories.map((canonical) => projectRenderablePackEvidence(canonical, roots)));
279
326
  const projectRoot = scopeRoots.get('project');
280
327
  if (!projectRoot) {
281
328
  return {
@@ -283,6 +330,7 @@ async function collectPackReport(scopeRoots, unavailableScopes, userManagedRoleM
283
330
  states,
284
331
  unavailableScopes,
285
332
  pjm: null,
333
+ evidence,
286
334
  };
287
335
  }
288
336
  const adoption = await dependencies.resolvePjmAdoption({
@@ -301,6 +349,7 @@ async function collectPackReport(scopeRoots, unavailableScopes, userManagedRoleM
301
349
  repoRoot: formatPackPath(adoption.repoRoot, roots),
302
350
  recovery: adoption.recovery,
303
351
  },
352
+ evidence,
304
353
  };
305
354
  }
306
355
  function describePackScope(state) {
@@ -375,29 +424,79 @@ async function collectScopeReports(scope, context, dependencies) {
375
424
  const manifestPath = join(scopeRoot, '.oat', 'sync', 'manifest.json');
376
425
  const syncConfigPath = join(scopeRoot, '.oat', 'sync', 'config.json');
377
426
  const userConfigDir = join(context.home, '.oat');
378
- const [manifest, canonicalEntries, syncConfig, userSyncConfig] = await Promise.all([
427
+ const [manifest, syncConfig, userSyncConfig] = await Promise.all([
379
428
  dependencies.loadManifest(manifestPath),
380
- dependencies.scanCanonical(scopeRoot, scope),
381
429
  dependencies.loadSyncConfig(syncConfigPath),
382
430
  dependencies.resolveUserSyncConfig(userConfigDir),
383
431
  ]);
384
- const adapters = dependencies.getAdapters();
385
- const { activeAdapters } = await dependencies.getConfigAwareAdapters(adapters, scopeRoot, scope === 'user' ? userSyncConfig : syncConfig);
432
+ const effectiveConfig = scope === 'user' ? userSyncConfig : syncConfig;
433
+ const providerContext = dependencies.resolveProviderScopeContext
434
+ ? await dependencies.resolveProviderScopeContext({
435
+ scope,
436
+ scopeRoot,
437
+ config: effectiveConfig,
438
+ })
439
+ : null;
440
+ const adapters = providerContext
441
+ ? providerContext.registrations.map(({ adapter }) => adapter)
442
+ : dependencies.getAdapters();
443
+ const activeAdapters = providerContext
444
+ ? adapters.filter(({ name }) => providerContext.activeProviders.includes(name))
445
+ : await dependencies
446
+ .getConfigAwareAdapters(adapters, scopeRoot, effectiveConfig)
447
+ .then((resolution) => resolution.activeAdapters);
448
+ const activeAdapterNames = activeAdapters.map(({ name }) => name);
449
+ const knownRegistrations = getProviderRegistrations().filter(({ adapter }) => activeAdapterNames.includes(adapter.name));
450
+ const coverageRegistrations = providerContext?.registrations ?? knownRegistrations;
451
+ const mappingScanTargets = activeAdapters
452
+ .flatMap((adapter) => dependencies.getSyncMappings(adapter, scope))
453
+ .map(({ contentType, canonicalDir }) => ({ contentType, canonicalDir }));
454
+ const capabilityScanTargets = coverageRegistrations
455
+ .filter(({ adapter }) => activeAdapterNames.includes(adapter.name))
456
+ .flatMap(({ capabilities }) => capabilities
457
+ .filter((capability) => capability.scope === scope &&
458
+ capability.support === 'supported' &&
459
+ capability.contentKind !== 'directory')
460
+ .map((capability) => ({
461
+ contentType: capability.contentKind,
462
+ canonicalDir: `.agents/${capability.contentKind}s`,
463
+ })));
464
+ const scanTargets = [...mappingScanTargets, ...capabilityScanTargets].filter((target, index, targets) => targets.findIndex((candidate) => candidate.contentType === target.contentType &&
465
+ candidate.canonicalDir === target.canonicalDir) === index);
466
+ const canonicalEntries = await dependencies.scanCanonical(scopeRoot, scope, scanTargets);
467
+ let agentCoverage = userAgentMaterializationCoverage({
468
+ registrations: coverageRegistrations,
469
+ activeProviders: activeAdapterNames,
470
+ });
471
+ if (scope === 'user' && agentCoverage !== 'all') {
472
+ const activeAgentMappings = activeAdapters.flatMap((adapter) => dependencies
473
+ .getSyncMappings(adapter, scope)
474
+ .filter(({ contentType }) => contentType === 'agent'));
475
+ if (activeAgentMappings.some(({ nativeRead }) => !nativeRead)) {
476
+ agentCoverage = 'all';
477
+ }
478
+ else if (activeAgentMappings.length > 0 && agentCoverage === 'none') {
479
+ agentCoverage = 'bundled';
480
+ }
481
+ }
386
482
  const reports = [];
387
483
  const strayCandidates = [];
388
484
  const trackedCanonicalByProvider = new Set(manifest.entries.map((entry) => `${entry.provider}|${normalizeToPosixPath(entry.canonicalPath)}`));
389
485
  const activeProviderNames = new Set(activeAdapters.map((adapter) => adapter.name));
486
+ const activeExtensionProviders = new Set(providerContext
487
+ ? providerContext.registrations
488
+ .filter(({ adapter }) => activeProviderNames.has(adapter.name))
489
+ .flatMap(({ extensions }) => extensions.map(({ provider }) => provider))
490
+ : [...activeProviderNames]);
390
491
  const extensionCanonicalEntries = scope === 'user' &&
391
- (activeProviderNames.has('codex') || activeProviderNames.has('cursor'))
392
- ? [
393
- ...canonicalEntries,
394
- ...(await dependencies.scanBundledManagedAgents()),
395
- ]
492
+ (activeExtensionProviders.has('codex') ||
493
+ activeExtensionProviders.has('cursor'))
494
+ ? mergeUserScopeMaterializationEntries(canonicalEntries, await dependencies.scanBundledManagedAgents())
396
495
  : canonicalEntries;
397
- const codexExtensionPlan = activeProviderNames.has('codex')
496
+ const codexExtensionPlan = activeExtensionProviders.has('codex')
398
497
  ? await dependencies.computeCodexProjectExtensionPlan(scopeRoot, extensionCanonicalEntries, undefined, { userConfigDir })
399
498
  : undefined;
400
- const cursorExtensionPlan = activeProviderNames.has('cursor')
499
+ const cursorExtensionPlan = activeExtensionProviders.has('cursor')
401
500
  ? await dependencies.computeCursorProjectExtensionPlan(scopeRoot, extensionCanonicalEntries, undefined, { userConfigDir })
402
501
  : undefined;
403
502
  const materializationPlans = [
@@ -429,9 +528,6 @@ async function collectScopeReports(scope, context, dependencies) {
429
528
  if (!mappingContentTypes.has(entry.contentType)) {
430
529
  continue;
431
530
  }
432
- if (!contentTypeAllowed(entry.contentType, scope)) {
433
- continue;
434
- }
435
531
  const matchedMapping = mappings.find((mapping) => mapping.contentType === entry.contentType &&
436
532
  entryInsideMapping(entry.providerPath, mapping.providerDir));
437
533
  if (!matchedMapping) {
@@ -549,7 +645,9 @@ async function collectScopeReports(scope, context, dependencies) {
549
645
  manifest,
550
646
  reports: filtered.reports,
551
647
  strayCandidates: filtered.candidates,
552
- activeAdapterNames: activeAdapters.map((adapter) => adapter.name),
648
+ activeAdapterNames,
649
+ userAgentMaterializationCoverage: scope === 'user' ? agentCoverage : 'none',
650
+ providerContext,
553
651
  };
554
652
  }
555
653
  async function runStatusCommand(context, dependencies, options = {}) {
@@ -577,6 +675,7 @@ async function runStatusCommand(context, dependencies, options = {}) {
577
675
  scopeRoots.set(scope, scopeReportCollection.scopeRoot);
578
676
  }
579
677
  const summary = summarizeReports(reports);
678
+ const providerRefreshAdvice = collectProviderRefreshAdvice(scopeCollections);
580
679
  const hasIssues = summary.total > 0 && summary.inSync !== summary.total;
581
680
  if (options.hook) {
582
681
  if (summary.drifted > 0 || summary.missing > 0) {
@@ -592,14 +691,19 @@ async function runStatusCommand(context, dependencies, options = {}) {
592
691
  }
593
692
  return;
594
693
  }
595
- const packReport = await collectPackReport(scopeRoots, unavailableScopes, scopeCollections.some(({ scope, activeAdapterNames }) => scope === 'user' &&
596
- activeAdapterNames.some((name) => name === 'codex' || name === 'cursor')), dependencies);
694
+ const packReport = await collectPackReport(scopeRoots, unavailableScopes, scopeCollections.some((collection) => collection.userAgentMaterializationCoverage === 'all')
695
+ ? 'all'
696
+ : scopeCollections.some((collection) => collection.userAgentMaterializationCoverage === 'bundled')
697
+ ? 'bundled'
698
+ : 'none', dependencies);
597
699
  if (context.json) {
598
700
  const payload = {
599
701
  scope: context.scope,
600
702
  reports,
601
703
  summary,
602
704
  packs: packReport,
705
+ packEvidence: packReport.evidence,
706
+ providerRefreshAdvice,
603
707
  };
604
708
  if (!context.interactive && summary.stray > 0) {
605
709
  payload.remediation = DEFAULT_REMEDIATION;
@@ -612,6 +716,9 @@ async function runStatusCommand(context, dependencies, options = {}) {
612
716
  if (packSummary) {
613
717
  context.logger.info(packSummary);
614
718
  }
719
+ const providerSummary = formatProviderRefreshAdvice(providerRefreshAdvice);
720
+ if (providerSummary)
721
+ context.logger.info(providerSummary);
615
722
  }
616
723
  if (summary.stray > 0) {
617
724
  if (context.interactive) {
@@ -758,6 +865,11 @@ export function createStatusCommand(overrides = {}) {
758
865
  ...DEFAULT_DEPENDENCIES,
759
866
  ...overrides,
760
867
  };
868
+ if (overrides.resolveProviderScopeContext === undefined &&
869
+ (overrides.getAdapters !== undefined ||
870
+ overrides.getConfigAwareAdapters !== undefined)) {
871
+ dependencies.resolveProviderScopeContext = undefined;
872
+ }
761
873
  return withScopeOption(new Command('status'))
762
874
  .description('Report provider sync and drift status')
763
875
  .option('--hook', 'Emit a minimal pre-commit message: warn on managed drift, info on strays')
@@ -1 +1 @@
1
- {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/apply.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EACV,aAAa,EACb,uBAAuB,EAExB,MAAM,cAAc,CAAC;AAyEtB,wBAAsB,YAAY,CAChC,OAAO,EAAE,cAAc,EACvB,UAAU,EAAE,aAAa,EAAE,EAC3B,YAAY,EAAE,uBAAuB,GACpC,OAAO,CAAC,IAAI,CAAC,CAgHf"}
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/apply.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAiB3D,OAAO,KAAK,EACV,aAAa,EACb,uBAAuB,EAExB,MAAM,cAAc,CAAC;AA2TtB,wBAAsB,YAAY,CAChC,OAAO,EAAE,cAAc,EACvB,UAAU,EAAE,aAAa,EAAE,EAC3B,YAAY,EAAE,uBAAuB,GACpC,OAAO,CAAC,IAAI,CAAC,CA4Kf"}
@@ -1,24 +1,161 @@
1
- import { countPlannedOperations } from './sync.utils.js';
2
- function countSkippedEntries(scopePlans) {
1
+ import { getProviderRegistrations, } from '../../providers/shared/registry.js';
2
+ import { adviseProviderRefresh, } from '../../providers/shared/restart-adviser.js';
3
+ import { buildCollectionLifecycle, countPlannedOperations, formatCollectionLifecycle, toSyncOutputPlan, } from './sync.utils.js';
4
+ function extensionResultContentKind(registrations, result) {
5
+ const registration = registrations.find(({ adapter }) => adapter.name === result.provider);
6
+ const candidates = registration?.capabilities.filter(({ scope, support, projectionModes }) => scope === result.scope &&
7
+ support === 'supported' &&
8
+ projectionModes.includes('materialization-extension'));
9
+ // Extension operation identities do not carry a canonical asset kind.
10
+ // Attribute only when provider capability evidence gives one unambiguous
11
+ // content kind; otherwise leave the exact result unattributed.
12
+ return candidates?.length === 1 ? candidates[0].contentKind : undefined;
13
+ }
14
+ function buildProviderRefreshAdvice(input) {
15
+ const registrations = getProviderRegistrations();
16
+ const changed = [
17
+ ...input.operationResults
18
+ .filter(({ status }) => status === 'changed')
19
+ .map(({ scope, provider, contentKind }) => ({
20
+ scope,
21
+ provider,
22
+ contentKind,
23
+ })),
24
+ ...input.extensionResults
25
+ .filter(({ status }) => status === 'changed')
26
+ .flatMap((result) => {
27
+ const contentKind = extensionResultContentKind(registrations, result);
28
+ return contentKind === undefined
29
+ ? []
30
+ : [{ scope: result.scope, provider: result.provider, contentKind }];
31
+ }),
32
+ ];
33
+ const seen = new Set();
34
+ return changed.flatMap(({ scope, provider, contentKind }) => {
35
+ const key = `${scope}:${provider}:${contentKind}`;
36
+ if (seen.has(key))
37
+ return [];
38
+ seen.add(key);
39
+ const policy = registrations
40
+ .find(({ adapter }) => adapter.name === provider)
41
+ ?.capabilities.find((capability) => capability.scope === scope && capability.contentKind === contentKind)?.catalogRefresh ?? {
42
+ state: 'unknown',
43
+ reason: 'Provider or capability is not registered',
44
+ };
45
+ return [
46
+ {
47
+ scope,
48
+ provider,
49
+ contentKind,
50
+ materialization: 'changed',
51
+ visibility: adviseProviderRefresh({
52
+ policy,
53
+ materialization: 'changed',
54
+ }),
55
+ },
56
+ ];
57
+ });
58
+ }
59
+ function formatProviderRefreshAdvice(advice) {
60
+ return advice
61
+ .map(({ scope, provider, contentKind, visibility }) => {
62
+ const recovery = visibility.recovery[0]?.message;
63
+ return `Provider visibility [${scope}] ${provider}/${contentKind}: ${visibility.state} — ${recovery ?? visibility.reason}`;
64
+ })
65
+ .join('\n');
66
+ }
67
+ function countSkippedExtensionEntries(scopePlans) {
3
68
  return scopePlans.reduce((total, scopePlan) => {
4
69
  const extensionSkipped = scopePlan.materializationExtensions.reduce((count, extension) => count +
5
70
  extension.operations.filter((operation) => operation.action === 'skip')
6
71
  .length, 0);
7
- return (total +
8
- scopePlan.plan.entries.filter((entry) => entry.operation === 'skip')
9
- .length +
10
- extensionSkipped);
72
+ return total + extensionSkipped;
11
73
  }, 0);
12
74
  }
13
- function buildSummary(scopePlans, applied, failed) {
75
+ function buildSummary(scopePlans, coreApplied, coreFailed, coreSkipped, extensionApplied, extensionFailed) {
14
76
  return {
15
77
  plannedOperations: countPlannedOperations(scopePlans),
16
- applied,
17
- failed,
18
- skipped: countSkippedEntries(scopePlans),
78
+ applied: coreApplied + extensionApplied,
79
+ failed: coreFailed + extensionFailed,
80
+ skipped: coreSkipped + countSkippedExtensionEntries(scopePlans),
19
81
  };
20
82
  }
21
- function formatAppliedOutput(scopePlans, dependencies) {
83
+ function normalizeOperationResults(plan, result) {
84
+ if (result.operations) {
85
+ return result.operations;
86
+ }
87
+ const plannedOperations = [...plan.entries, ...plan.removals];
88
+ return plannedOperations.map((operation) => {
89
+ return {
90
+ scope: plan.scope,
91
+ provider: operation.provider,
92
+ contentKind: operation.canonical.type,
93
+ asset: operation.canonical.name,
94
+ action: operation.operation,
95
+ status: operation.operation === 'skip' ? 'current' : 'unknown',
96
+ };
97
+ });
98
+ }
99
+ function materializationOperationIdentity(operation) {
100
+ return JSON.stringify([
101
+ operation.provider,
102
+ operation.target,
103
+ operation.path,
104
+ operation.action,
105
+ operation.entryName ?? null,
106
+ ]);
107
+ }
108
+ function syncOperationIdentity(operation) {
109
+ return JSON.stringify([
110
+ operation.scope,
111
+ operation.provider,
112
+ operation.contentKind,
113
+ operation.asset,
114
+ operation.action,
115
+ ]);
116
+ }
117
+ function coreHumanStatus(status) {
118
+ switch (status) {
119
+ case 'changed':
120
+ case 'current':
121
+ case 'failed':
122
+ case 'missing':
123
+ case 'unknown':
124
+ return status;
125
+ default:
126
+ return 'unknown';
127
+ }
128
+ }
129
+ function formatCoreResults(plan, evidence, dependencies) {
130
+ const operations = [...plan.entries, ...plan.removals];
131
+ if (operations.length === 0) {
132
+ if ((plan.collections?.length ?? 0) > 0) {
133
+ return 'Core results\nNo per-entry operations.';
134
+ }
135
+ return dependencies.formatSyncPlan(plan, true);
136
+ }
137
+ const resultsByIdentity = new Map((evidence?.operationResults ?? []).map((result) => [
138
+ syncOperationIdentity(result),
139
+ result,
140
+ ]));
141
+ const lines = operations.map((operation) => {
142
+ const result = resultsByIdentity.get(syncOperationIdentity({
143
+ scope: plan.scope,
144
+ provider: operation.provider,
145
+ contentKind: operation.canonical.type,
146
+ asset: operation.canonical.name,
147
+ action: operation.operation,
148
+ }));
149
+ const status = coreHumanStatus(result?.status);
150
+ const failure = result?.failure ? ` — ${result.failure}` : '';
151
+ return `- ${plan.scope}:${operation.provider}:${operation.canonical.type}:${operation.operation} ${operation.canonical.name}\n reason: ${operation.reason}\n result: ${status}${failure}`;
152
+ });
153
+ const aggregate = evidence?.aggregateOnly
154
+ ? `\nCore aggregate result: applied ${evidence.aggregate.applied}, failed ${evidence.aggregate.failed}, skipped ${evidence.aggregate.skipped}; non-skip named outcomes remain unknown.`
155
+ : '';
156
+ return `Core results${aggregate}\n${lines.join('\n')}`;
157
+ }
158
+ function formatAppliedOutput(scopePlans, coreApplyEvidence, dependencies) {
22
159
  if (scopePlans.length === 0) {
23
160
  return dependencies.formatSyncPlan({
24
161
  scope: 'project',
@@ -28,28 +165,53 @@ function formatAppliedOutput(scopePlans, dependencies) {
28
165
  }
29
166
  return scopePlans
30
167
  .map((scopePlan) => {
31
- const syncOutput = dependencies.formatSyncPlan(scopePlan.plan, true);
168
+ const syncOutput = formatCoreResults(scopePlan.plan, coreApplyEvidence.find((evidence) => evidence.plan === scopePlan.plan), dependencies);
169
+ const collectionOutput = formatCollectionLifecycle(buildCollectionLifecycle(scopePlan, coreApplyEvidence.find((evidence) => evidence.plan === scopePlan.plan)
170
+ ?.collectionResults));
32
171
  if (scopePlan.materializationExtensions.length === 0) {
33
- return `Scope: ${scopePlan.scope}\n${syncOutput}`;
172
+ return [`Scope: ${scopePlan.scope}\n${syncOutput}`, collectionOutput]
173
+ .filter(Boolean)
174
+ .join('\n\n');
34
175
  }
35
176
  const extensionSections = scopePlan.materializationExtensions.map((extension) => {
177
+ const resultsByIdentity = new Map((extension.operationResults ?? []).map((result) => [
178
+ materializationOperationIdentity(result),
179
+ result,
180
+ ]));
36
181
  const lines = extension.operations.map((operation) => {
37
182
  const entry = operation.entryName
38
183
  ? ` (${operation.entryName})`
39
184
  : '';
40
- return `- ${operation.provider}:${operation.target}:${operation.action} ${operation.path}${entry} (${operation.reason})`;
185
+ const result = resultsByIdentity.get(materializationOperationIdentity(operation));
186
+ const status = result?.status ?? 'unknown';
187
+ const failure = result?.failure ? ` — ${result.failure}` : '';
188
+ return `- ${operation.provider}:${operation.target}:${operation.action} ${operation.path}${entry}\n reason: ${operation.reason}\n result: ${status}${failure}`;
41
189
  });
42
- return `${extension.provider} extension (applied)\n${lines.join('\n')}`;
190
+ return `${extension.provider} extension results\n${lines.join('\n')}`;
43
191
  });
44
- return `Scope: ${scopePlan.scope}\n${syncOutput}\n\n${extensionSections.join('\n\n')}`;
192
+ return [
193
+ `Scope: ${scopePlan.scope}\n${syncOutput}`,
194
+ collectionOutput,
195
+ extensionSections.join('\n\n'),
196
+ ]
197
+ .filter(Boolean)
198
+ .join('\n\n');
45
199
  })
46
200
  .join('\n\n');
47
201
  }
48
202
  export async function runSyncApply(context, scopePlans, dependencies) {
49
- let applied = 0;
50
- let failed = 0;
203
+ let extensionApplied = 0;
204
+ let extensionFailed = 0;
205
+ let coreApplied = 0;
206
+ let coreFailed = 0;
207
+ let coreSkipped = 0;
208
+ const operationResults = [];
209
+ const coreApplyEvidence = [];
210
+ const extensionOperationResults = [];
51
211
  for (const scopePlan of scopePlans) {
52
- const hasSyncEntries = scopePlan.plan.entries.length > 0 || scopePlan.plan.removals.length > 0;
212
+ const hasSyncEntries = scopePlan.plan.entries.length > 0 ||
213
+ scopePlan.plan.removals.length > 0 ||
214
+ (scopePlan.plan.collections?.length ?? 0) > 0;
53
215
  const hasExtensionPlannedOperations = scopePlan.materializationExtensionPlans.some((plan) => plan.operations.some((operation) => operation.action !== 'skip'));
54
216
  // Derived from the same diagnostic that produced the pre-mutation advisory
55
217
  // (`detectVersionSkew` in ./index), so the restamp and the warning cannot
@@ -63,8 +225,18 @@ export async function runSyncApply(context, scopePlans, dependencies) {
63
225
  }
64
226
  if (hasSyncEntries || shouldRefreshManifestVersion) {
65
227
  const result = await dependencies.executeSyncPlan(scopePlan.plan, scopePlan.manifest, scopePlan.manifestPath);
66
- applied += result.applied;
67
- failed += result.failed;
228
+ coreApplied += result.applied;
229
+ coreFailed += result.failed;
230
+ coreSkipped += result.skipped;
231
+ const normalizedResults = normalizeOperationResults(scopePlan.plan, result);
232
+ operationResults.push(...normalizedResults);
233
+ coreApplyEvidence.push({
234
+ plan: scopePlan.plan,
235
+ operationResults: normalizedResults,
236
+ aggregateOnly: result.operations === undefined,
237
+ aggregate: result,
238
+ collectionResults: result.collectionResults ?? [],
239
+ });
68
240
  }
69
241
  for (const plan of scopePlan.materializationExtensionPlans) {
70
242
  if (!plan.operations.some((operation) => operation.action !== 'skip')) {
@@ -77,15 +249,22 @@ export async function runSyncApply(context, scopePlans, dependencies) {
77
249
  throw new Error(`Materialization extension ${plan.provider} disappeared before apply.`);
78
250
  }
79
251
  const result = await dependencies.applyMaterializationExtensionPlan(extension, scopePlan.scopeRoot, plan);
80
- applied += result.applied;
81
- failed += result.failed;
252
+ extensionApplied += result.applied;
253
+ extensionFailed += result.failed;
254
+ extensionOperationResults.push(...(result.operations ?? []).map((operation) => ({
255
+ ...operation,
256
+ scope: scopePlan.scope,
257
+ })));
82
258
  const summary = scopePlan.materializationExtensions.find((candidate) => candidate.provider === plan.provider);
83
259
  if (summary) {
84
- Object.assign(summary, result);
260
+ summary.applied = result.applied;
261
+ summary.failed = result.failed;
262
+ summary.skipped = result.skipped;
263
+ summary.operationResults = result.operations;
85
264
  }
86
265
  }
87
266
  }
88
- const summary = buildSummary(scopePlans, applied, failed);
267
+ const summary = buildSummary(scopePlans, coreApplied, coreFailed, coreSkipped, extensionApplied, extensionFailed);
89
268
  const providerMismatches = scopePlans
90
269
  .map((scopePlan) => scopePlan.providerMismatches)
91
270
  .filter((mismatch) => mismatch !== undefined);
@@ -109,29 +288,41 @@ export async function runSyncApply(context, scopePlans, dependencies) {
109
288
  failed: extension.failed,
110
289
  skipped: extension.skipped,
111
290
  }));
291
+ const providerRefreshAdvice = buildProviderRefreshAdvice({
292
+ operationResults,
293
+ extensionResults: extensionOperationResults,
294
+ });
295
+ const collectionOperations = scopePlans.flatMap((scopePlan) => buildCollectionLifecycle(scopePlan, coreApplyEvidence.find((evidence) => evidence.plan === scopePlan.plan)
296
+ ?.collectionResults));
112
297
  if (context.json) {
113
298
  context.logger.json({
114
299
  scope: context.scope,
115
300
  dryRun: false,
116
- plans: scopePlans.map((scopePlan) => scopePlan.plan),
301
+ plans: scopePlans.map(toSyncOutputPlan),
302
+ collectionOperations,
117
303
  summary,
118
304
  providerMismatches,
119
305
  versionSkew,
120
306
  materializationExtensions,
307
+ operationResults,
121
308
  codexExtensions,
309
+ providerRefreshAdvice,
122
310
  });
123
311
  }
124
312
  else {
125
- context.logger.info(formatAppliedOutput(scopePlans, dependencies));
313
+ context.logger.info(formatAppliedOutput(scopePlans, coreApplyEvidence, dependencies));
126
314
  if (summary.plannedOperations === 0) {
127
315
  context.logger.info('\nNo changes required.');
128
316
  }
129
- else if (failed > 0) {
317
+ else if (summary.failed > 0) {
130
318
  context.logger.warn('\nSync completed with partial failures.');
131
319
  }
132
320
  else {
133
321
  context.logger.success('\nSync applied successfully.');
134
322
  }
323
+ if (providerRefreshAdvice.length > 0) {
324
+ context.logger.info(`\n${formatProviderRefreshAdvice(providerRefreshAdvice)}`);
325
+ }
135
326
  }
136
- process.exitCode = failed > 0 ? 1 : 0;
327
+ process.exitCode = summary.failed > 0 ? 1 : 0;
137
328
  }