mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-17 19:20:39 +09:00
Adds the wireframe skill (low-fi black-and-white SVG wireframes + viewer page) as a bundled catalog skill under catalog/bundled/product/wireframe, alongside its references/ docs and assets/ templates. Regenerates generated/catalog.json (8 -> 9 skills). The skill ships static svg/html template assets, so its derived trust level is "assets" rather than "markdown_only". The server's real install-time security gate (assertCatalogSkillInstallable) blocks only "scripts_executables", and "assets" skills are installable, so the shipped-catalog markdown-only invariant is refined to gate on executable scripts instead. No skill ships executable scripts. Co-Authored-By: Paperclip <noreply@paperclip.ing>
94 lines
4.2 KiB
TypeScript
94 lines
4.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { catalogManifest, catalogSkills, resolveCatalogSkillRef } from "./index.js";
|
|
import type { CatalogSkill } from "./types.js";
|
|
|
|
const EXPECTED_BUNDLED_KEYS = [
|
|
"paperclipai/bundled/docs/doc-maintenance",
|
|
"paperclipai/bundled/paperclip-operations/issue-triage",
|
|
"paperclipai/bundled/paperclip-operations/task-planning",
|
|
"paperclipai/bundled/product/wireframe",
|
|
"paperclipai/bundled/quality/qa-acceptance",
|
|
"paperclipai/bundled/software-development/github-pr-workflow",
|
|
];
|
|
|
|
const EXPECTED_OPTIONAL_KEYS = [
|
|
"paperclipai/optional/browser/agent-browser",
|
|
"paperclipai/optional/content/release-announcement",
|
|
"paperclipai/optional/product/design-critique",
|
|
];
|
|
|
|
describe("shipped skills catalog", () => {
|
|
it("ships the expected bundled and optional skill set", () => {
|
|
const bundledKeys = catalogSkills
|
|
.filter((skill) => skill.kind === "bundled")
|
|
.map((skill) => skill.key)
|
|
.sort();
|
|
const optionalKeys = catalogSkills
|
|
.filter((skill) => skill.kind === "optional")
|
|
.map((skill) => skill.key)
|
|
.sort();
|
|
|
|
expect(bundledKeys).toEqual(EXPECTED_BUNDLED_KEYS);
|
|
expect(optionalKeys).toEqual(EXPECTED_OPTIONAL_KEYS);
|
|
});
|
|
|
|
it("keeps every shipped skill free of executable scripts until script-bearing skills clear security review", () => {
|
|
// The real install-time security boundary (server assertCatalogSkillInstallable) blocks
|
|
// only "scripts_executables". Static assets (svg/html templates, e.g. the wireframe skill)
|
|
// carry the "assets" trust level and are installable, so they are allowed in the catalog.
|
|
const scriptBearing = catalogSkills.filter((skill) => skill.trustLevel === "scripts_executables");
|
|
expect(scriptBearing, formatViolations("script-bearing skills require security review", scriptBearing)).toEqual([]);
|
|
});
|
|
|
|
it("populates browse/search-relevant fields for every shipped skill", () => {
|
|
const issues: string[] = [];
|
|
for (const skill of catalogSkills) {
|
|
if (skill.compatibility !== "compatible") {
|
|
issues.push(`${skill.key} compatibility=${skill.compatibility}`);
|
|
}
|
|
if (!skill.description || skill.description.length < 40) {
|
|
issues.push(`${skill.key} description must be at least 40 characters for catalog browse/search`);
|
|
}
|
|
if (skill.recommendedForRoles.length === 0) {
|
|
issues.push(`${skill.key} must list recommendedForRoles`);
|
|
}
|
|
if (skill.tags.length === 0) {
|
|
issues.push(`${skill.key} must list tags`);
|
|
}
|
|
}
|
|
expect(issues).toEqual([]);
|
|
});
|
|
|
|
it("uses canonical paperclipai keys derived from kind/category/slug", () => {
|
|
const violations: string[] = [];
|
|
for (const skill of catalogSkills) {
|
|
const expectedKey = `paperclipai/${skill.kind}/${skill.category}/${skill.slug}`;
|
|
const expectedId = `paperclipai:${skill.kind}:${skill.category}:${skill.slug}`;
|
|
if (skill.key !== expectedKey) violations.push(`${skill.key} should be ${expectedKey}`);
|
|
if (skill.id !== expectedId) violations.push(`${skill.id} should be ${expectedId}`);
|
|
}
|
|
expect(violations).toEqual([]);
|
|
});
|
|
|
|
it("exposes a stable manifest header for downstream consumers", () => {
|
|
expect(catalogManifest.schemaVersion).toBe(1);
|
|
expect(catalogManifest.packageName).toBe("@paperclipai/skills-catalog");
|
|
expect(catalogSkills.length).toBe(EXPECTED_BUNDLED_KEYS.length + EXPECTED_OPTIONAL_KEYS.length);
|
|
});
|
|
|
|
it("resolves shipped skills by id, key, and unique slug", () => {
|
|
const sample = catalogSkills.find((skill) => skill.key === "paperclipai/bundled/software-development/github-pr-workflow");
|
|
expect(sample, "expected github-pr-workflow to ship in the bundled catalog").toBeDefined();
|
|
if (!sample) return;
|
|
|
|
expect(resolveCatalogSkillRef(sample.id)).toMatchObject({ key: sample.key });
|
|
expect(resolveCatalogSkillRef(sample.key)).toMatchObject({ key: sample.key });
|
|
expect(resolveCatalogSkillRef(sample.slug)).toMatchObject({ key: sample.key });
|
|
});
|
|
});
|
|
|
|
function formatViolations(label: string, skills: CatalogSkill[]) {
|
|
if (skills.length === 0) return label;
|
|
const detail = skills.map((skill) => `${skill.key} (${skill.trustLevel})`).join(", ");
|
|
return `${label}: ${detail}`;
|
|
}
|