import { describe, expect, it } from "@effect/vitest";
import { BunServices } from "@effect/platform-bun";
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import * as Effect from "effect/Effect";
import {
formatSkillPullSummaryLine,
formatSkillsTable,
parseSkillMarker,
planSkillsPull,
readExistingSkillEntries,
removeSkillDirectory,
resolveEffectiveSkills,
serializeSkillMarker,
skillMarkerFor,
summarizeSkillPullActions,
writeSkillDirectory,
SKILL_MARKER_FILENAME,
type SkillDetail,
type SkillSummary,
} from "./skills";
const withTmp = (body: (dir: string) => Effect.Effect): Effect.Effect =>
Effect.acquireUseRelease(
Effect.sync(() => mkdtempSync(join(tmpdir(), "exec-skills-"))),
body,
(dir) => Effect.sync(() => rmSync(dir, { recursive: true, force: true })),
);
const ORIGIN = "https://example.executor.sh";
const summary = (
input: Partial & { owner: "org" | "user"; name: string },
): SkillSummary => ({
description: "A test skill.",
files: [{ path: "SKILL.md", size: 10, digest: "sha256:aaa" }],
updatedAt: Date.parse("2026-01-01T00:00:00.000Z"),
...input,
});
describe("resolveEffectiveSkills", () => {
it("keeps a single skill unaffected", () => {
const skills = [summary({ owner: "org", name: "docs" })];
expect(resolveEffectiveSkills(skills)).toEqual(skills);
});
it("prefers the user copy over an org copy of the same name", () => {
const org = summary({ owner: "org", name: "docs", description: "org copy" });
const user = summary({ owner: "user", name: "docs", description: "user copy" });
expect(resolveEffectiveSkills([org, user])).toEqual([user]);
// Order shouldn't matter.
expect(resolveEffectiveSkills([user, org])).toEqual([user]);
});
it("keeps distinct names separate", () => {
const a = summary({ owner: "org", name: "docs" });
const b = summary({ owner: "user", name: "release-notes" });
expect(resolveEffectiveSkills([a, b])).toEqual([a, b]);
});
});
describe("formatSkillsTable", () => {
it("reports an empty table", () => {
expect(formatSkillsTable([])).toEqual(["No skills found."]);
});
it("includes a header and one row per skill", () => {
const lines = formatSkillsTable([summary({ owner: "org", name: "docs" })]);
expect(lines).toHaveLength(2);
expect(lines[0]).toContain("OWNER");
expect(lines[1]).toContain("org");
expect(lines[1]).toContain("docs");
});
});
describe("skill markers", () => {
it("round-trips through serialize/parse", () => {
const marker = skillMarkerFor({
origin: ORIGIN,
skill: { owner: "user", name: "docs" },
files: [{ path: "SKILL.md", size: 10, digest: "sha256:aaa" }],
});
const parsed = parseSkillMarker(serializeSkillMarker(marker));
expect(parsed).toEqual(marker);
});
it("rejects malformed JSON", () => {
expect(parseSkillMarker("not json")).toBeUndefined();
});
it("rejects an object missing required fields", () => {
expect(parseSkillMarker(JSON.stringify({ origin: ORIGIN }))).toBeUndefined();
});
it("rejects an invalid owner", () => {
expect(
parseSkillMarker(
JSON.stringify({ origin: ORIGIN, owner: "nobody", name: "docs", digests: {} }),
),
).toBeUndefined();
});
it("rejects non-string digest values", () => {
expect(
parseSkillMarker(
JSON.stringify({ origin: ORIGIN, owner: "user", name: "docs", digests: { "SKILL.md": 1 } }),
),
).toBeUndefined();
});
});
describe("planSkillsPull", () => {
it("adds a skill with no existing directory", () => {
const actions = planSkillsPull({
origin: ORIGIN,
skills: [summary({ owner: "org", name: "docs" })],
existing: [],
});
expect(actions).toEqual([{ name: "docs", kind: "add" }]);
});
it("marks a matching-digest skill unchanged", () => {
const skill = summary({ owner: "org", name: "docs" });
const actions = planSkillsPull({
origin: ORIGIN,
skills: [skill],
existing: [
{
name: "docs",
marker: skillMarkerFor({ origin: ORIGIN, skill, files: skill.files }),
},
],
});
expect(actions).toEqual([{ name: "docs", kind: "unchanged" }]);
});
it("updates a skill whose digests changed", () => {
const skill = summary({ owner: "org", name: "docs" });
const staleMarker = skillMarkerFor({
origin: ORIGIN,
skill,
files: [{ path: "SKILL.md", size: 5, digest: "sha256:old" }],
});
const actions = planSkillsPull({
origin: ORIGIN,
skills: [skill],
existing: [{ name: "docs", marker: staleMarker }],
});
expect(actions).toEqual([{ name: "docs", kind: "update" }]);
});
it("never overwrites a directory with no marker", () => {
const skill = summary({ owner: "org", name: "docs" });
const actions = planSkillsPull({
origin: ORIGIN,
skills: [skill],
existing: [{ name: "docs", marker: undefined }],
});
expect(actions).toEqual([
{ name: "docs", kind: "skip", reason: expect.stringContaining(SKILL_MARKER_FILENAME) },
]);
});
it("skips a directory owned by a different server", () => {
const skill = summary({ owner: "org", name: "docs" });
const otherOriginMarker = skillMarkerFor({
origin: "https://other.example",
skill,
files: skill.files,
});
const actions = planSkillsPull({
origin: ORIGIN,
skills: [skill],
existing: [{ name: "docs", marker: otherOriginMarker }],
});
expect(actions).toEqual([
{ name: "docs", kind: "skip", reason: expect.stringContaining("different server") },
]);
});
it("removes a marker-bearing directory whose skill no longer exists upstream", () => {
const marker = skillMarkerFor({
origin: ORIGIN,
skill: { owner: "org", name: "gone" },
files: [{ path: "SKILL.md", size: 1, digest: "sha256:x" }],
});
const actions = planSkillsPull({
origin: ORIGIN,
skills: [],
existing: [{ name: "gone", marker }],
});
expect(actions).toEqual([{ name: "gone", kind: "remove", reason: expect.any(String) }]);
});
it("never removes an unmanaged directory that has no matching upstream skill", () => {
const actions = planSkillsPull({
origin: ORIGIN,
skills: [],
existing: [{ name: "my-notes", marker: undefined }],
});
expect(actions).toEqual([]);
});
it("never removes a directory owned by a different server", () => {
const marker = skillMarkerFor({
origin: "https://other.example",
skill: { owner: "org", name: "gone" },
files: [],
});
const actions = planSkillsPull({
origin: ORIGIN,
skills: [],
existing: [{ name: "gone", marker }],
});
expect(actions).toEqual([]);
});
});
describe("summarizeSkillPullActions / formatSkillPullSummaryLine", () => {
it("counts each action kind", () => {
const totals = summarizeSkillPullActions([
{ name: "a", kind: "add" },
{ name: "b", kind: "update" },
{ name: "c", kind: "unchanged" },
{ name: "d", kind: "remove" },
{ name: "e", kind: "skip" },
{ name: "f", kind: "add" },
]);
expect(totals).toEqual({ added: 2, updated: 1, unchanged: 1, removed: 1, skipped: 1 });
expect(formatSkillPullSummaryLine(totals)).toBe(
"2 added, 1 updated, 1 removed, 1 skipped (1 unchanged)",
);
});
it("omits the unchanged suffix when there are none", () => {
const totals = summarizeSkillPullActions([{ name: "a", kind: "add" }]);
expect(formatSkillPullSummaryLine(totals)).toBe("1 added, 0 updated, 0 removed, 0 skipped");
});
});
describe("filesystem I/O", () => {
it.effect("readExistingSkillEntries returns [] for a missing root", () =>
withTmp((dir) =>
Effect.gen(function* () {
const entries = yield* readExistingSkillEntries(join(dir, "does-not-exist"));
expect(entries).toEqual([]);
}),
).pipe(Effect.provide(BunServices.layer)),
);
it.effect("readExistingSkillEntries reports marker and unmarked directories", () =>
withTmp((dir) =>
Effect.gen(function* () {
const managed = join(dir, "docs");
mkdirSync(managed, { recursive: true });
const marker = skillMarkerFor({
origin: ORIGIN,
skill: { owner: "org", name: "docs" },
files: [{ path: "SKILL.md", size: 3, digest: "sha256:aaa" }],
});
writeFileSync(join(managed, SKILL_MARKER_FILENAME), serializeSkillMarker(marker));
const unmanaged = join(dir, "my-notes");
mkdirSync(unmanaged, { recursive: true });
// A plain file at the root must not be mistaken for a skill directory.
writeFileSync(join(dir, "readme.txt"), "hi");
const entries = yield* readExistingSkillEntries(dir);
const byName = new Map(entries.map((entry) => [entry.name, entry]));
expect(byName.size).toBe(2);
expect(byName.get("docs")?.marker).toEqual(marker);
expect(byName.get("my-notes")?.marker).toBeUndefined();
}),
).pipe(Effect.provide(BunServices.layer)),
);
it.effect("writeSkillDirectory writes nested files and a marker", () =>
withTmp((dir) =>
Effect.gen(function* () {
const detail: SkillDetail = {
owner: "user",
name: "docs",
description: "A test skill.",
updatedAt: Date.now(),
files: [
{ path: "SKILL.md", size: 5, digest: "sha256:aaa", content: "# Docs" },
{
path: "references/guide.md",
size: 6,
digest: "sha256:bbb",
content: "Guide.",
},
],
};
yield* writeSkillDirectory({ root: dir, origin: ORIGIN, skill: detail });
const entries = yield* readExistingSkillEntries(dir);
expect(entries).toHaveLength(1);
expect(entries[0]?.marker?.origin).toBe(ORIGIN);
expect(entries[0]?.marker?.digests).toEqual({
"SKILL.md": "sha256:aaa",
"references/guide.md": "sha256:bbb",
});
}),
).pipe(Effect.provide(BunServices.layer)),
);
it.effect("removeSkillDirectory deletes the directory recursively", () =>
withTmp((dir) =>
Effect.gen(function* () {
const target = join(dir, "docs", "references");
mkdirSync(target, { recursive: true });
writeFileSync(join(target, "guide.md"), "Guide.");
yield* removeSkillDirectory(dir, "docs");
const entries = yield* readExistingSkillEntries(dir);
expect(entries).toEqual([]);
}),
).pipe(Effect.provide(BunServices.layer)),
);
});