From ea52fa8f832d9fc28046eb0e251f4c4c2d44ee07 Mon Sep 17 00:00:00 2001 From: Mike Schwarz <171890339+mvschwarz@users.noreply.github.com> Date: Mon, 28 Sep 2026 19:21:34 -0700 Subject: [PATCH] fix(specs): add frontmatter to the shipped vault-user skill (#115) * fix(specs): give the vault-user skill the frontmatter every shipped skill needs (OPR.0.6.1.6) vault-user/SKILL.md was the only shipped SKILL.md without a name/description frontmatter block, so it failed the daemon's own parseSkillFrontmatter contract and external skill syncers rejected it. Adds name + description and leaves the body byte-identical. Adds a regression check that runs parseSkillFrontmatter over every SKILL.md under packages/daemon/specs and packages/daemon/assets, the two skill roots scripts/build-package.sh copies into the npm artifact. * test(daemon): correct the shipped-skill check's context-packs scope note (OPR.0.6.1.6) The projection's default source is packages/daemon/specs/agents/shared/skills, already inside the checked specs root. Manifest validation is not SKILL.md frontmatter validation. Comment only; test behavior unchanged. --------- Co-authored-by: v-openrig-build --- .../skills/vault-user/SKILL.md | 5 ++ .../test/shipped-skill-frontmatter.test.ts | 51 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 packages/daemon/test/shipped-skill-frontmatter.test.ts diff --git a/packages/daemon/specs/agents/apps/vault-specialist/skills/vault-user/SKILL.md b/packages/daemon/specs/agents/apps/vault-specialist/skills/vault-user/SKILL.md index 694b146f..5309b125 100644 --- a/packages/daemon/specs/agents/apps/vault-specialist/skills/vault-user/SKILL.md +++ b/packages/daemon/specs/agents/apps/vault-specialist/skills/vault-user/SKILL.md @@ -1,3 +1,8 @@ +--- +name: vault-user +description: Use when checking the health of this rig's HashiCorp Vault or writing, reading, listing, deleting or explaining its secrets. +--- + # Vault User You have access to a HashiCorp Vault instance managed by this rig's environment. diff --git a/packages/daemon/test/shipped-skill-frontmatter.test.ts b/packages/daemon/test/shipped-skill-frontmatter.test.ts new file mode 100644 index 00000000..56f832e3 --- /dev/null +++ b/packages/daemon/test/shipped-skill-frontmatter.test.ts @@ -0,0 +1,51 @@ +// OPR.0.6.1.6 — every SKILL.md the package ships must pass the daemon's own +// frontmatter contract (parseSkillFrontmatter: `---` block with name + +// description, non-empty body). scripts/build-package.sh copies +// packages/daemon/specs and packages/daemon/assets wholesale into the npm +// artifact, so those are the roots checked here. The package-time +// context-packs projection is generated from packages/daemon/specs/agents/ +// shared/skills (generate-context-packs.mjs), so its source skills are +// covered by the specs root. + +import { describe, it, expect } from "vitest"; +import { readFileSync, readdirSync, existsSync } from "node:fs"; +import { join, relative, resolve } from "node:path"; +import { parseSkillFrontmatter } from "../src/domain/skill-discovery.js"; + +const PACKAGE_ROOT = resolve(import.meta.dirname, ".."); +const SHIPPED_ROOTS = ["specs", "assets"].map((r) => join(PACKAGE_ROOT, r)); + +function findSkillFiles(dir: string): string[] { + const out: string[] = []; + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const full = join(dir, entry.name); + if (entry.isDirectory()) out.push(...findSkillFiles(full)); + else if (entry.isFile() && entry.name === "SKILL.md") out.push(full); + } + return out; +} + +describe("shipped SKILL.md frontmatter", () => { + const skillFiles = SHIPPED_ROOTS.filter((r) => existsSync(r)).flatMap(findSkillFiles); + + it("finds shipped skills under every shipped root", () => { + for (const root of SHIPPED_ROOTS) { + expect(skillFiles.some((f) => f.startsWith(root + "/")), `no SKILL.md under ${root}`).toBe(true); + } + }); + + it("every shipped SKILL.md passes parseSkillFrontmatter", () => { + const failures = skillFiles.flatMap((file) => { + const result = parseSkillFrontmatter(readFileSync(file, "utf8")); + return result.ok ? [] : [`${relative(PACKAGE_ROOT, file)}: ${result.reason}`]; + }); + expect(failures).toEqual([]); + }); + + it("rejects a shipped skill whose frontmatter is removed", () => { + const vault = join(PACKAGE_ROOT, "specs/agents/apps/vault-specialist/skills/vault-user/SKILL.md"); + const stripped = readFileSync(vault, "utf8").replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, ""); + const result = parseSkillFrontmatter(stripped); + expect(result.ok).toBe(false); + }); +});