Perform code optimization and document cleanup (#1)
check / check (ubuntu-latest) (push) Successful in 2m5s
check / check (macos-latest) (push) Has been cancelled
check-online / check-online (ubuntu-latest) (push) Successful in 1m53s

## Summary
- add repository-wide quality tooling and verification scaffolding, including CI workflows, pnpm workspace setup, ESLint/Prettier/markdown checks, and generated-output verification helpers
- reorganize skill sources and generation flow by introducing canonical `_source` variants, generator/manifests, reusable helper abstractions, and shared web-automation/browser utilities
- clean up and expand documentation so the root README flows into docs and skill docs, with clearer development, reviewer, installer, and workflow guidance

## Notable changes
- docs flow and consistency cleanup across `README.md`, `docs/README.md`, and related docs
- new scripts for `check`, docs verification, generated-file verification, shell portability, and safe directory replacement
- refactors in Atlassian and web-automation skill runtimes to reduce duplication and centralize reusable code
- changelog, development documentation, and CI surface updates

## Test Plan
- [ ] `pnpm run check`
- [ ] review generated/manifests and skill sync outputs
- [ ] smoke-check docs flow from `README.md` to `docs/README.md` to skill docs

## Notes
- this branch currently includes tracked `skills/web-automation/shared/node_modules` content that should be reviewed carefully as potentially noisy/accidental committed artifacts

Co-authored-by: Stefano Fiorini <stefano.fiorini@firsthorizon.com>
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-05-04 04:41:34 +00:00
parent 2deab1c1b4
commit 251148c3ff
373 changed files with 28504 additions and 1281 deletions
+365
View File
@@ -0,0 +1,365 @@
/**
* Unit tests for generate-skills.mjs — RED phase of TDD.
*
* Tests cover:
* - detectFileType: classification of files by extension
* - applyHeader: insertion per file-type-aware policy
* - makePackageJsonContent: unique name + private:true
* - getGeneratedRoots: returns the canonical generated-root list
*/
import assert from "node:assert/strict";
import { mkdtemp, mkdir, writeFile, rm, readFile } from "node:fs/promises";
import crypto from "node:crypto";
import { tmpdir } from "node:os";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SCRIPTS_DIR = path.resolve(__dirname, "..");
const {
detectFileType,
applyHeader,
makePackageJsonContent,
getGeneratedRoots,
buildManifest,
generateSkills,
} = await import(`${SCRIPTS_DIR}/generate-skills.mjs`);
// ── detectFileType ────────────────────────────────────────────────────────
test("detectFileType: .md files → markdown", () => {
assert.equal(detectFileType("SKILL.md"), "markdown");
assert.equal(detectFileType("templates/milestone-plan.md"), "markdown");
assert.equal(detectFileType("README.md"), "markdown");
});
test("detectFileType: .sh files → shell", () => {
assert.equal(detectFileType("run-review.sh"), "shell");
assert.equal(detectFileType("scripts/install.sh"), "shell");
});
test("detectFileType: .ts and .d.ts files → ts", () => {
assert.equal(detectFileType("src/cli.ts"), "ts");
assert.equal(detectFileType("turndown-plugin-gfm.d.ts"), "ts");
assert.equal(detectFileType("auth.ts"), "ts");
});
test("detectFileType: .js files → js", () => {
assert.equal(detectFileType("check-install.js"), "js");
assert.equal(detectFileType("extract.js"), "js");
});
test("detectFileType: .json files → json", () => {
assert.equal(detectFileType("package.json"), "json");
assert.equal(detectFileType("tsconfig.json"), "json");
});
test("detectFileType: .yaml and .yml files → yaml", () => {
assert.equal(detectFileType("pnpm-lock.yaml"), "yaml");
assert.equal(detectFileType("other.yml"), "yaml");
});
test("detectFileType: .jsonc files → jsonc", () => {
assert.equal(detectFileType(".markdownlint.jsonc"), "jsonc");
});
test("detectFileType: unknown extension → unknown", () => {
assert.equal(detectFileType("Makefile"), "unknown");
assert.equal(detectFileType("somefile"), "unknown");
});
// ── applyHeader ───────────────────────────────────────────────────────────
test("applyHeader: markdown with YAML front matter inserts HTML comment after closing ---", () => {
const content = "---\nname: create-plan\n---\n\n# Create Plan\n\nBody.\n";
const result = applyHeader(content, "markdown", "skills/create-plan/_source/claude-code/SKILL.md");
// Front matter block preserved verbatim at start
assert.ok(result.startsWith("---\nname: create-plan\n---\n"), "front matter at start");
// HTML comment present
assert.ok(result.includes("<!-- ⚠️"), "HTML comment present");
// Comment comes after front matter closer and before the title
const commentIdx = result.indexOf("<!-- ⚠️");
const titleIdx = result.indexOf("# Create Plan");
assert.ok(commentIdx !== -1 && titleIdx !== -1, "both positions found");
assert.ok(commentIdx < titleIdx, "comment before title");
// Comment must NOT appear before the first ---
assert.ok(commentIdx > 3, "comment not before front matter");
});
test("applyHeader: markdown without front matter inserts HTML comment at top", () => {
const content = "# Heading\n\nContent\n";
const result = applyHeader(content, "markdown", "skills/create-plan/_source/claude-code/templates/plan.md");
assert.ok(result.startsWith("<!-- ⚠️"), "comment at very top");
assert.ok(result.includes("# Heading"), "original content preserved");
});
test("applyHeader: shell with shebang inserts # comment after shebang", () => {
const content = "#!/usr/bin/env bash\nset -euo pipefail\necho hello\n";
const result = applyHeader(content, "shell", "skills/reviewer-runtime/run-review.sh");
assert.ok(result.startsWith("#!/usr/bin/env bash\n"), "shebang preserved at top");
assert.ok(result.includes("# ⚠️"), "hash comment present");
const shebangEnd = result.indexOf("\n") + 1;
const commentStart = result.indexOf("# ⚠️");
assert.ok(commentStart === shebangEnd, "comment immediately after shebang line");
});
test("applyHeader: ts file inserts // comment at top", () => {
const content = "import path from 'path';\nexport const x = 1;\n";
const result = applyHeader(content, "ts", "skills/atlassian/shared/scripts/src/cli.ts");
assert.ok(result.startsWith("// ⚠️"), "// comment at top");
assert.ok(result.includes("import path from 'path';"), "original content preserved");
});
test("applyHeader: ts file with shebang inserts // comment after shebang", () => {
const content = "#!/usr/bin/env npx tsx\nimport foo from 'foo';\n";
const result = applyHeader(content, "ts", "auth.ts");
assert.ok(result.startsWith("#!/usr/bin/env npx tsx\n"), "shebang preserved at top");
assert.ok(result.includes("// ⚠️"), "// comment present");
const shebangEnd = result.indexOf("\n") + 1;
const commentStart = result.indexOf("// ⚠️");
assert.ok(commentStart === shebangEnd, "// comment immediately after shebang");
});
test("applyHeader: js file with shebang inserts // comment after shebang", () => {
const content = "#!/usr/bin/env node\nconst x = 1;\n";
const result = applyHeader(content, "js", "check-install.js");
assert.ok(result.startsWith("#!/usr/bin/env node\n"), "shebang preserved at top");
assert.ok(result.includes("// ⚠️"), "// comment present");
});
test("applyHeader: js file inserts // comment at top", () => {
const content = "const x = require('x');\n";
const result = applyHeader(content, "js", "check-install.js");
assert.ok(result.startsWith("// ⚠️"), "// comment at top");
});
test("applyHeader: yaml file inserts # comment at top", () => {
const content = "lockfileVersion: '9.0'\n\npackages:\n";
const result = applyHeader(content, "yaml", "pnpm-lock.yaml");
assert.ok(result.startsWith("# ⚠️"), "# comment at top");
});
test("applyHeader: jsonc file inserts // comment at top", () => {
const content = "// existing comment\n{\n \"key\": true\n}\n";
const result = applyHeader(content, "jsonc", ".markdownlint.jsonc");
assert.ok(result.startsWith("// ⚠️"), "// comment at top");
});
test("applyHeader: json file returns content unchanged (no header)", () => {
const content = '{\n "name": "test"\n}\n';
const result = applyHeader(content, "json", "package.json");
assert.equal(result, content, "JSON file must not be modified");
});
test("applyHeader: unknown type returns content unchanged", () => {
const content = "raw content\n";
const result = applyHeader(content, "unknown", "Makefile");
assert.equal(result, content);
});
test("applyHeader: header contains canonical source hint", () => {
const hint = "skills/create-plan/_source/pi/SKILL.md";
const content = "---\nname: create-plan\n---\n\n# Title\n";
const result = applyHeader(content, "markdown", hint);
assert.ok(result.includes(hint), "canonical hint appears in header");
});
test("applyHeader: never inserts header before shebang", () => {
const content = "#!/usr/bin/env bash\nset -euo pipefail\n";
const result = applyHeader(content, "shell", "run.sh");
assert.ok(result.startsWith("#!/"), "shebang still first");
});
// ── makePackageJsonContent ────────────────────────────────────────────────
test("makePackageJsonContent: renames name to scoped unique form", () => {
const src = { name: "atlassian-skill-scripts", version: "1.0.0" };
const result = makePackageJsonContent(src, "atlassian", "claude-code");
assert.equal(result.name, "@ai-coding-skills/atlassian-claude-code");
});
test("makePackageJsonContent: adds private:true", () => {
const src = { name: "web-automation-scripts", version: "1.0.0" };
const result = makePackageJsonContent(src, "web-automation", "codex");
assert.equal(result.private, true);
});
test("makePackageJsonContent: preserves all other top-level fields", () => {
const src = {
name: "atlassian-skill-scripts",
version: "1.0.0",
type: "module",
scripts: { typecheck: "tsc --noEmit" },
dependencies: { commander: "^13.1.0" },
devDependencies: { tsx: "^4.20.5" },
packageManager: "pnpm@10.18.1",
};
const result = makePackageJsonContent(src, "atlassian", "cursor");
assert.equal(result.version, "1.0.0");
assert.equal(result.type, "module");
assert.deepEqual(result.scripts, { typecheck: "tsc --noEmit" });
assert.deepEqual(result.dependencies, { commander: "^13.1.0" });
assert.equal(result.packageManager, "pnpm@10.18.1");
});
test("makePackageJsonContent: pi-package mirror uses -pi agent suffix", () => {
const src = { name: "atlassian-skill-scripts" };
const result = makePackageJsonContent(src, "atlassian", "pi");
assert.equal(result.name, "@ai-coding-skills/atlassian-pi");
});
test("makePackageJsonContent: does not mutate the source object", () => {
const src = { name: "original", version: "1.0.0" };
makePackageJsonContent(src, "atlassian", "codex");
assert.equal(src.name, "original", "source object not mutated");
});
// ── getGeneratedRoots ─────────────────────────────────────────────────────
test("getGeneratedRoots: returns at least one root per agent per skills skill", () => {
const roots = getGeneratedRoots();
assert.ok(Array.isArray(roots), "returns array");
assert.ok(roots.length > 0, "non-empty");
const agents = ["claude-code", "codex", "cursor", "opencode", "pi"];
const skillsWithAgents = ["create-plan", "do-task", "implement-plan", "atlassian", "web-automation"];
for (const skill of skillsWithAgents) {
for (const agent of agents) {
const expected = `skills/${skill}/${agent}`;
assert.ok(roots.includes(expected), `missing generated root: ${expected}`);
}
}
});
test("getGeneratedRoots: includes pi-package mirrors for all skills", () => {
const roots = getGeneratedRoots();
const piPackageSkills = ["atlassian", "create-plan", "do-task", "implement-plan", "web-automation"];
for (const skill of piPackageSkills) {
const expected = `pi-package/skills/${skill}`;
assert.ok(roots.includes(expected), `missing pi-package mirror root: ${expected}`);
}
});
test("getGeneratedRoots: includes reviewer-runtime/pi", () => {
const roots = getGeneratedRoots();
assert.ok(roots.includes("skills/reviewer-runtime/pi"), "reviewer-runtime/pi missing");
});
test("getGeneratedRoots: does not include _source or shared directories", () => {
const roots = getGeneratedRoots();
for (const r of roots) {
assert.ok(!r.includes("_source"), `root should not contain _source: ${r}`);
assert.ok(!r.endsWith("/shared"), `root should not be shared: ${r}`);
assert.ok(!r.includes("shared/scripts"), `root should not contain shared/scripts: ${r}`);
}
});
// ── buildManifest ─────────────────────────────────────────────────────────
test("buildManifest: returns object with $schema and generator markers", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "manifest-test-"));
try {
await writeFile(path.join(dir, "SKILL.md"), "---\nname: test\n---\n# Test\n");
await mkdir(path.join(dir, "templates"), { recursive: true });
await writeFile(path.join(dir, "templates", "plan.md"), "# Plan\n");
// Write .generated-manifest.json itself (should be excluded from listing)
await writeFile(path.join(dir, ".generated-manifest.json"), "{}");
const manifest = await buildManifest(dir, "skills/test/claude-code");
assert.ok(manifest.$schema, "$schema field present");
assert.ok(manifest.generator, "generator field present");
assert.equal(manifest.generatedRoot, "skills/test/claude-code");
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("buildManifest: does not include .generated-manifest.json in files list", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "manifest-self-"));
try {
await writeFile(path.join(dir, "SKILL.md"), "# skill\n");
await writeFile(path.join(dir, ".generated-manifest.json"), "{}");
const manifest = await buildManifest(dir, "skills/test/pi");
const paths = manifest.files.map((f) => f.path);
assert.ok(!paths.includes(".generated-manifest.json"), "manifest must not list itself");
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("buildManifest: files list includes path, kind, mode, sha256", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "manifest-fields-"));
try {
await writeFile(path.join(dir, "SKILL.md"), "# skill\n");
const manifest = await buildManifest(dir, "skills/test/codex");
assert.equal(manifest.files.length, 1);
const entry = manifest.files[0];
assert.equal(entry.path, "SKILL.md");
assert.equal(entry.kind, "file");
assert.ok(typeof entry.mode === "string" && entry.mode.match(/^\d{3}$/), "mode is 3-digit octal string");
assert.ok(typeof entry.sha256 === "string" && entry.sha256.length === 64, "sha256 is 64-char hex");
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("buildManifest: files are sorted by path", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "manifest-sorted-"));
try {
await mkdir(path.join(dir, "templates"), { recursive: true });
await writeFile(path.join(dir, "SKILL.md"), "# skill\n");
await writeFile(path.join(dir, "templates", "z.md"), "z\n");
await writeFile(path.join(dir, "templates", "a.md"), "a\n");
const manifest = await buildManifest(dir, "skills/test/cursor");
const paths = manifest.files.map((f) => f.path);
const sorted = [...paths].sort();
assert.deepEqual(paths, sorted, "files must be sorted by path");
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("buildManifest: sha256 matches actual file content", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "manifest-hash-"));
try {
const content = "---\nname: test\n---\n# Title\n";
await writeFile(path.join(dir, "SKILL.md"), content);
const manifest = await buildManifest(dir, "skills/test/opencode");
const expected = crypto.createHash("sha256").update(content, "utf8").digest("hex");
assert.equal(manifest.files[0].sha256, expected, "sha256 matches file content");
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("generateSkills: clears pre-existing empty generated directories without EISDIR", async () => {
const targetRoot = await mkdtemp(path.join(tmpdir(), "generate-skills-target-"));
try {
await mkdir(path.join(targetRoot, "skills", "create-plan", "claude-code", "templates"), { recursive: true });
await generateSkills(path.resolve(SCRIPTS_DIR, ".."), { targetRoot });
await readFile(path.join(targetRoot, "skills", "create-plan", "claude-code", "SKILL.md"), "utf8");
await readFile(path.join(targetRoot, "skills", "create-plan", "claude-code", "templates", "milestone-plan.md"), "utf8");
} finally {
await rm(targetRoot, { recursive: true, force: true });
}
});
+139
View File
@@ -0,0 +1,139 @@
import assert from "node:assert/strict";
import { mkdtemp, mkdir, writeFile, readFile, readdir, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import test from "node:test";
import { safeReplaceDir } from "../lib/safe-replace-dir.mjs";
// ── Happy path ────────────────────────────────────────────────────────────
test("safeReplaceDir copies source content into the target", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "safe-replace-copy-"));
try {
const safetyRoot = path.join(dir, "root");
const source = path.join(dir, "source");
const target = path.join(safetyRoot, "target");
await mkdir(source, { recursive: true });
await writeFile(path.join(source, "file.txt"), "hello");
await mkdir(safetyRoot, { recursive: true });
await safeReplaceDir(source, target, safetyRoot);
const content = await readFile(path.join(target, "file.txt"), "utf8");
assert.equal(content, "hello");
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("safeReplaceDir removes existing content before replacing", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "safe-replace-stale-"));
try {
const safetyRoot = path.join(dir, "root");
const source = path.join(dir, "source");
const target = path.join(safetyRoot, "target");
await mkdir(target, { recursive: true });
await writeFile(path.join(target, "old.txt"), "stale");
await mkdir(source, { recursive: true });
await writeFile(path.join(source, "new.txt"), "fresh");
await safeReplaceDir(source, target, safetyRoot);
const files = await readdir(target);
assert.deepEqual(files.sort(), ["new.txt"]);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("safeReplaceDir creates target parent directories if they do not exist", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "safe-replace-mkdir-"));
try {
const safetyRoot = path.join(dir, "root");
const source = path.join(dir, "source");
const target = path.join(safetyRoot, "nested", "target");
await mkdir(source, { recursive: true });
await writeFile(path.join(source, "data.txt"), "data");
await mkdir(safetyRoot, { recursive: true });
// nested parent does NOT exist yet
await safeReplaceDir(source, target, safetyRoot);
const content = await readFile(path.join(target, "data.txt"), "utf8");
assert.equal(content, "data");
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("safeReplaceDir creates deeply nested parent directories (2+ levels missing)", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "safe-replace-deep-"));
try {
const safetyRoot = path.join(dir, "root");
const source = path.join(dir, "source");
// two parent levels (a/b) do NOT exist under safetyRoot
const target = path.join(safetyRoot, "a", "b", "target");
await mkdir(source, { recursive: true });
await writeFile(path.join(source, "deep.txt"), "deep");
await mkdir(safetyRoot, { recursive: true });
// a/ and a/b/ intentionally NOT created
await safeReplaceDir(source, target, safetyRoot);
const content = await readFile(path.join(target, "deep.txt"), "utf8");
assert.equal(content, "deep");
} finally {
await rm(dir, { recursive: true, force: true });
}
});
// ── Safety checks ─────────────────────────────────────────────────────────
test("safeReplaceDir refuses when target is outside the safety root", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "safe-replace-outside-"));
try {
const safetyRoot = path.join(dir, "root");
const source = path.join(dir, "source");
const outside = path.join(dir, "outside");
await mkdir(source, { recursive: true });
await mkdir(safetyRoot, { recursive: true });
await assert.rejects(
() => safeReplaceDir(source, outside, safetyRoot),
/outside safety root/,
);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("safeReplaceDir refuses when target equals the safety root", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "safe-replace-same-"));
try {
const safetyRoot = path.join(dir, "root");
const source = path.join(dir, "source");
await mkdir(source, { recursive: true });
await mkdir(safetyRoot, { recursive: true });
await assert.rejects(
() => safeReplaceDir(source, safetyRoot, safetyRoot),
/outside safety root/,
);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("safeReplaceDir refuses an empty target string", async () => {
await assert.rejects(
() => safeReplaceDir("/any", "", "/root"),
/unsafe target/,
);
});
@@ -0,0 +1,137 @@
import assert from "node:assert/strict";
import { mkdtemp, mkdir, writeFile, rm, lstat, symlink } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import test from "node:test";
import { removeTarget } from "../lib/skill-manager-core.mjs";
// ── Happy path: remove existing directory ─────────────────────────────────
test("removeTarget removes an installed skill directory", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "smc-remove-dir-"));
try {
const skillsRoot = path.join(dir, "skills");
const target = path.join(skillsRoot, "create-plan");
await mkdir(target, { recursive: true });
await writeFile(path.join(target, "SKILL.md"), "---\nname: create-plan\n---\n");
const op = { kind: "skill", action: "remove", target, skillsRoot };
const result = await removeTarget(op);
assert.equal(result.status, "ok");
let exists = true;
try {
await lstat(target);
} catch {
exists = false;
}
assert.equal(exists, false, "target directory should be gone");
} finally {
await rm(dir, { recursive: true, force: true });
}
});
// ── Happy path: remove symbolic link ─────────────────────────────────────
test("removeTarget removes a symlink without following it", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "smc-remove-sym-"));
try {
const skillsRoot = path.join(dir, "skills");
const realDir = path.join(dir, "real-skill");
const target = path.join(skillsRoot, "create-plan");
await mkdir(skillsRoot, { recursive: true });
await mkdir(realDir, { recursive: true });
await writeFile(path.join(realDir, "SKILL.md"), "---\nname: create-plan\n---\n");
await symlink(realDir, target, "dir");
const op = { kind: "skill", action: "remove", target, skillsRoot };
const result = await removeTarget(op);
assert.equal(result.status, "ok");
// symlink itself should be gone
let symlinkExists = true;
try {
await lstat(target);
} catch {
symlinkExists = false;
}
assert.equal(symlinkExists, false, "symlink should be removed");
// real directory should still exist
const realStat = await lstat(realDir);
assert.ok(realStat.isDirectory(), "real directory must not be touched");
} finally {
await rm(dir, { recursive: true, force: true });
}
});
// ── Missing skill (partial state): target does not exist ─────────────────
test("removeTarget succeeds when target does not exist (idempotent)", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "smc-remove-missing-"));
try {
const skillsRoot = path.join(dir, "skills");
const target = path.join(skillsRoot, "create-plan");
await mkdir(skillsRoot, { recursive: true });
// target intentionally NOT created
const op = { kind: "skill", action: "remove", target, skillsRoot };
const result = await removeTarget(op);
assert.equal(result.status, "ok");
} finally {
await rm(dir, { recursive: true, force: true });
}
});
// ── Partial state: directory exists but is empty ─────────────────────────
test("removeTarget removes an empty skill directory", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "smc-remove-empty-"));
try {
const skillsRoot = path.join(dir, "skills");
const target = path.join(skillsRoot, "create-plan");
await mkdir(target, { recursive: true });
// directory exists but has no SKILL.md (partial install state)
const op = { kind: "skill", action: "remove", target, skillsRoot };
const result = await removeTarget(op);
assert.equal(result.status, "ok");
let exists = true;
try {
await lstat(target);
} catch {
exists = false;
}
assert.equal(exists, false);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
// ── Safety: refuses to remove path outside skills root ────────────────────
test("removeTarget refuses to remove a path outside the skills root", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "smc-remove-outside-"));
try {
const skillsRoot = path.join(dir, "skills");
const outsideTarget = path.join(dir, "outside");
await mkdir(skillsRoot, { recursive: true });
await mkdir(outsideTarget, { recursive: true });
const op = {
kind: "skill",
action: "remove",
target: outsideTarget,
skillsRoot,
};
await assert.rejects(() => removeTarget(op), /outside skills root/);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
+1 -1
View File
@@ -404,7 +404,7 @@ test("cli exits without confirmation when no operations are planned", () => {
const output = execFileSync(process.execPath, [
path.join(REPO_ROOT, "scripts", "manage-skills.mjs"),
"--answers",
"/dev/stdin",
"-",
], {
cwd: REPO_ROOT,
encoding: "utf8",
+71
View File
@@ -0,0 +1,71 @@
/**
* verify-docs-flow.test.mjs — unit tests for the docs-flow verifier (M2, S-206)
*
* Tests the exported functions from scripts/verify-docs-flow.mjs.
* Each test is structured as a RED → GREEN cycle: we first verify the function
* exists and behaves correctly; any structural violation surfaces as a clear
* test failure rather than a cryptic runtime error.
*/
import { test, describe } from "node:test";
import assert from "node:assert/strict";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = path.resolve(__dirname, "../..");
// ── Helpers ────────────────────────────────────────────────────────────────
/**
* Import the verifier lazily so missing-module errors surface as test
* failures rather than crashing the whole suite.
*/
async function loadVerifier() {
const verifierPath = path.join(REPO_ROOT, "scripts", "verify-docs-flow.mjs");
return import(verifierPath);
}
// ── S-206 acceptance checks ────────────────────────────────────────────────
describe("verify-docs-flow.mjs", () => {
test("module exists and exports required functions", async () => {
const mod = await loadVerifier();
assert.equal(typeof mod.checkDocsIndexCoverage, "function",
"must export checkDocsIndexCoverage");
assert.equal(typeof mod.checkReviewerMatrixConsistency, "function",
"must export checkReviewerMatrixConsistency");
assert.equal(typeof mod.checkTelegramAgentCoverage, "function",
"must export checkTelegramAgentCoverage");
assert.equal(typeof mod.checkRepoPathsExist, "function",
"must export checkRepoPathsExist");
});
test("checkDocsIndexCoverage: every docs/*.md is linked from docs/README.md", async () => {
const { checkDocsIndexCoverage } = await loadVerifier();
const errors = await checkDocsIndexCoverage(REPO_ROOT);
assert.deepEqual(errors, [],
`docs/README.md coverage errors:\n${errors.join("\n")}`);
});
test("checkReviewerMatrixConsistency: reviewer tables consistent across canonical sources", async () => {
const { checkReviewerMatrixConsistency } = await loadVerifier();
const errors = await checkReviewerMatrixConsistency(REPO_ROOT);
assert.deepEqual(errors, [],
`Reviewer matrix inconsistency errors:\n${errors.join("\n")}`);
});
test("checkTelegramAgentCoverage: Telegram doc lists all agents with Pi helpers", async () => {
const { checkTelegramAgentCoverage } = await loadVerifier();
const errors = await checkTelegramAgentCoverage(REPO_ROOT);
assert.deepEqual(errors, [],
`Telegram coverage errors:\n${errors.join("\n")}`);
});
test("checkRepoPathsExist: all repo-relative paths in README.md and docs/ exist", async () => {
const { checkRepoPathsExist } = await loadVerifier();
const errors = await checkRepoPathsExist(REPO_ROOT);
assert.deepEqual(errors, [],
`Broken repo-relative path references:\n${errors.join("\n")}`);
});
});
+348
View File
@@ -0,0 +1,348 @@
/**
* Unit tests for verify-generated.mjs — RED phase of TDD.
*
* Key contract from acceptance criteria:
* - A stray file under skills/<skill>/_source/ or skills/<skill>/shared/
* does NOT cause verify:generated to flag it as stale.
* - A stray file under skills/<skill>/<agent>/ (other than
* .generated-manifest.json) DOES cause verify:generated to flag it.
*/
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 { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SCRIPTS_DIR = path.resolve(__dirname, "..");
const { verifyGenerated } = await import(`${SCRIPTS_DIR}/verify-generated.mjs`);
// ── Stray-file detection boundary tests ──────────────────────────────────
test("verifyGenerated: stray file in _source/ is NOT flagged as stale", async () => {
// This uses the real repo's canonical source - if stray file under _source
// is added, verify-generated should not complain about it.
// We test this by verifying the function signature accepts a repoRoot and
// generatedRoots override, and that the verifier only walks declared roots.
// (Full integration test against the real repo runs in pnpm run verify:generated)
const dir = await mkdtemp(path.join(tmpdir(), "vg-stray-source-"));
try {
// Create a fake skill structure with _source/ and a generated root
const skillName = "test-skill";
const agentName = "claude-code";
const sourceDir = path.join(dir, "skills", skillName, "_source", agentName);
const agentDir = path.join(dir, "skills", skillName, agentName);
const sharedDir = path.join(dir, "skills", skillName, "shared");
await mkdir(sourceDir, { recursive: true });
await mkdir(sharedDir, { recursive: true });
await mkdir(agentDir, { recursive: true });
const skillContent = "---\nname: test-skill\n---\n\n# Test Skill\n";
await writeFile(path.join(sourceDir, "SKILL.md"), skillContent);
// Add a STRAY file in _source/ — this should NOT trigger stale detection
await writeFile(path.join(sourceDir, "STRAY.md"), "stray content");
// Add a STRAY file in shared/ — this should NOT trigger stale detection
await writeFile(path.join(sharedDir, "shared-stray.txt"), "shared stray");
// The generated root is EMPTY (no manifest, no files) — but we're testing
// that _source/ and shared/ stray files don't appear in stale detection.
// We can test this indirectly: verifyGenerated with no declared roots for
// this dir returns no stale errors about _source/ or shared/.
const result = await verifyGenerated(dir, {
// Override generated roots to be empty for this minimal test
generatedRootsOverride: [],
});
// No stale errors from _source/ or shared/
const sourceErrors = result.errors.filter(
(e) => e.includes("_source") || e.includes("shared"),
);
assert.equal(sourceErrors.length, 0, `unexpected stale errors from _source/shared: ${JSON.stringify(sourceErrors)}`);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("verifyGenerated: stray file in agent dir IS flagged as stale", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "vg-stray-agent-"));
try {
const skillName = "test-skill";
const agentName = "claude-code";
const sourceDir = path.join(dir, "skills", skillName, "_source", agentName);
const agentDir = path.join(dir, "skills", skillName, agentName);
await mkdir(sourceDir, { recursive: true });
await mkdir(agentDir, { recursive: true });
const skillContent = "---\nname: test-skill\n---\n\n# Test Skill\n";
await writeFile(path.join(sourceDir, "SKILL.md"), skillContent);
// Generate the agent dir with proper content + manifest
const headerLine =
`<!-- ⚠️ GENERATED FILE do not edit directly. ` +
`Edit the canonical source in skills/${skillName}/_source/${agentName}/SKILL.md ` +
`and run \`pnpm run sync:pi\`. -->`;
const generatedContent = `---\nname: test-skill\n---\n\n${headerLine}\n\n# Test Skill\n`;
await writeFile(path.join(agentDir, "SKILL.md"), generatedContent);
// Add a STRAY file in the agent dir — this SHOULD be flagged
await writeFile(path.join(agentDir, "STRAY.md"), "stray content");
// Write a manifest that does NOT include STRAY.md
const { buildManifest } = await import(`${SCRIPTS_DIR}/generate-skills.mjs`);
const manifest = await buildManifest(agentDir, `skills/${skillName}/${agentName}`);
// Remove STRAY.md from manifest (simulate pre-stray-add manifest)
manifest.files = manifest.files.filter((f) => f.path !== "STRAY.md");
await writeFile(
path.join(agentDir, ".generated-manifest.json"),
JSON.stringify(manifest, null, 2) + "\n",
);
const result = await verifyGenerated(dir, {
generatedRootsOverride: [`skills/${skillName}/${agentName}`],
});
assert.equal(result.ok, false, "should fail when stray file present");
const strayError = result.errors.some((e) => e.includes("STRAY.md"));
assert.ok(strayError, `STRAY.md should appear in errors: ${JSON.stringify(result.errors)}`);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("verifyGenerated: .generated-manifest.json is excluded from stale-file detection", async () => {
// Even though .generated-manifest.json is in the generated root, it should
// not be considered a "stale file" just because it's not in the files list
const dir = await mkdtemp(path.join(tmpdir(), "vg-manifest-self-"));
try {
const skillName = "test-skill";
const agentName = "pi";
const sourceDir = path.join(dir, "skills", skillName, "_source", agentName);
const agentDir = path.join(dir, "skills", skillName, agentName);
await mkdir(sourceDir, { recursive: true });
await mkdir(agentDir, { recursive: true });
const skillContent = "---\nname: test-skill\n---\n\n# Test Skill\n";
await writeFile(path.join(sourceDir, "SKILL.md"), skillContent);
const headerLine =
`<!-- ⚠️ GENERATED FILE do not edit directly. ` +
`Edit the canonical source in skills/${skillName}/_source/${agentName}/SKILL.md ` +
`and run \`pnpm run sync:pi\`. -->`;
const generatedContent = `---\nname: test-skill\n---\n\n${headerLine}\n\n# Test Skill\n`;
await writeFile(path.join(agentDir, "SKILL.md"), generatedContent);
// Write manifest (will include SKILL.md, not itself)
const { buildManifest } = await import(`${SCRIPTS_DIR}/generate-skills.mjs`);
const manifest = await buildManifest(agentDir, `skills/${skillName}/${agentName}`);
await writeFile(
path.join(agentDir, ".generated-manifest.json"),
JSON.stringify(manifest, null, 2) + "\n",
);
const result = await verifyGenerated(dir, {
generatedRootsOverride: [`skills/${skillName}/${agentName}`],
});
// Should pass — .generated-manifest.json is excluded from stale detection
const manifestErrors = result.errors.filter((e) => e.includes(".generated-manifest.json"));
assert.equal(manifestErrors.length, 0, `manifest file should not appear as stale: ${JSON.stringify(manifestErrors)}`);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("verifyGenerated: missing file from manifest is flagged as deleted", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "vg-missing-file-"));
try {
const skillName = "test-skill";
const agentName = "cursor";
const sourceDir = path.join(dir, "skills", skillName, "_source", agentName);
const agentDir = path.join(dir, "skills", skillName, agentName);
await mkdir(sourceDir, { recursive: true });
await mkdir(agentDir, { recursive: true });
const skillContent = "---\nname: test-skill\n---\n\n# Test Skill\n";
await writeFile(path.join(sourceDir, "SKILL.md"), skillContent);
await writeFile(path.join(agentDir, "SKILL.md"), "generated content\n");
// Manifest claims templates/plan.md exists, but the file doesn't
const manifest = {
$schema: "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json",
generator: "scripts/generate-skills.mjs",
generatedRoot: `skills/${skillName}/${agentName}`,
files: [
{ path: "SKILL.md", kind: "file", mode: "644", sha256: "aaa" },
{ path: "templates/plan.md", kind: "file", mode: "644", sha256: "bbb" },
],
};
await writeFile(
path.join(agentDir, ".generated-manifest.json"),
JSON.stringify(manifest, null, 2) + "\n",
);
const result = await verifyGenerated(dir, {
generatedRootsOverride: [`skills/${skillName}/${agentName}`],
});
assert.equal(result.ok, false, "should fail on missing file");
const missingError = result.errors.some((e) => e.includes("templates/plan.md"));
assert.ok(missingError, `missing file should appear in errors: ${JSON.stringify(result.errors)}`);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("verifyGenerated: content mismatch is flagged", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "vg-content-mismatch-"));
try {
const skillName = "test-skill";
const agentName = "opencode";
const sourceDir = path.join(dir, "skills", skillName, "_source", agentName);
const agentDir = path.join(dir, "skills", skillName, agentName);
await mkdir(sourceDir, { recursive: true });
await mkdir(agentDir, { recursive: true });
await writeFile(path.join(sourceDir, "SKILL.md"), "---\nname: test-skill\n---\n\n# Original\n");
// Agent dir has DIFFERENT content than what manifest says
await writeFile(path.join(agentDir, "SKILL.md"), "---\nname: test-skill\n---\n\n# Modified!\n");
const manifest = {
$schema: "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json",
generator: "scripts/generate-skills.mjs",
generatedRoot: `skills/${skillName}/${agentName}`,
files: [
{
path: "SKILL.md",
kind: "file",
mode: "644",
// SHA of the ORIGINAL content (not what's on disk)
sha256: "0000000000000000000000000000000000000000000000000000000000000000",
},
],
};
await writeFile(
path.join(agentDir, ".generated-manifest.json"),
JSON.stringify(manifest, null, 2) + "\n",
);
const result = await verifyGenerated(dir, {
generatedRootsOverride: [`skills/${skillName}/${agentName}`],
});
assert.equal(result.ok, false, "should fail on content mismatch");
const mismatchError = result.errors.some((e) => e.includes("SKILL.md"));
assert.ok(mismatchError, `content mismatch should appear in errors: ${JSON.stringify(result.errors)}`);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("verifyGenerated: manifest entry with wrong sha256 is flagged even when paths match", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "vg-manifest-sha-"));
try {
const skillName = "test-skill";
const agentName = "codex";
const sourceDir = path.join(dir, "skills", skillName, "_source", agentName);
const agentDir = path.join(dir, "skills", skillName, agentName);
await mkdir(sourceDir, { recursive: true });
await mkdir(agentDir, { recursive: true });
const content = "---\nname: test-skill\n---\n\n# Test Skill\n";
await writeFile(path.join(sourceDir, "SKILL.md"), content);
await writeFile(path.join(agentDir, "SKILL.md"), content);
// Manifest has correct path but deliberately wrong sha256 (simulates corrupted metadata)
const manifest = {
$schema: "https://ai-coding-skills.dev/schemas/generated-manifest/v1.json",
generator: "scripts/generate-skills.mjs",
generatedRoot: `skills/${skillName}/${agentName}`,
files: [
{
path: "SKILL.md",
kind: "file",
mode: "644",
sha256: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
},
],
};
await writeFile(
path.join(agentDir, ".generated-manifest.json"),
JSON.stringify(manifest, null, 2) + "\n",
);
const result = await verifyGenerated(dir, {
generatedRootsOverride: [`skills/${skillName}/${agentName}`],
});
// Even though file paths match, the sha256 mismatch in the manifest should be detected
assert.equal(result.ok, false, "should fail on sha256 mismatch in manifest");
const sha256Error = result.errors.some(
(e) => e.includes("sha256") || e.includes("SKILL.md"),
);
assert.ok(sha256Error, `sha256 mismatch should appear in errors: ${JSON.stringify(result.errors)}`);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("verifyGenerated: manifest entry with wrong mode is flagged even when paths match", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "vg-manifest-mode-"));
try {
const skillName = "test-skill";
const agentName = "cursor";
const agentDir = path.join(dir, "skills", skillName, agentName);
await mkdir(agentDir, { recursive: true });
const content = "# Test Skill\n";
await writeFile(path.join(agentDir, "SKILL.md"), content);
// Build a correct manifest first (with real sha256)
const { buildManifest } = await import(`${SCRIPTS_DIR}/generate-skills.mjs`);
const correctManifest = await buildManifest(agentDir, `skills/${skillName}/${agentName}`);
// Tamper: change the mode field for the SKILL.md entry
const tamperedManifest = {
...correctManifest,
files: correctManifest.files.map((f) =>
f.path === "SKILL.md" ? { ...f, mode: "777" } : f,
),
};
await writeFile(
path.join(agentDir, ".generated-manifest.json"),
JSON.stringify(tamperedManifest, null, 2) + "\n",
);
const result = await verifyGenerated(dir, {
generatedRootsOverride: [`skills/${skillName}/${agentName}`],
});
// The mode mismatch in the manifest should be detected by diffManifests
assert.equal(result.ok, false, "should fail on mode mismatch in manifest");
const modeError = result.errors.some(
(e) => e.includes("mode") || e.includes("SKILL.md"),
);
assert.ok(modeError, `mode mismatch should appear in errors: ${JSON.stringify(result.errors)}`);
} finally {
await rm(dir, { recursive: true, force: true });
}
});