Normalize legacy Paperclip skill refs\n\nCo-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
79e0915a86
commit
0cfbc58842
3 changed files with 72 additions and 2 deletions
|
|
@ -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(
|
export function resolvePaperclipDesiredSkillNames(
|
||||||
config: Record<string, unknown>,
|
config: Record<string, unknown>,
|
||||||
availableEntries: Array<{ key: string; required?: boolean }>,
|
availableEntries: Array<{ key: string; runtimeName?: string | null; required?: boolean }>,
|
||||||
): string[] {
|
): string[] {
|
||||||
const preference = readPaperclipSkillSyncPreference(config);
|
const preference = readPaperclipSkillSyncPreference(config);
|
||||||
const requiredSkills = availableEntries
|
const requiredSkills = availableEntries
|
||||||
|
|
@ -402,7 +425,10 @@ export function resolvePaperclipDesiredSkillNames(
|
||||||
if (!preference.explicit) {
|
if (!preference.explicit) {
|
||||||
return Array.from(new Set(requiredSkills));
|
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(
|
export function writePaperclipSkillSyncPreference(
|
||||||
|
|
|
||||||
|
|
@ -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 === paperclipKey)?.state).toBe("configured");
|
||||||
expect(snapshot.entries.find((entry) => entry.key === createAgentKey)?.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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -86,4 +86,29 @@ describe("codex local skill sync", () => {
|
||||||
expect(after.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("installed");
|
expect(after.entries.find((entry) => entry.key === paperclipKey)?.state).toBe("installed");
|
||||||
expect((await fs.lstat(path.join(codexHome, "skills", "paperclip"))).isSymbolicLink()).toBe(true);
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue