@smicolon/ai-kit 0.5.1 → 0.5.2

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 +150 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -679,6 +679,114 @@ function isSymlink(p3) {
679
679
  return false;
680
680
  }
681
681
  }
682
+ function findOrphans(pack, projectDir) {
683
+ const orphans = [];
684
+ const canonicalBase = path5.join(projectDir, CANONICAL_SKILLS_DIR);
685
+ for (const sourceSkillDir of pack.skills) {
686
+ const skillName = path5.basename(sourceSkillDir);
687
+ const canonicalDest = path5.join(canonicalBase, skillName);
688
+ if (skillDirMatchesSource(canonicalDest, sourceSkillDir)) {
689
+ orphans.push(canonicalDest);
690
+ }
691
+ for (const toolId of TOOL_IDS) {
692
+ const toolSkillsDir = TOOL_REGISTRY[toolId].components.skills;
693
+ if (!toolSkillsDir) continue;
694
+ const linkPath = path5.join(projectDir, toolSkillsDir, skillName);
695
+ if (path5.resolve(linkPath) === path5.resolve(canonicalDest)) continue;
696
+ if (symlinkPointsInto(linkPath, canonicalDest)) {
697
+ orphans.push(linkPath);
698
+ } else if (!isSymlink(linkPath) && skillDirMatchesSource(linkPath, sourceSkillDir)) {
699
+ orphans.push(linkPath);
700
+ }
701
+ }
702
+ }
703
+ const collectFiles = (sourceFiles, component) => {
704
+ for (const sourceFile of sourceFiles) {
705
+ const base = path5.basename(sourceFile);
706
+ for (const toolId of TOOL_IDS) {
707
+ const dir = TOOL_REGISTRY[toolId].components[component];
708
+ if (!dir) continue;
709
+ let destPath;
710
+ let expected;
711
+ if (component === "rules" && toolId === "cursor") {
712
+ const mdcName = path5.basename(sourceFile, ".md") + ".mdc";
713
+ destPath = path5.join(projectDir, dir, mdcName);
714
+ expected = Buffer.from(convertToMdc(sourceFile, pack.name));
715
+ if (fileContentEquals(destPath, expected)) orphans.push(destPath);
716
+ const legacyPath = path5.join(projectDir, dir, base);
717
+ try {
718
+ const legacyExpected = fs6.readFileSync(sourceFile);
719
+ if (fileContentEquals(legacyPath, legacyExpected)) {
720
+ orphans.push(legacyPath);
721
+ }
722
+ } catch {
723
+ }
724
+ continue;
725
+ }
726
+ destPath = path5.join(projectDir, dir, base);
727
+ try {
728
+ expected = fs6.readFileSync(sourceFile);
729
+ } catch {
730
+ continue;
731
+ }
732
+ if (fileContentEquals(destPath, expected)) {
733
+ orphans.push(destPath);
734
+ }
735
+ }
736
+ }
737
+ };
738
+ collectFiles(pack.agents, "agents");
739
+ collectFiles(pack.commands, "commands");
740
+ collectFiles(pack.rules, "rules");
741
+ return [...new Set(orphans)];
742
+ }
743
+ function fileContentEquals(filePath, expected) {
744
+ try {
745
+ if (!fs6.existsSync(filePath) || isSymlink(filePath)) return false;
746
+ const stat = fs6.statSync(filePath);
747
+ if (!stat.isFile()) return false;
748
+ return fs6.readFileSync(filePath).equals(expected);
749
+ } catch {
750
+ return false;
751
+ }
752
+ }
753
+ function symlinkPointsInto(linkPath, expectedDir) {
754
+ try {
755
+ if (!isSymlink(linkPath)) return false;
756
+ const resolved = path5.resolve(path5.dirname(linkPath), fs6.readlinkSync(linkPath));
757
+ return path5.resolve(resolved) === path5.resolve(expectedDir);
758
+ } catch {
759
+ return false;
760
+ }
761
+ }
762
+ function skillDirMatchesSource(destDir, sourceDir) {
763
+ if (!fs6.existsSync(destDir) || isSymlink(destDir)) return false;
764
+ if (!fs6.statSync(destDir).isDirectory()) return false;
765
+ const sourceFiles = collectRelativeFiles(sourceDir);
766
+ const destFiles = collectRelativeFiles(destDir);
767
+ if (sourceFiles.length !== destFiles.length) return false;
768
+ const sourceSet = new Set(sourceFiles);
769
+ for (const f of destFiles) if (!sourceSet.has(f)) return false;
770
+ for (const rel of sourceFiles) {
771
+ const src = fs6.readFileSync(path5.join(sourceDir, rel));
772
+ const dst = fs6.readFileSync(path5.join(destDir, rel));
773
+ if (!src.equals(dst)) return false;
774
+ }
775
+ return true;
776
+ }
777
+ function collectRelativeFiles(dir, base = dir) {
778
+ const out = [];
779
+ if (!fs6.existsSync(dir)) return out;
780
+ for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
781
+ const full = path5.join(dir, entry.name);
782
+ if (entry.isDirectory()) {
783
+ out.push(...collectRelativeFiles(full, base));
784
+ } else if (entry.isFile()) {
785
+ out.push(path5.relative(base, full));
786
+ }
787
+ }
788
+ return out.sort();
789
+ }
682
790
 
