mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
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>
182 lines
7.4 KiB
JavaScript
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:`);
|
|
});
|
|
}
|
|
});
|