mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:28:46 +01:00
Fix local skill import inventory
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
139cdebe51
commit
5d01b75c60
2 files changed
+78
-2
No files matched your search
@@ -53,6 +53,26 @@ describeEmbeddedPostgres("companySkillService.list", () => {
|
||||
await tempDb?.cleanup();
|
||||
});
|
||||
|
||||
async function createCompany(companyId = randomUUID()) {
|
||||
await db.insert(companies).values({
|
||||
id: companyId,
|
||||
name: "Paperclip",
|
||||
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
});
|
||||
return companyId;
|
||||
}
|
||||
|
||||
async function writeSkillTree(skillDir: string, name: string) {
|
||||
await fs.mkdir(path.join(skillDir, "references"), { recursive: true });
|
||||
await fs.mkdir(path.join(skillDir, "scripts"), { recursive: true });
|
||||
await fs.mkdir(path.join(skillDir, "assets"), { recursive: true });
|
||||
await fs.writeFile(path.join(skillDir, "SKILL.md"), `---\nname: ${name}\n---\n\n# ${name}\n`, "utf8");
|
||||
await fs.writeFile(path.join(skillDir, "references", "how-to-guide-template.md"), "# Guide Template\n", "utf8");
|
||||
await fs.writeFile(path.join(skillDir, "scripts", "bootstrap.sh"), "echo ok\n", "utf8");
|
||||
await fs.writeFile(path.join(skillDir, "assets", "logo.svg"), "<svg />\n", "utf8");
|
||||
}
|
||||
|
||||
it("lists skills without exposing markdown content", async () => {
|
||||
const companyId = randomUUID();
|
||||
const skillId = randomUUID();
|
||||
@@ -101,6 +121,55 @@ describeEmbeddedPostgres("companySkillService.list", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("indexes nested files when importing a direct local skill directory", async () => {
|
||||
const companyId = await createCompany();
|
||||
const skillDir = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-direct-skill-"));
|
||||
cleanupDirs.add(skillDir);
|
||||
await writeSkillTree(skillDir, "Diataxis");
|
||||
|
||||
const result = await svc.importFromSource(companyId, skillDir);
|
||||
const imported = result.imported[0]!;
|
||||
const inventoryPaths = imported.fileInventory.map((entry) => entry.path);
|
||||
|
||||
expect(inventoryPaths).toEqual([
|
||||
"assets/logo.svg",
|
||||
"references/how-to-guide-template.md",
|
||||
"scripts/bootstrap.sh",
|
||||
"SKILL.md",
|
||||
]);
|
||||
expect(imported.fileInventory).toEqual(expect.arrayContaining([
|
||||
{ path: "references/how-to-guide-template.md", kind: "reference" },
|
||||
{ path: "scripts/bootstrap.sh", kind: "script" },
|
||||
{ path: "assets/logo.svg", kind: "asset" },
|
||||
]));
|
||||
|
||||
const reference = await svc.readFile(companyId, imported.id, "references/how-to-guide-template.md");
|
||||
expect(reference).toMatchObject({
|
||||
path: "references/how-to-guide-template.md",
|
||||
content: "# Guide Template\n",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps parent-directory local imports scoped to the requested skill", async () => {
|
||||
const companyId = await createCompany();
|
||||
const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-parent-skills-"));
|
||||
cleanupDirs.add(rootDir);
|
||||
await writeSkillTree(path.join(rootDir, "diataxis"), "Diataxis");
|
||||
await writeSkillTree(path.join(rootDir, "other-skill"), "Other Skill");
|
||||
|
||||
const result = await svc.importFromSource(companyId, `npx skills add ${rootDir} --skill diataxis`);
|
||||
const imported = result.imported[0]!;
|
||||
|
||||
expect(result.imported).toHaveLength(1);
|
||||
expect(imported.slug).toBe("diataxis");
|
||||
expect(imported.fileInventory).toEqual([
|
||||
{ path: "assets/logo.svg", kind: "asset" },
|
||||
{ path: "references/how-to-guide-template.md", kind: "reference" },
|
||||
{ path: "scripts/bootstrap.sh", kind: "script" },
|
||||
{ path: "SKILL.md", kind: "skill" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("rejects skill inventory refresh for a missing company", async () => {
|
||||
await expect(svc.list(randomUUID())).rejects.toMatchObject({
|
||||
status: 404,
|
||||
|
||||
@@ -1118,10 +1118,17 @@ async function readLocalSkillImports(companyId: string, sourcePath: string): Pro
|
||||
const imports: ImportedSkill[] = [];
|
||||
for (const skillPath of skillPaths) {
|
||||
const skillDir = path.posix.dirname(skillPath);
|
||||
const skillInventoryRoot = skillDir === "." ? "" : skillDir;
|
||||
const inventory = allFiles
|
||||
.filter((entry) => entry === skillPath || entry.startsWith(`${skillDir}/`))
|
||||
.filter((entry) =>
|
||||
entry === skillPath
|
||||
|| (skillInventoryRoot ? entry.startsWith(`${skillInventoryRoot}/`) : true))
|
||||
.map((entry) => {
|
||||
const relative = entry === skillPath ? "SKILL.md" : entry.slice(skillDir.length + 1);
|
||||
const relative = entry === skillPath
|
||||
? "SKILL.md"
|
||||
: skillInventoryRoot
|
||||
? entry.slice(skillInventoryRoot.length + 1)
|
||||
: entry;
|
||||
return {
|
||||
path: normalizePortablePath(relative),
|
||||
kind: classifyInventoryKind(relative),
|
||||
|
||||
Reference in new issue
Block a user