683
791
  // src/global-config.ts
684
792
  import fs7 from "fs";
@@ -1018,30 +1126,58 @@ ${pc3.dim(`${packs.length} packs available`)}`);
1018
1126
  import { Command as Command4 } from "commander";
1019
1127
  import path9 from "path";
1020
1128
  import pc4 from "picocolors";
1021
- var removeCommand = new Command4("remove").description("Remove a pack from your project").argument("<pack>", "Pack name to remove").option("--cwd <dir>", "Project directory").action((packName, opts) => {
1129
+ var removeCommand = new Command4("remove").description("Remove a pack from your project").argument("<pack>", "Pack name to remove").option("--cwd <dir>", "Project directory").action(async (packName, opts) => {
1022
1130
  const projectDir = opts.cwd ? path9.resolve(opts.cwd) : process.cwd();
1023
1131
  const config = readConfig(projectDir);
1024
- if (!config) {
1025
- console.log(pc4.dim("No packs installed."));
1132
+ const packConfig = config?.packs[packName];
1133
+ const trackedFiles2 = packConfig?.files ?? [];
1134
+ if (trackedFiles2.length > 0) {
1135
+ const removed2 = removePack2(projectDir, trackedFiles2);
1136
+ console.log(pc4.green(`Removed ${removed2} file(s) for ${packName}`));
1137
+ writeConfig(projectDir, removePack(config, packName));
1138
+ console.log(pc4.dim("Config updated."));
1026
1139
  return;
1027
1140
  }
1028
- const packConfig = config.packs[packName];
1029
- if (!packConfig) {
1141
+ const pack = await findPack(packName);
1142
+ if (!pack) {
1143
+ const installed = config && Object.keys(config.packs).length > 0 ? "\nInstalled: " + Object.keys(config.packs).join(", ") : "";
1030
1144
  console.error(
1031
- pc4.red(`Pack "${packName}" is not installed.`) + "\nInstalled: " + Object.keys(config.packs).join(", ")
1145
+ pc4.red(`Pack "${packName}" is not installed and not found in the marketplace.`) + installed
1032
1146
  );
1033
1147
  process.exit(1);
1034
1148
  }
1035
- const files = packConfig.files ?? [];
1036
- if (files.length === 0) {
1037
- console.log(pc4.yellow(`No tracked files for "${packName}". Removing from config only.`));
1149
+ const orphans = findOrphans(pack, projectDir);
1150
+ if (packConfig) {
1151
+ console.log(pc4.yellow(
1152
+ `"${packName}" has no tracked files in .ai-kit.json (legacy install).`
1153
+ ));
1154
+ }
1155
+ if (orphans.length === 0) {
1156
+ if (packConfig) {
1157
+ writeConfig(projectDir, removePack(config, packName));
1158
+ console.log(pc4.dim("Config entry removed; nothing to clean on disk."));
1159
+ } else {
1160
+ console.log(pc4.dim(`Nothing to remove for "${packName}" \u2014 no tracked entry and no orphan files found.`));
1161
+ }
1162
+ return;
1163
+ }
1164
+ if (!packConfig) {
1165
+ console.log(pc4.yellow(
1166
+ `"${packName}" not in .ai-kit.json \u2014 found ${orphans.length} orphan file(s) to clean up:`
1167
+ ));
1038
1168
  } else {
1039
- const removed = removePack2(projectDir, files);
1040
- console.log(pc4.green(`Removed ${removed} file(s) for ${packName}`));
1169
+ console.log(pc4.yellow(`Found ${orphans.length} orphan file(s):`));
1170
+ }
1171
+ for (const p3 of orphans) {
1172
+ console.log(pc4.dim(" " + path9.relative(projectDir, p3)));
1173
+ }
1174
+ const relPaths = orphans.map((p3) => path9.relative(projectDir, p3));
1175
+ const removed = removePack2(projectDir, relPaths);
1176
+ console.log(pc4.green(`Removed ${removed} orphan file(s) for ${packName}`));
1177
+ if (packConfig) {
1178
+ writeConfig(projectDir, removePack(config, packName));
1179
+ console.log(pc4.dim("Config updated."));
1041
1180
  }
1042
- const updated = removePack(config, packName);
1043
- writeConfig(projectDir, updated);
1044
- console.log(pc4.dim("Config updated."));
1045
1181
  });
1046
1182
 
1047
1183
  // src/commands/update.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smicolon/ai-kit",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "AI coding tool pack manager for Smicolon standards",
5
5
  "license": "MIT",
6
6
  "repository": {