Namespace company skill identities

Persist canonical namespaced skill keys, split adapter runtime names from skill keys, and update portability/import flows to carry the canonical identity end-to-end.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta
2026-03-16 18:27:20 -05:00
parent bb46423969
commit 5890b318c4
39 changed files with 9902 additions and 309 deletions

View File

@@ -95,7 +95,7 @@ function cursorSkillsHome(): string {
type EnsureCursorSkillsInjectedOptions = {
skillsDir?: string | null;
skillsEntries?: Array<{ name: string; source: string }>;
skillsEntries?: Array<{ key: string; runtimeName: string; source: string }>;
skillsHome?: string;
linkSkill?: (source: string, target: string) => Promise<void>;
};
@@ -108,7 +108,11 @@ export async function ensureCursorSkillsInjected(
?? (options.skillsDir
? (await fs.readdir(options.skillsDir, { withFileTypes: true }))
.filter((entry) => entry.isDirectory())
.map((entry) => ({ name: entry.name, source: path.join(options.skillsDir!, entry.name) }))
.map((entry) => ({
key: entry.name,
runtimeName: entry.name,
source: path.join(options.skillsDir!, entry.name),
}))
: await readPaperclipRuntimeSkillEntries({}, __moduleDir));
if (skillsEntries.length === 0) return;
@@ -124,7 +128,7 @@ export async function ensureCursorSkillsInjected(
}
const removedSkills = await removeMaintainerOnlySkillSymlinks(
skillsHome,
skillsEntries.map((entry) => entry.name),
skillsEntries.map((entry) => entry.runtimeName),
);
for (const skillName of removedSkills) {
await onLog(
@@ -134,19 +138,19 @@ export async function ensureCursorSkillsInjected(
}
const linkSkill = options.linkSkill ?? ((source: string, target: string) => fs.symlink(source, target));
for (const entry of skillsEntries) {
const target = path.join(skillsHome, entry.name);
const target = path.join(skillsHome, entry.runtimeName);
try {
const result = await ensurePaperclipSkillSymlink(entry.source, target, linkSkill);
if (result === "skipped") continue;
await onLog(
"stderr",
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Cursor skill "${entry.name}" into ${skillsHome}\n`,
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Cursor skill "${entry.key}" into ${skillsHome}\n`,
);
} catch (err) {
await onLog(
"stderr",
`[paperclip] Failed to inject Cursor skill "${entry.name}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`,
`[paperclip] Failed to inject Cursor skill "${entry.key}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`,
);
}
}
@@ -183,7 +187,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
const cursorSkillEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir);
const desiredCursorSkillNames = resolvePaperclipDesiredSkillNames(config, cursorSkillEntries);
await ensureCursorSkillsInjected(onLog, {
skillsEntries: cursorSkillEntries.filter((entry) => desiredCursorSkillNames.includes(entry.name)),
skillsEntries: cursorSkillEntries.filter((entry) => desiredCursorSkillNames.includes(entry.key)),
});
const envConfig = parseObject(config.env);