mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
- Default Copilot runtime to sequential inline execution instead of unreliable subagent spawning - Add spot-check fallback in step 3 (SUMMARY.md exists + git commits found) so orchestrator never blocks indefinitely on missing completion signals - Add runtime detection guidance in init step to force sequential mode under Copilot - Add regression test verifying execute-phase contains Copilot fallback and spot-check documentation
184 lines
7.5 KiB
JavaScript
184 lines
7.5 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 absent from all agents (breaks Gemini CLI)
|
|
* - 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 absent', () => {
|
|
for (const agent of ALL_AGENTS) {
|
|
test(`${agent} does not have 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} has skills: in frontmatter — skills: breaks Gemini CLI and must be removed`
|
|
);
|
|
});
|
|
}
|
|
});
|
|
|
|
// ─── 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'
|
|
);
|
|
});
|
|
|
|
test('execute-phase has Copilot sequential fallback in runtime_compatibility', () => {
|
|
const content = fs.readFileSync(
|
|
path.join(WORKFLOWS_DIR, 'execute-phase.md'), 'utf-8'
|
|
);
|
|
assert.ok(
|
|
content.includes('sequential inline execution'),
|
|
'execute-phase must document sequential inline execution as Copilot fallback'
|
|
);
|
|
assert.ok(
|
|
content.includes('spot-check'),
|
|
'execute-phase must have spot-check fallback for completion detection'
|
|
);
|
|
});
|
|
});
|
|
|
|
// ─── 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:`);
|
|
});
|
|
}
|
|
});
|