Normalize legacy Paperclip skill refs\n\nCo-Authored-By: Paperclip <noreply@paperclip.ing>

This commit is contained in:
Dotta
2026-03-16 19:13:00 -05:00
parent 79e0915a86
commit 0cfbc58842
3 changed files with 72 additions and 2 deletions

View File

@@ -391,9 +391,32 @@ export function readPaperclipSkillSyncPreference(config: Record<string, unknown>
};
}
function canonicalizeDesiredPaperclipSkillReference(
reference: string,
availableEntries: Array<{ key: string; runtimeName?: string | null }>,
): string {
const normalizedReference = reference.trim().toLowerCase();
if (!normalizedReference) return "";
const exactKey = availableEntries.find((entry) => entry.key.trim().toLowerCase() === normalizedReference);
if (exactKey) return exactKey.key;
const byRuntimeName = availableEntries.filter((entry) =>
typeof entry.runtimeName === "string" && entry.runtimeName.trim().toLowerCase() === normalizedReference,
);
if (byRuntimeName.length === 1) return byRuntimeName[0]!.key;
const slugMatches = availableEntries.filter((entry) =>
entry.key.trim().toLowerCase().split("/").pop() === normalizedReference,
);
if (slugMatches.length === 1) return slugMatches[0]!.key;
return normalizedReference;
}
export function resolvePaperclipDesiredSkillNames(
config: Record<string, unknown>,
availableEntries: Array<{ key: string; required?: boolean }>,
availableEntries: Array<{ key: string; runtimeName?: string | null; required?: boolean }>,
): string[] {
const preference = readPaperclipSkillSyncPreference(config);
const requiredSkills = availableEntries
@@ -402,7 +425,10 @@ export function resolvePaperclipDesiredSkillNames(
if (!preference.explicit) {
return Array.from(new Set(requiredSkills));
}
return Array.from(new Set([...requiredSkills, ...preference.desiredSkills]));
const desiredSkills = preference.desiredSkills
.map((reference) => canonicalizeDesiredPaperclipSkillReference(reference, availableEntries))
.filter(Boolean);
return Array.from(new Set([...requiredSkills, ...desiredSkills]));
}
export function writePaperclipSkillSyncPreference(

View File

@@ -39,4 +39,23 @@ describe("claude local skill sync", () => {
expect(snapshot.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("configured");
expect(snapshot.entries.find((entry) => entry.key === createAgentKey)?.state).toBe("configured");
});
it("normalizes legacy flat Paperclip skill refs to canonical keys", async () => {
const snapshot = await listClaudeSkills({
agentId: "agent-3",
companyId: "company-1",
adapterType: "claude_local",
config: {
paperclipSkillSync: {
desiredSkills: ["paperclip"],
},
},
});
expect(snapshot.warnings).toEqual([]);
expect(snapshot.desiredSkills).toContain(paperclipKey);
expect(snapshot.desiredSkills).not.toContain("paperclip");
expect(snapshot.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("configured");
expect(snapshot.entries.find((entry) => entry.key === "paperclip")).toBeUndefined();
});
});

View File

@@ -86,4 +86,29 @@ describe("codex local skill sync", () => {
expect(after.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("installed");
expect((await fs.lstat(path.join(codexHome, "skills", "paperclip"))).isSymbolicLink()).toBe(true);
});
it("normalizes legacy flat Paperclip skill refs before reporting persistent state", async () => {
const codexHome = await makeTempDir("paperclip-codex-legacy-skill-sync-");
cleanupDirs.add(codexHome);
const snapshot = await listCodexSkills({
agentId: "agent-3",
companyId: "company-1",
adapterType: "codex_local",
config: {
env: {
CODEX_HOME: codexHome,
},
paperclipSkillSync: {
desiredSkills: ["paperclip"],
},
},
});
expect(snapshot.warnings).toEqual([]);
expect(snapshot.desiredSkills).toContain(paperclipKey);
expect(snapshot.desiredSkills).not.toContain("paperclip");
expect(snapshot.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("missing");
expect(snapshot.entries.find((entry) => entry.key === "paperclip")).toBeUndefined();
});
});