@lifeaitools/rdc-skills 0.9.30 → 0.9.31
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/package.json
CHANGED
|
@@ -140,30 +140,53 @@ function buildPluginCache(cacheDir, version, gitSha) {
|
|
|
140
140
|
// Claude Code loads that directory AND the plugin cache, so any rdc skills left
|
|
141
141
|
// there produce duplicate registrations and break the resolver.
|
|
142
142
|
// This function nukes any entry whose frontmatter name starts with "rdc:".
|
|
143
|
+
// Scans BOTH the immediate dir and nested .md files (e.g. `user/skill.md`,
|
|
144
|
+
// `user/rdc-build/SKILL.md`) so pre-plugin orphans are caught regardless of
|
|
145
|
+
// naming convention.
|
|
143
146
|
function cleanUserSkills(userSkillsDir) {
|
|
144
147
|
if (!fs.existsSync(userSkillsDir)) return 0;
|
|
145
148
|
let removed = 0;
|
|
146
149
|
for (const entry of fs.readdirSync(userSkillsDir, { withFileTypes: true })) {
|
|
147
150
|
const candidate = path.join(userSkillsDir, entry.name);
|
|
148
|
-
let skillFile = null;
|
|
149
151
|
if (entry.isDirectory()) {
|
|
150
|
-
//
|
|
152
|
+
// Subdir form: <name>/SKILL.md or <name>/skill.md
|
|
153
|
+
let skillFile = null;
|
|
151
154
|
for (const sf of ['SKILL.md', 'skill.md']) {
|
|
152
155
|
const p = path.join(candidate, sf);
|
|
153
156
|
if (fs.existsSync(p)) { skillFile = p; break; }
|
|
154
157
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
if (!skillFile) continue;
|
|
159
|
+
const fm = readFrontmatter(skillFile);
|
|
160
|
+
if (fm.name && fm.name.startsWith('rdc:')) {
|
|
161
|
+
try { fs.rmSync(candidate, { recursive: true, force: true }); removed++; } catch {}
|
|
162
|
+
}
|
|
163
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
164
|
+
// ANY .md file at this level — including skill.md / SKILL.md / README.md
|
|
165
|
+
// if their frontmatter declares an rdc:* skill. A previous version skipped
|
|
166
|
+
// those names; that left an orphan rdc:build copy at user/skill.md which
|
|
167
|
+
// registered as a duplicate "user" skill.
|
|
168
|
+
const fm = readFrontmatter(candidate);
|
|
169
|
+
if (fm.name && fm.name.startsWith('rdc:')) {
|
|
170
|
+
try { fs.unlinkSync(candidate); removed++; } catch {}
|
|
171
|
+
}
|
|
158
172
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
173
|
+
}
|
|
174
|
+
return removed;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Scrub legacy rdc orphans from ~/.claude/skills/ (top-level), not just user/.
|
|
178
|
+
// Some older installs landed flat skill files alongside the plugin tree.
|
|
179
|
+
function cleanGlobalSkillsRoot(skillsDir) {
|
|
180
|
+
if (!fs.existsSync(skillsDir)) return 0;
|
|
181
|
+
let removed = 0;
|
|
182
|
+
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
183
|
+
if (entry.name === 'user') continue; // handled separately
|
|
184
|
+
const candidate = path.join(skillsDir, entry.name);
|
|
185
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
186
|
+
const fm = readFrontmatter(candidate);
|
|
187
|
+
if (fm.name && fm.name.startsWith('rdc:')) {
|
|
188
|
+
try { fs.unlinkSync(candidate); removed++; } catch {}
|
|
189
|
+
}
|
|
167
190
|
}
|
|
168
191
|
}
|
|
169
192
|
return removed;
|
|
@@ -192,11 +215,14 @@ function cleanStaleHooks(hooksDstDir) {
|
|
|
192
215
|
}
|
|
193
216
|
|
|
194
217
|
// ── Cache flush helper ────────────────────────────────────────────────────────
|
|
195
|
-
|
|
218
|
+
// Only `latest/` is ever kept. Earlier versions wrote BOTH `<version>/` and
|
|
219
|
+
// `latest/`, which caused the plugin loader to scan and register every rdc:*
|
|
220
|
+
// skill twice (once per directory). The fix is permanent single-dir layout.
|
|
221
|
+
function flushOldCaches(cacheBase /* keepVersion intentionally unused */) {
|
|
196
222
|
if (!fs.existsSync(cacheBase)) return 0;
|
|
197
223
|
let flushed = 0;
|
|
198
224
|
for (const entry of fs.readdirSync(cacheBase)) {
|
|
199
|
-
if (entry ===
|
|
225
|
+
if (entry === 'latest') continue;
|
|
200
226
|
try {
|
|
201
227
|
fs.rmSync(path.join(cacheBase, entry), { recursive: true, force: true });
|
|
202
228
|
flushed++;
|
|
@@ -211,7 +237,6 @@ function registerCLI(version, gitSha) {
|
|
|
211
237
|
const mktDir = path.join(pluginDir, 'marketplaces', MARKETPLACE);
|
|
212
238
|
const mktPlugDir = path.join(mktDir, '.claude-plugin');
|
|
213
239
|
const cacheBase = path.join(pluginDir, 'cache', MARKETPLACE, 'rdc-skills');
|
|
214
|
-
const cacheDir = path.join(cacheBase, version);
|
|
215
240
|
const latestDir = path.join(cacheBase, 'latest');
|
|
216
241
|
|
|
217
242
|
// 1. Marketplace manifest
|
|
@@ -224,12 +249,11 @@ function registerCLI(version, gitSha) {
|
|
|
224
249
|
knownMp[MARKETPLACE] = { source: { source: 'github', repo: 'LIFEAI/rdc-skills' }, installLocation: mktDir, lastUpdated: new Date().toISOString() };
|
|
225
250
|
writeJson(kmpPath, knownMp, 4);
|
|
226
251
|
|
|
227
|
-
// 3. Flush
|
|
228
|
-
|
|
252
|
+
// 3. Flush every cache dir except `latest/`, then rewrite `latest/`. We
|
|
253
|
+
// intentionally do NOT keep a versioned dir — the plugin loader registers
|
|
254
|
+
// every dir it finds, so two dirs = duplicate skills.
|
|
255
|
+
const flushed = flushOldCaches(cacheBase);
|
|
229
256
|
if (flushed > 0) info(` flushed : ${flushed} stale cache dir(s)`);
|
|
230
|
-
buildPluginCache(cacheDir, version, gitSha);
|
|
231
|
-
// Write to stable 'latest/' so open terminals can pick up changes if they
|
|
232
|
-
// re-read installed_plugins.json between skill invocations.
|
|
233
257
|
if (fs.existsSync(latestDir)) fs.rmSync(latestDir, { recursive: true, force: true });
|
|
234
258
|
buildPluginCache(latestDir, version, gitSha);
|
|
235
259
|
|
|
@@ -312,7 +336,6 @@ function registerCowork(version, gitSha) {
|
|
|
312
336
|
for (const { dir, settingsFile } of bases) {
|
|
313
337
|
const pluginsDir = path.join(dir, 'cowork_plugins');
|
|
314
338
|
const cacheBase = path.join(pluginsDir, 'cache', MARKETPLACE, 'rdc-skills');
|
|
315
|
-
const cacheDir = path.join(cacheBase, version);
|
|
316
339
|
const latestDir = path.join(cacheBase, 'latest');
|
|
317
340
|
const mktDir = path.join(pluginsDir, 'marketplaces', MARKETPLACE);
|
|
318
341
|
const mktPlugDir = path.join(mktDir, '.claude-plugin');
|
|
@@ -327,9 +350,8 @@ function registerCowork(version, gitSha) {
|
|
|
327
350
|
knownMp[MARKETPLACE] = { source: { source: 'github', repo: 'LIFEAI/rdc-skills' }, installLocation: mktDir, lastUpdated: new Date().toISOString() };
|
|
328
351
|
writeJson(kmpPath, knownMp, 4);
|
|
329
352
|
|
|
330
|
-
// Flush
|
|
331
|
-
flushOldCaches(cacheBase
|
|
332
|
-
buildPluginCache(cacheDir, version, gitSha);
|
|
353
|
+
// Flush all non-latest caches and rewrite `latest/` only — single-dir layout
|
|
354
|
+
flushOldCaches(cacheBase);
|
|
333
355
|
if (fs.existsSync(latestDir)) fs.rmSync(latestDir, { recursive: true, force: true });
|
|
334
356
|
buildPluginCache(latestDir, version, gitSha);
|
|
335
357
|
|
|
@@ -622,6 +644,10 @@ async function main() {
|
|
|
622
644
|
const userSkillsDir = path.join(claudeHome, 'skills', 'user');
|
|
623
645
|
const purged = cleanUserSkills(userSkillsDir);
|
|
624
646
|
if (purged > 0) ok(`[0.5a] Skills cleanup — removed ${purged} stale rdc: skill(s) from skills/user/`);
|
|
647
|
+
// Also scan the parent ~/.claude/skills/ for any flat-file rdc orphans.
|
|
648
|
+
const skillsRoot = path.join(claudeHome, 'skills');
|
|
649
|
+
const rootPurged = cleanGlobalSkillsRoot(skillsRoot);
|
|
650
|
+
if (rootPurged > 0) ok(`[0.5a] Skills cleanup — removed ${rootPurged} stale rdc: file(s) from skills/`);
|
|
625
651
|
}
|
|
626
652
|
|
|
627
653
|
// 0.5b. Stale hook cleanup — remove hooks we no longer ship
|
|
@@ -742,6 +768,53 @@ async function main() {
|
|
|
742
768
|
console.log(' \x1b[36mPreflight:\x1b[0m');
|
|
743
769
|
runPreflight();
|
|
744
770
|
|
|
771
|
+
// 6.5 Post-install verification — duplication guard
|
|
772
|
+
console.log('');
|
|
773
|
+
console.log(' \x1b[36mPost-install verification:\x1b[0m');
|
|
774
|
+
let verifyFailed = false;
|
|
775
|
+
{
|
|
776
|
+
const cacheBase = path.join(claudeHome, 'plugins', 'cache', MARKETPLACE, 'rdc-skills');
|
|
777
|
+
const cacheDirs = fs.existsSync(cacheBase) ? fs.readdirSync(cacheBase) : [];
|
|
778
|
+
if (cacheDirs.length === 1 && cacheDirs[0] === 'latest') {
|
|
779
|
+
ok(`plugin cache : 1 dir (latest/)`);
|
|
780
|
+
} else {
|
|
781
|
+
fail(`plugin cache : expected exactly [latest/], found [${cacheDirs.join(', ')}]`);
|
|
782
|
+
verifyFailed = true;
|
|
783
|
+
}
|
|
784
|
+
const ipPath = path.join(claudeHome, 'plugins', 'installed_plugins.json');
|
|
785
|
+
const installed = readJson(ipPath, { plugins: {} });
|
|
786
|
+
const rdcEntries = installed.plugins[PLUGIN_KEY] || [];
|
|
787
|
+
if (rdcEntries.length === 1) {
|
|
788
|
+
ok(`installed_plugins: 1 entry for ${PLUGIN_KEY}`);
|
|
789
|
+
} else {
|
|
790
|
+
fail(`installed_plugins: expected 1 entry, found ${rdcEntries.length}`);
|
|
791
|
+
verifyFailed = true;
|
|
792
|
+
}
|
|
793
|
+
const userSkillsDir = path.join(claudeHome, 'skills', 'user');
|
|
794
|
+
const stillThere = fs.existsSync(userSkillsDir)
|
|
795
|
+
? fs.readdirSync(userSkillsDir).filter(f => {
|
|
796
|
+
const p = path.join(userSkillsDir, f);
|
|
797
|
+
const skillFile = fs.statSync(p).isDirectory()
|
|
798
|
+
? ['SKILL.md','skill.md'].map(s => path.join(p, s)).find(fs.existsSync)
|
|
799
|
+
: (f.endsWith('.md') ? p : null);
|
|
800
|
+
if (!skillFile) return false;
|
|
801
|
+
const fm = readFrontmatter(skillFile);
|
|
802
|
+
return fm.name && fm.name.startsWith('rdc:');
|
|
803
|
+
})
|
|
804
|
+
: [];
|
|
805
|
+
if (stillThere.length === 0) {
|
|
806
|
+
ok(`skills/user/ : no rdc: orphans`);
|
|
807
|
+
} else {
|
|
808
|
+
fail(`skills/user/ : still has rdc: orphans: ${stillThere.join(', ')}`);
|
|
809
|
+
verifyFailed = true;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (verifyFailed) {
|
|
813
|
+
console.log('');
|
|
814
|
+
fail('Post-install verification FAILED — duplicates or orphans remain. Investigate.');
|
|
815
|
+
process.exit(2);
|
|
816
|
+
}
|
|
817
|
+
|
|
745
818
|
// Done
|
|
746
819
|
console.log('');
|
|
747
820
|
console.log(' \x1b[32mDone!\x1b[0m');
|