Files

183 lines
6.2 KiB
JavaScript

import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import { createJiti } from "jiti";
const jiti = createJiti(import.meta.url);
const { annotateSkillsWithInstallInfo, getGlobalSkillsLockPath } =
await jiti.import("./skill-lock.ts");
function writeJson(path, value) {
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
}
function makeSkill(name, filePath, scope) {
return {
name,
description: `${name} description`,
filePath,
baseDir: filePath.slice(0, -"/SKILL.md".length),
disableModelInvocation: false,
sourceInfo: { scope },
};
}
test("uses the CLI global lock location", () => {
assert.equal(
getGlobalSkillsLockPath({ homeDir: "/home/test", xdgStateHome: undefined }),
join("/home/test", ".agents", ".skill-lock.json"),
);
assert.equal(
getGlobalSkillsLockPath({ homeDir: "/home/test", xdgStateHome: "/state" }),
join("/state", "skills", ".skill-lock.json"),
);
});
test("annotates only lock entries that exist in the matching Pi scope", () => {
const root = mkdtempSync(join(tmpdir(), "pi-web-skill-lock-"));
try {
const cwd = join(root, "project");
const agentDir = join(root, "home", ".pi", "agent");
const globalLockPath = join(root, "global-lock.json");
const projectLockPath = join(cwd, "skills-lock.json");
const globalSkillPath = join(agentDir, "skills", "edge-tts", "SKILL.md");
const projectSkillPath = join(cwd, ".pi", "skills", "find-skills", "SKILL.md");
const manualSkillPath = join(agentDir, "skills", "manual", "SKILL.md");
const otherAgentSkillPath = join(root, "other-agent", "tts", "SKILL.md");
for (const path of [globalSkillPath, projectSkillPath, manualSkillPath, otherAgentSkillPath]) {
mkdirSync(join(path, ".."), { recursive: true });
writeFileSync(path, "---\nname: test\n---\n", "utf8");
}
writeJson(globalLockPath, {
version: 3,
skills: {
"edge-tts": {
source: "https://github.com/aahl/skills.git",
sourceType: "github",
skillPath: "skills/edge-tts/SKILL.md",
skillFolderHash: "global-version",
},
tts: { source: "noizai/skills", sourceType: "github" },
},
});
writeJson(projectLockPath, {
version: 1,
skills: {
"find-skills": {
source: "vercel-labs/skills",
sourceType: "github",
skillPath: "skills/find-skills/SKILL.md",
computedHash: "project-version",
},
},
});
const annotated = annotateSkillsWithInstallInfo(
[
makeSkill("edge-tts", globalSkillPath, "user"),
makeSkill("find-skills", projectSkillPath, "project"),
makeSkill("manual", manualSkillPath, "user"),
makeSkill("tts", otherAgentSkillPath, "user"),
],
{ cwd, agentDir, globalLockPath, projectLockPath },
);
assert.deepEqual(annotated[0].install, {
package: "aahl/skills@edge-tts",
scope: "global",
source: "aahl/skills",
sourceType: "github",
skillsShUrl: "https://skills.sh/aahl/skills/edge-tts",
skillPath: "skills/edge-tts/SKILL.md",
versionHash: "global-version",
canCheckForUpdates: true,
});
assert.deepEqual(annotated[1].install, {
package: "vercel-labs/skills@find-skills",
scope: "project",
source: "vercel-labs/skills",
sourceType: "github",
skillsShUrl: "https://skills.sh/vercel-labs/skills/find-skills",
skillPath: "skills/find-skills/SKILL.md",
versionHash: "project-version",
canCheckForUpdates: true,
});
assert.equal(annotated[2].install, undefined);
assert.equal(annotated[3].install, undefined);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
test("ignores stale lock entries and malformed lock files", () => {
const root = mkdtempSync(join(tmpdir(), "pi-web-skill-lock-"));
try {
const cwd = join(root, "project");
const agentDir = join(root, "agent");
const missingPath = join(agentDir, "skills", "missing", "SKILL.md");
const projectSkillPath = join(cwd, ".pi", "skills", "broken", "SKILL.md");
const globalLockPath = join(root, "global-lock.json");
const projectLockPath = join(root, "project-lock.json");
mkdirSync(join(projectSkillPath, ".."), { recursive: true });
writeFileSync(projectSkillPath, "---\nname: broken\n---\n", "utf8");
writeJson(globalLockPath, {
version: 3,
skills: {
missing: { source: "owner/repo", sourceType: "github" },
},
});
writeFileSync(projectLockPath, "not json", "utf8");
const skills = annotateSkillsWithInstallInfo(
[
makeSkill("missing", missingPath, "user"),
makeSkill("broken", projectSkillPath, "project"),
],
{ cwd, agentDir, globalLockPath, projectLockPath },
);
assert.equal(skills[0].install, undefined);
assert.equal(skills[1].install, undefined);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
test("does not compare a project ref with the default skills.sh snapshot", () => {
const root = mkdtempSync(join(tmpdir(), "pi-web-skill-lock-"));
try {
const cwd = join(root, "project");
const agentDir = join(root, "agent");
const projectLockPath = join(cwd, "skills-lock.json");
const projectSkillPath = join(cwd, ".pi", "skills", "preview", "SKILL.md");
mkdirSync(join(projectSkillPath, ".."), { recursive: true });
writeFileSync(projectSkillPath, "---\nname: preview\n---\n", "utf8");
writeJson(projectLockPath, {
version: 1,
skills: {
preview: {
source: "owner/repo",
sourceType: "github",
skillPath: "skills/preview/SKILL.md",
ref: "preview",
computedHash: "project-version",
},
},
});
const [skill] = annotateSkillsWithInstallInfo(
[makeSkill("preview", projectSkillPath, "project")],
{ cwd, agentDir, projectLockPath, globalLockPath: join(root, "missing.json") },
);
assert.equal(skill.install.ref, "preview");
assert.equal(skill.install.canCheckForUpdates, false);
} finally {
rmSync(root, { recursive: true, force: true });
}
});