import assert from "node:assert/strict" import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" import path from "node:path" import { test } from "node:test" import { diffSnapshots, hashDir, listSkillDirs, recordNewSkills } from "../scripts/sync-upstream.ts" function fixture() { const dir = mkdtempSync(path.join(tmpdir(), "sync-test-")) const skill = (root: string, bucket: string, name: string, body: string, extra?: string) => { mkdirSync(path.join(root, bucket, name), { recursive: true }) writeFileSync(path.join(root, bucket, name, "SKILL.md"), body) if (extra) writeFileSync(path.join(root, bucket, name, "EXTRA.md"), extra) } return { dir, skill } } test("listSkillDirs finds only dirs containing SKILL.md", () => { const { dir, skill } = fixture() try { skill(dir, "engineering", "tdd", "---\nname: tdd\n---\n") skill(dir, "misc", "wizard", "---\nname: wizard\n---\n") mkdirSync(path.join(dir, "deprecated"), { recursive: true }) writeFileSync(path.join(dir, "deprecated", "README.md"), "empty bucket") mkdirSync(path.join(dir, "engineering", "not-a-skill"), { recursive: true }) assert.deepEqual(listSkillDirs(dir), ["engineering/tdd", "misc/wizard"]) assert.deepEqual(listSkillDirs(path.join(dir, "missing")), []) } finally { rmSync(dir, { recursive: true, force: true }) } }) test("diffSnapshots reports added, removed and changed skill dirs", () => { const { dir, skill } = fixture() const before = path.join(dir, "before") const after = path.join(dir, "after") try { skill(before, "engineering", "tdd", "v1") skill(before, "engineering", "gone", "v1") skill(after, "engineering", "tdd", "v2") skill(after, "productivity", "fresh", "v1") assert.deepEqual(diffSnapshots(before, after), { added: ["productivity/fresh"], removed: ["engineering/gone"], changed: ["engineering/tdd"], }) } finally { rmSync(dir, { recursive: true, force: true }) } }) test("hashDir ignores file mtime and is order-independent", () => { const { dir, skill } = fixture() const a = path.join(dir, "a") const b = path.join(dir, "b") try { skill(a, "x", "s", "same", "extra") skill(b, "x", "s", "same", "extra") assert.equal(hashDir(path.join(a, "x", "s")), hashDir(path.join(b, "x", "s"))) writeFileSync(path.join(b, "x", "s", "EXTRA.md"), "different") assert.notEqual(hashDir(path.join(a, "x", "s")), hashDir(path.join(b, "x", "s"))) } finally { rmSync(dir, { recursive: true, force: true }) } }) test("recordNewSkills appends needs-review entries once and leaves known skills alone", () => { const { dir } = fixture() const file = path.join(dir, "classification.json") try { writeFileSync( file, JSON.stringify({ skills: [{ name: "tdd", bucket: "engineering", status: "analyzed" }] }), ) const fresh = recordNewSkills(file, ["engineering/tdd", "misc/newcomer"]) assert.deepEqual(fresh, ["newcomer"]) const doc = JSON.parse(readFileSync(file, "utf8")) assert.equal(doc.skills.length, 2) assert.equal(doc.skills[1].status, "needs-review") assert.equal(doc.skills[1].classification, "skill") // idempotent: second run adds nothing assert.deepEqual(recordNewSkills(file, ["misc/newcomer"]), []) } finally { rmSync(dir, { recursive: true, force: true }) } })