57 lines
4.2 KiB
TypeScript
57 lines
4.2 KiB
TypeScript
/** Pure checks for world settings, Markdown definitions, overlays, and skill frontmatter. */
|
|
import assert from 'node:assert/strict'
|
|
import {readFileSync,readdirSync} from 'node:fs'
|
|
import {parseConfig,parseDefinition,parseFrontmatter,definitionRuns,toolsFor} from './agents.ts'
|
|
let count=0
|
|
function test(name:string,run:()=>void){run();console.log(`ok ${++count} - ${name}`)}
|
|
const config=parseConfig(readFileSync('config/config.yaml','utf8'))
|
|
const definitions=readdirSync('config/roster').sort().map(name=>parseDefinition(readFileSync(`config/roster/${name}`,'utf8'),`config/roster/${name}`))
|
|
test('exactly eight built-in Markdown definitions and no inline roster',()=>{
|
|
assert.deepEqual(definitions.map(d=>d.name),['coordinator','designer','dream-pass','reviewer','sausage','scout','watcher','worker'])
|
|
assert.equal(config.roster,undefined);assert.equal(config.main_agent_name,'sausage')
|
|
for(const d of definitions){
|
|
const model=['coordinator','designer','sausage'].includes(d.name)?'anthropic/claude-opus-5-5':d.name==='reviewer'?'openai/gpt-6-astra':['scout','watcher'].includes(d.name)?'openai/gpt-6-luna':'openai/gpt-6.1-sol'
|
|
assert.equal(d.model,model)
|
|
assert.equal(d.thinking,['scout','watcher'].includes(d.name)?'low':['worker','dream-pass'].includes(d.name)?'medium':'high')
|
|
}
|
|
})
|
|
test('shared prompt contains state inspection and current world paths',()=>{
|
|
for(const text of ['http://host.sausage/harness/','whoami','conversations/ID/entries?after=N','events?after=N','credential-requests','/proc/processes','/proc/extensions','jaq','/etc/sausage/config.yaml'])assert(config.prompt.includes(text),text)
|
|
})
|
|
test('main role describes direct self-change and state inspection',()=>{
|
|
const role=definitions.find(d=>d.name===config.main_agent_name)!
|
|
assert(role.instructions.includes('Act directly'));assert(role.instructions.includes('instead of guessing'))
|
|
})
|
|
test('filename defines the name and model IDs preserve slashes',()=>{
|
|
const d=parseDefinition('---\ndescription: Example\nmodel: local/org/model\n---\nRole','/roster/new-name.md')
|
|
assert.equal(d.name,'new-name');assert.deepEqual(definitionRuns(d).model,{provider:'local',modelId:'org/model'})
|
|
})
|
|
test('an additive project overlay retains base settings and appends instructions',()=>{
|
|
const base=parseDefinition('---\ndescription: Base\nmodel: local/model\ntools: [read]\n---\nBase instructions','/roster/worker.md')
|
|
const overlay=parseDefinition('---\naddTools: [bash]\ndenyTools: [read]\n---\nProject instructions','/project/.sausage/roster/worker.md',base)
|
|
assert.equal(overlay.model,'local/model');assert.deepEqual(toolsFor(overlay,[]),['bash'])
|
|
assert(overlay.instructions.includes('Base instructions'));assert(overlay.instructions.includes('Project instructions'))
|
|
})
|
|
test('a complete project definition replaces the body',()=>{
|
|
const base=definitions.find(d=>d.name==='worker')!
|
|
const next=parseDefinition('---\ndescription: Project worker\nmodel: local/model\n---\nReplacement','/project/.sausage/roster/worker.md',base)
|
|
assert.equal(next.instructions,'Replacement')
|
|
})
|
|
test('missing settings and malformed definition fields fail with a path',()=>{
|
|
assert.throws(()=>parseConfig('main_agent_name: sausage'),/prompt/)
|
|
assert.throws(()=>parseConfig('prompt: text'),/main_agent_name/)
|
|
assert.throws(()=>parseDefinition('---\nmodel: local/model\n---\nRole','/roster/example.md'),/description/)
|
|
assert.throws(()=>parseDefinition('---\ndescription: Example\nmodel: invalid\n---\nRole','/roster/example.md'),/example.md/)
|
|
assert.throws(()=>parseDefinition('---\ndescription: Example\nmodel: local/model\nthinking: invalid\n---\nRole','/roster/example.md'),/thinking/)
|
|
})
|
|
test('tool denials do not mutate inherited tools',()=>{
|
|
const inherited=['read','bash'];assert.deepEqual(toolsFor({...definitions[0],denyTools:['bash']},inherited),['read']);assert.deepEqual(inherited,['read','bash'])
|
|
})
|
|
test('all built-in skills contain valid YAML frontmatter',()=>{
|
|
for(const name of readdirSync('skills',{withFileTypes:true}).filter(e=>e.isDirectory()).map(e=>e.name)){
|
|
const skill=parseFrontmatter(readFileSync(`skills/${name}/SKILL.md`,'utf8'))
|
|
assert.equal(skill.frontmatter.name,name);assert.equal(typeof skill.frontmatter.description,'string');assert(skill.body.length>0)
|
|
}
|
|
})
|
|
console.log(`${count} tests passed`)
|