skiller 0.9.10 → 0.9.11

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.
@@ -79,6 +79,46 @@ async function applyAfterSkillsLifecycleStep(projectRoot, verbose) {
79
79
  verbose,
80
80
  });
81
81
  }
82
+ async function pruneSkillOutputs(projectRoot, skillNames) {
83
+ const normalizedNames = [...new Set(skillNames.filter(Boolean))];
84
+ if (normalizedNames.length === 0)
85
+ return;
86
+ const skillDirs = new Set([(0, SkillOwnership_1.getCanonicalSkillsDir)(projectRoot)]);
87
+ for (const agent of agents_2.allAgents) {
88
+ if (!agent.supportsNativeSkills?.() || !agent.getSkillsPath)
89
+ continue;
90
+ const skillsPath = agent.getSkillsPath(projectRoot);
91
+ if (skillsPath) {
92
+ skillDirs.add(skillsPath);
93
+ }
94
+ }
95
+ for (const skillName of normalizedNames) {
96
+ for (const skillsDir of skillDirs) {
97
+ await fs.rm(path.join(skillsDir, skillName), {
98
+ force: true,
99
+ recursive: true,
100
+ });
101
+ }
102
+ }
103
+ }
104
+ async function pruneStaleLockBackedSkills(projectRoot) {
105
+ const [nativePrune, agentPrune] = await Promise.all([
106
+ (0, AgentSourceCompatibility_1.pruneMissingNativeSkillsFromLock)(projectRoot),
107
+ (0, AgentSourceCompatibility_1.pruneMissingAgentSkillsFromLock)(projectRoot),
108
+ ]);
109
+ if (nativePrune.prunedOutputNames.length > 0) {
110
+ await pruneSkillOutputs(projectRoot, nativePrune.prunedOutputNames);
111
+ console.log(`[skiller] Pruned ${nativePrune.prunedKeys.length} stale upstream skill(s): ${nativePrune.prunedKeys.join(', ')}`);
112
+ }
113
+ if (agentPrune.prunedOutputNames.length > 0) {
114
+ await pruneSkillOutputs(projectRoot, agentPrune.prunedOutputNames);
115
+ console.log(`[skiller] Pruned ${agentPrune.prunedKeys.length} stale agent-derived skill(s): ${agentPrune.prunedKeys.join(', ')}`);
116
+ }
117
+ const warnings = [...nativePrune.warnings, ...agentPrune.warnings];
118
+ if (warnings.length > 0) {
119
+ console.log(warnings.map((warning) => `[skiller] ${warning}`).join('\n'));
120
+ }
121
+ }
82
122
  function normalizeRequestedSkillNames(args) {
83
123
  if (!args || args.length === 0)
84
124
  return [];
@@ -681,6 +721,7 @@ async function addHandler(argv) {
681
721
  await applyAfterSkillsLifecycleStep(projectRoot, argv.verbose ?? false);
682
722
  }
683
723
  async function installHandler(argv) {
724
+ await pruneStaleLockBackedSkills(argv['project-root']);
684
725
  await executeSkillsWrapper(argv['project-root'], [
685
726
  'experimental_install',
686
727
  ...(argv.args ?? []),
@@ -733,6 +774,7 @@ async function checkHandler(argv) {
733
774
  ]);
734
775
  }
735
776
  async function updateHandler(argv) {
777
+ await pruneStaleLockBackedSkills(argv['project-root']);
736
778
  await executeSkillsWrapper(argv['project-root'], [
737
779
  'update',
738
780
  ...(argv.args ?? []),
@@ -44,6 +44,8 @@ exports.restoreAgentSkillsFromLock = restoreAgentSkillsFromLock;
44
44
  exports.getOutdatedAgentSkills = getOutdatedAgentSkills;
45
45
  exports.updateAgentSkillsFromLock = updateAgentSkillsFromLock;
46
46
  exports.removeAgentManagedSkills = removeAgentManagedSkills;
47
+ exports.pruneMissingNativeSkillsFromLock = pruneMissingNativeSkillsFromLock;
48
+ exports.pruneMissingAgentSkillsFromLock = pruneMissingAgentSkillsFromLock;
47
49
  const crypto = __importStar(require("crypto"));
48
50
  const fs = __importStar(require("fs/promises"));
49
51
  const path = __importStar(require("path"));
@@ -67,12 +69,43 @@ const SKIP_DIRS = new Set([
67
69
  'tmp',
68
70
  'tmp-fixtures',
69
71
  ]);
72
+ const NATIVE_SKILLS_LOCK_VERSION = 1;
70
73
  function hashContent(content) {
71
74
  return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
72
75
  }
73
76
  function normalizeSkillNameForFilesystem(name) {
74
77
  return name.trim().replace(/:/g, '-');
75
78
  }
79
+ function createEmptyNativeSkillsLock() {
80
+ return {
81
+ version: NATIVE_SKILLS_LOCK_VERSION,
82
+ skills: {},
83
+ };
84
+ }
85
+ async function readNativeSkillsLock(projectRoot) {
86
+ try {
87
+ const raw = JSON.parse(await fs.readFile(path.join(projectRoot, 'skills-lock.json'), 'utf8'));
88
+ if (raw.version !== NATIVE_SKILLS_LOCK_VERSION ||
89
+ !raw.skills ||
90
+ typeof raw.skills !== 'object') {
91
+ return createEmptyNativeSkillsLock();
92
+ }
93
+ return raw;
94
+ }
95
+ catch {
96
+ return createEmptyNativeSkillsLock();
97
+ }
98
+ }
99
+ async function writeNativeSkillsLock(projectRoot, lock) {
100
+ const sortedSkills = {};
101
+ for (const key of Object.keys(lock.skills).sort((a, b) => a.localeCompare(b))) {
102
+ sortedSkills[key] = lock.skills[key];
103
+ }
104
+ await fs.writeFile(path.join(projectRoot, 'skills-lock.json'), JSON.stringify({
105
+ version: NATIVE_SKILLS_LOCK_VERSION,
106
+ skills: sortedSkills,
107
+ }, null, 2) + '\n', 'utf8');
108
+ }
76
109
  function isLocalPath(input) {
77
110
  return (path.isAbsolute(input) ||
78
111
  input.startsWith('./') ||
@@ -708,3 +741,84 @@ async function removeAgentManagedSkills(projectRoot, skillNames) {
708
741
  }
709
742
  return removed;
710
743
  }
744
+ async function pruneMissingNativeSkillsFromLock(projectRoot) {
745
+ const lock = await readNativeSkillsLock(projectRoot);
746
+ const prunedKeys = [];
747
+ const prunedOutputNames = [];
748
+ const warnings = [];
749
+ for (const entries of groupLockEntriesBySource(lock.skills).values()) {
750
+ const [, entry] = entries[0];
751
+ if (entry.sourceType === 'node_modules' ||
752
+ entry.sourceType === 'well-known') {
753
+ continue;
754
+ }
755
+ let workspace = null;
756
+ try {
757
+ workspace = await withSourceWorkspace(entry.source);
758
+ const availableSkillNames = new Set(await discoverSkillNames(workspace.searchPath));
759
+ for (const [skillName] of entries) {
760
+ const installName = normalizeSkillNameForFilesystem(skillName);
761
+ if (availableSkillNames.has(installName))
762
+ continue;
763
+ prunedKeys.push(skillName);
764
+ prunedOutputNames.push(installName);
765
+ }
766
+ }
767
+ catch (error) {
768
+ warnings.push(`Could not inspect '${entry.source}' for stale skills: ${error instanceof Error ? error.message : String(error)}`);
769
+ }
770
+ finally {
771
+ if (workspace) {
772
+ await workspace.cleanup();
773
+ }
774
+ }
775
+ }
776
+ if (prunedKeys.length > 0) {
777
+ for (const skillName of prunedKeys) {
778
+ delete lock.skills[skillName];
779
+ }
780
+ await writeNativeSkillsLock(projectRoot, lock);
781
+ }
782
+ return {
783
+ prunedKeys: prunedKeys.sort((a, b) => a.localeCompare(b)),
784
+ prunedOutputNames: prunedOutputNames.sort((a, b) => a.localeCompare(b)),
785
+ warnings,
786
+ };
787
+ }
788
+ async function pruneMissingAgentSkillsFromLock(projectRoot) {
789
+ const lock = await (0, SkillerLock_1.readSkillerLock)(projectRoot);
790
+ const prunedKeys = [];
791
+ const prunedOutputNames = [];
792
+ const warnings = [];
793
+ for (const entries of groupLockEntriesBySource(lock.skills).values()) {
794
+ const [, entry] = entries[0];
795
+ let workspace = null;
796
+ try {
797
+ workspace = await withParsedSourceWorkspace(parseSourceFromLockEntry(entry));
798
+ const agentSkills = await discoverAgentSkillCandidates(workspace.searchPath, workspace.rootPath);
799
+ const candidates = new Set(agentSkills.map((candidate) => candidate.sourceRelPath));
800
+ for (const [skillName, lockEntry] of entries) {
801
+ if (candidates.has(lockEntry.sourceRelPath))
802
+ continue;
803
+ prunedKeys.push(skillName);
804
+ prunedOutputNames.push(normalizeSkillNameForFilesystem(skillName));
805
+ }
806
+ }
807
+ catch (error) {
808
+ warnings.push(`Could not inspect '${entry.source}' for stale agent-derived skills: ${error instanceof Error ? error.message : String(error)}`);
809
+ }
810
+ finally {
811
+ if (workspace) {
812
+ await workspace.cleanup();
813
+ }
814
+ }
815
+ }
816
+ if (prunedKeys.length > 0) {
817
+ await (0, SkillerLock_1.removeSkillerLockEntries)(projectRoot, prunedKeys);
818
+ }
819
+ return {
820
+ prunedKeys: prunedKeys.sort((a, b) => a.localeCompare(b)),
821
+ prunedOutputNames: prunedOutputNames.sort((a, b) => a.localeCompare(b)),
822
+ warnings,
823
+ };
824
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skiller",
3
- "version": "0.9.10",
3
+ "version": "0.9.11",
4
4
  "description": "Skiller — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "publishConfig": {