feat: skill generator with manifest, frontmatter parser and mapping-doc matrix
This commit is contained in:
1 parent
b21b974293
commit
4ce4466a97
63 files changed
+4552
-42
No files matched your search
@@ -0,0 +1,168 @@
|
||||
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)
|
||||
})
|
||||
Reference in new issue
Block a user