feat: scan project workspaces for skills

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta
2026-03-16 19:09:33 -05:00
parent 52978e84ba
commit 56f7807732
9 changed files with 595 additions and 34 deletions

View File

@@ -1,5 +1,30 @@
import { describe, expect, it } from "vitest";
import { parseSkillImportSourceInput } from "../services/company-skills.js";
import os from "node:os";
import path from "node:path";
import { promises as fs } from "node:fs";
import { afterEach, describe, expect, it } from "vitest";
import {
discoverProjectWorkspaceSkillDirectories,
parseSkillImportSourceInput,
readLocalSkillImportFromDirectory,
} from "../services/company-skills.js";
const cleanupDirs = new Set<string>();
afterEach(async () => {
await Promise.all(Array.from(cleanupDirs, (dir) => fs.rm(dir, { recursive: true, force: true })));
cleanupDirs.clear();
});
async function makeTempDir(prefix: string) {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
cleanupDirs.add(dir);
return dir;
}
async function writeSkillDir(skillDir: string, name: string) {
await fs.mkdir(skillDir, { recursive: true });
await fs.writeFile(path.join(skillDir, "SKILL.md"), `---\nname: ${name}\n---\n\n# ${name}\n`, "utf8");
}
describe("company skill import source parsing", () => {
it("parses a skills.sh command without executing shell input", () => {
@@ -28,3 +53,58 @@ describe("company skill import source parsing", () => {
expect(parsed.requestedSkillSlug).toBe("remotion-best-practices");
});
});
describe("project workspace skill discovery", () => {
it("finds bounded skill roots under supported workspace paths", async () => {
const workspace = await makeTempDir("paperclip-skill-workspace-");
await writeSkillDir(workspace, "Workspace Root");
await writeSkillDir(path.join(workspace, "skills", "find-skills"), "Find Skills");
await writeSkillDir(path.join(workspace, ".agents", "skills", "release"), "Release");
await writeSkillDir(path.join(workspace, "skills", ".system", "paperclip"), "Paperclip");
await fs.writeFile(path.join(workspace, "README.md"), "# ignore\n", "utf8");
const discovered = await discoverProjectWorkspaceSkillDirectories({
projectId: "11111111-1111-1111-1111-111111111111",
projectName: "Repo",
workspaceId: "22222222-2222-2222-2222-222222222222",
workspaceName: "Main",
workspaceCwd: workspace,
});
expect(discovered).toEqual([
{ skillDir: path.resolve(workspace), inventoryMode: "project_root" },
{ skillDir: path.resolve(workspace, ".agents", "skills", "release"), inventoryMode: "full" },
{ skillDir: path.resolve(workspace, "skills", ".system", "paperclip"), inventoryMode: "full" },
{ skillDir: path.resolve(workspace, "skills", "find-skills"), inventoryMode: "full" },
]);
});
it("limits root SKILL.md imports to skill-related support folders", async () => {
const workspace = await makeTempDir("paperclip-root-skill-");
await writeSkillDir(workspace, "Workspace Skill");
await fs.mkdir(path.join(workspace, "references"), { recursive: true });
await fs.mkdir(path.join(workspace, "scripts"), { recursive: true });
await fs.mkdir(path.join(workspace, "assets"), { recursive: true });
await fs.mkdir(path.join(workspace, "src"), { recursive: true });
await fs.writeFile(path.join(workspace, "references", "checklist.md"), "# Checklist\n", "utf8");
await fs.writeFile(path.join(workspace, "scripts", "run.sh"), "echo ok\n", "utf8");
await fs.writeFile(path.join(workspace, "assets", "logo.svg"), "<svg />\n", "utf8");
await fs.writeFile(path.join(workspace, "README.md"), "# Repo\n", "utf8");
await fs.writeFile(path.join(workspace, "src", "index.ts"), "export {};\n", "utf8");
const imported = await readLocalSkillImportFromDirectory(
"33333333-3333-4333-8333-333333333333",
workspace,
{ inventoryMode: "project_root", metadata: { sourceKind: "project_scan" } },
);
expect(new Set(imported.fileInventory.map((entry) => entry.path))).toEqual(new Set([
"assets/logo.svg",
"references/checklist.md",
"scripts/run.sh",
"SKILL.md",
]));
expect(imported.fileInventory.map((entry) => entry.kind)).toContain("script");
expect(imported.metadata?.sourceKind).toBe("project_scan");
});
});