From 4f5757a1f2f70a2201167ce9899052f1a728a254 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sat, 14 Mar 2026 00:12:42 -0500 Subject: [PATCH] test: add usage consistency and pipe guard tests Usage consistency test cross-checks Usage: patterns in implementation against COMMAND_DESCRIPTIONS using structural skeleton comparison. Pipe guard test ensures descriptions don't contain | which would break markdown table rendering. --- test/gen-skill-docs.test.ts | 8 ++++++ test/skill-validation.test.ts | 53 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index ce7c98ea..9d3f3b9b 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -139,6 +139,14 @@ describe('description quality evals', () => { } }); + // Guard: descriptions must not contain pipe (breaks markdown table cells) + // Usage strings are backtick-wrapped in the table so pipes there are safe. + test('no command description contains pipe character', () => { + for (const [cmd, meta] of Object.entries(COMMAND_DESCRIPTIONS)) { + expect(meta.description).not.toContain('|'); + } + }); + // Guard: generated output uses → not -> test('generated SKILL.md uses unicode arrows', () => { const content = fs.readFileSync(path.join(ROOT, 'SKILL.md'), 'utf-8'); diff --git a/test/skill-validation.test.ts b/test/skill-validation.test.ts index 1c4025a2..4bf6b6dd 100644 --- a/test/skill-validation.test.ts +++ b/test/skill-validation.test.ts @@ -80,6 +80,59 @@ describe('Command registry consistency', () => { }); }); +describe('Usage string consistency', () => { + // Normalize a usage string to its structural skeleton for comparison. + // Replaces with <>, [optional] with [], strips parenthetical hints. + // This catches format mismatches (e.g., : vs ) + // without tripping on abbreviation differences (e.g., vs ). + function skeleton(usage: string): string { + return usage + .replace(/\(.*?\)/g, '') // strip parenthetical hints like (e.g., Enter, Tab) + .replace(/<[^>]*>/g, '<>') // normalize → <> + .replace(/\[[^\]]*\]/g, '[]') // normalize [optional] → [] + .replace(/\s+/g, ' ') // collapse whitespace + .trim(); + } + + // Cross-check Usage: patterns in implementation against COMMAND_DESCRIPTIONS + test('implementation Usage: structural format matches COMMAND_DESCRIPTIONS', () => { + const implFiles = [ + path.join(ROOT, 'browse', 'src', 'write-commands.ts'), + path.join(ROOT, 'browse', 'src', 'read-commands.ts'), + path.join(ROOT, 'browse', 'src', 'meta-commands.ts'), + ]; + + // Extract "Usage: browse " from throw new Error(...) calls + const usagePattern = /throw new Error\(['"`]Usage:\s*browse\s+(.+?)['"`]\)/g; + const implUsages = new Map(); + + for (const file of implFiles) { + const content = fs.readFileSync(file, 'utf-8'); + let match; + while ((match = usagePattern.exec(content)) !== null) { + const usage = match[1].split('\\n')[0].trim(); + const cmd = usage.split(/\s/)[0]; + implUsages.set(cmd, usage); + } + } + + // Compare structural skeletons + const mismatches: string[] = []; + for (const [cmd, implUsage] of implUsages) { + const desc = COMMAND_DESCRIPTIONS[cmd]; + if (!desc) continue; + if (!desc.usage) continue; + const descSkel = skeleton(desc.usage); + const implSkel = skeleton(implUsage); + if (descSkel !== implSkel) { + mismatches.push(`${cmd}: docs "${desc.usage}" (${descSkel}) vs impl "${implUsage}" (${implSkel})`); + } + } + + expect(mismatches).toEqual([]); + }); +}); + describe('Generated SKILL.md freshness', () => { test('no unresolved {{placeholders}} in generated SKILL.md', () => { const content = fs.readFileSync(path.join(ROOT, 'SKILL.md'), 'utf-8');