claudekit-cli 3.41.4-dev.48 → 3.41.4-dev.49

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
@@ -12416,6 +12416,50 @@ function normalizeChecksum(checksum) {
12416
12416
  function isUnknownChecksum(checksum) {
12417
12417
  return normalizeChecksum(checksum) === UNKNOWN_CHECKSUM;
12418
12418
  }
12419
+ function getReasonCopy(code, _ctx) {
12420
+ switch (code) {
12421
+ case "new-item":
12422
+ return "New — not previously installed";
12423
+ case "new-provider-for-item":
12424
+ return "New provider for existing item";
12425
+ case "target-deleted-source-changed":
12426
+ return "You deleted this, CK has updates — reinstalling";
12427
+ case "target-dir-empty-reinstall":
12428
+ return "Provider directory is empty — reinstalling";
12429
+ case "force-reinstall":
12430
+ return "Force reinstall (target was deleted)";
12431
+ case "force-overwrite":
12432
+ return "Force overwrite (you edited this, --force active)";
12433
+ case "registry-upgrade-reinstall":
12434
+ return "Target deleted — reinstalling after registry upgrade";
12435
+ case "source-changed":
12436
+ return "CK updated, you didn't edit — safe to overwrite";
12437
+ case "registry-upgrade-heal":
12438
+ return "Healing stale target after registry upgrade";
12439
+ case "no-changes":
12440
+ return "Already up to date";
12441
+ case "user-edits-preserved":
12442
+ return "You edited this, CK unchanged — keeping your edits";
12443
+ case "user-deleted-respected":
12444
+ return "You deleted this, CK unchanged — respecting your choice";
12445
+ case "target-up-to-date-backfill":
12446
+ return "Already up to date — registry checksums will be backfilled";
12447
+ case "provider-checksum-unavailable":
12448
+ return "Provider checksum unavailable — cannot verify safely";
12449
+ case "target-state-unknown":
12450
+ return "Target state unavailable, CK unchanged — preserving target";
12451
+ case "source-removed-orphan":
12452
+ return "No longer shipped by CK — will be removed";
12453
+ case "renamed-cleanup":
12454
+ return "Renamed — cleaning up old path";
12455
+ case "path-migrated-cleanup":
12456
+ return "Path migrated — cleaning up old location";
12457
+ case "both-changed":
12458
+ return "Both you and CK changed this — pick one";
12459
+ case "target-state-unknown-source-changed":
12460
+ return "Target state unavailable while CK changed — manual review required";
12461
+ }
12462
+ }
12419
12463
  var UNKNOWN_CHECKSUM = "unknown";
12420
12464
 
12421
12465
  // src/commands/portable/portable-registry.ts
@@ -54357,7 +54401,7 @@ var init_reconcile_registry_backfill = __esm(() => {
54357
54401
  });
54358
54402
 
54359
54403
  // src/commands/portable/reconcile-state-builders.ts
54360
- import { existsSync as existsSync27 } from "node:fs";
54404
+ import { existsSync as existsSync27, readdirSync as readdirSync3, statSync as statSync5 } from "node:fs";
54361
54405
  import { readFile as readFile23 } from "node:fs/promises";
54362
54406
  function getProviderPathKeyForPortableType2(type) {
54363
54407
  switch (type) {
@@ -54453,6 +54497,95 @@ function buildSourceItemState(item, type, selectedProviders, options2) {
54453
54497
  targetChecksums
54454
54498
  };
54455
54499
  }
54500
+ function buildTypeDirectoryStates(providerConfigs, types4) {
54501
+ const results = [];
54502
+ for (const { provider, global: isGlobal } of providerConfigs) {
54503
+ const providerConfig = providers[provider];
54504
+ if (!providerConfig)
54505
+ continue;
54506
+ for (const type of types4) {
54507
+ const pathKey = portableTypeToProviderPathKey(type);
54508
+ const pathConfig = providerConfig[pathKey];
54509
+ if (!pathConfig)
54510
+ continue;
54511
+ if (pathConfig.writeStrategy === "merge-single" || pathConfig.writeStrategy === "single-file") {
54512
+ continue;
54513
+ }
54514
+ const dirPath = isGlobal ? pathConfig.globalPath : pathConfig.projectPath;
54515
+ if (!dirPath)
54516
+ continue;
54517
+ const exists = existsSync27(dirPath);
54518
+ if (!exists) {
54519
+ results.push({
54520
+ provider,
54521
+ type,
54522
+ global: isGlobal,
54523
+ path: dirPath,
54524
+ exists: false,
54525
+ isEmpty: true,
54526
+ fileCount: 0
54527
+ });
54528
+ continue;
54529
+ }
54530
+ let stat7 = null;
54531
+ try {
54532
+ stat7 = statSync5(dirPath);
54533
+ } catch {
54534
+ results.push({
54535
+ provider,
54536
+ type,
54537
+ global: isGlobal,
54538
+ path: dirPath,
54539
+ exists: false,
54540
+ isEmpty: true,
54541
+ fileCount: 0
54542
+ });
54543
+ continue;
54544
+ }
54545
+ if (!stat7.isDirectory()) {
54546
+ results.push({
54547
+ provider,
54548
+ type,
54549
+ global: isGlobal,
54550
+ path: dirPath,
54551
+ exists: true,
54552
+ isEmpty: false,
54553
+ fileCount: 1
54554
+ });
54555
+ continue;
54556
+ }
54557
+ const ext = pathConfig.fileExtension ?? "";
54558
+ let entries = [];
54559
+ try {
54560
+ entries = readdirSync3(dirPath);
54561
+ } catch {
54562
+ results.push({
54563
+ provider,
54564
+ type,
54565
+ global: isGlobal,
54566
+ path: dirPath,
54567
+ exists: true,
54568
+ isEmpty: true,
54569
+ fileCount: 0
54570
+ });
54571
+ continue;
54572
+ }
54573
+ const managedFiles = ext === "" ? entries.filter((f3) => {
54574
+ return f3.endsWith(".json") || f3.endsWith(".sh") || f3.endsWith(".js");
54575
+ }) : entries.filter((f3) => f3.endsWith(ext));
54576
+ results.push({
54577
+ provider,
54578
+ type,
54579
+ global: isGlobal,
54580
+ path: dirPath,
54581
+ exists: true,
54582
+ isEmpty: managedFiles.length === 0,
54583
+ fileCount: managedFiles.length
54584
+ });
54585
+ }
54586
+ }
54587
+ return results;
54588
+ }
54456
54589
  async function buildTargetStates(entries, options2) {
54457
54590
  const targetStates = new Map;
54458
54591
  const entriesByPath = new Map;
@@ -54481,11 +54614,13 @@ async function buildTargetStates(entries, options2) {
54481
54614
  }
54482
54615
  return targetStates;
54483
54616
  }
54617
+ var portableTypeToProviderPathKey;
54484
54618
  var init_reconcile_state_builders = __esm(() => {
54485
54619
  init_checksum_utils();
54486
54620
  init_converters();
54487
54621
  init_merge_single_sections();
54488
54622
  init_provider_registry();
54623
+ portableTypeToProviderPathKey = getProviderPathKeyForPortableType2;
54489
54624
  });
54490
54625
 
54491
54626
  // src/commands/portable/reconciler.ts
@@ -54538,6 +54673,9 @@ function makeItemTypeKey(item, type) {
54538
54673
  function makeRegistryIdentityKey(entry) {
54539
54674
  return JSON.stringify([entry.item, entry.type, entry.provider, entry.global]);
54540
54675
  }
54676
+ function makeDirStateKey(provider, type, global3) {
54677
+ return JSON.stringify([provider, type, global3]);
54678
+ }
54541
54679
  function dedupeProviderConfigs(providerConfigs) {
54542
54680
  const seen = new Set;
54543
54681
  const unique = [];
@@ -54564,6 +54702,14 @@ function buildTargetStateIndex(targetStates) {
54564
54702
  }
54565
54703
  return index;
54566
54704
  }
54705
+ function buildDirStateIndex(dirStates) {
54706
+ const index = new Map;
54707
+ for (const ds of dirStates) {
54708
+ const key = makeDirStateKey(ds.provider, ds.type, ds.global);
54709
+ index.set(key, ds);
54710
+ }
54711
+ return index;
54712
+ }
54567
54713
  function lookupTargetState(targetStateIndex, pathValue) {
54568
54714
  return targetStateIndex.get(normalizePortablePath(pathValue));
54569
54715
  }
@@ -54647,6 +54793,66 @@ function suppressOverlappingActions(actions) {
54647
54793
  }
54648
54794
  return filtered;
54649
54795
  }
54796
+ function applyEmptyDirOverride(actions, dirStates, respectDeletions) {
54797
+ if (dirStates.length === 0) {
54798
+ return { actions, banners: [] };
54799
+ }
54800
+ const dirIndex = buildDirStateIndex(dirStates);
54801
+ const banners = [];
54802
+ const flippedGroups = new Map;
54803
+ for (const action of actions) {
54804
+ if (action.action !== "skip" || action.reasonCode !== "user-deleted-respected") {
54805
+ continue;
54806
+ }
54807
+ const key = makeDirStateKey(action.provider, action.type, action.global);
54808
+ const dirState = dirIndex.get(key);
54809
+ if (!dirState?.isEmpty)
54810
+ continue;
54811
+ if (respectDeletions) {
54812
+ const existing2 = flippedGroups.get(key);
54813
+ if (existing2) {
54814
+ existing2.count++;
54815
+ } else {
54816
+ flippedGroups.set(key, { dirState, count: 1 });
54817
+ }
54818
+ continue;
54819
+ }
54820
+ action.action = "install";
54821
+ action.reasonCode = "target-dir-empty-reinstall";
54822
+ action.reasonCopy = getReasonCopy("target-dir-empty-reinstall");
54823
+ action.reason = action.reasonCopy;
54824
+ const existing = flippedGroups.get(key);
54825
+ if (existing) {
54826
+ existing.count++;
54827
+ } else {
54828
+ flippedGroups.set(key, { dirState, count: 1 });
54829
+ }
54830
+ }
54831
+ for (const [, { dirState, count }] of flippedGroups) {
54832
+ if (respectDeletions) {
54833
+ banners.push({
54834
+ kind: "empty-dir-respected",
54835
+ provider: dirState.provider,
54836
+ type: dirState.type,
54837
+ global: dirState.global,
54838
+ path: dirState.path,
54839
+ itemCount: count,
54840
+ message: `Detected empty ${dirState.path} — respecting your deletions (${count} items skipped).`
54841
+ });
54842
+ } else {
54843
+ banners.push({
54844
+ kind: "empty-dir",
54845
+ provider: dirState.provider,
54846
+ type: dirState.type,
54847
+ global: dirState.global,
54848
+ path: dirState.path,
54849
+ itemCount: count,
54850
+ message: `Detected empty ${dirState.path} — ${count} item${count === 1 ? "" : "s"} will be reinstalled. Uncheck any to skip.`
54851
+ });
54852
+ }
54853
+ }
54854
+ return { actions, banners };
54855
+ }
54650
54856
  function reconcile(input) {
54651
54857
  const actions = [];
54652
54858
  const targetStateIndex = buildTargetStateIndex(input.targetStates);
@@ -54676,7 +54882,10 @@ function reconcile(input) {
54676
54882
  const orphanActions = detectOrphans(input, renamedFromKeys);
54677
54883
  actions.push(...orphanActions);
54678
54884
  const normalizedActions = suppressOverlappingActions(dedupeActions(actions));
54679
- return buildPlan(normalizedActions);
54885
+ const dirStates = input.typeDirectoryStates ?? [];
54886
+ const respectDeletions = input.respectDeletions ?? false;
54887
+ const { actions: finalActions, banners } = applyEmptyDirOverride(normalizedActions, dirStates, respectDeletions);
54888
+ return buildPlan(finalActions, banners);
54680
54889
  }
54681
54890
  function determineAction(source, providerConfig, input, targetStateIndex, deletedIdentityKeys) {
54682
54891
  let registryEntry = findRegistryEntry(source, providerConfig, input.registry);
@@ -54689,12 +54898,14 @@ function determineAction(source, providerConfig, input, targetStateIndex, delete
54689
54898
  if (registryEntry && deletedIdentityKeys.has(identityKey)) {
54690
54899
  registryEntry = null;
54691
54900
  }
54901
+ const isDirectoryItem = source.type === "skill";
54692
54902
  const common = {
54693
54903
  item: source.item,
54694
54904
  type: source.type,
54695
54905
  provider: providerConfig.provider,
54696
54906
  global: providerConfig.global,
54697
- targetPath: ""
54907
+ targetPath: "",
54908
+ isDirectoryItem: isDirectoryItem || undefined
54698
54909
  };
54699
54910
  const convertedChecksumRaw = source.convertedChecksums[providerConfig.provider];
54700
54911
  const convertedChecksum = normalizeChecksum(convertedChecksumRaw);
@@ -54702,30 +54913,39 @@ function determineAction(source, providerConfig, input, targetStateIndex, delete
54702
54913
  if (!convertedChecksumRaw || isUnknownChecksum(convertedChecksumRaw)) {
54703
54914
  if (registryEntry) {
54704
54915
  common.targetPath = registryEntry.path;
54916
+ const code3 = "provider-checksum-unavailable";
54705
54917
  return {
54706
54918
  ...common,
54707
54919
  action: "skip",
54708
54920
  reason: "Provider checksum unavailable — cannot verify safely",
54921
+ reasonCode: code3,
54922
+ reasonCopy: getReasonCopy(code3),
54709
54923
  sourceChecksum: UNKNOWN_CHECKSUM,
54710
54924
  registeredSourceChecksum: normalizeChecksum(registryEntry.sourceChecksum),
54711
54925
  registeredTargetChecksum: normalizeChecksum(registryEntry.targetChecksum)
54712
54926
  };
54713
54927
  }
54714
54928
  const itemExistsElsewhere = input.registry.installations.some((i) => i.item === source.item && i.type === source.type);
54929
+ const code2 = itemExistsElsewhere ? "new-provider-for-item" : "new-item";
54715
54930
  return {
54716
54931
  ...common,
54717
54932
  action: "install",
54718
54933
  reason: itemExistsElsewhere ? "New provider for existing item" : "New item, not previously installed",
54934
+ reasonCode: code2,
54935
+ reasonCopy: getReasonCopy(code2),
54719
54936
  sourceChecksum: UNKNOWN_CHECKSUM
54720
54937
  };
54721
54938
  }
54722
54939
  if (!registryEntry) {
54723
54940
  const itemExistsElsewhere = input.registry.installations.some((i) => i.item === source.item && i.type === source.type);
54941
+ const code2 = itemExistsElsewhere ? "new-provider-for-item" : "new-item";
54724
54942
  const reason = itemExistsElsewhere ? "New provider for existing item" : "New item, not previously installed";
54725
54943
  return {
54726
54944
  ...common,
54727
54945
  action: "install",
54728
54946
  reason,
54947
+ reasonCode: code2,
54948
+ reasonCopy: getReasonCopy(code2),
54729
54949
  sourceChecksum: convertedChecksum
54730
54950
  };
54731
54951
  }
@@ -54737,36 +54957,48 @@ function determineAction(source, providerConfig, input, targetStateIndex, delete
54737
54957
  const targetMatchesExpectedOutput = targetState?.exists === true && !isUnknownChecksum(expectedTargetChecksum) && currentTargetChecksum === expectedTargetChecksum;
54738
54958
  if (isUnknownChecksum(registeredSourceChecksum)) {
54739
54959
  if (targetMatchesExpectedOutput) {
54960
+ const code3 = "target-up-to-date-backfill";
54740
54961
  return {
54741
54962
  ...common,
54742
54963
  action: "skip",
54743
54964
  reason: "Target up-to-date after registry upgrade — checksums will be backfilled",
54965
+ reasonCode: code3,
54966
+ reasonCopy: getReasonCopy(code3),
54744
54967
  sourceChecksum: convertedChecksum,
54745
54968
  currentTargetChecksum,
54746
54969
  backfillRegistry: true
54747
54970
  };
54748
54971
  }
54749
54972
  if (!targetState || !targetState.exists) {
54973
+ const code3 = "registry-upgrade-reinstall";
54750
54974
  return {
54751
54975
  ...common,
54752
54976
  action: "install",
54753
54977
  reason: "Target deleted — reinstalling after registry upgrade",
54978
+ reasonCode: code3,
54979
+ reasonCopy: getReasonCopy(code3),
54754
54980
  sourceChecksum: convertedChecksum
54755
54981
  };
54756
54982
  }
54983
+ const code2 = "registry-upgrade-heal";
54757
54984
  return {
54758
54985
  ...common,
54759
54986
  action: "update",
54760
54987
  reason: "Healing stale target after registry upgrade",
54988
+ reasonCode: code2,
54989
+ reasonCopy: getReasonCopy(code2),
54761
54990
  sourceChecksum: convertedChecksum,
54762
54991
  currentTargetChecksum
54763
54992
  };
54764
54993
  }
54765
54994
  if (targetMatchesExpectedOutput && (convertedChecksum !== registeredSourceChecksum || currentTargetChecksum !== registeredTargetChecksum)) {
54995
+ const code2 = "target-up-to-date-backfill";
54766
54996
  return {
54767
54997
  ...common,
54768
54998
  action: "skip",
54769
54999
  reason: "Target up-to-date — registry checksums will be backfilled",
55000
+ reasonCode: code2,
55001
+ reasonCopy: getReasonCopy(code2),
54770
55002
  sourceChecksum: convertedChecksum,
54771
55003
  registeredSourceChecksum,
54772
55004
  currentTargetChecksum,
@@ -54778,19 +55010,49 @@ function determineAction(source, providerConfig, input, targetStateIndex, delete
54778
55010
  const targetChangeState = getTargetChangeState(targetState, registryEntry, registeredTargetChecksum);
54779
55011
  if (targetChangeState === "deleted") {
54780
55012
  const forceReinstall = input.force && !sourceChanged;
55013
+ if (sourceChanged) {
55014
+ const code3 = "target-deleted-source-changed";
55015
+ return {
55016
+ ...common,
55017
+ action: "install",
55018
+ reason: "Target was deleted, CK has updates — reinstalling",
55019
+ reasonCode: code3,
55020
+ reasonCopy: getReasonCopy(code3),
55021
+ sourceChecksum: convertedChecksum,
55022
+ registeredSourceChecksum
55023
+ };
55024
+ }
55025
+ if (forceReinstall) {
55026
+ const code3 = "force-reinstall";
55027
+ return {
55028
+ ...common,
55029
+ action: "install",
55030
+ reason: "Force reinstall (target was deleted)",
55031
+ reasonCode: code3,
55032
+ reasonCopy: getReasonCopy(code3),
55033
+ sourceChecksum: convertedChecksum,
55034
+ registeredSourceChecksum
55035
+ };
55036
+ }
55037
+ const code2 = "user-deleted-respected";
54781
55038
  return {
54782
55039
  ...common,
54783
- action: sourceChanged || forceReinstall ? "install" : "skip",
54784
- reason: sourceChanged ? "Target was deleted, CK has updates — reinstalling" : forceReinstall ? "Force reinstall (target was deleted)" : "Target was deleted by user, CK unchanged — respecting deletion",
55040
+ action: "skip",
55041
+ reason: "Target was deleted by user, CK unchanged — respecting deletion",
55042
+ reasonCode: code2,
55043
+ reasonCopy: getReasonCopy(code2),
54785
55044
  sourceChecksum: convertedChecksum,
54786
55045
  registeredSourceChecksum
54787
55046
  };
54788
55047
  }
54789
55048
  if (targetChangeState === "unknown") {
55049
+ const code2 = sourceChanged ? "target-state-unknown-source-changed" : "target-state-unknown";
54790
55050
  return {
54791
55051
  ...common,
54792
55052
  action: sourceChanged ? "conflict" : "skip",
54793
55053
  reason: sourceChanged ? "Target state unavailable while CK changed — manual review required" : "Target state unavailable, CK unchanged — preserving target",
55054
+ reasonCode: code2,
55055
+ reasonCopy: getReasonCopy(code2),
54794
55056
  sourceChecksum: convertedChecksum,
54795
55057
  registeredSourceChecksum,
54796
55058
  currentTargetChecksum,
@@ -54799,19 +55061,38 @@ function determineAction(source, providerConfig, input, targetStateIndex, delete
54799
55061
  }
54800
55062
  const targetChanged = targetChangeState === "changed";
54801
55063
  if (!sourceChanged && !targetChanged) {
55064
+ const code2 = "no-changes";
54802
55065
  return {
54803
55066
  ...common,
54804
55067
  action: "skip",
54805
55068
  reason: "No changes",
55069
+ reasonCode: code2,
55070
+ reasonCopy: getReasonCopy(code2),
54806
55071
  sourceChecksum: convertedChecksum,
54807
55072
  currentTargetChecksum
54808
55073
  };
54809
55074
  }
54810
55075
  if (!sourceChanged && targetChanged) {
55076
+ if (input.force) {
55077
+ return {
55078
+ ...common,
55079
+ action: "install",
55080
+ reason: "Force overwrite (user edits)",
55081
+ reasonCode: "force-overwrite",
55082
+ reasonCopy: getReasonCopy("force-overwrite"),
55083
+ sourceChecksum: convertedChecksum,
55084
+ registeredSourceChecksum,
55085
+ currentTargetChecksum,
55086
+ registeredTargetChecksum
55087
+ };
55088
+ }
55089
+ const code2 = "user-edits-preserved";
54811
55090
  return {
54812
55091
  ...common,
54813
- action: input.force ? "install" : "skip",
54814
- reason: input.force ? "Force overwrite (user edits)" : "User edited, CK unchanged — preserving edits",
55092
+ action: "skip",
55093
+ reason: "User edited, CK unchanged — preserving edits",
55094
+ reasonCode: code2,
55095
+ reasonCopy: getReasonCopy(code2),
54815
55096
  sourceChecksum: convertedChecksum,
54816
55097
  registeredSourceChecksum,
54817
55098
  currentTargetChecksum,
@@ -54819,20 +55100,26 @@ function determineAction(source, providerConfig, input, targetStateIndex, delete
54819
55100
  };
54820
55101
  }
54821
55102
  if (sourceChanged && !targetChanged) {
55103
+ const code2 = "source-changed";
54822
55104
  return {
54823
55105
  ...common,
54824
55106
  action: "update",
54825
55107
  reason: "CK updated, no user edits — safe overwrite",
55108
+ reasonCode: code2,
55109
+ reasonCopy: getReasonCopy(code2),
54826
55110
  sourceChecksum: convertedChecksum,
54827
55111
  registeredSourceChecksum,
54828
55112
  currentTargetChecksum,
54829
55113
  registeredTargetChecksum
54830
55114
  };
54831
55115
  }
55116
+ const code = "both-changed";
54832
55117
  return {
54833
55118
  ...common,
54834
55119
  action: "conflict",
54835
55120
  reason: "Both CK and user modified this item",
55121
+ reasonCode: code,
55122
+ reasonCopy: getReasonCopy(code),
54836
55123
  sourceChecksum: convertedChecksum,
54837
55124
  registeredSourceChecksum,
54838
55125
  currentTargetChecksum,
@@ -54868,6 +55155,7 @@ function detectOrphans(input, renamedFromKeys) {
54868
55155
  if (entry.type === "config" && hasConfigSource)
54869
55156
  continue;
54870
55157
  if (!sourceItemKeys.has(sourceItemKey)) {
55158
+ const code = "source-removed-orphan";
54871
55159
  actions.push({
54872
55160
  action: "delete",
54873
55161
  item: entry.item,
@@ -54875,7 +55163,9 @@ function detectOrphans(input, renamedFromKeys) {
54875
55163
  provider: entry.provider,
54876
55164
  global: entry.global,
54877
55165
  targetPath: entry.path,
54878
- reason: "Item no longer in CK source — orphaned"
55166
+ reason: "Item no longer in CK source — orphaned",
55167
+ reasonCode: code,
55168
+ reasonCopy: getReasonCopy(code)
54879
55169
  });
54880
55170
  }
54881
55171
  }
@@ -54894,6 +55184,7 @@ function detectRenames(input) {
54894
55184
  const normalizedFrom = normalizePortablePath(rename8.from);
54895
55185
  const oldEntries = input.registry.installations.filter((e2) => normalizePortablePath(e2.sourcePath) === normalizedFrom);
54896
55186
  for (const oldEntry of oldEntries) {
55187
+ const code = "renamed-cleanup";
54897
55188
  actions.push({
54898
55189
  deleteAction: {
54899
55190
  action: "delete",
@@ -54903,6 +55194,8 @@ function detectRenames(input) {
54903
55194
  global: oldEntry.global,
54904
55195
  targetPath: oldEntry.path,
54905
55196
  reason: `Renamed: ${rename8.from} -> ${rename8.to}`,
55197
+ reasonCode: code,
55198
+ reasonCopy: getReasonCopy(code),
54906
55199
  previousItem: oldEntry.item
54907
55200
  },
54908
55201
  newItem: oldEntry.item
@@ -54919,6 +55212,7 @@ function detectPathMigrations(input) {
54919
55212
  for (const migration of applicable) {
54920
55213
  const affectedEntries = input.registry.installations.filter((e2) => e2.provider === migration.provider && e2.type === migration.type && pathContainsSegments(e2.path, migration.from));
54921
55214
  for (const entry of affectedEntries) {
55215
+ const code = "path-migrated-cleanup";
54922
55216
  actions.push({
54923
55217
  deleteAction: {
54924
55218
  action: "delete",
@@ -54928,6 +55222,8 @@ function detectPathMigrations(input) {
54928
55222
  global: entry.global,
54929
55223
  targetPath: entry.path,
54930
55224
  reason: `Provider path migrated: ${migration.from} -> ${migration.to}`,
55225
+ reasonCode: code,
55226
+ reasonCopy: getReasonCopy(code),
54931
55227
  previousPath: entry.path
54932
55228
  }
54933
55229
  });
@@ -54938,7 +55234,7 @@ function detectPathMigrations(input) {
54938
55234
  function detectSectionRenames(_input) {
54939
55235
  return [];
54940
55236
  }
54941
- function buildPlan(actions) {
55237
+ function buildPlan(actions, banners) {
54942
55238
  const summary = { install: 0, update: 0, skip: 0, conflict: 0, delete: 0 };
54943
55239
  for (const action of actions) {
54944
55240
  summary[action.action]++;
@@ -54946,7 +55242,8 @@ function buildPlan(actions) {
54946
55242
  return {
54947
55243
  actions,
54948
55244
  summary,
54949
- hasConflicts: summary.conflict > 0
55245
+ hasConflicts: summary.conflict > 0,
55246
+ banners
54950
55247
  };
54951
55248
  }
54952
55249
  var init_reconciler = __esm(() => {
@@ -55969,6 +56266,28 @@ function registerMigrationRoutes(app) {
55969
56266
  return;
55970
56267
  }
55971
56268
  const configSource = sourceParsed.value;
56269
+ const reinstallEmptyDirsParsed = parseBooleanLike(req.query.reinstallEmptyDirs);
56270
+ if (!reinstallEmptyDirsParsed.ok) {
56271
+ res.status(400).json({ error: `reinstallEmptyDirs ${reinstallEmptyDirsParsed.error}` });
56272
+ return;
56273
+ }
56274
+ const reinstallEmptyDirs = reinstallEmptyDirsParsed.value !== false;
56275
+ const respectDeletionsParsed = parseBooleanLike(req.query.respectDeletions);
56276
+ if (!respectDeletionsParsed.ok) {
56277
+ res.status(400).json({ error: `respectDeletions ${respectDeletionsParsed.error}` });
56278
+ return;
56279
+ }
56280
+ const respectDeletions = respectDeletionsParsed.value === true;
56281
+ const modeRaw = req.query.mode;
56282
+ let reconcileMode = "reconcile";
56283
+ if (modeRaw !== undefined) {
56284
+ const modeStr = String(modeRaw).trim().toLowerCase();
56285
+ if (modeStr !== "reconcile" && modeStr !== "install") {
56286
+ res.status(400).json({ error: "mode must be 'reconcile' or 'install'" });
56287
+ return;
56288
+ }
56289
+ reconcileMode = modeStr;
56290
+ }
55972
56291
  const discovered = await discoverMigrationItems(include, configSource);
55973
56292
  const sourceItems = [];
55974
56293
  for (const agent of discovered.agents) {
@@ -56054,20 +56373,33 @@ function registerMigrationRoutes(app) {
56054
56373
  provider,
56055
56374
  global: globalParam
56056
56375
  }));
56376
+ const enabledTypes = ["agent", "command", "skill", "config", "rules", "hooks"].filter((type) => {
56377
+ const key = type === "agent" ? "agents" : type === "command" ? "commands" : type === "skill" ? "skills" : type === "config" ? "config" : type === "rules" ? "rules" : "hooks";
56378
+ return include[key];
56379
+ });
56380
+ const typeDirectoryStates = reinstallEmptyDirs ? buildTypeDirectoryStates(providerConfigs.map((p) => ({
56381
+ provider: p.provider,
56382
+ global: p.global
56383
+ })), enabledTypes) : undefined;
56057
56384
  const input = {
56058
56385
  sourceItems,
56059
56386
  registry,
56060
56387
  targetStates,
56061
56388
  manifest,
56062
- providerConfigs
56389
+ providerConfigs,
56390
+ typeDirectoryStates,
56391
+ respectDeletions
56063
56392
  };
56064
56393
  const plan = reconcile(input);
56394
+ const hasUnknownChecksum = registry.installations.some((inst) => isUnknownChecksum(inst.sourceChecksum) || isUnknownChecksum(inst.targetChecksum));
56395
+ const suggestedMode = hasUnknownChecksum ? "install" : "reconcile";
56065
56396
  const planWithMeta = {
56066
56397
  ...plan,
56067
56398
  meta: {
56068
56399
  include,
56069
56400
  providers: selectedProviders,
56070
56401
  source: configSource,
56402
+ mode: reconcileMode,
56071
56403
  items: {
56072
56404
  agents: discovered.agents.map((item) => item.name),
56073
56405
  commands: discovered.commands.map((item) => item.name),
@@ -56078,7 +56410,10 @@ function registerMigrationRoutes(app) {
56078
56410
  }
56079
56411
  }
56080
56412
  };
56081
- res.status(200).json({ plan: planWithMeta });
56413
+ res.status(200).json({
56414
+ plan: planWithMeta,
56415
+ suggestedMode
56416
+ });
56082
56417
  } catch (error) {
56083
56418
  res.status(500).json({
56084
56419
  error: "Failed to compute reconcile plan",
@@ -56086,6 +56421,123 @@ function registerMigrationRoutes(app) {
56086
56421
  });
56087
56422
  }
56088
56423
  });
56424
+ app.get("/api/migrate/install-discovery", async (req, res) => {
56425
+ try {
56426
+ let addCandidates = function(items, type, isDirectoryItem) {
56427
+ for (const item of items) {
56428
+ const sourcePath = item.sourcePath ?? item.path ?? "";
56429
+ for (const provider of selectedProviders) {
56430
+ const registryEntry = registry.installations.find((inst) => inst.item === item.name && inst.type === type && inst.provider === provider && inst.global === globalParam);
56431
+ const alreadyInstalled = registryEntry !== undefined;
56432
+ candidates.push({
56433
+ item: item.name,
56434
+ type,
56435
+ provider,
56436
+ global: globalParam,
56437
+ isDirectoryItem,
56438
+ description: item.description,
56439
+ sourcePath,
56440
+ alreadyInstalled,
56441
+ registryPath: alreadyInstalled ? registryEntry?.path : undefined
56442
+ });
56443
+ }
56444
+ }
56445
+ };
56446
+ const providersParsed = parseProvidersFromQuery(req.query.providers);
56447
+ if (!providersParsed.ok || !providersParsed.value) {
56448
+ res.status(400).json({ error: providersParsed.error || "Invalid providers parameter" });
56449
+ return;
56450
+ }
56451
+ const selectedProviders = providersParsed.value;
56452
+ const includeParsed = parseIncludeOptionsStrict({
56453
+ agents: req.query.agents,
56454
+ commands: req.query.commands,
56455
+ skills: req.query.skills,
56456
+ config: req.query.config,
56457
+ rules: req.query.rules,
56458
+ hooks: req.query.hooks
56459
+ }, "");
56460
+ if (!includeParsed.ok || !includeParsed.value) {
56461
+ res.status(400).json({ error: includeParsed.error || "Invalid include options" });
56462
+ return;
56463
+ }
56464
+ const include = includeParsed.value;
56465
+ const globalParsed = parseBooleanLike(req.query.global);
56466
+ if (!globalParsed.ok) {
56467
+ res.status(400).json({ error: `global ${globalParsed.error}` });
56468
+ return;
56469
+ }
56470
+ const globalParam = globalParsed.value === true;
56471
+ const sourceParsed = parseConfigSource(req.query.source);
56472
+ if (!sourceParsed.ok) {
56473
+ res.status(400).json({ error: sourceParsed.error || "Invalid source value" });
56474
+ return;
56475
+ }
56476
+ const configSource = sourceParsed.value;
56477
+ const discovered = await discoverMigrationItems(include, configSource);
56478
+ const registry = await readPortableRegistry();
56479
+ const candidates = [];
56480
+ if (include.agents) {
56481
+ addCandidates(discovered.agents.map((a3) => ({
56482
+ name: a3.name,
56483
+ description: a3.description,
56484
+ sourcePath: a3.sourcePath ?? ""
56485
+ })), "agent", false);
56486
+ }
56487
+ if (include.commands) {
56488
+ addCandidates(discovered.commands.map((c2) => ({
56489
+ name: c2.name,
56490
+ description: c2.description,
56491
+ sourcePath: c2.sourcePath ?? ""
56492
+ })), "command", false);
56493
+ }
56494
+ if (include.skills) {
56495
+ addCandidates(discovered.skills.map((s) => ({
56496
+ name: s.name,
56497
+ description: s.description,
56498
+ path: s.path
56499
+ })), "skill", true);
56500
+ }
56501
+ if (include.config && discovered.configItem) {
56502
+ addCandidates([
56503
+ {
56504
+ name: discovered.configItem.name,
56505
+ description: undefined,
56506
+ sourcePath: discovered.configItem.sourcePath ?? ""
56507
+ }
56508
+ ], "config", false);
56509
+ }
56510
+ if (include.rules) {
56511
+ addCandidates(discovered.ruleItems.map((r2) => ({
56512
+ name: r2.name,
56513
+ description: undefined,
56514
+ sourcePath: r2.sourcePath ?? ""
56515
+ })), "rules", false);
56516
+ }
56517
+ if (include.hooks) {
56518
+ addCandidates(discovered.hookItems.map((h2) => ({
56519
+ name: h2.name,
56520
+ description: undefined,
56521
+ sourcePath: h2.sourcePath ?? ""
56522
+ })), "hooks", false);
56523
+ }
56524
+ const providerConfigs = selectedProviders.map((provider) => ({
56525
+ provider,
56526
+ global: globalParam
56527
+ }));
56528
+ const enabledTypes = ["agent", "command", "skill", "config", "rules", "hooks"].filter((type) => {
56529
+ const key = type === "agent" ? "agents" : type === "command" ? "commands" : type === "skill" ? "skills" : type === "config" ? "config" : type === "rules" ? "rules" : "hooks";
56530
+ return include[key];
56531
+ });
56532
+ const typeDirectoryStates = buildTypeDirectoryStates(providerConfigs, enabledTypes);
56533
+ res.status(200).json({ candidates, typeDirectoryStates });
56534
+ } catch (error) {
56535
+ res.status(500).json({
56536
+ error: "Failed to discover install candidates",
56537
+ message: sanitizeUntrusted(error, 260)
56538
+ });
56539
+ }
56540
+ });
56089
56541
  app.post("/api/migrate/execute", async (req, res) => {
56090
56542
  try {
56091
56543
  const planBased = req.body?.plan !== undefined;
@@ -56488,7 +56940,7 @@ function registerMigrationRoutes(app) {
56488
56940
  }
56489
56941
  });
56490
56942
  }
56491
- var MIGRATION_TYPES, MAX_PROVIDER_COUNT = 20, MAX_PLAN_ACTIONS = 5000, ALLOWED_CONFIG_SOURCE_KEYS, CONFLICT_RESOLUTION_SCHEMA, RECONCILE_ACTION_SCHEMA, RECONCILE_PLAN_SCHEMA, PLAN_EXECUTE_PAYLOAD_SCHEMA, PLURAL_TO_SINGULAR, shellHookWarningShown = false;
56943
+ var MIGRATION_TYPES, MAX_PROVIDER_COUNT = 20, MAX_PLAN_ACTIONS = 5000, ALLOWED_CONFIG_SOURCE_KEYS, CONFLICT_RESOLUTION_SCHEMA, RECONCILE_ACTION_SCHEMA, RECONCILE_BANNER_SCHEMA, RECONCILE_PLAN_SCHEMA, PLAN_EXECUTE_PAYLOAD_SCHEMA, PLURAL_TO_SINGULAR, shellHookWarningShown = false;
56492
56944
  var init_migration_routes = __esm(() => {
56493
56945
  init_agents_discovery();
56494
56946
  init_commands_discovery();
@@ -56540,8 +56992,20 @@ var init_migration_routes = __esm(() => {
56540
56992
  ownedSections: exports_external.array(exports_external.string()).optional(),
56541
56993
  affectedSections: exports_external.array(exports_external.string()).optional(),
56542
56994
  diff: exports_external.string().optional(),
56543
- resolution: CONFLICT_RESOLUTION_SCHEMA.optional()
56995
+ resolution: CONFLICT_RESOLUTION_SCHEMA.optional(),
56996
+ reasonCode: exports_external.string().optional(),
56997
+ reasonCopy: exports_external.string().optional(),
56998
+ isDirectoryItem: exports_external.boolean().optional()
56544
56999
  }).passthrough();
57000
+ RECONCILE_BANNER_SCHEMA = exports_external.object({
57001
+ kind: exports_external.enum(["empty-dir", "empty-dir-respected"]),
57002
+ provider: exports_external.string(),
57003
+ type: exports_external.string(),
57004
+ global: exports_external.boolean(),
57005
+ path: exports_external.string(),
57006
+ itemCount: exports_external.number().int().nonnegative(),
57007
+ message: exports_external.string()
57008
+ });
56545
57009
  RECONCILE_PLAN_SCHEMA = exports_external.object({
56546
57010
  actions: exports_external.array(RECONCILE_ACTION_SCHEMA).max(MAX_PLAN_ACTIONS),
56547
57011
  summary: exports_external.object({
@@ -56551,11 +57015,13 @@ var init_migration_routes = __esm(() => {
56551
57015
  conflict: exports_external.number().int().nonnegative(),
56552
57016
  delete: exports_external.number().int().nonnegative()
56553
57017
  }),
56554
- hasConflicts: exports_external.boolean()
57018
+ hasConflicts: exports_external.boolean(),
57019
+ banners: exports_external.array(RECONCILE_BANNER_SCHEMA).optional().default([])
56555
57020
  }).passthrough();
56556
57021
  PLAN_EXECUTE_PAYLOAD_SCHEMA = exports_external.object({
56557
57022
  plan: RECONCILE_PLAN_SCHEMA,
56558
- resolutions: exports_external.record(CONFLICT_RESOLUTION_SCHEMA).optional().default({})
57023
+ resolutions: exports_external.record(CONFLICT_RESOLUTION_SCHEMA).optional().default({}),
57024
+ mode: exports_external.enum(["reconcile", "install"]).optional()
56559
57025
  }).passthrough();
56560
57026
  PLURAL_TO_SINGULAR = {
56561
57027
  agents: "agent",
@@ -56568,7 +57034,7 @@ var init_migration_routes = __esm(() => {
56568
57034
  });
56569
57035
 
56570
57036
  // src/domains/plan-parser/plan-metadata.ts
56571
- import { readFileSync as readFileSync6, statSync as statSync5 } from "node:fs";
57037
+ import { readFileSync as readFileSync6, statSync as statSync6 } from "node:fs";
56572
57038
  function readMatter(filePath) {
56573
57039
  try {
56574
57040
  return import_gray_matter6.default(readFileSync6(filePath, "utf8")).data;
@@ -56645,7 +57111,7 @@ function parseEffortHours(value) {
56645
57111
  }
56646
57112
  function readPlanMetadata(planFile, counts) {
56647
57113
  const frontmatter = readMatter(planFile);
56648
- const stats = statSync5(planFile);
57114
+ const stats = statSync6(planFile);
56649
57115
  return {
56650
57116
  title: typeof frontmatter.title === "string" ? frontmatter.title : undefined,
56651
57117
  description: typeof frontmatter.description === "string" ? frontmatter.description : undefined,
@@ -56662,7 +57128,7 @@ function readPlanMetadata(planFile, counts) {
56662
57128
  }
56663
57129
  function readPhaseMetadata(phaseFile) {
56664
57130
  const frontmatter = readMatter(phaseFile);
56665
- const stats = statSync5(phaseFile);
57131
+ const stats = statSync6(phaseFile);
56666
57132
  return {
56667
57133
  title: typeof frontmatter.title === "string" ? frontmatter.title : undefined,
56668
57134
  status: normalizePhaseStatus(frontmatter.status),
@@ -57059,7 +57525,7 @@ var init_plan_table_parser = __esm(() => {
57059
57525
 
57060
57526
  // src/domains/plan-parser/activity-tracker.ts
57061
57527
  import { spawnSync as spawnSync2 } from "node:child_process";
57062
- import { readdirSync as readdirSync3, statSync as statSync6 } from "node:fs";
57528
+ import { readdirSync as readdirSync4, statSync as statSync7 } from "node:fs";
57063
57529
  import { join as join42, relative as relative8 } from "node:path";
57064
57530
  function startOfDay(date) {
57065
57531
  const copy = new Date(date);
@@ -57069,7 +57535,7 @@ function startOfDay(date) {
57069
57535
  function enumerateMarkdownFiles(dir, depth = 0) {
57070
57536
  if (depth >= MAX_DEPTH)
57071
57537
  return [];
57072
- const entries = readdirSync3(dir, { withFileTypes: true });
57538
+ const entries = readdirSync4(dir, { withFileTypes: true });
57073
57539
  return entries.flatMap((entry) => {
57074
57540
  const entryPath = join42(dir, entry.name);
57075
57541
  if (entry.isDirectory())
@@ -57113,7 +57579,7 @@ function getGitSamples(dir, samples) {
57113
57579
  }
57114
57580
  function getMtimeSamples(dir, samples) {
57115
57581
  for (const file of enumerateMarkdownFiles(dir)) {
57116
- const mtimeKey = startOfDay(statSync6(file).mtime).toISOString();
57582
+ const mtimeKey = startOfDay(statSync7(file).mtime).toISOString();
57117
57583
  const sample = samples.get(mtimeKey);
57118
57584
  if (!sample)
57119
57585
  continue;
@@ -57176,13 +57642,13 @@ var DAY_MS = 86400000, CELL_COUNT = 84, MAX_DEPTH = 10;
57176
57642
  var init_activity_tracker = () => {};
57177
57643
 
57178
57644
  // src/domains/plan-parser/plan-scanner.ts
57179
- import { existsSync as existsSync29, readdirSync as readdirSync4 } from "node:fs";
57645
+ import { existsSync as existsSync29, readdirSync as readdirSync5 } from "node:fs";
57180
57646
  import { join as join43 } from "node:path";
57181
57647
  function scanPlanDir(dir) {
57182
57648
  if (!existsSync29(dir))
57183
57649
  return [];
57184
57650
  try {
57185
- return readdirSync4(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join43(dir, entry.name, "plan.md")).filter(existsSync29);
57651
+ return readdirSync5(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join43(dir, entry.name, "plan.md")).filter(existsSync29);
57186
57652
  } catch {
57187
57653
  return [];
57188
57654
  }
@@ -60228,7 +60694,7 @@ var init_skill_browser_routes = __esm(() => {
60228
60694
  });
60229
60695
 
60230
60696
  // src/commands/skills/agents.ts
60231
- import { existsSync as existsSync37, readdirSync as readdirSync5, statSync as statSync7 } from "node:fs";
60697
+ import { existsSync as existsSync37, readdirSync as readdirSync6, statSync as statSync8 } from "node:fs";
60232
60698
  import { homedir as homedir37, platform as platform5 } from "node:os";
60233
60699
  import { join as join54 } from "node:path";
60234
60700
  function hasInstallSignal2(path6) {
@@ -60236,9 +60702,9 @@ function hasInstallSignal2(path6) {
60236
60702
  return false;
60237
60703
  }
60238
60704
  try {
60239
- const stat10 = statSync7(path6);
60705
+ const stat10 = statSync8(path6);
60240
60706
  if (stat10.isDirectory()) {
60241
- return readdirSync5(path6).length > 0;
60707
+ return readdirSync6(path6).length > 0;
60242
60708
  }
60243
60709
  if (stat10.isFile()) {
60244
60710
  return true;
@@ -61617,7 +62083,7 @@ var package_default;
61617
62083
  var init_package = __esm(() => {
61618
62084
  package_default = {
61619
62085
  name: "claudekit-cli",
61620
- version: "3.41.4-dev.48",
62086
+ version: "3.41.4-dev.49",
61621
62087
  description: "CLI tool for bootstrapping and updating ClaudeKit projects",
61622
62088
  type: "module",
61623
62089
  repository: {
@@ -61660,6 +62126,8 @@ var init_package = __esm(() => {
61660
62126
  "test:integration": "CK_RUN_CLI_INTEGRATION=1 bun test tests/integration/cli.test.ts",
61661
62127
  "test:watch": "bun test --watch",
61662
62128
  "test:quick": "./scripts/dev-quick-start.sh test",
62129
+ "test:e2e": "playwright test --config=playwright.config.ts",
62130
+ "test:e2e:ui": "playwright test --ui --config=playwright.config.ts",
61663
62131
  lint: "biome check .",
61664
62132
  "lint:fix": "biome check --fix .",
61665
62133
  "lint:fix-unsafe": "biome check --fix --unsafe .",
@@ -61718,6 +62186,7 @@ var init_package = __esm(() => {
61718
62186
  },
61719
62187
  devDependencies: {
61720
62188
  "@biomejs/biome": "^1.9.4",
62189
+ "@playwright/test": "^1.59.1",
61721
62190
  "@semantic-release/changelog": "^6.0.3",
61722
62191
  "@semantic-release/git": "^10.0.1",
61723
62192
  "@tauri-apps/cli": "^2",
@@ -72915,7 +73384,7 @@ var init_content_validator = __esm(() => {
72915
73384
 
72916
73385
  // src/commands/content/phases/context-cache-manager.ts
72917
73386
  import { createHash as createHash9 } from "node:crypto";
72918
- import { existsSync as existsSync75, mkdirSync as mkdirSync5, readFileSync as readFileSync18, readdirSync as readdirSync10, statSync as statSync13 } from "node:fs";
73387
+ import { existsSync as existsSync75, mkdirSync as mkdirSync5, readFileSync as readFileSync18, readdirSync as readdirSync11, statSync as statSync14 } from "node:fs";
72919
73388
  import { rename as rename14, writeFile as writeFile38 } from "node:fs/promises";
72920
73389
  import { homedir as homedir52 } from "node:os";
72921
73390
  import { basename as basename30, join as join158 } from "node:path";
@@ -72951,7 +73420,7 @@ function computeSourceHash(repoPath) {
72951
73420
  const paths = getDocSourcePaths(repoPath);
72952
73421
  for (const filePath of paths) {
72953
73422
  try {
72954
- const stat25 = statSync13(filePath);
73423
+ const stat25 = statSync14(filePath);
72955
73424
  hash.update(`${filePath}:${stat25.mtimeMs}`);
72956
73425
  } catch {
72957
73426
  hash.update(`${filePath}:0`);
@@ -72964,7 +73433,7 @@ function getDocSourcePaths(repoPath) {
72964
73433
  const docsDir = join158(repoPath, "docs");
72965
73434
  if (existsSync75(docsDir)) {
72966
73435
  try {
72967
- const files = readdirSync10(docsDir);
73436
+ const files = readdirSync11(docsDir);
72968
73437
  for (const f3 of files) {
72969
73438
  if (f3.endsWith(".md"))
72970
73439
  paths.push(join158(docsDir, f3));
@@ -72977,7 +73446,7 @@ function getDocSourcePaths(repoPath) {
72977
73446
  const stylesDir = join158(repoPath, "assets", "writing-styles");
72978
73447
  if (existsSync75(stylesDir)) {
72979
73448
  try {
72980
- const files = readdirSync10(stylesDir);
73449
+ const files = readdirSync11(stylesDir);
72981
73450
  for (const f3 of files) {
72982
73451
  paths.push(join158(stylesDir, f3));
72983
73452
  }
@@ -73172,7 +73641,7 @@ function extractContentFromResponse(response) {
73172
73641
 
73173
73642
  // src/commands/content/phases/docs-summarizer.ts
73174
73643
  import { execSync as execSync7 } from "node:child_process";
73175
- import { existsSync as existsSync76, readFileSync as readFileSync19, readdirSync as readdirSync11 } from "node:fs";
73644
+ import { existsSync as existsSync76, readFileSync as readFileSync19, readdirSync as readdirSync12 } from "node:fs";
73176
73645
  import { join as join159 } from "node:path";
73177
73646
  async function summarizeProjectDocs(repoPath, contentLogger) {
73178
73647
  const rawContent = collectRawDocs(repoPath);
@@ -73230,7 +73699,7 @@ function collectRawDocs(repoPath) {
73230
73699
  const docsDir = join159(repoPath, "docs");
73231
73700
  if (existsSync76(docsDir)) {
73232
73701
  try {
73233
- const files = readdirSync11(docsDir).filter((f3) => f3.endsWith(".md")).sort();
73702
+ const files = readdirSync12(docsDir).filter((f3) => f3.endsWith(".md")).sort();
73234
73703
  for (const f3 of files) {
73235
73704
  const content = readCapped(join159(docsDir, f3), 5000);
73236
73705
  if (content) {
@@ -73254,7 +73723,7 @@ ${content}`);
73254
73723
  const stylesDir = join159(repoPath, "assets", "writing-styles");
73255
73724
  if (existsSync76(stylesDir)) {
73256
73725
  try {
73257
- const files = readdirSync11(stylesDir).slice(0, 3);
73726
+ const files = readdirSync12(stylesDir).slice(0, 3);
73258
73727
  styles3 = files.map((f3) => readCapped(join159(stylesDir, f3), 1000)).filter(Boolean).join(`
73259
73728
 
73260
73729
  `);
@@ -73445,7 +73914,7 @@ IMPORTANT: Generate the image and output the path as JSON: {"imagePath": "/path/
73445
73914
 
73446
73915
  // src/commands/content/phases/photo-generator.ts
73447
73916
  import { execSync as execSync8 } from "node:child_process";
73448
- import { existsSync as existsSync77, mkdirSync as mkdirSync6, readdirSync as readdirSync12 } from "node:fs";
73917
+ import { existsSync as existsSync77, mkdirSync as mkdirSync6, readdirSync as readdirSync13 } from "node:fs";
73449
73918
  import { homedir as homedir53 } from "node:os";
73450
73919
  import { join as join160 } from "node:path";
73451
73920
  async function generatePhoto(_content, context, config, platform17, contentId, contentLogger) {
@@ -73470,7 +73939,7 @@ async function generatePhoto(_content, context, config, platform17, contentId, c
73470
73939
  return { path: imagePath, ...dimensions, format: "png" };
73471
73940
  }
73472
73941
  }
73473
- const files = readdirSync12(mediaDir);
73942
+ const files = readdirSync13(mediaDir);
73474
73943
  const imageFile = files.find((f3) => /\.(png|jpg|jpeg|webp)$/i.test(f3));
73475
73944
  if (imageFile) {
73476
73945
  const ext2 = imageFile.split(".").pop() ?? "png";
@@ -73562,7 +74031,7 @@ var init_content_creator = __esm(() => {
73562
74031
  });
73563
74032
 
73564
74033
  // src/commands/content/phases/content-logger.ts
73565
- import { createWriteStream as createWriteStream4, existsSync as existsSync78, mkdirSync as mkdirSync7, statSync as statSync14 } from "node:fs";
74034
+ import { createWriteStream as createWriteStream4, existsSync as existsSync78, mkdirSync as mkdirSync7, statSync as statSync15 } from "node:fs";
73566
74035
  import { homedir as homedir54 } from "node:os";
73567
74036
  import { join as join161 } from "node:path";
73568
74037
 
@@ -73628,7 +74097,7 @@ class ContentLogger {
73628
74097
  if (this.maxBytes > 0 && this.stream) {
73629
74098
  const logPath = join161(this.logDir, `content-${this.currentDate}.log`);
73630
74099
  try {
73631
- const stat25 = statSync14(logPath);
74100
+ const stat25 = statSync15(logPath);
73632
74101
  if (stat25.size >= this.maxBytes) {
73633
74102
  this.close();
73634
74103
  const suffix = Date.now();
@@ -73857,7 +74326,7 @@ function isNoiseCommit(title, author) {
73857
74326
 
73858
74327
  // src/commands/content/phases/change-detector.ts
73859
74328
  import { execSync as execSync10, spawnSync as spawnSync9 } from "node:child_process";
73860
- import { existsSync as existsSync80, readFileSync as readFileSync20, readdirSync as readdirSync13, statSync as statSync15 } from "node:fs";
74329
+ import { existsSync as existsSync80, readFileSync as readFileSync20, readdirSync as readdirSync14, statSync as statSync16 } from "node:fs";
73861
74330
  import { join as join162 } from "node:path";
73862
74331
  function detectCommits(repo, since) {
73863
74332
  try {
@@ -73973,7 +74442,7 @@ function detectCompletedPlans(repo, since) {
73973
74442
  const sinceMs = new Date(since).getTime();
73974
74443
  const events = [];
73975
74444
  try {
73976
- const entries = readdirSync13(plansDir, { withFileTypes: true });
74445
+ const entries = readdirSync14(plansDir, { withFileTypes: true });
73977
74446
  for (const entry of entries) {
73978
74447
  if (!entry.isDirectory())
73979
74448
  continue;
@@ -73981,7 +74450,7 @@ function detectCompletedPlans(repo, since) {
73981
74450
  if (!existsSync80(planFile))
73982
74451
  continue;
73983
74452
  try {
73984
- const stat25 = statSync15(planFile);
74453
+ const stat25 = statSync16(planFile);
73985
74454
  if (stat25.mtimeMs < sinceMs)
73986
74455
  continue;
73987
74456
  const content = readFileSync20(planFile, "utf-8");
@@ -74054,7 +74523,7 @@ function classifyCommit(event) {
74054
74523
 
74055
74524
  // src/commands/content/phases/repo-discoverer.ts
74056
74525
  import { execSync as execSync11 } from "node:child_process";
74057
- import { readdirSync as readdirSync14 } from "node:fs";
74526
+ import { readdirSync as readdirSync15 } from "node:fs";
74058
74527
  import { join as join163 } from "node:path";
74059
74528
  function discoverRepos2(cwd2) {
74060
74529
  const repos = [];
@@ -74064,7 +74533,7 @@ function discoverRepos2(cwd2) {
74064
74533
  repos.push(info);
74065
74534
  }
74066
74535
  try {
74067
- const entries = readdirSync14(cwd2, { withFileTypes: true });
74536
+ const entries = readdirSync15(cwd2, { withFileTypes: true });
74068
74537
  for (const entry of entries) {
74069
74538
  if (!entry.isDirectory() || entry.name.startsWith("."))
74070
74539
  continue;
@@ -76774,19 +77243,40 @@ var init_migrate_command_help = __esm(() => {
76774
77243
  usage: "ck migrate [options]",
76775
77244
  examples: [
76776
77245
  {
76777
- command: "ck migrate --agent codex --dry-run",
76778
- description: "Preview the destination-aware migration plan before writing files"
77246
+ command: "ck migrate --install",
77247
+ description: "Pick items to install interactively (install picker mode)"
76779
77248
  },
76780
77249
  {
76781
- command: "ck migrate --agent codex -g",
76782
- description: "Write to Codex global paths such as ~/.codex/ and ~/.agents/skills"
77250
+ command: "ck migrate --agent codex --dry-run",
77251
+ description: "Preview the destination-aware reconcile plan before writing files"
76783
77252
  },
76784
77253
  {
76785
- command: "CK_FORCE_ASCII=1 ck migrate --agent codex",
76786
- description: "Force ASCII borders on legacy Windows terminals (cmd.exe, older PowerShell)"
77254
+ command: "ck migrate --respect-deletions",
77255
+ description: "Preserve empty directories do not auto-reinstall deleted items"
76787
77256
  }
76788
77257
  ],
76789
77258
  optionGroups: [
77259
+ {
77260
+ title: "Mode Options",
77261
+ options: [
77262
+ {
77263
+ flags: "--install",
77264
+ description: "Opt-in install picker mode — interactively select which items to install (default when registry is empty or has unknown checksums)"
77265
+ },
77266
+ {
77267
+ flags: "--reconcile",
77268
+ description: "Force reconcile mode — compute diff vs registry and apply only changes (default when registry is valid)"
77269
+ },
77270
+ {
77271
+ flags: "--reinstall-empty-dirs",
77272
+ description: "Reinstall all items when their type directory is empty or missing (default: true). Use --respect-deletions to disable."
77273
+ },
77274
+ {
77275
+ flags: "--respect-deletions",
77276
+ description: "Preserve deletion even when a type directory is empty — skip reinstall heuristic. Mutually exclusive with --reinstall-empty-dirs."
77277
+ }
77278
+ ]
77279
+ },
76790
77280
  {
76791
77281
  title: "Target Options",
76792
77282
  options: [
@@ -76849,6 +77339,19 @@ var init_migrate_command_help = __esm(() => {
76849
77339
  }
76850
77340
  ]
76851
77341
  }
77342
+ ],
77343
+ sections: [
77344
+ {
77345
+ title: "Gotchas",
77346
+ content: [
77347
+ " --install and --reconcile are mutually exclusive — pass only one",
77348
+ " --reinstall-empty-dirs and --respect-deletions are mutually exclusive — pass only one",
77349
+ " Default mode is smart-detected: no/stale registry → install, valid registry → reconcile",
77350
+ " --respect-deletions disables the auto-reinstall heuristic for empty directories",
77351
+ " --force overrides skip decisions per item; --reinstall-empty-dirs is a per-directory heuristic"
77352
+ ].join(`
77353
+ `)
77354
+ }
76852
77355
  ]
76853
77356
  };
76854
77357
  });
@@ -84326,7 +84829,7 @@ async function checkCliInstallMethod() {
84326
84829
  };
84327
84830
  }
84328
84831
  // src/domains/health-checks/checkers/claude-md-checker.ts
84329
- import { existsSync as existsSync48, statSync as statSync8 } from "node:fs";
84832
+ import { existsSync as existsSync48, statSync as statSync9 } from "node:fs";
84330
84833
  import { join as join76 } from "node:path";
84331
84834
  function checkClaudeMd(setup, projectDir) {
84332
84835
  const results = [];
@@ -84352,7 +84855,7 @@ function checkClaudeMdFile(path6, name, id) {
84352
84855
  };
84353
84856
  }
84354
84857
  try {
84355
- const stat13 = statSync8(path6);
84858
+ const stat13 = statSync9(path6);
84356
84859
  const sizeKB = (stat13.size / 1024).toFixed(1);
84357
84860
  if (stat13.size === 0) {
84358
84861
  return {
@@ -85363,7 +85866,7 @@ init_command_normalizer();
85363
85866
  init_logger();
85364
85867
  init_path_resolver();
85365
85868
  import { spawnSync as spawnSync3 } from "node:child_process";
85366
- import { existsSync as existsSync55, readFileSync as readFileSync15, statSync as statSync9, writeFileSync as writeFileSync5 } from "node:fs";
85869
+ import { existsSync as existsSync55, readFileSync as readFileSync15, statSync as statSync10, writeFileSync as writeFileSync5 } from "node:fs";
85367
85870
  import { readdir as readdir21 } from "node:fs/promises";
85368
85871
  import { homedir as homedir43, tmpdir as tmpdir2 } from "node:os";
85369
85872
  import { join as join86, resolve as resolve30 } from "node:path";
@@ -86211,7 +86714,7 @@ async function checkHookLogs(projectDir) {
86211
86714
  };
86212
86715
  }
86213
86716
  try {
86214
- const logStats = statSync9(logPath);
86717
+ const logStats = statSync10(logPath);
86215
86718
  if (logStats.size > MAX_LOG_FILE_SIZE_BYTES) {
86216
86719
  return {
86217
86720
  id: "hook-logs",
@@ -98253,7 +98756,7 @@ async function handleDownload(ctx) {
98253
98756
  import { join as join122 } from "node:path";
98254
98757
 
98255
98758
  // src/domains/installation/deletion-handler.ts
98256
- import { existsSync as existsSync61, lstatSync as lstatSync3, readdirSync as readdirSync6, rmSync as rmSync2, rmdirSync, unlinkSync as unlinkSync4 } from "node:fs";
98759
+ import { existsSync as existsSync61, lstatSync as lstatSync3, readdirSync as readdirSync7, rmSync as rmSync2, rmdirSync, unlinkSync as unlinkSync4 } from "node:fs";
98257
98760
  import { dirname as dirname35, join as join108, relative as relative17, resolve as resolve34, sep as sep10 } from "node:path";
98258
98761
 
98259
98762
  // src/services/file-operations/manifest/manifest-reader.ts
@@ -98447,7 +98950,7 @@ function collectFilesRecursively(dir, baseDir) {
98447
98950
  if (!existsSync61(dir))
98448
98951
  return results;
98449
98952
  try {
98450
- const entries = readdirSync6(dir, { withFileTypes: true });
98953
+ const entries = readdirSync7(dir, { withFileTypes: true });
98451
98954
  for (const entry of entries) {
98452
98955
  const fullPath = join108(dir, entry.name);
98453
98956
  const relativePath = relative17(baseDir, fullPath);
@@ -98485,7 +98988,7 @@ function cleanupEmptyDirectories(filePath, claudeDir3) {
98485
98988
  while (currentDir !== normalizedClaudeDir && currentDir.startsWith(normalizedClaudeDir) && iterations < MAX_CLEANUP_ITERATIONS) {
98486
98989
  iterations++;
98487
98990
  try {
98488
- const entries = readdirSync6(currentDir);
98991
+ const entries = readdirSync7(currentDir);
98489
98992
  if (entries.length === 0) {
98490
98993
  rmdirSync(currentDir);
98491
98994
  logger.debug(`Removed empty directory: ${currentDir}`);
@@ -104163,7 +104666,7 @@ async function runPreflightChecks() {
104163
104666
 
104164
104667
  // src/domains/installation/fresh-installer.ts
104165
104668
  init_metadata_migration();
104166
- import { existsSync as existsSync63, readdirSync as readdirSync7, rmSync as rmSync3, rmdirSync as rmdirSync2, unlinkSync as unlinkSync5 } from "node:fs";
104669
+ import { existsSync as existsSync63, readdirSync as readdirSync8, rmSync as rmSync3, rmdirSync as rmdirSync2, unlinkSync as unlinkSync5 } from "node:fs";
104167
104670
  import { basename as basename24, dirname as dirname39, join as join134, resolve as resolve36 } from "node:path";
104168
104671
  init_logger();
104169
104672
  init_safe_spinner();
@@ -104216,7 +104719,7 @@ function cleanupEmptyDirectories2(filePath, claudeDir3) {
104216
104719
  let currentDir = resolve36(dirname39(filePath));
104217
104720
  while (currentDir !== normalizedClaudeDir && currentDir.startsWith(normalizedClaudeDir)) {
104218
104721
  try {
104219
- const entries = readdirSync7(currentDir);
104722
+ const entries = readdirSync8(currentDir);
104220
104723
  if (entries.length === 0) {
104221
104724
  rmdirSync2(currentDir);
104222
104725
  logger.debug(`Removed empty directory: ${currentDir}`);
@@ -106916,6 +107419,30 @@ function buildProviderScopeSubtitle(selectedProviders, global3) {
106916
107419
  }
106917
107420
  return `${selectedProviders.length} providers -> ${scope}`;
106918
107421
  }
107422
+ function renderBannerLines(banner) {
107423
+ const width = 64;
107424
+ const bar = `+${"=".repeat(width)}+`;
107425
+ const homePath = process.env.HOME ?? "";
107426
+ const displayPath = banner.path.replace(homePath, "~");
107427
+ if (banner.kind === "empty-dir") {
107428
+ return [
107429
+ bar,
107430
+ `| [i] Detected empty ${displayPath}`,
107431
+ `| ${banner.itemCount} item(s) below will be reinstalled.`,
107432
+ "| Use --respect-deletions to preserve deletion.",
107433
+ bar
107434
+ ];
107435
+ }
107436
+ if (banner.kind === "empty-dir-respected") {
107437
+ return [
107438
+ bar,
107439
+ `| [i] Detected empty ${displayPath}`,
107440
+ `| ${banner.itemCount} item(s) skipped (--respect-deletions active).`,
107441
+ bar
107442
+ ];
107443
+ }
107444
+ return [];
107445
+ }
106919
107446
  function buildSourceSummaryLines(counts, origins) {
106920
107447
  const parts = [];
106921
107448
  if (counts.agents > 0)
@@ -106939,6 +107466,148 @@ function buildSourceSummaryLines(counts, origins) {
106939
107466
 
106940
107467
  // src/commands/migrate/migrate-command.ts
106941
107468
  init_skill_directory_installer();
107469
+ function validateMutualExclusion(options2) {
107470
+ if (options2.install && options2.reconcile) {
107471
+ return "Pass either --install or --reconcile, not both.";
107472
+ }
107473
+ if (options2.reinstallEmptyDirs && options2.respectDeletions) {
107474
+ return "Pass either --reinstall-empty-dirs or --respect-deletions, not both.";
107475
+ }
107476
+ return null;
107477
+ }
107478
+ function resolveMigrationMode(options2, hasUnknownChecksums) {
107479
+ if (options2.install)
107480
+ return "install";
107481
+ if (options2.reconcile)
107482
+ return "reconcile";
107483
+ if (hasUnknownChecksums)
107484
+ return "install";
107485
+ return "reconcile";
107486
+ }
107487
+ function renderBanners(banners) {
107488
+ if (banners.length === 0)
107489
+ return;
107490
+ for (const banner of banners) {
107491
+ const lines = renderBannerLines(banner);
107492
+ if (lines.length > 0) {
107493
+ console.log();
107494
+ for (const line of lines) {
107495
+ console.log(line);
107496
+ }
107497
+ }
107498
+ }
107499
+ }
107500
+ async function runInstallMode(options2, discoveredItems, _selectedProviders, _installGlobally) {
107501
+ const interactive = process.stdout.isTTY && !options2.yes;
107502
+ if (!interactive) {
107503
+ return discoveredItems;
107504
+ }
107505
+ const toOption = (item) => ({
107506
+ value: item.name,
107507
+ label: item.name,
107508
+ hint: item.recommended ? "Recommended" : undefined
107509
+ });
107510
+ let selectedAgents = discoveredItems.agents;
107511
+ let selectedCommands = discoveredItems.commands;
107512
+ let selectedSkills = discoveredItems.skills;
107513
+ let selectedConfig = discoveredItems.configItem;
107514
+ let selectedRules = discoveredItems.ruleItems;
107515
+ let selectedHooks = discoveredItems.hookItems;
107516
+ if (discoveredItems.agents.length > 0) {
107517
+ const picked = await ae({
107518
+ message: `Select agents to install (${discoveredItems.agents.length} available)`,
107519
+ options: discoveredItems.agents.map(toOption),
107520
+ initialValues: discoveredItems.agents.map((a3) => a3.name),
107521
+ required: false
107522
+ });
107523
+ if (lD(picked)) {
107524
+ ue("Migrate cancelled");
107525
+ process.exit(0);
107526
+ }
107527
+ const pickedSet = new Set(picked);
107528
+ selectedAgents = discoveredItems.agents.filter((a3) => pickedSet.has(a3.name));
107529
+ }
107530
+ if (discoveredItems.commands.length > 0) {
107531
+ const picked = await ae({
107532
+ message: `Select commands to install (${discoveredItems.commands.length} available)`,
107533
+ options: discoveredItems.commands.map(toOption),
107534
+ initialValues: discoveredItems.commands.map((c2) => c2.name),
107535
+ required: false
107536
+ });
107537
+ if (lD(picked)) {
107538
+ ue("Migrate cancelled");
107539
+ process.exit(0);
107540
+ }
107541
+ const pickedSet = new Set(picked);
107542
+ selectedCommands = discoveredItems.commands.filter((c2) => pickedSet.has(c2.name));
107543
+ }
107544
+ if (discoveredItems.skills.length > 0) {
107545
+ const picked = await ae({
107546
+ message: `Select skills to install (${discoveredItems.skills.length} available, directory-level)`,
107547
+ options: discoveredItems.skills.map((s) => ({
107548
+ value: s.name,
107549
+ label: s.name,
107550
+ hint: "skill directory"
107551
+ })),
107552
+ initialValues: discoveredItems.skills.map((s) => s.name),
107553
+ required: false
107554
+ });
107555
+ if (lD(picked)) {
107556
+ ue("Migrate cancelled");
107557
+ process.exit(0);
107558
+ }
107559
+ const pickedSet = new Set(picked);
107560
+ selectedSkills = discoveredItems.skills.filter((s) => pickedSet.has(s.name));
107561
+ }
107562
+ if (discoveredItems.configItem) {
107563
+ const include = await se({
107564
+ message: "Include CLAUDE.md config?",
107565
+ initialValue: true
107566
+ });
107567
+ if (lD(include)) {
107568
+ ue("Migrate cancelled");
107569
+ process.exit(0);
107570
+ }
107571
+ if (!include)
107572
+ selectedConfig = null;
107573
+ }
107574
+ if (discoveredItems.ruleItems.length > 0) {
107575
+ const picked = await ae({
107576
+ message: `Select rules to install (${discoveredItems.ruleItems.length} available)`,
107577
+ options: discoveredItems.ruleItems.map(toOption),
107578
+ initialValues: discoveredItems.ruleItems.map((r2) => r2.name),
107579
+ required: false
107580
+ });
107581
+ if (lD(picked)) {
107582
+ ue("Migrate cancelled");
107583
+ process.exit(0);
107584
+ }
107585
+ const pickedSet = new Set(picked);
107586
+ selectedRules = discoveredItems.ruleItems.filter((r2) => pickedSet.has(r2.name));
107587
+ }
107588
+ if (discoveredItems.hookItems.length > 0) {
107589
+ const picked = await ae({
107590
+ message: `Select hooks to install (${discoveredItems.hookItems.length} available)`,
107591
+ options: discoveredItems.hookItems.map(toOption),
107592
+ initialValues: discoveredItems.hookItems.map((h2) => h2.name),
107593
+ required: false
107594
+ });
107595
+ if (lD(picked)) {
107596
+ ue("Migrate cancelled");
107597
+ process.exit(0);
107598
+ }
107599
+ const pickedSet = new Set(picked);
107600
+ selectedHooks = discoveredItems.hookItems.filter((h2) => pickedSet.has(h2.name));
107601
+ }
107602
+ return {
107603
+ agents: selectedAgents,
107604
+ commands: selectedCommands,
107605
+ skills: selectedSkills,
107606
+ configItem: selectedConfig,
107607
+ ruleItems: selectedRules,
107608
+ hookItems: selectedHooks
107609
+ };
107610
+ }
106942
107611
  function getProviderPathKey(type) {
106943
107612
  switch (type) {
106944
107613
  case "agent":
@@ -106953,8 +107622,6 @@ function getProviderPathKey(type) {
106953
107622
  return "hooks";
106954
107623
  case "skill":
106955
107624
  return "skills";
106956
- default:
106957
- return type;
106958
107625
  }
106959
107626
  }
106960
107627
  function shouldExecuteAction2(action) {
@@ -107036,6 +107703,11 @@ function inferKitTypeFromSourceMetadata(sourceMetadata) {
107036
107703
  }
107037
107704
  async function migrateCommand(options2) {
107038
107705
  console.log();
107706
+ const mutexError = validateMutualExclusion(options2);
107707
+ if (mutexError) {
107708
+ f2.error(mutexError);
107709
+ process.exit(1);
107710
+ }
107039
107711
  try {
107040
107712
  const scope = resolveMigrationScope(process.argv.slice(2), options2);
107041
107713
  const spinner = de();
@@ -107229,31 +107901,66 @@ async function migrateCommand(options2) {
107229
107901
  setTaxonomyOverrides(ckConfigResult.config.modelTaxonomy);
107230
107902
  const reconcileSpinner = de();
107231
107903
  reconcileSpinner.start("Computing migration plan...");
107904
+ const registry = await readPortableRegistry();
107905
+ const hasUnknownChecksums = registry.installations.some((entry) => !entry.sourceChecksum || entry.sourceChecksum === "unknown" || !entry.targetChecksum || entry.targetChecksum === "unknown");
107906
+ const migrationMode = resolveMigrationMode(options2, hasUnknownChecksums);
107907
+ let effectiveAgents = agents2;
107908
+ let effectiveCommands = commands;
107909
+ let effectiveSkills = skills;
107910
+ let effectiveConfigItem = configItem;
107911
+ let effectiveRuleItems = ruleItems;
107912
+ let effectiveHookItems = hookItems;
107913
+ if (migrationMode === "install") {
107914
+ reconcileSpinner.stop("Discovery complete");
107915
+ if (!options2.yes && process.stdout.isTTY) {
107916
+ f2.info(`[i] Smart default: ${hasUnknownChecksums ? "unknown checksums detected" : "--install flag"} — entering install picker mode.`);
107917
+ }
107918
+ const picked = await runInstallMode(options2, {
107919
+ agents: agents2,
107920
+ commands,
107921
+ skills,
107922
+ configItem,
107923
+ ruleItems,
107924
+ hookItems
107925
+ }, selectedProviders, installGlobally);
107926
+ effectiveAgents = picked.agents;
107927
+ effectiveCommands = picked.commands;
107928
+ effectiveSkills = picked.skills;
107929
+ effectiveConfigItem = picked.configItem;
107930
+ effectiveRuleItems = picked.ruleItems;
107931
+ effectiveHookItems = picked.hookItems;
107932
+ reconcileSpinner.start("Computing migration plan...");
107933
+ }
107232
107934
  const sourceStates = await computeSourceStates({
107233
- agents: agents2,
107234
- commands,
107235
- config: configItem,
107236
- rules: ruleItems,
107237
- hooks: hookItems
107935
+ agents: effectiveAgents,
107936
+ commands: effectiveCommands,
107937
+ config: effectiveConfigItem,
107938
+ rules: effectiveRuleItems,
107939
+ hooks: effectiveHookItems
107238
107940
  }, selectedProviders);
107239
107941
  const targetStates = await computeTargetStates(selectedProviders, installGlobally);
107240
- const registry = await readPortableRegistry();
107241
107942
  const providerConfigs = selectedProviders.map((provider) => ({
107242
107943
  provider,
107243
107944
  global: installGlobally
107244
107945
  }));
107946
+ const portableTypes = ["agent", "command", "config", "rules", "hooks"];
107947
+ const typeDirectoryStates = buildTypeDirectoryStates(selectedProviders.map((provider) => ({ provider, global: installGlobally })), [...portableTypes]);
107948
+ const reinstallEmptyDirs = options2.respectDeletions ? false : options2.reinstallEmptyDirs ?? true;
107245
107949
  const plan = reconcile({
107246
107950
  sourceItems: sourceStates,
107247
107951
  registry,
107248
107952
  targetStates,
107249
107953
  providerConfigs,
107250
- force: options2.force
107954
+ force: options2.force,
107955
+ typeDirectoryStates,
107956
+ respectDeletions: !reinstallEmptyDirs
107251
107957
  });
107252
107958
  reconcileSpinner.stop("Plan computed");
107253
107959
  const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
107960
+ renderBanners(plan.banners);
107254
107961
  displayReconcilePlan(plan, { color: useColor });
107255
107962
  if (options2.dryRun) {
107256
- displayMigrationSummary(plan, buildDryRunFallbackResults(skills, selectedProviders, installGlobally, plan.actions), { color: useColor, dryRun: true });
107963
+ displayMigrationSummary(plan, buildDryRunFallbackResults(effectiveSkills, selectedProviders, installGlobally, plan.actions), { color: useColor, dryRun: true });
107257
107964
  return;
107258
107965
  }
107259
107966
  if (plan.hasConflicts) {
@@ -107263,7 +107970,7 @@ async function migrateCommand(options2) {
107263
107970
  if (!action.diff && action.targetPath && existsSync64(action.targetPath)) {
107264
107971
  try {
107265
107972
  const targetContent = await readFile61(action.targetPath, "utf-8");
107266
- const sourceItem = agents2.find((a3) => a3.name === action.item) || commands.find((c2) => c2.name === action.item) || (configItem?.name === action.item ? configItem : null) || ruleItems.find((r2) => r2.name === action.item) || hookItems.find((h2) => h2.name === action.item);
107973
+ const sourceItem = effectiveAgents.find((a3) => a3.name === action.item) || effectiveCommands.find((c2) => c2.name === action.item) || (effectiveConfigItem?.name === action.item ? effectiveConfigItem : null) || effectiveRuleItems.find((r2) => r2.name === action.item) || effectiveHookItems.find((h2) => h2.name === action.item);
107267
107974
  if (sourceItem) {
107268
107975
  const providerConfig = providers[action.provider];
107269
107976
  const pathConfigKey = getProviderPathKey(action.type);
@@ -107301,12 +108008,12 @@ async function migrateCommand(options2) {
107301
108008
  }
107302
108009
  let allResults = [];
107303
108010
  const installOpts = { global: installGlobally };
107304
- const agentByName = new Map(agents2.map((item) => [item.name, item]));
107305
- const commandByName = new Map(commands.map((item) => [item.name, item]));
107306
- const skillByName = new Map(skills.map((item) => [item.name, item]));
107307
- const configByName = new Map(configItem ? [[configItem.name, configItem]] : []);
107308
- const ruleByName = new Map(ruleItems.map((item) => [item.name, item]));
107309
- const hookByName = new Map(hookItems.map((item) => [item.name, item]));
108011
+ const agentByName = new Map(effectiveAgents.map((item) => [item.name, item]));
108012
+ const commandByName = new Map(effectiveCommands.map((item) => [item.name, item]));
108013
+ const skillByName = new Map(effectiveSkills.map((item) => [item.name, item]));
108014
+ const configByName = new Map(effectiveConfigItem ? [[effectiveConfigItem.name, effectiveConfigItem]] : []);
108015
+ const ruleByName = new Map(effectiveRuleItems.map((item) => [item.name, item]));
108016
+ const hookByName = new Map(effectiveHookItems.map((item) => [item.name, item]));
107310
108017
  const successfulHookFiles = new Map;
107311
108018
  const successfulHookAbsPaths = new Map;
107312
108019
  const postProgressWarnings = [];
@@ -107358,10 +108065,10 @@ async function migrateCommand(options2) {
107358
108065
  }
107359
108066
  }
107360
108067
  const plannedSkillActions = plannedExecActions.filter((action) => action.type === "skill").length;
107361
- if (skills.length > 0 && plannedSkillActions === 0) {
108068
+ if (effectiveSkills.length > 0 && plannedSkillActions === 0) {
107362
108069
  const skillProviders = selectedProviders.filter((pv) => getProvidersSupporting("skills").includes(pv));
107363
108070
  for (const provider of skillProviders) {
107364
- for (const skill of skills) {
108071
+ for (const skill of effectiveSkills) {
107365
108072
  writeTasks.push({ item: skill, provider, type: "skill" });
107366
108073
  }
107367
108074
  }
@@ -108069,7 +108776,7 @@ Please use only one download method.`);
108069
108776
  }
108070
108777
  // src/commands/plan/plan-command.ts
108071
108778
  init_output_manager();
108072
- import { existsSync as existsSync67, statSync as statSync11 } from "node:fs";
108779
+ import { existsSync as existsSync67, statSync as statSync12 } from "node:fs";
108073
108780
  import { dirname as dirname46, isAbsolute as isAbsolute11, join as join147, parse as parse7, resolve as resolve45 } from "node:path";
108074
108781
 
108075
108782
  // src/commands/plan/plan-read-handlers.ts
@@ -108079,7 +108786,7 @@ init_plans_registry();
108079
108786
  init_logger();
108080
108787
  init_output_manager();
108081
108788
  var import_picocolors32 = __toESM(require_picocolors(), 1);
108082
- import { existsSync as existsSync66, statSync as statSync10 } from "node:fs";
108789
+ import { existsSync as existsSync66, statSync as statSync11 } from "node:fs";
108083
108790
  import { basename as basename27, dirname as dirname44, join as join146, relative as relative27, resolve as resolve43 } from "node:path";
108084
108791
 
108085
108792
  // src/commands/plan/plan-dependencies.ts
@@ -108249,7 +108956,7 @@ async function handleStatus(target, options2) {
108249
108956
  }
108250
108957
  const effectiveTarget = !resolvedTarget && globalBaseDir ? globalBaseDir : resolvedTarget;
108251
108958
  const t = effectiveTarget ? resolve43(effectiveTarget) : null;
108252
- const plansDir = t && existsSync66(t) && statSync10(t).isDirectory() && !existsSync66(join146(t, "plan.md")) ? t : null;
108959
+ const plansDir = t && existsSync66(t) && statSync11(t).isDirectory() && !existsSync66(join146(t, "plan.md")) ? t : null;
108253
108960
  if (plansDir) {
108254
108961
  const planFiles = scanPlanDir(plansDir);
108255
108962
  if (planFiles.length === 0) {
@@ -108661,7 +109368,7 @@ function resolveTargetPath(target, baseDir) {
108661
109368
  function resolvePlanFile(target, baseDir) {
108662
109369
  const t = target ? resolveTargetPath(target, baseDir) : baseDir ? resolve45(baseDir) : process.cwd();
108663
109370
  if (existsSync67(t)) {
108664
- const stat23 = statSync11(t);
109371
+ const stat23 = statSync12(t);
108665
109372
  if (stat23.isFile())
108666
109373
  return t;
108667
109374
  const candidate = join147(t, "plan.md");
@@ -109874,7 +110581,7 @@ async function detectInstallations() {
109874
110581
  }
109875
110582
 
109876
110583
  // src/commands/uninstall/removal-handler.ts
109877
- import { readdirSync as readdirSync9, rmSync as rmSync5 } from "node:fs";
110584
+ import { readdirSync as readdirSync10, rmSync as rmSync5 } from "node:fs";
109878
110585
  import { basename as basename29, join as join150, resolve as resolve47, sep as sep12 } from "node:path";
109879
110586
  init_logger();
109880
110587
  init_safe_prompts();
@@ -109883,7 +110590,7 @@ var import_fs_extra44 = __toESM(require_lib3(), 1);
109883
110590
 
109884
110591
  // src/commands/uninstall/analysis-handler.ts
109885
110592
  init_metadata_migration();
109886
- import { readdirSync as readdirSync8, rmSync as rmSync4 } from "node:fs";
110593
+ import { readdirSync as readdirSync9, rmSync as rmSync4 } from "node:fs";
109887
110594
  import { dirname as dirname47, join as join149 } from "node:path";
109888
110595
  init_logger();
109889
110596
  init_safe_prompts();
@@ -109909,7 +110616,7 @@ async function cleanupEmptyDirectories3(filePath, installationRoot) {
109909
110616
  let currentDir = dirname47(filePath);
109910
110617
  while (currentDir !== installationRoot && currentDir.startsWith(installationRoot)) {
109911
110618
  try {
109912
- const entries = readdirSync8(currentDir);
110619
+ const entries = readdirSync9(currentDir);
109913
110620
  if (entries.length === 0) {
109914
110621
  rmSync4(currentDir, { recursive: true });
109915
110622
  cleaned++;
@@ -110150,7 +110857,7 @@ async function removeInstallations(installations, options2) {
110150
110857
  }
110151
110858
  }
110152
110859
  try {
110153
- const remaining = readdirSync9(installation.path);
110860
+ const remaining = readdirSync10(installation.path);
110154
110861
  if (remaining.length === 0) {
110155
110862
  rmSync5(installation.path, { recursive: true });
110156
110863
  logger.debug(`Removed empty installation directory: ${installation.path}`);
@@ -112003,7 +112710,7 @@ Run this command from a directory with a GitHub remote.`);
112003
112710
  // src/commands/watch/phases/watch-logger.ts
112004
112711
  init_logger();
112005
112712
  init_path_resolver();
112006
- import { createWriteStream as createWriteStream3, statSync as statSync12 } from "node:fs";
112713
+ import { createWriteStream as createWriteStream3, statSync as statSync13 } from "node:fs";
112007
112714
  import { existsSync as existsSync73 } from "node:fs";
112008
112715
  import { mkdir as mkdir38, rename as rename13 } from "node:fs/promises";
112009
112716
  import { join as join156 } from "node:path";
@@ -112071,7 +112778,7 @@ class WatchLogger {
112071
112778
  return;
112072
112779
  if (this.maxBytes > 0 && this.logPath) {
112073
112780
  try {
112074
- const stats = statSync12(this.logPath);
112781
+ const stats = statSync13(this.logPath);
112075
112782
  if (stats.size >= this.maxBytes) {
112076
112783
  this.rotateLog();
112077
112784
  }
@@ -112449,7 +113156,7 @@ function registerCommands(cli) {
112449
113156
  cli.command("api [action] [service] [path]", "Interact with ClaudeKit API and proxy services").option("--method <method>", "HTTP method for proxy requests (default: GET)").option("--body <json>", "Request body as JSON string (proxy only)").option("--query <json>", "Query params as JSON string (proxy only)").option("--key <key>", "API key to use (setup only)").option("--force", "Force re-setup even if key exists (setup only)").option("--json", "Output raw JSON instead of formatted display").option("--locale <locale>", "Locale for vidcap summary/caption (default: en)").option("--max-results <n>", "Max results for vidcap search").option("--second <s>", "Timestamp in seconds for vidcap screenshot").option("--order <order>", "Sort order for vidcap comments (time/relevance)").option("--format <fmt>", "Summary format for reviewweb (bullet/paragraph)").option("--max-length <n>", "Max summary length for reviewweb").option("--instructions <text>", "Extraction instructions for reviewweb extract").option("--template <json>", "JSON template for reviewweb extract").option("--type <type>", "Link type filter for reviewweb links (web/image/file/all)").option("--country <code>", "Country code for reviewweb SEO commands").action(async (action, service, path16, options2) => {
112450
113157
  await apiCommand(action, service, path16, options2);
112451
113158
  });
112452
- cli.command("migrate", "Migrate agents, commands, skills, config, rules, and hooks to other providers").option("-a, --agent <agents...>", "Target providers (cursor, codex, droid, opencode, etc.)").option("-g, --global", "Install globally instead of project-level").option("--all", "Migrate to all supported providers").option("-y, --yes", "Skip confirmation prompts").option("--config", "Migrate CLAUDE.md config only").option("--rules", "Migrate .claude/rules/ only").option("--hooks", "Migrate .claude/hooks/ only").option("--skip-config", "Skip config migration").option("--skip-rules", "Skip rules migration").option("--skip-hooks", "Skip hooks migration").option("--source <path>", "Custom CLAUDE.md source path (config only, not agents/commands/skills/hooks)").option("--dry-run", "Preview migration targets without writing files").option("-f, --force", "Force reinstall deleted/edited items").action(async (options2) => {
113159
+ cli.command("migrate", "Migrate agents, commands, skills, config, rules, and hooks to other providers").option("-a, --agent <agents...>", "Target providers (cursor, codex, droid, opencode, etc.)").option("-g, --global", "Install globally instead of project-level").option("--all", "Migrate to all supported providers").option("-y, --yes", "Skip confirmation prompts").option("--config", "Migrate CLAUDE.md config only").option("--rules", "Migrate .claude/rules/ only").option("--hooks", "Migrate .claude/hooks/ only").option("--skip-config", "Skip config migration").option("--skip-rules", "Skip rules migration").option("--skip-hooks", "Skip hooks migration").option("--source <path>", "Custom CLAUDE.md source path (config only, not agents/commands/skills/hooks)").option("--dry-run", "Preview migration targets without writing files").option("-f, --force", "Force reinstall deleted/edited items").option("--install", "Opt-in install picker mode (select specific items to install)").option("--reconcile", "Force reconcile mode (current default when registry is valid)").option("--reinstall-empty-dirs", "Reinstall all items when their type directory is empty (default: true)").option("--respect-deletions", "Preserve deletion even when type directory is empty (disables reinstall-empty-dirs)").action(async (options2) => {
112453
113160
  if (options2.agent && !Array.isArray(options2.agent)) {
112454
113161
  options2.agent = [options2.agent];
112455
113162
  }