115 lines
5.4 KiB
JavaScript
115 lines
5.4 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
CLIENTS,
|
|
SKILLS,
|
|
buildOperationPlan,
|
|
detectInstalledSkills,
|
|
getSkillSource,
|
|
parseReviewerShorthand,
|
|
validateRemoveTarget,
|
|
} from "../lib/skill-manager-core.mjs";
|
|
|
|
test("manifest records supported variants and helper allowlists", () => {
|
|
assert.deepEqual(SKILLS["web-automation"].variants, ["codex", "claude-code", "opencode", "pi"]);
|
|
assert.equal(SKILLS["web-automation"].variants.includes("cursor"), false);
|
|
assert.deepEqual(CLIENTS.codex.reviewerRuntime.files, ["run-review.sh", "notify-telegram.sh"]);
|
|
assert.deepEqual(CLIENTS.pi.reviewerRuntime.files, ["run-review.sh", "notify-telegram.sh"]);
|
|
});
|
|
|
|
test("parseReviewerShorthand keeps provider-qualified model ids verbatim", () => {
|
|
assert.deepEqual(parseReviewerShorthand("pi/claude-opus-4-7"), {
|
|
reviewerCli: "pi",
|
|
reviewerModel: "claude-opus-4-7",
|
|
});
|
|
assert.deepEqual(parseReviewerShorthand("pi/anthropic/claude-opus-4-7"), {
|
|
reviewerCli: "pi",
|
|
reviewerModel: "anthropic/claude-opus-4-7",
|
|
});
|
|
assert.equal(parseReviewerShorthand("codex/gpt-5"), null);
|
|
});
|
|
|
|
test("unsupported skill variant is reported as unsupported", () => {
|
|
assert.equal(getSkillSource("web-automation", "cursor"), null);
|
|
assert.ok(getSkillSource("web-automation", "pi").endsWith("pi-package/skills/web-automation"));
|
|
});
|
|
|
|
test("detectInstalledSkills reports installed and missing skills", async () => {
|
|
const dir = await mkdtemp(path.join(tmpdir(), "skill-manager-detect-"));
|
|
try {
|
|
await mkdir(path.join(dir, "skills", "create-plan"), { recursive: true });
|
|
await writeFile(path.join(dir, "skills", "create-plan", "SKILL.md"), "---\nname: create-plan\n---\n");
|
|
const state = await detectInstalledSkills({ skillsRoot: path.join(dir, "skills"), clientId: "codex" });
|
|
assert.equal(state["create-plan"].state, "installed");
|
|
assert.equal(state["web-automation"].state, "not-installed");
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("plan install workflow skill includes reviewer-runtime and missing Superpowers prompt", async () => {
|
|
const dir = await mkdtemp(path.join(tmpdir(), "skill-manager-plan-"));
|
|
try {
|
|
await mkdir(path.join(dir, "repo", "skills", "create-plan", "codex"), { recursive: true });
|
|
await writeFile(path.join(dir, "repo", "skills", "create-plan", "codex", "SKILL.md"), "---\nname: create-plan\n---\n");
|
|
const plan = await buildOperationPlan({
|
|
selections: [{ clientId: "codex", scope: "global", skillsRoot: path.join(dir, "install"), actions: { "create-plan": "install" } }],
|
|
assumeYes: true,
|
|
repoRoot: path.join(dir, "repo"),
|
|
superpowersByClient: { codex: [] },
|
|
});
|
|
assert.equal(plan.operations.some((op) => op.kind === "skill" && op.skill === "create-plan" && op.action === "install"), true);
|
|
assert.equal(plan.operations.some((op) => op.kind === "helper" && op.helper === "reviewer-runtime"), true);
|
|
assert.equal(plan.prompts.some((prompt) => prompt.kind === "missing-superpowers" && prompt.clientId === "codex"), true);
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("plan removing last workflow skill prompts for optional Superpowers removal", async () => {
|
|
const dir = await mkdtemp(path.join(tmpdir(), "skill-manager-remove-"));
|
|
try {
|
|
await mkdir(path.join(dir, "skills", "do-task"), { recursive: true });
|
|
await writeFile(path.join(dir, "skills", "do-task", "SKILL.md"), "---\nname: do-task\n---\n");
|
|
const plan = await buildOperationPlan({
|
|
selections: [{ clientId: "codex", scope: "global", skillsRoot: path.join(dir, "skills"), actions: { "do-task": "remove" } }],
|
|
assumeYes: true,
|
|
repoRoot: process.cwd(),
|
|
});
|
|
assert.equal(plan.prompts.some((prompt) => prompt.kind === "remove-superpowers" && prompt.clientId === "codex"), true);
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("pi package mode plans full package install instead of per-skill copy", async () => {
|
|
const dir = await mkdtemp(path.join(tmpdir(), "skill-manager-pi-package-"));
|
|
try {
|
|
const plan = await buildOperationPlan({
|
|
selections: [{ clientId: "pi", scope: "packageLocal", action: "install", actions: { "create-plan": "skip", atlassian: "remove" } }],
|
|
repoRoot: dir,
|
|
});
|
|
assert.equal(plan.operations.some((op) => op.kind === "pi-package" && op.piInstallArg === "-l"), true);
|
|
assert.equal(plan.operations.some((op) => op.kind === "skill"), false);
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("validateRemoveTarget rejects paths outside the manifest root", async () => {
|
|
const dir = await mkdtemp(path.join(tmpdir(), "skill-manager-safe-"));
|
|
try {
|
|
await mkdir(path.join(dir, "skills", "create-plan"), { recursive: true });
|
|
await mkdir(path.join(dir, "outside"), { recursive: true });
|
|
assert.equal(await validateRemoveTarget(path.join(dir, "skills", "create-plan"), path.join(dir, "skills")), true);
|
|
await assert.rejects(() => validateRemoveTarget(path.join(dir, "outside"), path.join(dir, "skills")), /outside skills root/);
|
|
await assert.rejects(() => validateRemoveTarget(dir, dir), /refusing to remove skills root itself/);
|
|
} finally {
|
|
await rm(dir, { recursive: true, force: true });
|
|
}
|
|
});
|