mirror of
https://github.com/NomaDamas/k-skill.git
synced 2026-06-24 02:04:11 +00:00
105 lines
3.9 KiB
JavaScript
105 lines
3.9 KiB
JavaScript
"use strict";
|
|
|
|
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const fs = require("node:fs");
|
|
const os = require("node:os");
|
|
const path = require("node:path");
|
|
|
|
const {
|
|
discoverSkillPaths,
|
|
buildManifest,
|
|
serialize,
|
|
run,
|
|
manifestPathFor,
|
|
EXCLUDED_SKILLS,
|
|
} = require("./generate-plugin-manifest.js");
|
|
|
|
/** Create a throwaway repo-like tree and return its root path. */
|
|
function makeFixtureRoot(layout) {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "k-skill-manifest-"));
|
|
for (const [relPath, contents] of Object.entries(layout)) {
|
|
const full = path.join(root, relPath);
|
|
fs.mkdirSync(path.dirname(full), { recursive: true });
|
|
fs.writeFileSync(full, contents);
|
|
}
|
|
return root;
|
|
}
|
|
|
|
const SKILL_FM = "---\nname: x\ndescription: y\n---\n";
|
|
|
|
test("discoverSkillPaths returns sorted ./-prefixed dirs that contain SKILL.md", () => {
|
|
const root = makeFixtureRoot({
|
|
"lotto-results/SKILL.md": SKILL_FM,
|
|
"ktx-booking/SKILL.md": SKILL_FM,
|
|
"not-a-skill/README.md": "no skill here",
|
|
"top-level-file.md": "ignored",
|
|
});
|
|
assert.deepEqual(discoverSkillPaths(root), ["./ktx-booking", "./lotto-results"]);
|
|
});
|
|
|
|
test("discoverSkillPaths excludes infrastructure dirs and nested fixtures", () => {
|
|
const root = makeFixtureRoot({
|
|
"lotto-results/SKILL.md": SKILL_FM,
|
|
// Excluded root dirs that happen to contain a SKILL.md somewhere.
|
|
"packages/k-lotto/SKILL.md": SKILL_FM,
|
|
"scripts/SKILL.md": SKILL_FM,
|
|
"tools/k-skill-qa-bot/test/fixtures/skills/kbo-results/SKILL.md": SKILL_FM,
|
|
"docs/SKILL.md": SKILL_FM,
|
|
// Dot-directory must be skipped regardless of contents.
|
|
".github/SKILL.md": SKILL_FM,
|
|
});
|
|
assert.deepEqual(discoverSkillPaths(root), ["./lotto-results"]);
|
|
});
|
|
|
|
test("discoverSkillPaths drops deprecated EXCLUDED_SKILLS", () => {
|
|
assert.ok(EXCLUDED_SKILLS.has("blue-ribbon-nearby"));
|
|
const root = makeFixtureRoot({
|
|
"blue-ribbon-nearby/SKILL.md": SKILL_FM,
|
|
"lotto-results/SKILL.md": SKILL_FM,
|
|
});
|
|
assert.deepEqual(discoverSkillPaths(root), ["./lotto-results"]);
|
|
});
|
|
|
|
test("buildManifest backfills identity fields and preserves author overrides", () => {
|
|
const root = makeFixtureRoot({ "lotto-results/SKILL.md": SKILL_FM });
|
|
// Pre-seed a manifest with a custom description that must survive.
|
|
fs.mkdirSync(path.join(root, ".claude-plugin"), { recursive: true });
|
|
fs.writeFileSync(
|
|
manifestPathFor(root),
|
|
serialize({ name: "k-skill", description: "custom desc", skills: [] }),
|
|
);
|
|
|
|
const manifest = buildManifest(root);
|
|
assert.equal(manifest.description, "custom desc"); // not clobbered
|
|
assert.equal(manifest.license, "MIT"); // backfilled from default
|
|
assert.deepEqual(manifest.skills, ["./lotto-results"]); // always refreshed
|
|
});
|
|
|
|
test("run --check passes when manifest matches, fails after drift", () => {
|
|
const root = makeFixtureRoot({ "lotto-results/SKILL.md": SKILL_FM });
|
|
|
|
// First write, then a check should agree.
|
|
const written = run({ root });
|
|
assert.equal(written.written, true);
|
|
assert.equal(run({ root, check: true }).ok, true);
|
|
|
|
// Add a new skill on disk -> check must now report drift.
|
|
fs.mkdirSync(path.join(root, "ktx-booking"));
|
|
fs.writeFileSync(path.join(root, "ktx-booking", "SKILL.md"), SKILL_FM);
|
|
assert.equal(run({ root, check: true }).ok, false);
|
|
});
|
|
|
|
test("run writes deterministic, trailing-newline JSON", () => {
|
|
const root = makeFixtureRoot({ "lotto-results/SKILL.md": SKILL_FM });
|
|
run({ root });
|
|
const raw = fs.readFileSync(manifestPathFor(root), "utf8");
|
|
assert.ok(raw.endsWith("\n"));
|
|
assert.equal(raw, serialize(buildManifest(root)));
|
|
});
|
|
|
|
test("marketplace manifest uses Claude validator-supported top-level keys", () => {
|
|
const marketplacePath = path.join(__dirname, "..", ".claude-plugin", "marketplace.json");
|
|
const marketplace = JSON.parse(fs.readFileSync(marketplacePath, "utf8"));
|
|
assert.deepEqual(Object.keys(marketplace).sort(), ["name", "owner", "plugins"]);
|
|
});
|