/** * The JS parser, as an oracle. * * Runs the REAL frontend module graph through Vite — `import.meta.glob`, the * `@/` alias and raw Markdown loading behave exactly as they do in the app, the * same technique `scripts/skill-check.mjs` uses. A mock of the registry would * reproduce none of the behaviour this file exists to capture. * * Emits one JSON document: for every shipped definition and every adversarial * case, what the JS parser did with it. That document is the fixture the Go * conformance suite asserts against, so "the Go parser agrees with the JS * parser" is a comparison against the JS parser's actual output rather than * against anybody's description of it. */ import { readFileSync, readdirSync, writeFileSync, statSync } from 'node:fs'; import { join, relative } from 'node:path'; /* The frontend checkout. Overridable so this runs anywhere the two repos are checked out side by side, which is the layout it defaults to. */ const FRONTEND = process.env.KROW_FRONTEND || new URL('../../krow-demo', import.meta.url).pathname; const { createServer } = await import(join(FRONTEND, 'node_modules/vite/dist/node/index.js')); const { CASES } = await import(new URL('./cases.mjs', import.meta.url).href); const server = await createServer({ root: FRONTEND, server: { middlewareMode: true }, appType: 'custom', logLevel: 'error', }); const skillReg = await server.ssrLoadModule('/src/lib/skills/registry.js'); const agentReg = await server.ssrLoadModule('/src/lib/agents/registry.js'); /** Every .md under a directory, recursively, repo-relative. */ function walk(dir) { const out = []; for (const entry of readdirSync(dir)) { const full = join(dir, entry); if (statSync(full).isDirectory()) out.push(...walk(full)); else if (entry.endsWith('.md')) out.push(full); } return out.sort(); } /** The fields the backend contract actually projects out of a definition. */ const projectSkill = (s) => ({ id: s.id, name: s.name, description: s.description, status: s.status, pages: s.pages, kind: s.kind, category: s.category, actions: s.actions, triggers: s.triggers, declaredTriggers: s.declaredTriggers, prompt: s.prompt ?? null, facets: s.facets, skillId: s.skillId ?? null, }); const projectAgent = (a) => ({ id: a.id, name: a.name, description: a.description, status: a.status, version: a.version, pages: a.pages, icon: a.icon, reasoning: a.reasoning, trigger: a.trigger, webSearch: a.webSearch, skills: a.skills, subagents: a.subagents, starters: a.starters, permissions: a.permissions, errors: a.errors, }); /** One definition, as the JS side sees it end to end. */ function observe(raw, kind) { const out = { kind }; /* Layer 1 — the fence. */ try { out.hasFrontmatter = skillReg.hasFrontmatter(raw); const { data, body } = skillReg.parseFrontmatter(raw); out.frontmatter = { ok: true, data, body }; } catch (error) { out.hasFrontmatter = (() => { try { return skillReg.hasFrontmatter(raw); } catch { return null; } })(); out.frontmatter = { ok: false, error: String(error?.message ?? error) }; } /* Layer 2 — the definition. */ try { const parsed = kind === 'agent' ? agentReg.parseAgent(raw, { custom: true }) : skillReg.parseSkill(raw, { custom: true }); out.parse = { ok: true }; out.normalized = kind === 'agent' ? projectAgent(parsed) : projectSkill(parsed); out.markdownVerbatim = parsed.markdown === raw; } catch (error) { out.parse = { ok: false, error: String(error?.message ?? error) }; out.normalized = null; out.markdownVerbatim = null; } /* Layer 3 — the save gate. This is the accept/reject contract. */ const problem = kind === 'agent' ? agentReg.validateAgentSource(raw) : skillReg.validateSkillSource(raw); out.accepted = problem === null; out.rejection = problem; return out; } /* ── The shipped corpus ───────────────────────────────────────────────────── */ const corpus = []; const groups = [ { dir: join(FRONTEND, 'src/agents'), type: 'agent', kind: 'agent' }, { dir: join(FRONTEND, 'src/skills'), type: 'skill', kind: 'skill' }, { dir: join(FRONTEND, 'skill-examples'), type: 'example', kind: 'skill' }, ]; for (const { dir, type, kind } of groups) { for (const file of walk(dir)) { const raw = readFileSync(file, 'utf8'); corpus.push({ path: relative(FRONTEND, file), type, rawBase64: Buffer.from(raw, 'utf8').toString('base64'), bytes: Buffer.byteLength(raw, 'utf8'), ...observe(raw, kind), }); } } /* ── The adversarial cases ────────────────────────────────────────────────── */ const cases = CASES.map((c) => ({ name: c.name, rawBase64: Buffer.from(c.raw, 'utf8').toString('base64'), bytes: Buffer.byteLength(c.raw, 'utf8'), ...observe(c.raw, c.kind), })); /* ── The closed vocabularies, read out of the JS tables themselves ────────── */ const surfaces = await server.ssrLoadModule('/src/lib/skills/surfaces.js'); const agentVocab = await server.ssrLoadModule('/src/lib/agents/vocabulary.js'); const vocabulary = { pages: surfaces.SKILL_SURFACES.map((s) => ({ id: s.id, aliases: s.aliases || [] })), agentStatuses: agentVocab.AGENT_STATUSES, defaultAgentStatus: agentVocab.DEFAULT_AGENT_STATUS, reasoning: agentVocab.SUPPORTED_REASONING, defaultReasoning: agentVocab.DEFAULT_REASONING, icons: agentVocab.AGENT_ICONS, defaultIcon: agentVocab.DEFAULT_AGENT_ICON, knowledgeKinds: agentVocab.KNOWLEDGE_KINDS, defaultKnowledgeKind: agentVocab.DEFAULT_KNOWLEDGE_KIND, access: agentVocab.AGENT_ACCESS, defaultAccess: agentVocab.DEFAULT_AGENT_ACCESS, roles: agentVocab.PERMISSION_ROLES, defaultRole: agentVocab.DEFAULT_PERMISSION_ROLE, }; await server.close(); const doc = { generatedBy: 'scripts/oracle.mjs against the frontend module graph', frontendParser: { yaml: 'src/lib/skills/yaml.js (hand-written YAML subset, no dependency)', frontmatter: 'src/lib/skills/registry.js — normalizeDefinition / hasFrontmatter / parseFrontmatter', skill: 'src/lib/skills/registry.js — parseSkill / validateSkillSource', agent: 'src/lib/agents/registry.js — parseAgent / validateAgentSource', }, vocabulary, corpus, cases, }; const target = process.argv[2]; writeFileSync(target, JSON.stringify(doc, null, 2) + '\n'); const acc = (xs) => xs.filter((x) => x.accepted).length; console.log(`corpus ${corpus.length} files — ${acc(corpus)} accepted, ${corpus.length - acc(corpus)} rejected`); console.log(`cases ${cases.length} — ${acc(cases)} accepted, ${cases.length - acc(cases)} rejected`); console.log(`markdown verbatim: ${[...corpus, ...cases].every((x) => x.markdownVerbatim !== false)}`); console.log(`written: ${target}`);