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.
package/dist/cli/handlers.js
CHANGED
|
@@ -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
|
+
}
|