Files
get-shit-done/tests/agent-frontmatter.test.cjs
Tibsfox 3d926496d9 test(agents): add 47 agent frontmatter and spawn consistency tests
New test suite covering:
- HDOC: anti-heredoc instruction present in all 9 file-writing agents
- SKILL: skills: frontmatter present in all 11 agents
- HOOK: commented hooks pattern in file-writing agents
- SPAWN: no stale workaround patterns, valid agent type references
- AGENT: required frontmatter fields (name, description, tools, color)

509 total tests (462 existing + 47 new), 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:01:54 -06:00

182 lines
7.4 KiB
JavaScript

/**
* GSD Agent Frontmatter Tests
*
* Validates that all agent .md files have correct frontmatter fields:
* - Anti-heredoc instruction present in file-writing agents
* - skills: field in all agents
* - Commented hooks: pattern in file-writing agents
* - Spawn type consistency across workflows
*/
const { test, describe } = require('node:test');
const assert = require('node:assert');
const fs = require('fs');
const path = require('path');
const AGENTS_DIR = path.join(__dirname, '..', 'agents');
const WORKFLOWS_DIR = path.join(__dirname, '..', 'get-shit-done', 'workflows');
const COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
const ALL_AGENTS = fs.readdirSync(AGENTS_DIR)
.filter(f => f.startsWith('gsd-') && f.endsWith('.md'))
.map(f => f.replace('.md', ''));
const FILE_WRITING_AGENTS = ALL_AGENTS.filter(name => {
const content = fs.readFileSync(path.join(AGENTS_DIR, name + '.md'), 'utf-8');
const toolsMatch = content.match(/^tools:\s*(.+)$/m);
return toolsMatch && toolsMatch[1].includes('Write');
});
const READ_ONLY_AGENTS = ALL_AGENTS.filter(name => !FILE_WRITING_AGENTS.includes(name));
// ─── Anti-Heredoc Instruction ────────────────────────────────────────────────
describe('HDOC: anti-heredoc instruction', () => {
for (const agent of FILE_WRITING_AGENTS) {
test(`${agent} has anti-heredoc instruction`, () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, agent + '.md'), 'utf-8');
assert.ok(
content.includes("never use `Bash(cat << 'EOF')` or heredoc"),
`${agent} missing anti-heredoc instruction`
);
});
}
test('no active heredoc patterns in any agent file', () => {
for (const agent of ALL_AGENTS) {
const content = fs.readFileSync(path.join(AGENTS_DIR, agent + '.md'), 'utf-8');
// Match actual heredoc commands (not references in anti-heredoc instruction)
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Skip lines that are part of the anti-heredoc instruction or markdown code fences
if (line.includes('never use') || line.includes('NEVER') || line.trim().startsWith('```')) continue;
// Check for actual heredoc usage instructions
if (/^cat\s+<<\s*'?EOF'?\s*>/.test(line.trim())) {
assert.fail(`${agent}:${i + 1} has active heredoc pattern: ${line.trim()}`);
}
}
}
});
});
// ─── Skills Frontmatter ──────────────────────────────────────────────────────
describe('SKILL: skills frontmatter', () => {
for (const agent of ALL_AGENTS) {
test(`${agent} has skills: in frontmatter`, () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, agent + '.md'), 'utf-8');
const frontmatter = content.split('---')[1] || '';
assert.ok(
frontmatter.includes('skills:'),
`${agent} missing skills: in frontmatter`
);
});
}
test('skill references follow naming convention', () => {
for (const agent of ALL_AGENTS) {
const content = fs.readFileSync(path.join(AGENTS_DIR, agent + '.md'), 'utf-8');
const frontmatter = content.split('---')[1] || '';
const skillLines = frontmatter.split('\n').filter(l => l.trim().startsWith('- gsd-'));
for (const line of skillLines) {
const skillName = line.trim().replace('- ', '');
assert.match(skillName, /^gsd-[\w-]+-workflow$/, `Invalid skill name: ${skillName}`);
}
}
});
});
// ─── Hooks Frontmatter ───────────────────────────────────────────────────────
describe('HOOK: hooks frontmatter pattern', () => {
for (const agent of FILE_WRITING_AGENTS) {
test(`${agent} has commented hooks pattern`, () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, agent + '.md'), 'utf-8');
const frontmatter = content.split('---')[1] || '';
assert.ok(
frontmatter.includes('# hooks:'),
`${agent} missing commented hooks: pattern in frontmatter`
);
});
}
for (const agent of READ_ONLY_AGENTS) {
test(`${agent} (read-only) does not need hooks`, () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, agent + '.md'), 'utf-8');
const frontmatter = content.split('---')[1] || '';
// Read-only agents may or may not have hooks — just verify they parse
assert.ok(frontmatter.includes('name:'), `${agent} has valid frontmatter`);
});
}
});
// ─── Spawn Type Consistency ──────────────────────────────────────────────────
describe('SPAWN: spawn type consistency', () => {
test('no "First, read agent .md" workaround pattern remains', () => {
const dirs = [WORKFLOWS_DIR, COMMANDS_DIR];
for (const dir of dirs) {
if (!fs.existsSync(dir)) continue;
const files = fs.readdirSync(dir).filter(f => f.endsWith('.md'));
for (const file of files) {
const content = fs.readFileSync(path.join(dir, file), 'utf-8');
const hasWorkaround = content.includes('First, read ~/.claude/agents/gsd-');
assert.ok(
!hasWorkaround,
`${file} still has "First, read agent .md" workaround — use named subagent_type instead`
);
}
}
});
test('named agent spawns use correct agent names', () => {
const validAgentTypes = new Set([
...ALL_AGENTS,
'general-purpose', // Allowed for orchestrator spawns
]);
const dirs = [WORKFLOWS_DIR, COMMANDS_DIR];
for (const dir of dirs) {
if (!fs.existsSync(dir)) continue;
const files = fs.readdirSync(dir).filter(f => f.endsWith('.md'));
for (const file of files) {
const content = fs.readFileSync(path.join(dir, file), 'utf-8');
const matches = content.matchAll(/subagent_type="([^"]+)"/g);
for (const match of matches) {
const agentType = match[1];
assert.ok(
validAgentTypes.has(agentType),
`${file} references unknown agent type: ${agentType}`
);
}
}
}
});
test('diagnose-issues uses gsd-debugger (not general-purpose)', () => {
const content = fs.readFileSync(
path.join(WORKFLOWS_DIR, 'diagnose-issues.md'), 'utf-8'
);
assert.ok(
content.includes('subagent_type="gsd-debugger"'),
'diagnose-issues should spawn gsd-debugger, not general-purpose'
);
});
});
// ─── Required Frontmatter Fields ─────────────────────────────────────────────
describe('AGENT: required frontmatter fields', () => {
for (const agent of ALL_AGENTS) {
test(`${agent} has name, description, tools, color`, () => {
const content = fs.readFileSync(path.join(AGENTS_DIR, agent + '.md'), 'utf-8');
const frontmatter = content.split('---')[1] || '';
assert.ok(frontmatter.includes('name:'), `${agent} missing name:`);
assert.ok(frontmatter.includes('description:'), `${agent} missing description:`);
assert.ok(frontmatter.includes('tools:'), `${agent} missing tools:`);
assert.ok(frontmatter.includes('color:'), `${agent} missing color:`);
});
}
});