Files
mattpocock-skills-opencode/test/drift.test.ts
T

125 lines
4.8 KiB
TypeScript

import assert from "node:assert/strict"
import { existsSync, readFileSync, readdirSync } from "node:fs"
import path from "node:path"
import { test } from "node:test"
import { fileURLToPath } from "node:url"
import { parseFrontmatter } from "../src/frontmatter.ts"
import { loadClassification } from "../scripts/generate.ts"
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
const entries = loadClassification(path.join(root, ".opencode/state/classification.json"))
const vendored = entries.filter((e) => e.artifacts.includes("skill:verbatim"))
const agentFiles = readdirSync(path.join(root, "agents")).filter((f) => f.endsWith(".md"))
// V2-native frontmatter only (AGENTS.md convention); legacy v1 keys must never appear.
const V2_KEYS = new Set([
"name",
"description",
"mode",
"model",
"color",
"permissions",
"hidden",
"steps",
"disabled",
"request",
"system",
])
test("agent frontmatter uses V2-native keys only", () => {
for (const file of agentFiles) {
const { frontmatter } = parseFrontmatter(readFileSync(path.join(root, "agents", file), "utf8"))
for (const key of Object.keys(frontmatter)) {
assert.ok(V2_KEYS.has(key), `${file}: non-V2 frontmatter key '${key}'`)
}
assert.ok(frontmatter.mode, `${file}: mode required`)
assert.ok(frontmatter.description, `${file}: description required`)
}
})
test("every skill an agent names still exists in the upstream snapshot", () => {
const allNames = new Set(entries.map((e) => e.name))
for (const file of agentFiles) {
const body = readFileSync(path.join(root, "agents", file), "utf8")
const tokens = new Set(
[...body.matchAll(/`([a-z][a-z0-9-]*)`/g)].map((m) => m[1]).filter((t) => allNames.has(t)),
)
for (const name of tokens) {
const entry = entries.find((e) => e.name === name)!
const dir = path.join(root, "upstream/skills", entry.bucket, name)
assert.ok(
existsSync(path.join(dir, "SKILL.md")),
`${file} names skill '${name}' which no longer exists upstream (renamed or removed?)`,
)
assert.ok(
entry.artifacts.includes("skill:verbatim"),
`${file} names skill '${name}' which is excluded from this package`,
)
}
}
})
test("worker may only load model-invoked vendored skills", () => {
const worker = readFileSync(path.join(root, "agents/pocock-worker.md"), "utf8")
const allowed = [
...worker.matchAll(/action:\s*skill,\s*resource:\s*"([^"]+)",\s*effect:\s*allow/g),
].map((m) => m[1])
assert.ok(allowed.length >= 3, "worker skill allow-list parsed")
const userInvoked = new Set(entries.filter((e) => e.user_invoked).map((e) => e.name))
const vendoredNames = new Set(vendored.map((e) => e.name))
for (const name of allowed) {
assert.ok(!userInvoked.has(name), `worker allows user-invoked skill '${name}' (needs a person)`)
assert.ok(vendoredNames.has(name), `worker allows '${name}' which is not a vendored skill`)
}
for (const core of ["tdd", "codebase-design", "diagnosing-bugs"]) {
assert.ok(allowed.includes(core), `worker missing core skill '${core}'`)
}
})
test("reviewer is read-only: edit/subagent denied, only code-review allowed", () => {
const reviewer = readFileSync(path.join(root, "agents/pocock-reviewer.md"), "utf8")
assert.match(reviewer, /action:\s*edit,\s*resource:\s*"\*",\s*effect:\s*deny/)
assert.match(reviewer, /action:\s*subagent,\s*resource:\s*"\*",\s*effect:\s*deny/)
const allowedSkills = [
...reviewer.matchAll(/action:\s*skill,\s*resource:\s*"([^"]+)",\s*effect:\s*allow/g),
].map((m) => m[1])
assert.deepEqual(allowedSkills, ["code-review"])
})
test("worker and reviewer cannot spawn subagents (no circular delegation)", () => {
for (const file of ["pocock-worker.md", "pocock-reviewer.md"]) {
const body = readFileSync(path.join(root, "agents", file), "utf8")
assert.match(
body,
/action:\s*subagent,\s*resource:\s*"\*",\s*effect:\s*deny/,
`${file}: subagent deny`,
)
}
})
test("classification is fully reviewed and matches the generated tree", () => {
const needsReview = entries.filter((e) => e.status === "needs-review")
assert.deepEqual(
needsReview.map((e) => e.name),
[],
"needs-review classifications block the build until reviewed",
)
const manifest = JSON.parse(readFileSync(path.join(root, "manifest.json"), "utf8"))
assert.deepEqual(
manifest.map((m: { id: string }) => m.id).sort(),
vendored.map((e) => e.name).sort(),
)
for (const e of vendored) {
assert.ok(
existsSync(path.join(root, "skills", e.name, "SKILL.md")),
`skills/${e.name} generated`,
)
}
for (const e of entries.filter((x) => x.artifacts.includes("excluded"))) {
assert.ok(
!existsSync(path.join(root, "skills", e.name)),
`excluded skill ${e.name} must not ship`,
)
}
})