/** Node-only test runner. The implementation imports no Node APIs. Run from the repository root. */ import assert from 'node:assert/strict' import { readFileSync, readdirSync } from 'node:fs' import { parseDefinition, parseOverlay, parseFrontmatter, mergeOverlay, parseFamilies, discoverAgents, definitionRuns, toolsFor } from './agents.ts' let count = 0 function test(name: string, run: () => void) { run(); console.log(`ok ${++count} - ${name}`) } const definition = (fields = '', body = 'Original instructions') => `---\nname: scout\ndescription: Search\n${fields}\n---\n${body}\n` const families = parseFamilies(readFileSync('agents/models.json', 'utf8')) const models = [...new Set(Object.values(families).map(family => family.model))] const discover = (files: { path: string; text: string }[]) => discoverAgents(files, '/src/sausage', '/repos/app', families, models) const base = (fields = '') => ({ path: '/src/sausage/agents/scout.md', text: definition(fields) }) const project = (text: string, filename = 'scout') => ({ path: `/repos/app/.sausage/agents/${filename}.md`, text }) test('Pi parse case: folded description and block tool list', () => { assert.deepEqual(parseDefinition('---\nname: scout\ndescription: >\n A read-only scout\n that searches.\nmodel: openai/gpt-6.1-sol\nthinking: max\ntools:\n - read\n - bash\n---\nSearch\n', 'scout.md'), { name: 'scout', description: 'A read-only scout that searches.', path: 'scout.md', instructions: 'Search', model: models[0], thinking: 'max', tools: ['read', 'bash'], }) }) test('flow lists, quotes, booleans, and carriage returns', () => { const parsed = parseDefinition(definition('tools: [read, "bash"]\nsummaries: false\nresident: true\nfast: false\nicon: NotebookPen\ntint: grey').replaceAll('\n', '\r\n'), 'test') assert.deepEqual(parsed.tools, ['read', 'bash']); assert.equal(parsed.summaries, false) assert.equal(parsed.resident, true); assert.equal(parsed.fast, false) assert.equal(parseFrontmatter("---\nname: 'it''s'\n---\nBody").frontmatter.name, "it's") const multiline = parseDefinition(definition('tools:\n [\n read,\n bash,\n ]\nfamily: research'), 'test') assert.deepEqual(multiline.tools, ['read', 'bash']); assert.equal(multiline.family, 'research') assert.deepEqual(parseDefinition(definition('tools: [read, "unknown,tool"]'), 'test').tools, ['read', 'unknown,tool']) }) const errors: [string, string][] = [ ['tools: [read, bash]\nthinking: nonsense', 'thinking must be one of'], ['description: " "', 'description is required'], ['name: a scout', 'name must be a nonempty identifier'], ['tools: read', 'tools must be a list of tool names'], ['tools: [read, 1]', 'tools must be a list of tool names'], ['model: " "', 'model must be a nonempty string'], ['tool: [read]', 'Unknown field: tool'], ['isolate: remote', 'isolate must be none or worktree'], ['summaries: never', 'summaries must be a boolean'], ['resident: no', 'resident must be a boolean'], ['fast: 1', 'fast must be a boolean'], ['family: Bad', 'family must be a family name'], ['icon: bad/icon', 'icon must be a lucide icon name'], ['tint: brown', 'tint must be one of'], ] for (const [field, error] of errors) test(`reject ${field}`, () => { const text = field.startsWith('description:') ? definition().replace('description: Search', field) : field.startsWith('name:') ? definition().replace('name: scout', field) : definition(field) assert.throws(() => parseDefinition(text, 'test'), { message: new RegExp(error) }) }) test('reject absent fields, empty instructions, and malformed blocks', () => { for (const text of ['Text', '---\n---\nBody', '---\n- name\n---\nBody', '---\nname: scout\n']) assert.throws(() => parseDefinition(text, 'test'), /Expected frontmatter/) assert.throws(() => parseDefinition(definition().replace('description: Search\n', ''), 'test'), /description is required/) assert.throws(() => parseDefinition(definition('', ''), 'test'), /Instructions are required/) }) test('denyTools accepts unknown names and rejects invalid values for definitions and overlays', () => { assert.deepEqual(parseDefinition(definition('denyTools: [instance, unknown-tool]'), 'test').denyTools, ['instance', 'unknown-tool']) assert.deepEqual(parseDefinition(definition('denyTools: []'), 'test').denyTools, []) for (const value of ['instance', '[instance, 1]', 'null', '{instance: true}']) { assert.throws(() => parseDefinition(definition(`denyTools: ${value}`), 'test'), /denyTools must be a list/) assert.throws(() => parseOverlay(definition(`addTools: []\ndenyTools: ${value}`), 'test'), /denyTools must be a list/) } }) test('ordered overlays add tools and append bodies without changing source', () => { const found = discover([project('---\nname: scout\naddTools: [write, read]\n---\nFirst', '01'), base('tools: [read, bash]'), project('---\nname: scout\naddTools: [message]\n---\nSecond', '02')]) assert.deepEqual(found.errors, []); assert.deepEqual(found.replaced, []) const agent = found.definitions[0] assert.equal(agent.source, 'builtin'); assert.equal(agent.path, base().path) assert.deepEqual(agent.tools, ['read', 'bash', 'write', 'message']) assert.equal(agent.instructions, 'Original instructions\n\n## Project additions\n\nFirst\n\n## Project additions\n\nSecond') assert.deepEqual(agent.overlays, [project('', '01').path, project('', '02').path]) }) test('overlay settings replace while denials accumulate through empty overlay', () => { const found = discover([base('tools: [read]\ndenyTools: [unknown-tool]\nthinking: low'), project('---\nname: scout\naddTools: []\ndenyTools: [unknown-tool, bash]\nmodel: anthropic/claude-opus-5-5\nthinking: high\nfast: true\ndescription: Our scout\n---', '01'), project('---\nname: scout\naddTools: []\ndenyTools: []\n---', '02')]) assert.deepEqual(found.errors, []) assert.deepEqual(found.definitions[0].denyTools, ['unknown-tool', 'bash']) assert.equal(found.definitions[0].thinking, 'high'); assert.equal(found.definitions[0].fast, true) assert.equal(found.definitions[0].description, 'Our scout') assert.deepEqual(found.definitions[0].tools, ['read']) }) test('inherited tools retain deferred additions and do not mutate parent', () => { const parent = ['read', 'bash', 'write', 'subagent'] const child = mergeOverlay(parseDefinition(definition('denyTools: [write, subagent]'), 'test'), parseOverlay('---\nname: scout\naddTools: [message]\n---', 'overlay')) assert.equal(child.tools, undefined); assert.deepEqual(child.addTools, ['message']) assert.deepEqual(toolsFor(child, parent), ['read', 'bash', 'message']) assert.deepEqual(toolsFor(parseDefinition(definition('tools: [read, write]\ndenyTools: [write]'), 'test'), parent), ['read']) assert.deepEqual(parent, ['read', 'bash', 'write', 'subagent']) }) test('overlay errors do not replace a valid base', () => { const found = discover([base(), project('---\nname: scout\naddTools: [x]\ntools: [read]\n---', 'listed'), project('---\nname: scout\naddTools: []\ncolour: red\n---', 'unknown'), project('---\nname: nobody\naddTools: [x]\n---', 'orphan')]) assert.deepEqual(Object.fromEntries(found.errors.map(error => [error.name, error.error])), { listed: 'Error: An overlay adds tools with addTools, not tools', unknown: 'Error: Unknown field: colour', orphan: 'Error: addTools needs an earlier definition named nobody', }) assert.equal(found.definitions[0].instructions, 'Original instructions') assert.throws(() => parseOverlay('---\nname: scout\naddTools: read\n---', 'test'), /addTools must be a list/) }) test('replacement uses name, not filename, and respects exact roots', () => { const found = discover([base(), project(definition('family: research', 'Replacement'), 'different'), { path: '/repos/other/.sausage/agents/scout.md', text: definition() }, { path: '/src/sausage/agents/nested/no.md', text: definition() }]) assert.deepEqual(found.errors, []); assert.equal(found.definitions.length, 1) assert.equal(found.definitions[0].source, 'project'); assert.equal(found.definitions[0].instructions, 'Replacement') assert.deepEqual(found.replaced, [{ name: 'scout', path: project('', 'different').path, source: 'project' }]) }) test('family fallback, thinking override, model suffix, and unknown models', () => { const run = (fields: string) => definitionRuns(parseDefinition(definition(fields), 'test'), families, models) assert.equal(run('family: missing').family, 'default') assert.equal(run('family: constructor').family, 'default') assert.equal(run('family: research').thinking, 'high') assert.equal(run('family: research\nthinking: low').thinking, 'low') assert.equal(run('model: openai/gpt-6.1-sol').thinking, undefined) assert.equal(run('model: openai/gpt-6.1-sol:high').thinking, 'high') assert.equal(run('model: openai/gpt-6.1-sol:high\nthinking: low').thinking, 'low') for (const model of ['anthropic/unknown', 'unknown']) assert.match(discover([base(`model: ${model}`)]).errors[0].error, /model: Model .* not found/) const found = discover([base('model: openai/gpt-6.1-sol'), project('---\nname: scout\nfamily: lead\naddTools: []\n---')]) assert.equal(found.definitions[0].model, undefined); assert.equal(found.definitions[0].runs.family, 'lead') const suffix = discover([base('thinking: low'), project('---\nname: scout\nmodel: openai/gpt-6.1-sol:high\naddTools: []\n---')]) assert.equal(suffix.definitions[0].runs.thinking, 'high') }) test('family world file requires valid refs and default', () => { for (const text of ['null', '[]', '{}', '{"default":{"model":"bad"}}', '{"Default":{"model":"openai/a"}}', '{"default":{"model":"openai/a","thinking":"bad"}}']) assert.throws(() => parseFamilies(text)) assert.deepEqual(Object.keys(families), ['default', 'fast', 'review', 'research', 'design', 'lead']) assert.equal(families.review.thinking, 'xhigh') assert.equal(families.design.thinking, 'high') }) test('all shipped definitions and overlays parse and discover', () => { const files = readdirSync('agents').filter(name => name.endsWith('.md')).map(name => ({ path: `/src/sausage/agents/${name}`, text: readFileSync(`agents/${name}`, 'utf8') })) assert.equal(files.length, 12) const available = ['read', 'bash', 'edit', 'write', 'message', 'note', 'notify', 'automation', 'branch', 'compact', 'remember', 'memory', 'todo', 'goal', 'subagent', 'plan', 'workspace'] for (const file of files) { const agent = 'addTools' in parseFrontmatter(file.text).frontmatter ? parseOverlay(file.text, file.path) : parseDefinition(file.text, file.path) assert.ok(!/\b(pnpm|vitest|clone|instance|restart|web_search|mcp|setup|codemode|debug)\b|~\/\.ultimate/.test(agent.instructions), file.path) const fields = 'settings' in agent ? agent.settings : agent for (const tool of [...(fields.tools ?? []), ...(fields.denyTools ?? []), ...('addTools' in agent ? agent.addTools ?? [] : [])]) assert.ok(available.includes(tool), `${file.path}: ${tool}`) } const found = discover(files) assert.deepEqual(found.errors, []); assert.equal(found.definitions.length, 11) assert.equal(found.definitions.find(agent => agent.name === 'coordinator')!.runs.family, 'lead') for (const agent of found.definitions.filter(agent => agent.name.startsWith('reviewer'))) assert.equal(agent.runs.thinking, 'xhigh') }) test('all applicable skills parse and avoid unavailable tools', () => { let skills = 0 for (const name of readdirSync('skills')) { if (name === 'README.md') continue const parsed = parseFrontmatter(readFileSync(`skills/${name}/SKILL.md`, 'utf8')) assert.equal(parsed.frontmatter.name, name) assert.equal(typeof parsed.frontmatter.description, 'string'); assert.ok(parsed.body) assert.ok(!/\b(pnpm|vitest|clone|instance|restart|web_search|mcp|setup|codemode|debug)\b|~\/\.ultimate/.test(parsed.body), name) skills++ } assert.equal(skills, 4) }) console.log(`${count} checks passed`)