← back to The Ai Factory
src/stages/smoke_test.js
36 lines
// Stage 5 — smoke_test. Cheap structural validation on the final scaffold file.
// Pure-local, deterministic — no LLM, no Claude CLI. Catches the most common
// failure modes (missing frontmatter, name drift, empty body) without burning
// quota or wall-clock.
const fs = require('node:fs/promises');
function escapeRe(s) { return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
async function runSmokeTest({ outPath, spec }) {
const content = await fs.readFile(outPath, 'utf8');
const checks = [
{ name: 'has_frontmatter_open', pass: /^---\s*\n/.test(content) },
{ name: 'has_frontmatter_close', pass: /\n---\s*\n/.test(content) },
{ name: 'name_matches_spec', pass: new RegExp(`^name:\\s*${escapeRe(spec.artifact_name)}\\s*$`, 'm').test(content) },
{ name: 'has_description', pass: /^description:\s*\S/m.test(content) },
{ name: 'body_nonempty', pass: content.length > 200 },
{ name: 'no_unrendered_template', pass: !/<<<|>>>|TODO_FILL|XXX_REPLACE/.test(content) }
];
if (spec.artifact_type === 'subagent') {
checks.push({ name: 'has_tools', pass: /^tools:\s*\S/m.test(content) });
checks.push({ name: 'has_model', pass: /^model:\s*(sonnet|opus|haiku)\s*$/m.test(content) });
}
const passed = checks.filter(c => c.pass).length;
return {
checks,
passed,
total: checks.length,
passing: passed === checks.length,
failing_checks: checks.filter(c => !c.pass).map(c => c.name)
};
}
module.exports = { runSmokeTest };