claudekit-cli 3.41.2-dev.3 → 3.41.3-dev.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.
Files changed (2) hide show
  1. package/dist/index.js +161 -33
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -57255,7 +57255,7 @@ var package_default;
57255
57255
  var init_package = __esm(() => {
57256
57256
  package_default = {
57257
57257
  name: "claudekit-cli",
57258
- version: "3.41.2-dev.3",
57258
+ version: "3.41.3-dev.1",
57259
57259
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
57260
57260
  type: "module",
57261
57261
  repository: {
@@ -81441,7 +81441,7 @@ class OwnershipChecker {
81441
81441
  return { path: filePath, ownership: "user", exists: true };
81442
81442
  }
81443
81443
  const relativePath = relative7(claudeDir2, filePath).replace(/\\/g, "/");
81444
- const tracked = allTrackedFiles.find((f3) => f3.path === relativePath);
81444
+ const tracked = allTrackedFiles.find((f3) => f3.path.replace(/\\/g, "/") === relativePath);
81445
81445
  if (!tracked) {
81446
81446
  return { path: filePath, ownership: "user", exists: true };
81447
81447
  }
@@ -92128,6 +92128,7 @@ async function findFileInInstalledKits(claudeDir2, relativePath, excludeKit) {
92128
92128
  };
92129
92129
  }
92130
92130
  async function getUninstallManifest(claudeDir2, kit) {
92131
+ const normalizeTrackedPath = (relativePath) => relativePath.replace(/\\/g, "/");
92131
92132
  const detection = await detectMetadataFormat(claudeDir2);
92132
92133
  if (detection.format === "multi-kit" && detection.metadata?.kits) {
92133
92134
  const installedKits = Object.keys(detection.metadata.kits);
@@ -92142,14 +92143,14 @@ async function getUninstallManifest(claudeDir2, kit) {
92142
92143
  remainingKits: installedKits.filter((k2) => k2 !== kit)
92143
92144
  };
92144
92145
  }
92145
- const kitFiles = kitMeta.files.map((f3) => f3.path);
92146
+ const kitFiles = kitMeta.files.map((f3) => normalizeTrackedPath(f3.path));
92146
92147
  const sharedFiles = new Set;
92147
92148
  for (const otherKit of installedKits) {
92148
92149
  if (otherKit !== kit) {
92149
92150
  const otherMeta = detection.metadata.kits[otherKit];
92150
92151
  if (otherMeta?.files) {
92151
92152
  for (const f3 of otherMeta.files) {
92152
- sharedFiles.add(f3.path);
92153
+ sharedFiles.add(normalizeTrackedPath(f3.path));
92153
92154
  }
92154
92155
  }
92155
92156
  }
@@ -92169,7 +92170,7 @@ async function getUninstallManifest(claudeDir2, kit) {
92169
92170
  }
92170
92171
  const allFiles = getAllTrackedFiles(detection.metadata);
92171
92172
  return {
92172
- filesToRemove: allFiles.map((f3) => f3.path),
92173
+ filesToRemove: allFiles.map((f3) => normalizeTrackedPath(f3.path)),
92173
92174
  filesToPreserve: USER_CONFIG_PATTERNS,
92174
92175
  hasManifest: true,
92175
92176
  isMultiKit: true,
@@ -92177,8 +92178,8 @@ async function getUninstallManifest(claudeDir2, kit) {
92177
92178
  };
92178
92179
  }
92179
92180
  if (detection.format === "legacy" && detection.metadata) {
92180
- const legacyFiles2 = detection.metadata.files?.map((f3) => f3.path) || [];
92181
- const installedFiles = detection.metadata.installedFiles || [];
92181
+ const legacyFiles2 = detection.metadata.files?.map((f3) => normalizeTrackedPath(f3.path)) || [];
92182
+ const installedFiles = (detection.metadata.installedFiles || []).map((path14) => normalizeTrackedPath(path14));
92182
92183
  const hasFiles = legacyFiles2.length > 0 || installedFiles.length > 0;
92183
92184
  if (!hasFiles) {
92184
92185
  const legacyDirs2 = ["commands", "agents", "skills", "rules", "workflows", "hooks", "scripts"];
@@ -95065,6 +95066,90 @@ async function removeKitFromManifest(claudeDir2, kit, options2) {
95065
95066
  }
95066
95067
  }
95067
95068
  }
95069
+ async function retainTrackedFilesInManifest(claudeDir2, retainedPaths, options2) {
95070
+ const metadataPath = join95(claudeDir2, "metadata.json");
95071
+ if (!await import_fs_extra17.pathExists(metadataPath))
95072
+ return false;
95073
+ const normalizedPaths = new Set(retainedPaths.map((path15) => path15.replace(/\\/g, "/")));
95074
+ if (normalizedPaths.size === 0)
95075
+ return false;
95076
+ let release = null;
95077
+ try {
95078
+ if (!options2?.lockHeld) {
95079
+ release = await acquireInstallationStateLock(claudeDir2);
95080
+ }
95081
+ const metadata = await readManifest(claudeDir2);
95082
+ if (!metadata)
95083
+ return false;
95084
+ if (metadata.kits) {
95085
+ const retainedKits = Object.entries(metadata.kits).reduce((acc, [kitName, kitMeta]) => {
95086
+ if (kitName === options2?.excludeKit) {
95087
+ return acc;
95088
+ }
95089
+ const keptFiles = (kitMeta.files || []).flatMap((file) => {
95090
+ const normalizedPath = file.path.replace(/\\/g, "/");
95091
+ if (!normalizedPaths.has(normalizedPath)) {
95092
+ return [];
95093
+ }
95094
+ return [
95095
+ {
95096
+ ...file,
95097
+ path: normalizedPath
95098
+ }
95099
+ ];
95100
+ });
95101
+ if (keptFiles.length > 0) {
95102
+ acc[kitName] = {
95103
+ ...kitMeta,
95104
+ files: keptFiles
95105
+ };
95106
+ }
95107
+ return acc;
95108
+ }, {});
95109
+ if (Object.keys(retainedKits).length === 0) {
95110
+ return false;
95111
+ }
95112
+ const retainedFiles2 = Object.values(retainedKits).flatMap((kitMeta) => kitMeta.files || []);
95113
+ const retainedInstalledFiles2 = retainedFiles2.map((file) => file.path);
95114
+ const updated2 = MetadataSchema.parse({
95115
+ ...metadata,
95116
+ kits: retainedKits,
95117
+ files: retainedFiles2.length > 0 ? retainedFiles2 : undefined,
95118
+ installedFiles: retainedInstalledFiles2.length > 0 ? retainedInstalledFiles2 : undefined
95119
+ });
95120
+ await import_fs_extra17.writeFile(metadataPath, JSON.stringify(updated2, null, 2), "utf-8");
95121
+ return true;
95122
+ }
95123
+ const retainedFiles = (metadata.files || []).flatMap((file) => {
95124
+ const normalizedPath = file.path.replace(/\\/g, "/");
95125
+ if (!normalizedPaths.has(normalizedPath)) {
95126
+ return [];
95127
+ }
95128
+ return [
95129
+ {
95130
+ ...file,
95131
+ path: normalizedPath
95132
+ }
95133
+ ];
95134
+ });
95135
+ const retainedInstalledFiles = (metadata.installedFiles || []).filter((path15) => normalizedPaths.has(path15.replace(/\\/g, "/")));
95136
+ if (retainedFiles.length === 0 && retainedInstalledFiles.length === 0) {
95137
+ return false;
95138
+ }
95139
+ const updated = MetadataSchema.parse({
95140
+ ...metadata,
95141
+ files: retainedFiles.length > 0 ? retainedFiles : undefined,
95142
+ installedFiles: retainedInstalledFiles.length > 0 ? retainedInstalledFiles : undefined
95143
+ });
95144
+ await import_fs_extra17.writeFile(metadataPath, JSON.stringify(updated, null, 2), "utf-8");
95145
+ return true;
95146
+ } finally {
95147
+ if (release) {
95148
+ await release();
95149
+ logger.debug(`Released lock on ${metadataPath}`);
95150
+ }
95151
+ }
95152
+ }
95068
95153
 
95069
95154
  // src/services/file-operations/manifest/manifest-tracker.ts
95070
95155
  class ManifestTracker {
@@ -95232,6 +95317,9 @@ class ManifestWriter {
95232
95317
  static async removeKitFromManifest(claudeDir2, kit, options2) {
95233
95318
  return removeKitFromManifest(claudeDir2, kit, options2);
95234
95319
  }
95320
+ static async retainTrackedFilesInManifest(claudeDir2, retainedPaths, options2) {
95321
+ return retainTrackedFilesInManifest(claudeDir2, retainedPaths, options2);
95322
+ }
95235
95323
  }
95236
95324
 
95237
95325
  // src/domains/migration/legacy-migration.ts
@@ -102307,7 +102395,7 @@ import { basename as basename22, join as join128, resolve as resolve35, sep as s
102307
102395
  init_logger();
102308
102396
  init_safe_prompts();
102309
102397
  init_safe_spinner();
102310
- var import_fs_extra40 = __toESM(require_lib(), 1);
102398
+ var import_fs_extra41 = __toESM(require_lib(), 1);
102311
102399
 
102312
102400
  // src/commands/uninstall/analysis-handler.ts
102313
102401
  init_metadata_migration();
@@ -102315,7 +102403,11 @@ import { readdirSync as readdirSync7, rmSync as rmSync5 } from "node:fs";
102315
102403
  import { dirname as dirname33, join as join127 } from "node:path";
102316
102404
  init_logger();
102317
102405
  init_safe_prompts();
102406
+ var import_fs_extra40 = __toESM(require_lib(), 1);
102318
102407
  var import_picocolors37 = __toESM(require_picocolors(), 1);
102408
+ function normalizeTrackedPath(relativePath) {
102409
+ return relativePath.replace(/\\/g, "/");
102410
+ }
102319
102411
  function classifyFileByOwnership(ownership, forceOverwrite, deleteReason) {
102320
102412
  if (ownership === "ck") {
102321
102413
  return { action: "delete", reason: deleteReason };
@@ -102352,17 +102444,30 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
102352
102444
  const result = {
102353
102445
  toDelete: [],
102354
102446
  toPreserve: [],
102447
+ retainedManifestPaths: [],
102448
+ protectedTrackedPaths: [],
102355
102449
  remainingKits: []
102356
102450
  };
102357
102451
  const metadata = await ManifestWriter.readManifest(installation.path);
102358
102452
  const uninstallManifest = await ManifestWriter.getUninstallManifest(installation.path, kit);
102359
102453
  result.remainingKits = uninstallManifest.remainingKits;
102360
102454
  if (uninstallManifest.isMultiKit && kit && metadata?.kits?.[kit]) {
102455
+ const preservedPaths = new Set(uninstallManifest.filesToPreserve.map((filePath) => normalizeTrackedPath(filePath)));
102456
+ for (const remainingKit of result.remainingKits) {
102457
+ const remainingFiles = metadata.kits?.[remainingKit]?.files || [];
102458
+ for (const file of remainingFiles) {
102459
+ const relativePath = normalizeTrackedPath(file.path);
102460
+ if (await import_fs_extra40.pathExists(join127(installation.path, relativePath))) {
102461
+ result.retainedManifestPaths.push(relativePath);
102462
+ }
102463
+ }
102464
+ }
102361
102465
  const kitFiles = metadata.kits[kit].files || [];
102362
102466
  for (const trackedFile of kitFiles) {
102363
- const filePath = join127(installation.path, trackedFile.path);
102364
- if (uninstallManifest.filesToPreserve.includes(trackedFile.path)) {
102365
- result.toPreserve.push({ path: trackedFile.path, reason: "shared with other kit" });
102467
+ const relativePath = normalizeTrackedPath(trackedFile.path);
102468
+ const filePath = join127(installation.path, relativePath);
102469
+ if (preservedPaths.has(relativePath)) {
102470
+ result.toPreserve.push({ path: relativePath, reason: "shared with other kit" });
102366
102471
  continue;
102367
102472
  }
102368
102473
  const ownershipResult = await OwnershipChecker.checkOwnership(filePath, metadata, installation.path);
@@ -102370,12 +102475,14 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
102370
102475
  continue;
102371
102476
  const classification = classifyFileByOwnership(ownershipResult.ownership, forceOverwrite, `${kit} kit (pristine)`);
102372
102477
  if (classification.action === "delete") {
102373
- result.toDelete.push({ path: trackedFile.path, reason: classification.reason });
102478
+ result.toDelete.push({ path: relativePath, reason: classification.reason });
102374
102479
  } else {
102375
- result.toPreserve.push({ path: trackedFile.path, reason: classification.reason });
102480
+ result.toPreserve.push({ path: relativePath, reason: classification.reason });
102481
+ result.protectedTrackedPaths.push(relativePath);
102482
+ result.retainedManifestPaths.push(relativePath);
102376
102483
  }
102377
102484
  }
102378
- if (result.remainingKits.length === 0) {
102485
+ if (result.retainedManifestPaths.length === 0) {
102379
102486
  result.toDelete.push({ path: "metadata.json", reason: "metadata file" });
102380
102487
  }
102381
102488
  return result;
@@ -102390,18 +102497,23 @@ async function analyzeInstallation(installation, forceOverwrite, kit) {
102390
102497
  return result;
102391
102498
  }
102392
102499
  for (const trackedFile of allTrackedFiles) {
102393
- const filePath = join127(installation.path, trackedFile.path);
102500
+ const relativePath = normalizeTrackedPath(trackedFile.path);
102501
+ const filePath = join127(installation.path, relativePath);
102394
102502
  const ownershipResult = await OwnershipChecker.checkOwnership(filePath, metadata, installation.path);
102395
102503
  if (!ownershipResult.exists)
102396
102504
  continue;
102397
102505
  const classification = classifyFileByOwnership(ownershipResult.ownership, forceOverwrite, "CK-owned (pristine)");
102398
102506
  if (classification.action === "delete") {
102399
- result.toDelete.push({ path: trackedFile.path, reason: classification.reason });
102507
+ result.toDelete.push({ path: relativePath, reason: classification.reason });
102400
102508
  } else {
102401
- result.toPreserve.push({ path: trackedFile.path, reason: classification.reason });
102509
+ result.toPreserve.push({ path: relativePath, reason: classification.reason });
102510
+ result.protectedTrackedPaths.push(relativePath);
102511
+ result.retainedManifestPaths.push(relativePath);
102402
102512
  }
102403
102513
  }
102404
- result.toDelete.push({ path: "metadata.json", reason: "metadata file" });
102514
+ if (result.retainedManifestPaths.length === 0) {
102515
+ result.toDelete.push({ path: "metadata.json", reason: "metadata file" });
102516
+ }
102405
102517
  return result;
102406
102518
  }
102407
102519
  function displayDryRunPreview(analysis, installationType) {
@@ -102435,7 +102547,7 @@ function displayDryRunPreview(analysis, installationType) {
102435
102547
  // src/commands/uninstall/removal-handler.ts
102436
102548
  async function isDirectory(filePath) {
102437
102549
  try {
102438
- const stats = await import_fs_extra40.lstat(filePath);
102550
+ const stats = await import_fs_extra41.lstat(filePath);
102439
102551
  return stats.isDirectory();
102440
102552
  } catch {
102441
102553
  logger.debug(`Failed to check if path is directory: ${filePath}`);
@@ -102443,7 +102555,7 @@ async function isDirectory(filePath) {
102443
102555
  }
102444
102556
  }
102445
102557
  function getUninstallMutatePaths(options2) {
102446
- if (options2.kit && options2.remainingKits.length > 0) {
102558
+ if (options2.retainedManifestPaths.length > 0) {
102447
102559
  return ["metadata.json"];
102448
102560
  }
102449
102561
  return [];
@@ -102466,9 +102578,9 @@ async function isPathSafeToRemove(filePath, baseDir) {
102466
102578
  logger.debug(`Path outside installation directory: ${filePath}`);
102467
102579
  return false;
102468
102580
  }
102469
- const stats = await import_fs_extra40.lstat(filePath);
102581
+ const stats = await import_fs_extra41.lstat(filePath);
102470
102582
  if (stats.isSymbolicLink()) {
102471
- const realPath = await import_fs_extra40.realpath(filePath);
102583
+ const realPath = await import_fs_extra41.realpath(filePath);
102472
102584
  const resolvedReal = resolve35(realPath);
102473
102585
  if (!resolvedReal.startsWith(resolvedBase + sep10) && resolvedReal !== resolvedBase) {
102474
102586
  logger.debug(`Symlink points outside installation directory: ${filePath} -> ${realPath}`);
@@ -102482,6 +102594,7 @@ async function isPathSafeToRemove(filePath, baseDir) {
102482
102594
  }
102483
102595
  }
102484
102596
  async function removeInstallations(installations, options2) {
102597
+ const summaries = [];
102485
102598
  for (const installation of installations) {
102486
102599
  let releaseInstallationLock = null;
102487
102600
  try {
@@ -102500,8 +102613,7 @@ async function removeInstallations(installations, options2) {
102500
102613
  continue;
102501
102614
  }
102502
102615
  const mutatePaths = getUninstallMutatePaths({
102503
- kit: options2.kit,
102504
- remainingKits: analysis.remainingKits
102616
+ retainedManifestPaths: analysis.retainedManifestPaths
102505
102617
  });
102506
102618
  let backup = null;
102507
102619
  if (analysis.toDelete.length > 0 || mutatePaths.length > 0) {
@@ -102530,26 +102642,27 @@ async function removeInstallations(installations, options2) {
102530
102642
  let cleanedDirs = 0;
102531
102643
  for (const item of analysis.toDelete) {
102532
102644
  const filePath = join128(installation.path, item.path);
102533
- if (!await import_fs_extra40.pathExists(filePath))
102645
+ if (!await import_fs_extra41.pathExists(filePath))
102534
102646
  continue;
102535
102647
  if (!await isPathSafeToRemove(filePath, installation.path)) {
102536
102648
  logger.debug(`Skipping unsafe path: ${item.path}`);
102537
102649
  continue;
102538
102650
  }
102539
102651
  const isDir = await isDirectory(filePath);
102540
- await import_fs_extra40.remove(filePath);
102652
+ await import_fs_extra41.remove(filePath);
102541
102653
  removedCount++;
102542
102654
  logger.debug(`Removed ${isDir ? "directory" : "file"}: ${item.path}`);
102543
102655
  if (!isDir) {
102544
102656
  cleanedDirs += await cleanupEmptyDirectories3(filePath, installation.path);
102545
102657
  }
102546
102658
  }
102547
- if (options2.kit && analysis.remainingKits.length > 0) {
102548
- const removed = await ManifestWriter.removeKitFromManifest(installation.path, options2.kit, {
102659
+ if (analysis.retainedManifestPaths.length > 0) {
102660
+ const retained = await ManifestWriter.retainTrackedFilesInManifest(installation.path, [...new Set(analysis.retainedManifestPaths)], {
102661
+ excludeKit: options2.kit && analysis.protectedTrackedPaths.length === 0 ? options2.kit : undefined,
102549
102662
  lockHeld: true
102550
102663
  });
102551
- if (!removed) {
102552
- throw new Error(`Failed to update metadata.json for ${options2.kit} kit uninstall`);
102664
+ if (!retained) {
102665
+ throw new Error("Failed to update metadata.json after partial uninstall");
102553
102666
  }
102554
102667
  }
102555
102668
  try {
@@ -102568,6 +102681,15 @@ async function removeInstallations(installations, options2) {
102568
102681
  log.message(` ... and ${analysis.toPreserve.length - 5} more`);
102569
102682
  }
102570
102683
  }
102684
+ if (analysis.protectedTrackedPaths.length > 0) {
102685
+ log.warn("Protected ClaudeKit files were preserved. Metadata was retained so this installation does not fall back to legacy detection.");
102686
+ log.info("Use --force-overwrite to remove those files on the next uninstall run.");
102687
+ }
102688
+ summaries.push({
102689
+ path: installation.path,
102690
+ preservedCustomizations: analysis.toPreserve.length,
102691
+ protectedTrackedPaths: [...analysis.protectedTrackedPaths]
102692
+ });
102571
102693
  } catch (error) {
102572
102694
  spinner.fail(`Failed to remove ${installation.type} installation`);
102573
102695
  if (backup) {
@@ -102581,6 +102703,7 @@ async function removeInstallations(installations, options2) {
102581
102703
  }
102582
102704
  }
102583
102705
  }
102706
+ return summaries;
102584
102707
  }
102585
102708
 
102586
102709
  // src/commands/uninstall/uninstall-command.ts
@@ -102730,12 +102853,17 @@ ${import_picocolors38.default.yellow("User modifications will be permanently del
102730
102853
  return;
102731
102854
  }
102732
102855
  }
102733
- await removeInstallations(installations, {
102856
+ const results = await removeInstallations(installations, {
102734
102857
  dryRun: false,
102735
102858
  forceOverwrite: validOptions.forceOverwrite,
102736
102859
  kit: validOptions.kit
102737
102860
  });
102861
+ const hasProtectedFiles = results.some((result) => result.protectedTrackedPaths.length > 0);
102738
102862
  const kitMsg = validOptions.kit ? ` (${validOptions.kit} kit)` : "";
102863
+ if (hasProtectedFiles) {
102864
+ prompts.outro(`ClaudeKit${kitMsg} uninstall completed with preserved customizations. Use --force-overwrite for full removal.`);
102865
+ return;
102866
+ }
102739
102867
  prompts.outro(`ClaudeKit${kitMsg} uninstalled successfully!`);
102740
102868
  });
102741
102869
  } catch (error) {
@@ -105278,7 +105406,7 @@ var output2 = new OutputManager2;
105278
105406
 
105279
105407
  // src/shared/temp-cleanup.ts
105280
105408
  init_logger();
105281
- var import_fs_extra41 = __toESM(require_lib(), 1);
105409
+ var import_fs_extra42 = __toESM(require_lib(), 1);
105282
105410
  import { rmSync as rmSync7 } from "node:fs";
105283
105411
  var tempDirs2 = new Set;
105284
105412
  async function cleanup() {
@@ -105287,7 +105415,7 @@ async function cleanup() {
105287
105415
  logger.debug(`Cleaning up ${tempDirs2.size} temporary director(ies)...`);
105288
105416
  for (const dir of tempDirs2) {
105289
105417
  try {
105290
- await import_fs_extra41.remove(dir);
105418
+ await import_fs_extra42.remove(dir);
105291
105419
  logger.debug(`Cleaned up temp directory: ${dir}`);
105292
105420
  } catch (error) {
105293
105421
  logger.debug(`Failed to clean temp directory ${dir}: ${error}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudekit-cli",
3
- "version": "3.41.2-dev.3",
3
+ "version": "3.41.3-dev.1",
4
4
  "description": "CLI tool for bootstrapping and updating ClaudeKit projects",
5
5
  "type": "module",
6
6
  "repository": {