@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/README.md +3 -1
- package/dist/bundle-translation.js +22 -1
- package/dist/bundle-translation.js.map +1 -1
- package/dist/cli.d.ts +9 -1
- package/dist/cli.js +83 -2
- package/dist/cli.js.map +1 -1
- package/dist/cli.test-support.d.ts +54 -0
- package/dist/cli.test-support.js +220 -0
- package/dist/cli.test-support.js.map +1 -0
- package/dist/index.js +763 -22
- package/dist/index.js.map +1 -1
- package/dist/utils/testing.d.ts +38 -0
- package/dist/utils/testing.js +70 -0
- package/dist/utils/testing.js.map +1 -0
- package/package.json +1 -1
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
}
|