@solaqua/skul 0.1.0 → 0.1.1

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.
package/dist/index.js CHANGED
@@ -44,14 +44,41 @@ async function run(argv, options = {}) {
44
44
  return (0, cli_1.createHelpText)(parsed.command);
45
45
  }
46
46
  switch (parsed.command) {
47
- case "add":
47
+ case "add": {
48
+ const addPrompts = parsed.options.yes
49
+ ? createYesPromptClient(prompts, { selectAllAgents: true })
50
+ : prompts;
51
+ if (parsed.options.all) {
52
+ const addSource = parsed.options.source;
53
+ if (addSource === undefined) {
54
+ throw new Error("Command add --all requires a source");
55
+ }
56
+ return applyAllBundles({
57
+ cwd,
58
+ homeDir: options.homeDir ?? node_os_1.default.homedir(),
59
+ prompts: addPrompts,
60
+ registryFile: stateLayout.registryFile,
61
+ libraryDir: stateLayout.libraryDir,
62
+ source: addSource,
63
+ protocol: parsed.options.protocol,
64
+ agents: parsed.options.agents,
65
+ dryRun: parsed.options.dryRun,
66
+ ref: parsed.options.ref,
67
+ global: parsed.options.global,
68
+ disableModelInvocation: parsed.options.disableModelInvocation,
69
+ });
70
+ }
71
+ const addBundle = parsed.options.bundle;
72
+ if (addBundle === undefined) {
73
+ throw new Error("Command add requires a bundle name");
74
+ }
48
75
  if (parsed.options.global) {
49
76
  return applyBundleGlobal({
50
77
  homeDir: options.homeDir ?? node_os_1.default.homedir(),
51
- prompts,
78
+ prompts: addPrompts,
52
79
  registryFile: stateLayout.registryFile,
53
80
  libraryDir: stateLayout.libraryDir,
54
- bundle: parsed.options.bundle,
81
+ bundle: addBundle,
55
82
  source: parsed.options.source,
56
83
  protocol: parsed.options.protocol,
57
84
  agents: parsed.options.agents,
@@ -65,10 +92,10 @@ async function run(argv, options = {}) {
65
92
  }
66
93
  return applyBundle({
67
94
  cwd,
68
- prompts,
95
+ prompts: addPrompts,
69
96
  registryFile: stateLayout.registryFile,
70
97
  libraryDir: stateLayout.libraryDir,
71
- bundle: parsed.options.bundle,
98
+ bundle: addBundle,
72
99
  source: parsed.options.source,
73
100
  protocol: parsed.options.protocol,
74
101
  agents: parsed.options.agents,
@@ -79,6 +106,7 @@ async function run(argv, options = {}) {
79
106
  inferredBundleFromSource: parsed.options.inferredBundleFromSource,
80
107
  disableModelInvocation: parsed.options.disableModelInvocation,
81
108
  });
109
+ }
82
110
  case "list":
83
111
  return renderBundleList({
84
112
  libraryDir: stateLayout.libraryDir,
@@ -108,7 +136,7 @@ async function run(argv, options = {}) {
108
136
  case "update":
109
137
  return updateBundles({
110
138
  cwd,
111
- prompts,
139
+ prompts: parsed.options.yes ? createYesPromptClient(prompts) : prompts,
112
140
  registryFile: stateLayout.registryFile,
113
141
  libraryDir: stateLayout.libraryDir,
114
142
  bundle: parsed.options.bundle,
@@ -129,22 +157,48 @@ async function run(argv, options = {}) {
129
157
  if (parsed.options.global) {
130
158
  return resetGlobal({
131
159
  homeDir: options.homeDir ?? node_os_1.default.homedir(),
132
- prompts,
160
+ prompts: parsed.options.yes
161
+ ? createYesPromptClient(prompts)
162
+ : prompts,
133
163
  registryFile: stateLayout.registryFile,
134
164
  dryRun: parsed.options.dryRun,
135
165
  });
136
166
  }
137
167
  return resetWorktree({
138
168
  cwd,
139
- prompts,
169
+ prompts: parsed.options.yes ? createYesPromptClient(prompts) : prompts,
140
170
  registryFile: stateLayout.registryFile,
141
171
  dryRun: parsed.options.dryRun,
142
172
  });
143
173
  case "remove":
174
+ if (parsed.options.all) {
175
+ const removePrompts = parsed.options.yes
176
+ ? createYesPromptClient(prompts)
177
+ : prompts;
178
+ return parsed.options.global
179
+ ? removeAllGlobalBundles({
180
+ homeDir: options.homeDir ?? node_os_1.default.homedir(),
181
+ prompts: removePrompts,
182
+ registryFile: stateLayout.registryFile,
183
+ libraryDir: stateLayout.libraryDir,
184
+ source: parsed.options.source,
185
+ dryRun: parsed.options.dryRun,
186
+ })
187
+ : removeAllWorktreeBundles({
188
+ cwd,
189
+ prompts: removePrompts,
190
+ registryFile: stateLayout.registryFile,
191
+ libraryDir: stateLayout.libraryDir,
192
+ source: parsed.options.source,
193
+ dryRun: parsed.options.dryRun,
194
+ });
195
+ }
144
196
  if (parsed.options.global) {
145
197
  return removeGlobalBundle({
146
198
  homeDir: options.homeDir ?? node_os_1.default.homedir(),
147
- prompts,
199
+ prompts: parsed.options.yes
200
+ ? createYesPromptClient(prompts)
201
+ : prompts,
148
202
  registryFile: stateLayout.registryFile,
149
203
  libraryDir: stateLayout.libraryDir,
150
204
  bundle: parsed.options.bundle,
@@ -157,7 +211,7 @@ async function run(argv, options = {}) {
157
211
  }
158
212
  return removeBundle({
159
213
  cwd,
160
- prompts,
214
+ prompts: parsed.options.yes ? createYesPromptClient(prompts) : prompts,
161
215
  registryFile: stateLayout.registryFile,
162
216
  libraryDir: stateLayout.libraryDir,
163
217
  bundle: parsed.options.bundle,
@@ -171,7 +225,9 @@ async function run(argv, options = {}) {
171
225
  if (parsed.options.global) {
172
226
  return applyGlobal({
173
227
  homeDir: options.homeDir ?? node_os_1.default.homedir(),
174
- prompts,
228
+ prompts: parsed.options.yes
229
+ ? createYesPromptClient(prompts)
230
+ : prompts,
175
231
  registryFile: stateLayout.registryFile,
176
232
  libraryDir: stateLayout.libraryDir,
177
233
  dryRun: parsed.options.dryRun,
@@ -179,7 +235,7 @@ async function run(argv, options = {}) {
179
235
  }
180
236
  return applyWorktree({
181
237
  cwd,
182
- prompts,
238
+ prompts: parsed.options.yes ? createYesPromptClient(prompts) : prompts,
183
239
  registryFile: stateLayout.registryFile,
184
240
  libraryDir: stateLayout.libraryDir,
185
241
  dryRun: parsed.options.dryRun,
@@ -188,6 +244,451 @@ async function run(argv, options = {}) {
188
244
  return assertUnreachable(parsed);
189
245
  }
190
246
  }
247
+ function createYesPromptClient(prompts, options = {}) {
248
+ return {
249
+ ...prompts,
250
+ ...(options.selectAllAgents
251
+ ? {
252
+ selectAgents: async (availableAgents) => availableAgents,
253
+ }
254
+ : {}),
255
+ resolveFileConflict: async () => ({ action: "overwrite" }),
256
+ confirmManagedFileRemoval: async () => true,
257
+ };
258
+ }
259
+ async function applyAllBundles(options) {
260
+ if (options.dryRun) {
261
+ const { cached } = (0, bundle_fetch_1.readCachedSourceRevision)({
262
+ source: options.source,
263
+ libraryDir: options.libraryDir,
264
+ protocol: options.protocol,
265
+ });
266
+ if (!cached) {
267
+ return [
268
+ pc.dim(`(would clone ${options.source})`),
269
+ `${pc.yellow("DRY RUN:")} Would apply all bundles from ${options.source}`,
270
+ ].join("\n");
271
+ }
272
+ return renderAllApplyDryRun({
273
+ libraryDir: options.libraryDir,
274
+ source: options.source,
275
+ agents: options.agents,
276
+ global: options.global,
277
+ });
278
+ }
279
+ const refreshedSources = new Set();
280
+ const refreshedSourceUpdates = new Map();
281
+ const cloneLines = refreshBundleSourceForApply({
282
+ source: options.source,
283
+ libraryDir: options.libraryDir,
284
+ protocol: options.protocol,
285
+ ref: options.ref,
286
+ }, refreshedSources, refreshedSourceUpdates);
287
+ const bundles = listAllApplyBundles({
288
+ libraryDir: options.libraryDir,
289
+ source: options.source,
290
+ agents: options.agents,
291
+ global: options.global,
292
+ });
293
+ if (bundles.length === 0) {
294
+ const agentLabel = options.agents.length > 0
295
+ ? ` supporting ${options.agents.join(", ")}`
296
+ : "";
297
+ throw new Error(`No bundles found for ${options.source}${agentLabel}`);
298
+ }
299
+ const outputLines = [];
300
+ for (const bundle of bundles) {
301
+ outputLines.push(options.global
302
+ ? await applyBundleGlobal({
303
+ homeDir: options.homeDir,
304
+ prompts: options.prompts,
305
+ registryFile: options.registryFile,
306
+ libraryDir: options.libraryDir,
307
+ bundle: bundle.bundle,
308
+ source: bundle.source,
309
+ protocol: options.protocol,
310
+ agents: options.agents,
311
+ includeItems: [],
312
+ selectItems: false,
313
+ dryRun: options.dryRun,
314
+ ref: options.ref,
315
+ refreshedSources,
316
+ refreshedSourceUpdates,
317
+ disableModelInvocation: options.disableModelInvocation,
318
+ })
319
+ : await applyBundle({
320
+ cwd: options.cwd,
321
+ prompts: options.prompts,
322
+ registryFile: options.registryFile,
323
+ libraryDir: options.libraryDir,
324
+ bundle: bundle.bundle,
325
+ source: bundle.source,
326
+ protocol: options.protocol,
327
+ agents: options.agents,
328
+ includeItems: [],
329
+ selectItems: false,
330
+ dryRun: options.dryRun,
331
+ ref: options.ref,
332
+ refreshedSources,
333
+ refreshedSourceUpdates,
334
+ disableModelInvocation: options.disableModelInvocation,
335
+ }));
336
+ }
337
+ return [...cloneLines, ...outputLines].filter(Boolean).join("\n");
338
+ }
339
+ function renderAllApplyDryRun(options) {
340
+ const bundles = listAllApplyBundles(options);
341
+ if (bundles.length === 0) {
342
+ const agentLabel = options.agents.length > 0
343
+ ? ` supporting ${options.agents.join(", ")}`
344
+ : "";
345
+ throw new Error(`No bundles found for ${options.source}${agentLabel}`);
346
+ }
347
+ return bundles
348
+ .map((bundle) => {
349
+ const toolLabel = formatAllApplyDryRunToolLabel({
350
+ bundle,
351
+ agents: options.agents,
352
+ global: options.global,
353
+ });
354
+ const message = options.global
355
+ ? formatApplyGlobalBundleMessage({
356
+ bundle: bundle.bundle,
357
+ toolLabel,
358
+ })
359
+ : formatApplyBundleMessage({
360
+ bundle: bundle.bundle,
361
+ toolLabel,
362
+ });
363
+ return `${pc.yellow("DRY RUN:")} Would ${message}`;
364
+ })
365
+ .join("\n");
366
+ }
367
+ function formatAllApplyDryRunToolLabel(options) {
368
+ if (options.agents.length > 0) {
369
+ return options.agents.join(", ");
370
+ }
371
+ const bundleTools = Object.keys(options.bundle.manifest.tools);
372
+ const toolNames = options.global
373
+ ? bundleTools.filter((toolName) => (0, tool_mapping_1.globalCapableToolNames)().includes(toolName))
374
+ : bundleTools;
375
+ return toolNames.join(", ");
376
+ }
377
+ function listAllApplyBundles(options) {
378
+ const globalTools = (0, tool_mapping_1.globalCapableToolNames)();
379
+ if (options.global &&
380
+ options.agents.some((toolName) => !globalTools.includes(toolName))) {
381
+ throw new Error(`Global mode only supports: ${globalTools.join(", ")}. Unsupported: ${options.agents.filter((toolName) => !globalTools.includes(toolName)).join(", ")}`);
382
+ }
383
+ return (0, bundle_discovery_1.listCachedBundles)({ libraryDir: options.libraryDir })
384
+ .filter((bundle) => isBundleSelectionCandidate({
385
+ bundle,
386
+ source: options.source,
387
+ requestedTools: options.agents,
388
+ }))
389
+ .filter((bundle) => !options.global ||
390
+ Object.keys(bundle.manifest.tools).some((toolName) => globalTools.includes(toolName)))
391
+ .sort((left, right) => compareBundleSelections({ bundle: left.bundle, source: left.source }, { bundle: right.bundle, source: right.source }));
392
+ }
393
+ async function removeAllWorktreeBundles(options) {
394
+ const gitContext = requireGitContext(options.cwd, "remove");
395
+ let registry = readRegistryWithGuidance(options.registryFile);
396
+ const repoState = registry.repos[gitContext.repoFingerprint];
397
+ const selections = listActiveRemoveBundleSelections({
398
+ repoState,
399
+ worktreeState: registry.worktrees[gitContext.worktreeId],
400
+ source: options.source,
401
+ });
402
+ if (selections.length === 0) {
403
+ throw new Error(options.source
404
+ ? `No active bundles found for ${options.source}. Run "skul add ${options.source} <bundle>" to add one first`
405
+ : 'No active bundles found. Run "skul add <bundle>" to add one first');
406
+ }
407
+ if (options.dryRun) {
408
+ return selections
409
+ .map((selection) => renderWorktreeRemoveDryRun({
410
+ selection,
411
+ worktreeState: registry.worktrees[gitContext.worktreeId],
412
+ }))
413
+ .join("\n");
414
+ }
415
+ const worktreeState = registry.worktrees[gitContext.worktreeId];
416
+ const selectionKeys = new Set(selections.map(encodeBundleIdentity));
417
+ const materializedTargets = Object.entries(worktreeState?.materialized_state.bundles ?? {}).filter(([bundle, bundleState]) => selectionKeys.has(encodeBundleIdentity({
418
+ bundle,
419
+ ...(bundleState.source !== undefined
420
+ ? { source: bundleState.source }
421
+ : {}),
422
+ })));
423
+ const shadowedFilePaths = Object.entries(worktreeState?.shadowed_files ?? {})
424
+ .filter(([, shadowedFile]) => selections.some((selection) => selection.bundle === shadowedFile.bundle))
425
+ .map(([filePath]) => filePath);
426
+ const removedBundlePaths = mergeManagedRemovalPaths(materializedTargets.map(([, bundleState]) => flattenBundleState(bundleState)));
427
+ const removedRootInstructionPaths = new Set(removedBundlePaths.files.filter((filePath) => (0, root_instruction_render_1.isRootInstructionPath)(filePath)));
428
+ const remainingBundles = {
429
+ ...(worktreeState?.materialized_state.bundles ?? {}),
430
+ };
431
+ for (const [bundle] of materializedTargets) {
432
+ delete remainingBundles[bundle];
433
+ }
434
+ const remainingDesiredState = repoState?.desired_state.filter((entry) => !selectionKeys.has(encodeBundleIdentity(entry))) ?? [];
435
+ const rewrittenRootInstructionPaths = new Set(Array.from((0, root_instruction_state_1.collectManagedRootInstructionTargets)(remainingBundles)).filter((filePath) => removedRootInstructionPaths.has(filePath)));
436
+ (0, root_instruction_state_1.assertManagedRootInstructionSyncSourcesCached)({
437
+ desiredState: remainingDesiredState,
438
+ materializedBundles: remainingBundles,
439
+ targetPaths: rewrittenRootInstructionPaths,
440
+ resolveCachedBundle: (entry) => resolveDesiredCachedBundle(options.libraryDir, entry),
441
+ });
442
+ if (removedBundlePaths.files.length > 0 || shadowedFilePaths.length > 0) {
443
+ const removeAllowed = await confirmManagedFileRemovals(gitContext.worktreeRoot, removedBundlePaths, options.prompts, "remove");
444
+ if (!removeAllowed) {
445
+ throw new Error("Removal aborted because a modified managed file was kept");
446
+ }
447
+ }
448
+ let currentShadowedFiles = { ...(worktreeState?.shadowed_files ?? {}) };
449
+ currentShadowedFiles = retireTrackedRootInstructionShadows({
450
+ repoRoot: gitContext.worktreeRoot,
451
+ shadowedFiles: currentShadowedFiles,
452
+ filePaths: shadowedFilePaths,
453
+ });
454
+ if (Object.keys(remainingBundles).length > 0) {
455
+ assertTrackedRootInstructionShadowSafetyForPaths({
456
+ repoRoot: gitContext.worktreeRoot,
457
+ operation: "refresh",
458
+ filePaths: Array.from(rewrittenRootInstructionPaths),
459
+ });
460
+ }
461
+ removeManagedPaths(gitContext.worktreeRoot, removedBundlePaths);
462
+ const rootInstructionBaseContents = worktreeState?.materialized_state.root_instruction_base_contents;
463
+ const remainingRootInstructionTargets = (0, root_instruction_state_1.collectManagedRootInstructionTargets)(remainingBundles);
464
+ const restoredRootInstructionPaths = new Set(Array.from(removedRootInstructionPaths).filter((filePath) => !remainingRootInstructionTargets.has(filePath)));
465
+ (0, root_instruction_state_1.restoreRootInstructionBaseContents)({
466
+ repoRoot: gitContext.worktreeRoot,
467
+ baseContents: rootInstructionBaseContents,
468
+ targetPaths: restoredRootInstructionPaths,
469
+ });
470
+ const nextRootInstructionBaseContents = rootInstructionBaseContents
471
+ ? Object.fromEntries(Object.entries(rootInstructionBaseContents).filter(([filePath]) => !restoredRootInstructionPaths.has(filePath)))
472
+ : undefined;
473
+ if (Object.keys(remainingBundles).length > 0) {
474
+ const syncedRootInstructionPaths = (0, root_instruction_state_1.syncManagedRootInstructionFiles)({
475
+ repoRoot: gitContext.worktreeRoot,
476
+ desiredState: remainingDesiredState,
477
+ materializedBundles: remainingBundles,
478
+ rootInstructionBaseContents: nextRootInstructionBaseContents,
479
+ targetPaths: rewrittenRootInstructionPaths,
480
+ resolveCachedBundle: (entry) => resolveDesiredCachedBundle(options.libraryDir, entry),
481
+ });
482
+ const refreshedRemainingBundles = (0, root_instruction_state_1.refreshManagedFileFingerprintsForPaths)(gitContext.worktreeRoot, remainingBundles, syncedRootInstructionPaths);
483
+ const newMatState = {
484
+ bundles: refreshedRemainingBundles,
485
+ exclude_configured: false,
486
+ ...(nextRootInstructionBaseContents !== undefined &&
487
+ Object.keys(nextRootInstructionBaseContents).length > 0
488
+ ? { root_instruction_base_contents: nextRootInstructionBaseContents }
489
+ : {}),
490
+ };
491
+ const managedFiles = collectAllFiles(newMatState);
492
+ newMatState.exclude_configured = managedFiles.length > 0;
493
+ if (managedFiles.length > 0) {
494
+ (0, git_exclude_1.configureSkulExcludeBlock)({
495
+ gitDir: gitContext.gitDir,
496
+ files: managedFiles,
497
+ });
498
+ }
499
+ else {
500
+ (0, git_exclude_1.removeSkulExcludeBlock)({ gitDir: gitContext.gitDir });
501
+ }
502
+ registry = (0, registry_1.upsertWorktreeState)(registry, gitContext.worktreeId, {
503
+ repo_fingerprint: gitContext.repoFingerprint,
504
+ path: gitContext.worktreeRoot,
505
+ materialized_state: newMatState,
506
+ shadowed_files: currentShadowedFiles,
507
+ });
508
+ }
509
+ else {
510
+ (0, git_exclude_1.removeSkulExcludeBlock)({ gitDir: gitContext.gitDir });
511
+ if (Object.keys(currentShadowedFiles).length > 0) {
512
+ registry = (0, registry_1.upsertWorktreeState)(registry, gitContext.worktreeId, {
513
+ repo_fingerprint: gitContext.repoFingerprint,
514
+ path: gitContext.worktreeRoot,
515
+ materialized_state: {
516
+ bundles: {},
517
+ exclude_configured: false,
518
+ },
519
+ shadowed_files: currentShadowedFiles,
520
+ });
521
+ }
522
+ else {
523
+ registry = (0, registry_1.removeWorktreeState)(registry, gitContext.worktreeId);
524
+ }
525
+ }
526
+ if (repoState) {
527
+ registry = (0, registry_1.upsertRepoState)(registry, gitContext.repoFingerprint, {
528
+ ...repoState,
529
+ repo_root: gitContext.repoRoot,
530
+ desired_state: remainingDesiredState,
531
+ });
532
+ }
533
+ (0, registry_1.writeRegistryFile)(options.registryFile, registry);
534
+ return selections
535
+ .map((selection) => `Removed ${selection.bundle}`)
536
+ .join("\n");
537
+ }
538
+ function renderWorktreeRemoveDryRun(options) {
539
+ const bundleMaterializedState = findMaterializedBundleState({
540
+ worktreeState: options.worktreeState,
541
+ bundle: options.selection.bundle,
542
+ source: options.selection.source,
543
+ });
544
+ const shadowedFilesForBundle = Object.entries(options.worktreeState?.shadowed_files ?? {}).filter(([, shadowedFile]) => shadowedFile.bundle === options.selection.bundle);
545
+ return renderRemoveDryRun({
546
+ bundle: options.selection.bundle,
547
+ prefix: "",
548
+ materializedState: bundleMaterializedState,
549
+ extraFiles: shadowedFilesForBundle.map(([filePath]) => filePath),
550
+ desiredStateLabel: "desired state",
551
+ });
552
+ }
553
+ function mergeManagedRemovalPaths(states) {
554
+ return {
555
+ files: Array.from(new Set(states.flatMap((state) => state.files))),
556
+ file_fingerprints: Object.assign({}, ...states.map((state) => state.file_fingerprints ?? {})),
557
+ directories: Array.from(new Set(states.flatMap((state) => state.directories ?? []))),
558
+ };
559
+ }
560
+ async function removeAllGlobalBundles(options) {
561
+ let registry = readRegistryWithGuidance(options.registryFile);
562
+ const selections = listActiveGlobalRemoveBundleSelections({
563
+ globalState: registry.global,
564
+ source: options.source,
565
+ });
566
+ if (selections.length === 0) {
567
+ throw new Error(options.source
568
+ ? `No active global bundles found for ${options.source}. Run "skul add --global ${options.source} <bundle>" to add one first`
569
+ : 'No active global bundles found. Run "skul add --global <bundle>" to add one first');
570
+ }
571
+ if (options.dryRun) {
572
+ return selections
573
+ .map((selection) => renderGlobalRemoveDryRun({
574
+ selection,
575
+ globalState: registry.global,
576
+ }))
577
+ .join("\n");
578
+ }
579
+ const globalState = registry.global;
580
+ const selectionKeys = new Set(selections.map(encodeBundleIdentity));
581
+ const materializedTargets = Object.entries(globalState?.materialized_state.bundles ?? {}).filter(([bundle, bundleState]) => selectionKeys.has(encodeBundleIdentity({
582
+ bundle,
583
+ ...(bundleState.source !== undefined
584
+ ? { source: bundleState.source }
585
+ : {}),
586
+ })));
587
+ const removedBundlePaths = mergeManagedRemovalPaths(materializedTargets.map(([, bundleState]) => flattenBundleState(bundleState)));
588
+ const removedRootInstructionPaths = new Set(removedBundlePaths.files.filter((filePath) => (0, root_instruction_render_1.isRootInstructionPath)(filePath)));
589
+ const remainingBundles = {
590
+ ...(globalState?.materialized_state.bundles ?? {}),
591
+ };
592
+ for (const [bundle] of materializedTargets) {
593
+ delete remainingBundles[bundle];
594
+ }
595
+ const remainingDesiredState = globalState?.desired_state.filter((entry) => !selectionKeys.has(encodeBundleIdentity(entry))) ?? [];
596
+ const rewrittenRootInstructionPaths = new Set(Array.from((0, root_instruction_state_1.collectManagedRootInstructionTargets)(remainingBundles)).filter((filePath) => removedRootInstructionPaths.has(filePath)));
597
+ (0, root_instruction_state_1.assertManagedRootInstructionSyncSourcesCached)({
598
+ desiredState: remainingDesiredState,
599
+ materializedBundles: remainingBundles,
600
+ targetPaths: rewrittenRootInstructionPaths,
601
+ resolveCachedBundle: (entry) => resolveDesiredCachedBundle(options.libraryDir, entry),
602
+ });
603
+ if (removedBundlePaths.files.length > 0) {
604
+ const removeAllowed = await confirmManagedFileRemovals(options.homeDir, removedBundlePaths, options.prompts, "remove");
605
+ if (!removeAllowed) {
606
+ throw new Error("Removal aborted because a modified managed file was kept");
607
+ }
608
+ }
609
+ removeManagedPaths(options.homeDir, removedBundlePaths);
610
+ const rootInstructionBaseContents = globalState?.materialized_state.root_instruction_base_contents;
611
+ const remainingRootInstructionTargets = (0, root_instruction_state_1.collectManagedRootInstructionTargets)(remainingBundles);
612
+ const restoredRootInstructionPaths = new Set(Array.from(removedRootInstructionPaths).filter((filePath) => !remainingRootInstructionTargets.has(filePath)));
613
+ (0, root_instruction_state_1.restoreRootInstructionBaseContents)({
614
+ repoRoot: options.homeDir,
615
+ baseContents: rootInstructionBaseContents,
616
+ targetPaths: restoredRootInstructionPaths,
617
+ });
618
+ const nextRootInstructionBaseContents = rootInstructionBaseContents
619
+ ? Object.fromEntries(Object.entries(rootInstructionBaseContents).filter(([filePath]) => !restoredRootInstructionPaths.has(filePath)))
620
+ : undefined;
621
+ if (Object.keys(remainingBundles).length > 0 ||
622
+ remainingDesiredState.length > 0) {
623
+ if (Object.keys(remainingBundles).length > 0) {
624
+ const syncedRootInstructionPaths = (0, root_instruction_state_1.syncManagedRootInstructionFiles)({
625
+ repoRoot: options.homeDir,
626
+ desiredState: remainingDesiredState,
627
+ materializedBundles: remainingBundles,
628
+ rootInstructionBaseContents: nextRootInstructionBaseContents,
629
+ targetPaths: rewrittenRootInstructionPaths,
630
+ resolveCachedBundle: (entry) => resolveDesiredCachedBundle(options.libraryDir, entry),
631
+ repoRelPathRemapper: tool_mapping_1.GLOBAL_TOOL_MATERIALIZATION_LAYOUT.remapRepoRelPath,
632
+ });
633
+ const refreshedBundles = (0, root_instruction_state_1.refreshManagedFileFingerprintsForPaths)(options.homeDir, remainingBundles, syncedRootInstructionPaths);
634
+ registry = (0, registry_1.upsertGlobalState)(registry, {
635
+ desired_state: remainingDesiredState,
636
+ materialized_state: {
637
+ bundles: refreshedBundles,
638
+ ...(nextRootInstructionBaseContents !== undefined &&
639
+ Object.keys(nextRootInstructionBaseContents).length > 0
640
+ ? {
641
+ root_instruction_base_contents: nextRootInstructionBaseContents,
642
+ }
643
+ : {}),
644
+ },
645
+ });
646
+ }
647
+ else {
648
+ registry = (0, registry_1.upsertGlobalState)(registry, {
649
+ desired_state: remainingDesiredState,
650
+ materialized_state: { bundles: {} },
651
+ });
652
+ }
653
+ }
654
+ else {
655
+ registry = { ...registry, global: undefined };
656
+ }
657
+ (0, registry_1.writeRegistryFile)(options.registryFile, registry);
658
+ return selections
659
+ .map((selection) => `Removed global ${selection.bundle}`)
660
+ .join("\n");
661
+ }
662
+ function renderGlobalRemoveDryRun(options) {
663
+ const bundleMaterializedState = findGlobalMaterializedBundleState({
664
+ globalState: options.globalState,
665
+ bundle: options.selection.bundle,
666
+ source: options.selection.source,
667
+ });
668
+ return renderRemoveDryRun({
669
+ bundle: options.selection.bundle,
670
+ prefix: "global ",
671
+ materializedState: bundleMaterializedState,
672
+ extraFiles: [],
673
+ desiredStateLabel: "global desired state",
674
+ });
675
+ }
676
+ function renderRemoveDryRun(options) {
677
+ if (options.materializedState || options.extraFiles.length > 0) {
678
+ const materializedFiles = options.materializedState
679
+ ? flattenBundleState(options.materializedState).files
680
+ : [];
681
+ const files = [...materializedFiles, ...options.extraFiles];
682
+ const lines = [
683
+ `${pc.yellow("DRY RUN:")} Would remove ${options.prefix}${options.bundle} (${files.length} file(s))`,
684
+ ];
685
+ for (const file of files) {
686
+ lines.push(` ${file}`);
687
+ }
688
+ return lines.join("\n");
689
+ }
690
+ return `${pc.yellow("DRY RUN:")} Would remove ${options.bundle} from ${options.desiredStateLabel}`;
691
+ }
191
692
  function shadowWorktree(options) {
192
693
  const gitContext = requireGitContext(options.cwd, "shadow");
193
694
  let registry = readRegistryWithGuidance(options.registryFile);
@@ -1144,6 +1645,7 @@ async function applyBundle(options) {
1144
1645
  existingDesiredState: registryBeforePrepare.repos[gitContext.repoFingerprint]?.desired_state ??
1145
1646
  [],
1146
1647
  refreshedSources: options.refreshedSources,
1648
+ refreshedSourceUpdates: options.refreshedSourceUpdates,
1147
1649
  });
1148
1650
  if (options.dryRun) {
1149
1651
  return [
@@ -1339,6 +1841,7 @@ async function applyBundle(options) {
1339
1841
  items: preparedBundle.replacesItemSelection
1340
1842
  ? preparedBundle.selectedItems
1341
1843
  : undefined,
1844
+ updated: preparedBundle.sourceUpdated,
1342
1845
  })),
1343
1846
  ].join("\n");
1344
1847
  }
@@ -1347,6 +1850,7 @@ function formatAppliedBundleMessage(options) {
1347
1850
  bundle: options.bundle,
1348
1851
  toolLabel: options.toolLabel,
1349
1852
  items: options.items,
1853
+ updated: options.updated,
1350
1854
  action: "Applied",
1351
1855
  });
1352
1856
  }
@@ -1354,7 +1858,8 @@ function formatApplyBundleMessage(options) {
1354
1858
  const itemLabel = options.items !== undefined && options.items.length > 0
1355
1859
  ? `: ${options.items.join(", ")}`
1356
1860
  : "";
1357
- return `${options.action ?? "apply"} ${options.bundle} for ${options.toolLabel}${itemLabel}`;
1861
+ const updatedLabel = options.updated ? " (Updated)" : "";
1862
+ return `${options.action ?? "apply"} ${options.bundle} for ${options.toolLabel}${itemLabel}${updatedLabel}`;
1358
1863
  }
1359
1864
  function shouldApplySelectedItemsAcrossSourceBundles(options) {
1360
1865
  return (options.selectItems &&
@@ -1363,12 +1868,13 @@ function shouldApplySelectedItemsAcrossSourceBundles(options) {
1363
1868
  }
1364
1869
  async function applySelectedItemsAcrossSourceBundles(options) {
1365
1870
  const refreshedSources = new Set();
1871
+ const refreshedSourceUpdates = new Map();
1366
1872
  const cloneLines = refreshBundleSourceForApply({
1367
1873
  source: options.source,
1368
1874
  libraryDir: options.libraryDir,
1369
1875
  protocol: options.protocol,
1370
1876
  ref: options.ref,
1371
- }, refreshedSources);
1877
+ }, refreshedSources, refreshedSourceUpdates);
1372
1878
  const selection = await selectSourceBundleItemApplyTargets({
1373
1879
  libraryDir: options.libraryDir,
1374
1880
  source: options.source,
@@ -1376,6 +1882,7 @@ async function applySelectedItemsAcrossSourceBundles(options) {
1376
1882
  requestedItems: options.includeItems,
1377
1883
  prompts: options.prompts,
1378
1884
  existingDesiredState: options.existingDesiredState,
1885
+ sourceUpdate: getRefreshedSourceUpdate(refreshedSourceUpdates, options.source),
1379
1886
  });
1380
1887
  const outputLines = [];
1381
1888
  for (const target of selection.removeTargets) {
@@ -1407,6 +1914,7 @@ async function applySelectedItemsAcrossSourceBundles(options) {
1407
1914
  dryRun: options.dryRun,
1408
1915
  ref: options.ref,
1409
1916
  refreshedSources,
1917
+ refreshedSourceUpdates,
1410
1918
  disableModelInvocation: options.disableModelInvocation,
1411
1919
  }));
1412
1920
  }
@@ -1418,6 +1926,7 @@ async function selectSourceBundleItemApplyTargets(options) {
1418
1926
  libraryDir: options.libraryDir,
1419
1927
  source: options.source,
1420
1928
  tools: selectedTools,
1929
+ sourceUpdate: options.sourceUpdate,
1421
1930
  });
1422
1931
  if (choices.length === 0) {
1423
1932
  throw new Error(`No selectable bundle items found for ${options.source}`);
@@ -1494,6 +2003,13 @@ function listSourceBundleItemApplyChoices(options) {
1494
2003
  item,
1495
2004
  }),
1496
2005
  label: `${bundle.bundle}: ${item}`,
2006
+ ...(getUpdatedItemsForBundle({
2007
+ sourceUpdate: options.sourceUpdate,
2008
+ bundle: bundle.bundle,
2009
+ tools: bundleTools,
2010
+ }).has(item)
2011
+ ? { hint: "Updated" }
2012
+ : {}),
1497
2013
  source: bundle.source,
1498
2014
  bundle: bundle.bundle,
1499
2015
  item,
@@ -1616,7 +2132,8 @@ function encodeBundleItemApplyChoice(choice) {
1616
2132
  }
1617
2133
  async function prepareApplyBundle(options) {
1618
2134
  const refreshedSources = options.refreshedSources ?? new Set();
1619
- const cloneLines = refreshBundleSourceForApply(options, refreshedSources);
2135
+ const refreshedSourceUpdates = options.refreshedSourceUpdates ?? new Map();
2136
+ const cloneLines = refreshBundleSourceForApply(options, refreshedSources, refreshedSourceUpdates);
1620
2137
  let cachedBundle;
1621
2138
  let bundleSource;
1622
2139
  let selectedToolsBeforeBundle;
@@ -1662,7 +2179,7 @@ async function prepareApplyBundle(options) {
1662
2179
  libraryDir: options.libraryDir,
1663
2180
  protocol: options.protocol,
1664
2181
  ref: options.ref,
1665
- }, refreshedSources));
2182
+ }, refreshedSources, refreshedSourceUpdates));
1666
2183
  cachedBundle = findCachedBundleWithGuidance({
1667
2184
  libraryDir: options.libraryDir,
1668
2185
  bundle: cachedBundle.bundle,
@@ -1691,6 +2208,14 @@ async function prepareApplyBundle(options) {
1691
2208
  selectedRequestedTools.length < availableTools.length;
1692
2209
  const nextToolNames = selectedRequestedTools;
1693
2210
  const existingDesiredEntry = options.existingDesiredState.find((entry) => entry.bundle === cachedBundle.bundle);
2211
+ const sourceUpdate = bundleSource
2212
+ ? getRefreshedSourceUpdate(refreshedSourceUpdates, bundleSource)
2213
+ : createEmptyRefreshedSourceUpdate();
2214
+ const updatedItems = getUpdatedItemsForBundle({
2215
+ sourceUpdate,
2216
+ bundle: cachedBundle.bundle,
2217
+ tools: nextToolNames,
2218
+ });
1694
2219
  const selectedItems = await resolveSelectedBundleItems({
1695
2220
  bundleDir: node_path_1.default.dirname(cachedBundle.manifestFile),
1696
2221
  manifest: cachedBundle.manifest,
@@ -1700,12 +2225,18 @@ async function prepareApplyBundle(options) {
1700
2225
  replaceItems: options.replaceItems,
1701
2226
  prompts: options.prompts,
1702
2227
  existingItems: existingDesiredEntry?.items,
2228
+ updatedItems,
2229
+ });
2230
+ const sourceUpdated = isSelectedBundleUpdated({
2231
+ updatedItems,
2232
+ selectedItems,
1703
2233
  });
1704
2234
  return {
1705
2235
  cloneLines,
1706
2236
  cachedBundle,
1707
2237
  bundleSource,
1708
2238
  sourceRevision,
2239
+ sourceUpdated,
1709
2240
  ...(hasToolSelection ? { selectedTools: selectedRequestedTools } : {}),
1710
2241
  ...(selectedItems !== undefined ? { selectedItems } : {}),
1711
2242
  nextToolNames,
@@ -1760,6 +2291,13 @@ async function resolveSelectedBundleItems(options) {
1760
2291
  if (!options.selectItems) {
1761
2292
  return mergedItems;
1762
2293
  }
2294
+ if (options.updatedItems && options.updatedItems.size > 0) {
2295
+ return options.prompts.selectBundleItemChoices(availableItems.map((item) => ({
2296
+ value: item,
2297
+ label: item,
2298
+ ...(options.updatedItems?.has(item) ? { hint: "Updated" } : {}),
2299
+ })), mergedItems ?? [], "install");
2300
+ }
1763
2301
  return options.prompts.selectBundleItems(availableItems, mergedItems ?? []);
1764
2302
  }
1765
2303
  function shouldPromptForInferredBundle(options) {
@@ -1772,8 +2310,11 @@ function shouldPromptForInferredBundle(options) {
1772
2310
  }
1773
2311
  return (0, bundle_discovery_1.listCachedBundles)({ libraryDir: options.libraryDir }).some((bundle) => bundle.source === options.source);
1774
2312
  }
1775
- function refreshBundleSourceForApply(options, refreshedSources) {
1776
- if (!options.source || refreshedSources.has(options.source)) {
2313
+ function refreshBundleSourceForApply(options, refreshedSources, refreshedSourceUpdates) {
2314
+ if (!options.source) {
2315
+ return [];
2316
+ }
2317
+ if (refreshedSources.has(options.source)) {
1777
2318
  return [];
1778
2319
  }
1779
2320
  refreshedSources.add(options.source);
@@ -1782,26 +2323,219 @@ function refreshBundleSourceForApply(options, refreshedSources) {
1782
2323
  libraryDir: options.libraryDir,
1783
2324
  protocol: options.protocol,
1784
2325
  });
2326
+ const initialItemFingerprints = initialRevision.cached
2327
+ ? collectSourceItemFingerprints({
2328
+ libraryDir: options.libraryDir,
2329
+ source: options.source,
2330
+ })
2331
+ : new Map();
1785
2332
  if (initialRevision.cached && initialRevision.remoteUrl === undefined) {
2333
+ refreshedSourceUpdates.set(options.source, createEmptyRefreshedSourceUpdate());
1786
2334
  return [];
1787
2335
  }
2336
+ let updated = false;
1788
2337
  if (!options.ref && initialRevision.cached) {
1789
2338
  (0, bundle_fetch_1.clearAndRefetchCachedRemoteSource)({
1790
2339
  source: options.source,
1791
2340
  libraryDir: options.libraryDir,
1792
2341
  protocol: options.protocol,
1793
2342
  });
2343
+ const refreshedRevision = (0, bundle_fetch_1.readCachedSourceRevision)({
2344
+ source: options.source,
2345
+ libraryDir: options.libraryDir,
2346
+ protocol: options.protocol,
2347
+ });
2348
+ updated =
2349
+ initialRevision.currentCommit !== undefined &&
2350
+ refreshedRevision.currentCommit !== undefined &&
2351
+ initialRevision.currentCommit !== refreshedRevision.currentCommit;
1794
2352
  }
1795
2353
  else {
1796
- (0, bundle_fetch_1.updateCachedRemoteSource)({
2354
+ const refreshed = (0, bundle_fetch_1.updateCachedRemoteSource)({
1797
2355
  source: options.source,
1798
2356
  libraryDir: options.libraryDir,
1799
2357
  protocol: options.protocol,
1800
2358
  ref: options.ref,
1801
2359
  });
1802
- }
2360
+ updated =
2361
+ initialRevision.cached &&
2362
+ refreshed.previousCommit !== undefined &&
2363
+ refreshed.currentCommit !== undefined &&
2364
+ refreshed.previousCommit !== refreshed.currentCommit;
2365
+ }
2366
+ refreshedSourceUpdates.set(options.source, {
2367
+ updated,
2368
+ before: initialItemFingerprints,
2369
+ after: updated
2370
+ ? collectSourceItemFingerprints({
2371
+ libraryDir: options.libraryDir,
2372
+ source: options.source,
2373
+ })
2374
+ : new Map(),
2375
+ });
1803
2376
  return initialRevision.cached ? [] : [pc.dim(`Cloned ${options.source}`)];
1804
2377
  }
2378
+ function collectSourceItemFingerprints(options) {
2379
+ return new Map((0, bundle_discovery_1.listCachedBundles)({ libraryDir: options.libraryDir })
2380
+ .filter((bundle) => bundle.source === options.source)
2381
+ .map((bundle) => [
2382
+ bundle.bundle,
2383
+ collectBundleToolItemFingerprints({
2384
+ bundleDir: node_path_1.default.dirname(bundle.manifestFile),
2385
+ manifest: bundle.manifest,
2386
+ }),
2387
+ ]));
2388
+ }
2389
+ function collectBundleToolItemFingerprints(options) {
2390
+ return new Map(Object.keys(options.manifest.tools).map((toolName) => [
2391
+ toolName,
2392
+ collectToolItemFingerprints({
2393
+ bundleDir: options.bundleDir,
2394
+ manifest: options.manifest,
2395
+ toolName,
2396
+ }),
2397
+ ]));
2398
+ }
2399
+ function collectToolItemFingerprints(options) {
2400
+ return new Map((0, bundle_items_1.listSelectableBundleItems)({
2401
+ bundleDir: options.bundleDir,
2402
+ manifest: options.manifest,
2403
+ tools: [options.toolName],
2404
+ }).map((item) => [
2405
+ item,
2406
+ fingerprintBundleItem({
2407
+ bundleDir: options.bundleDir,
2408
+ manifest: options.manifest,
2409
+ tools: [options.toolName],
2410
+ item,
2411
+ }),
2412
+ ]));
2413
+ }
2414
+ function fingerprintBundleItem(options) {
2415
+ const itemPaths = listBundleItemSourcePaths(options);
2416
+ const content = itemPaths
2417
+ .map((itemPath) => {
2418
+ const relativePath = node_path_1.default.relative(options.bundleDir, itemPath);
2419
+ return `${relativePath}\0${fingerprintPath(itemPath)}`;
2420
+ })
2421
+ .join("\0");
2422
+ return (0, node_crypto_1.createHash)("sha256").update(content).digest("hex");
2423
+ }
2424
+ function listBundleItemSourcePaths(options) {
2425
+ const sourcePaths = [];
2426
+ for (const toolName of options.tools) {
2427
+ const targets = options.manifest.tools[toolName];
2428
+ if (!targets) {
2429
+ continue;
2430
+ }
2431
+ if (options.item === "root-instruction") {
2432
+ const rootInstructionPath = targets.root_instruction?.path;
2433
+ if (rootInstructionPath) {
2434
+ sourcePaths.push(node_path_1.default.join(options.bundleDir, rootInstructionPath));
2435
+ }
2436
+ continue;
2437
+ }
2438
+ const [targetName, itemName] = options.item.split("/");
2439
+ const target = targets[targetName];
2440
+ if (!target || !("path" in target) || !itemName) {
2441
+ continue;
2442
+ }
2443
+ sourcePaths.push(...listDirectoryItemSourcePaths({
2444
+ sourceDir: node_path_1.default.join(options.bundleDir, target.path),
2445
+ targetName,
2446
+ itemName,
2447
+ }));
2448
+ }
2449
+ return Array.from(new Set(sourcePaths)).sort((left, right) => left.localeCompare(right));
2450
+ }
2451
+ function listDirectoryItemSourcePaths(options) {
2452
+ if (!node_fs_1.default.existsSync(options.sourceDir)) {
2453
+ return [];
2454
+ }
2455
+ return node_fs_1.default
2456
+ .readdirSync(options.sourceDir, { withFileTypes: true })
2457
+ .filter((entry) => isMatchingBundleItemEntry(entry, options))
2458
+ .map((entry) => node_path_1.default.join(options.sourceDir, entry.name));
2459
+ }
2460
+ function isMatchingBundleItemEntry(entry, options) {
2461
+ if (options.targetName === "skills" && !entry.isDirectory()) {
2462
+ return false;
2463
+ }
2464
+ if (options.targetName !== "skills" && !entry.isFile()) {
2465
+ return false;
2466
+ }
2467
+ return stripKnownBundleItemExtension(entry.name) === options.itemName;
2468
+ }
2469
+ function stripKnownBundleItemExtension(value) {
2470
+ if (value.endsWith(".agent.md")) {
2471
+ return value.slice(0, -".agent.md".length);
2472
+ }
2473
+ return value.replace(/\.(md|toml|yaml|yml|json)$/i, "");
2474
+ }
2475
+ function fingerprintPath(filePath) {
2476
+ if (!node_fs_1.default.existsSync(filePath)) {
2477
+ return "";
2478
+ }
2479
+ const stat = node_fs_1.default.lstatSync(filePath);
2480
+ if (stat.isDirectory()) {
2481
+ return fingerprintDirectory(filePath);
2482
+ }
2483
+ if (stat.isFile()) {
2484
+ return (0, node_crypto_1.createHash)("sha256").update(node_fs_1.default.readFileSync(filePath)).digest("hex");
2485
+ }
2486
+ return "";
2487
+ }
2488
+ function fingerprintDirectory(directoryPath) {
2489
+ const entries = node_fs_1.default
2490
+ .readdirSync(directoryPath, { withFileTypes: true })
2491
+ .filter((entry) => entry.isDirectory() || entry.isFile())
2492
+ .map((entry) => {
2493
+ const entryPath = node_path_1.default.join(directoryPath, entry.name);
2494
+ return `${entry.name}\0${fingerprintPath(entryPath)}`;
2495
+ })
2496
+ .sort((left, right) => left.localeCompare(right))
2497
+ .join("\0");
2498
+ return (0, node_crypto_1.createHash)("sha256").update(entries).digest("hex");
2499
+ }
2500
+ function getRefreshedSourceUpdate(updates, source) {
2501
+ return updates.get(source) ?? createEmptyRefreshedSourceUpdate();
2502
+ }
2503
+ function createEmptyRefreshedSourceUpdate() {
2504
+ return { updated: false, before: new Map(), after: new Map() };
2505
+ }
2506
+ function getUpdatedItemsForBundle(options) {
2507
+ const updatedItems = new Set();
2508
+ if (!options.sourceUpdate?.updated) {
2509
+ return updatedItems;
2510
+ }
2511
+ for (const toolName of options.tools) {
2512
+ const afterItems = options.sourceUpdate.after
2513
+ .get(options.bundle)
2514
+ ?.get(toolName);
2515
+ if (!afterItems) {
2516
+ continue;
2517
+ }
2518
+ for (const [item, afterFingerprint] of afterItems) {
2519
+ const beforeFingerprint = options.sourceUpdate.before
2520
+ .get(options.bundle)
2521
+ ?.get(toolName)
2522
+ ?.get(item);
2523
+ if (beforeFingerprint !== afterFingerprint) {
2524
+ updatedItems.add(item);
2525
+ }
2526
+ }
2527
+ }
2528
+ return updatedItems;
2529
+ }
2530
+ function isSelectedBundleUpdated(options) {
2531
+ if (!options.updatedItems || options.updatedItems.size === 0) {
2532
+ return false;
2533
+ }
2534
+ if (!options.selectedItems) {
2535
+ return true;
2536
+ }
2537
+ return options.selectedItems.some((item) => options.updatedItems?.has(item));
2538
+ }
1805
2539
  function assertBundleSupportsRequestedTools(requestedTools, availableTools) {
1806
2540
  const unsupportedTools = requestedTools.filter((toolName) => !availableTools.includes(toolName));
1807
2541
  if (unsupportedTools.length === 0) {
@@ -3389,6 +4123,7 @@ async function applyBundleGlobal(options) {
3389
4123
  preBundlePrompts: options.prompts,
3390
4124
  inferredBundleFromSource: options.inferredBundleFromSource,
3391
4125
  refreshedSources: options.refreshedSources,
4126
+ refreshedSourceUpdates: options.refreshedSourceUpdates,
3392
4127
  });
3393
4128
  const availableGlobalTools = preparedBundle.nextToolNames.filter((t) => supportedTools.includes(t));
3394
4129
  if (availableGlobalTools.length === 0) {
@@ -3533,6 +4268,7 @@ async function applyBundleGlobal(options) {
3533
4268
  items: preparedBundle.replacesItemSelection
3534
4269
  ? preparedBundle.selectedItems
3535
4270
  : undefined,
4271
+ updated: preparedBundle.sourceUpdated,
3536
4272
  })),
3537
4273
  ];
3538
4274
  if (skippedTools.length > 0) {
@@ -3545,6 +4281,7 @@ function formatAppliedGlobalBundleMessage(options) {
3545
4281
  bundle: options.bundle,
3546
4282
  toolLabel: options.toolLabel,
3547
4283
  items: options.items,
4284
+ updated: options.updated,
3548
4285
  action: "Applied",
3549
4286
  });
3550
4287
  }
@@ -3552,16 +4289,18 @@ function formatApplyGlobalBundleMessage(options) {
3552
4289
  const itemLabel = options.items !== undefined && options.items.length > 0
3553
4290
  ? `: ${options.items.join(", ")}`
3554
4291
  : "";
3555
- return `${options.action ?? "apply"} ${options.bundle} globally for ${options.toolLabel}${itemLabel}`;
4292
+ const updatedLabel = options.updated ? " (Updated)" : "";
4293
+ return `${options.action ?? "apply"} ${options.bundle} globally for ${options.toolLabel}${itemLabel}${updatedLabel}`;
3556
4294
  }
3557
4295
  async function applySelectedItemsAcrossGlobalSourceBundles(options) {
3558
4296
  const refreshedSources = new Set();
4297
+ const refreshedSourceUpdates = new Map();
3559
4298
  const cloneLines = refreshBundleSourceForApply({
3560
4299
  source: options.source,
3561
4300
  libraryDir: options.libraryDir,
3562
4301
  protocol: options.protocol,
3563
4302
  ref: options.ref,
3564
- }, refreshedSources);
4303
+ }, refreshedSources, refreshedSourceUpdates);
3565
4304
  const selection = await selectSourceBundleItemApplyTargets({
3566
4305
  libraryDir: options.libraryDir,
3567
4306
  source: options.source,
@@ -3570,6 +4309,7 @@ async function applySelectedItemsAcrossGlobalSourceBundles(options) {
3570
4309
  prompts: options.prompts,
3571
4310
  existingDesiredState: options.existingDesiredState,
3572
4311
  global: true,
4312
+ sourceUpdate: getRefreshedSourceUpdate(refreshedSourceUpdates, options.source),
3573
4313
  });
3574
4314
  const outputLines = [];
3575
4315
  for (const target of selection.removeTargets) {
@@ -3601,6 +4341,7 @@ async function applySelectedItemsAcrossGlobalSourceBundles(options) {
3601
4341
  dryRun: options.dryRun,
3602
4342
  ref: options.ref,
3603
4343
  refreshedSources,
4344
+ refreshedSourceUpdates,
3604
4345
  disableModelInvocation: options.disableModelInvocation,
3605
4346
  }));
3606
4347
  }