169 lines
7.2 KiB
TypeScript
169 lines
7.2 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
|
import { tmpdir } from "node:os"
|
|
import path from "node:path"
|
|
import { test } from "node:test"
|
|
import { fileURLToPath } from "node:url"
|
|
import {
|
|
buildSkills,
|
|
loadClassification,
|
|
renderMatrix,
|
|
updateDocMatrix,
|
|
type ClassificationEntry,
|
|
} from "../scripts/generate.ts"
|
|
import { isUserInvoked, parseFrontmatter } from "../scripts/frontmatter.ts"
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|
|
|
test("parseFrontmatter: plain, quoted with escapes and colons, DMI, nested blocks skipped", () => {
|
|
const plain = parseFrontmatter("---\nname: tdd\ndescription: Test-driven development\n---\nBody\n")
|
|
assert.equal(plain.frontmatter.name, "tdd")
|
|
assert.equal(plain.frontmatter.description, "Test-driven development")
|
|
assert.equal(plain.body, "Body\n")
|
|
|
|
const quoted = parseFrontmatter(
|
|
'---\nname: code-review\ndescription: "Review since a fixed point: commit, branch, or \\"X\\"."\nmetadata:\n credits:\n author: Dex\n---\nx',
|
|
)
|
|
assert.equal(
|
|
quoted.frontmatter.description,
|
|
'Review since a fixed point: commit, branch, or "X".',
|
|
)
|
|
assert.equal(quoted.frontmatter.metadata, "") // nested block skipped, not misparsed
|
|
|
|
const dmi = parseFrontmatter("---\nname: to-spec\ndisable-model-invocation: true\n---\nx")
|
|
assert.equal(isUserInvoked(dmi.frontmatter), true)
|
|
assert.equal(isUserInvoked(plain.frontmatter), false)
|
|
|
|
const none = parseFrontmatter("no frontmatter here")
|
|
assert.deepEqual(none.frontmatter, {})
|
|
})
|
|
|
|
function fixtureSkill(upstream: string, bucket: string, name: string, opts: { dmi?: boolean } = {}) {
|
|
const dir = path.join(upstream, bucket, name)
|
|
mkdirSync(path.join(dir, "agents"), { recursive: true })
|
|
writeFileSync(
|
|
path.join(dir, "SKILL.md"),
|
|
`---\nname: ${name}\ndescription: "The ${name} skill"\n` +
|
|
(opts.dmi ? "disable-model-invocation: true\n" : "") +
|
|
`---\n\nBody of ${name}.\n`,
|
|
)
|
|
writeFileSync(path.join(dir, "agents", "openai.yaml"), "interface:\n display_name: X\n")
|
|
writeFileSync(path.join(dir, "EXTRA.md"), "supporting file")
|
|
return dir
|
|
}
|
|
|
|
function entry(name: string, bucket: string, over: Partial<ClassificationEntry> = {}) {
|
|
return {
|
|
name,
|
|
bucket,
|
|
classification: "skill",
|
|
user_invoked: false,
|
|
artifacts: ["skill:verbatim"],
|
|
depends_on: [],
|
|
status: "analyzed",
|
|
rationale: "r",
|
|
...over,
|
|
} as ClassificationEntry
|
|
}
|
|
|
|
test("buildSkills copies verbatim minus agents/, builds manifest, honors exclusions", () => {
|
|
const dir = mkdtempSync(path.join(tmpdir(), "gen-test-"))
|
|
try {
|
|
const upstream = path.join(dir, "upstream")
|
|
fixtureSkill(upstream, "engineering", "tdd")
|
|
fixtureSkill(upstream, "engineering", "to-spec", { dmi: true })
|
|
fixtureSkill(upstream, "in-progress", "beta")
|
|
const out = path.join(dir, "skills")
|
|
const manifest = buildSkills({
|
|
upstreamSkills: upstream,
|
|
entries: [entry("tdd", "engineering"), entry("to-spec", "engineering"), entry("beta", "in-progress", { artifacts: ["excluded"] })],
|
|
skillsOut: out,
|
|
})
|
|
assert.deepEqual(
|
|
manifest,
|
|
[
|
|
{ id: "tdd", name: "tdd", description: "The tdd skill", autoinvoke: true, path: "skills/tdd/SKILL.md" },
|
|
{ id: "to-spec", name: "to-spec", description: "The to-spec skill", autoinvoke: false, path: "skills/to-spec/SKILL.md" },
|
|
].sort((a, b) => a.id.localeCompare(b.id)),
|
|
)
|
|
assert.ok(existsSync(path.join(out, "tdd/SKILL.md")))
|
|
assert.ok(existsSync(path.join(out, "tdd/EXTRA.md")), "supporting files preserved")
|
|
assert.ok(!existsSync(path.join(out, "tdd/agents")), "agents/openai.yaml stripped")
|
|
assert.ok(!existsSync(path.join(out, "beta")), "excluded skill not copied")
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
test("buildSkills throws on unclassified upstream skills and missing classified ones", () => {
|
|
const dir = mkdtempSync(path.join(tmpdir(), "gen-test-"))
|
|
try {
|
|
const upstream = path.join(dir, "upstream")
|
|
fixtureSkill(upstream, "engineering", "mystery")
|
|
assert.throws(
|
|
() => buildSkills({ upstreamSkills: upstream, entries: [], skillsOut: path.join(dir, "out") }),
|
|
/unclassified upstream skill: engineering\/mystery/,
|
|
)
|
|
assert.throws(
|
|
() =>
|
|
buildSkills({
|
|
upstreamSkills: upstream,
|
|
entries: [entry("ghost", "engineering")],
|
|
skillsOut: path.join(dir, "out"),
|
|
}),
|
|
/classified skill missing upstream: engineering\/ghost/,
|
|
)
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
test("updateDocMatrix replaces only the marked section", () => {
|
|
const dir = mkdtempSync(path.join(tmpdir(), "gen-test-"))
|
|
try {
|
|
const doc = path.join(dir, "doc.md")
|
|
writeFileSync(doc, "prose\n<!-- matrix:start -->\nold\n<!-- matrix:end -->\nmore prose\n")
|
|
updateDocMatrix(doc, "NEW TABLE")
|
|
assert.equal(readFileSync(doc, "utf8"), "prose\n<!-- matrix:start -->\n\nNEW TABLE\n\n<!-- matrix:end -->\nmore prose\n")
|
|
assert.throws(() => updateDocMatrix(doc.replace("doc", "x"), "t"), /ENOENT/)
|
|
writeFileSync(path.join(dir, "bad.md"), "no markers")
|
|
assert.throws(() => updateDocMatrix(path.join(dir, "bad.md"), "t"), /missing matrix markers/)
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
test("renderMatrix: one row per entry with escaped cells", () => {
|
|
const md = renderMatrix([entry("tdd", "engineering", { depends_on: ["codebase-design"], rationale: "a | b" })])
|
|
const [header, sep, row] = md.split("\n")
|
|
assert.match(header, /^\| Upstream skill \| Bucket \| DMI \|/)
|
|
assert.match(sep, /^\| --- \|/)
|
|
assert.equal(row, "| tdd | eng | | skill | skill:verbatim | codebase-design | analyzed | a \\| b |")
|
|
})
|
|
|
|
// Regression against the real vendored snapshot: every classified-verbatim skill must parse,
|
|
// and upstream disable-model-invocation must agree with classification.json user_invoked.
|
|
test("real snapshot: all vendored skills parse and DMI flags match classification", () => {
|
|
const entries = loadClassification(path.join(root, ".opencode/state/classification.json"))
|
|
const manifest = JSON.parse(readFileSync(path.join(root, "manifest.json"), "utf8"))
|
|
const vendored = entries.filter((e) => e.artifacts.includes("skill:verbatim"))
|
|
assert.equal(vendored.length, manifest.length)
|
|
assert.equal(entries.filter((e) => e.status === "needs-review").length, 0)
|
|
|
|
for (const e of vendored) {
|
|
const md = readFileSync(path.join(root, "skills", e.name, "SKILL.md"), "utf8")
|
|
const { frontmatter } = parseFrontmatter(md)
|
|
assert.ok(frontmatter.name, `${e.name}: name parsed`)
|
|
assert.ok(frontmatter.description, `${e.name}: description parsed`)
|
|
assert.equal(
|
|
isUserInvoked(frontmatter),
|
|
e.user_invoked,
|
|
`${e.name}: DMI flag disagrees with classification.json`,
|
|
)
|
|
const m = manifest.find((x: { id: string }) => x.id === e.name)
|
|
assert.equal(m.autoinvoke, !e.user_invoked, `${e.name}: manifest autoinvoke`)
|
|
assert.ok(!existsSync(path.join(root, "skills", e.name, "agents")), `${e.name}: openai.yaml stripped`)
|
|
}
|
|
assert.equal(manifest.filter((m: { autoinvoke: boolean }) => !m.autoinvoke).length, 16)
|
|
})
|