import { test } from "node:test"; import assert from "node:assert/strict"; import { readdir, readFile, access } from "node:fs/promises"; import { join } from "node:path"; // Lint dos manifests de skills da CLI (skills//SKILL.md). // // Religado pela auditoria 6A.1 (2026-06-09): este arquivo era órfão (nenhum runner // coletava tests/unit/docs/) e apodreceu — filtrava dirs `omniroute*`, mas os skills // foram renomeados para `cli-*`; com 0 dirs o segundo teste passava VACUOSAMENTE. // Atualizado para o estado real: todo dir de skills/ com SKILL.md é validado, e o // invariante de uso é "referencia as env vars ($OMNIROUTE_URL/OMNIROUTE_KEY) OU // comandos da CLI (`omniroute …`)" — 3 skills (health/keys/batches) usam só a CLI. const SKILLS_DIR = join(process.cwd(), "skills"); const REQUIRED_FRONTMATTER = ["name:", "description:"]; async function listSkillDirs(): Promise { const entries = await readdir(SKILLS_DIR, { withFileTypes: true }); const dirs: string[] = []; for (const e of entries) { if (!e.isDirectory()) continue; try { await access(join(SKILLS_DIR, e.name, "SKILL.md")); dirs.push(e.name); } catch { // dir sem SKILL.md é coberto pelo teste de completude abaixo } } return dirs; } test("each skill dir has SKILL.md with frontmatter", async () => { const dirs = await listSkillDirs(); assert.ok(dirs.length >= 40, `Expected ≥40 skill dirs, got ${dirs.length}`); for (const dir of dirs) { const path = join(SKILLS_DIR, dir, "SKILL.md"); const content = await readFile(path, "utf-8"); assert.ok(content.startsWith("---\n"), `${dir}: missing opening frontmatter`); for (const key of REQUIRED_FRONTMATTER) { assert.ok(content.includes(key), `${dir}: missing frontmatter key ${key}`); } // Skills `omni-*` são GERADOS por src/lib/agentSkills/generator.ts (alguns em // estado "no endpoints mapped yet", sem refs de uso) — o invariante de uso vale // só para os manifests manuscritos (cli-* e config-*). if (!dir.startsWith("omni-")) { assert.ok( content.includes("OMNIROUTE_") || content.includes("omniroute "), `${dir}: missing usage references (OMNIROUTE_* env vars or omniroute CLI commands)` ); } } }); test("every directory under skills/ ships a SKILL.md", async () => { const entries = await readdir(SKILLS_DIR, { withFileTypes: true }); const missing: string[] = []; for (const e of entries) { if (!e.isDirectory()) continue; try { await access(join(SKILLS_DIR, e.name, "SKILL.md")); } catch { missing.push(e.name); } } assert.deepEqual(missing, [], `skill dirs without SKILL.md: ${missing.join(", ")}`); }); test("description field is meaningful (≥50 chars)", async () => { const dirs = await listSkillDirs(); assert.ok(dirs.length > 0, "no skill dirs found — listSkillDirs is broken"); for (const dir of dirs) { const content = await readFile(join(SKILLS_DIR, dir, "SKILL.md"), "utf-8"); const match = content.match(/^description:\s*(.+?)$/m); assert.ok(match, `${dir}: no description field`); const desc = match![1]; // Nota: o assert antigo exigia a frase-gatilho "Use when" — nenhum dos 43 skills // reais a usa; o invariante verificável é descrição substantiva (≥50 chars). assert.ok(desc.length >= 50, `${dir}: description too short (${desc.length})`); } });