kilo-loop/test/goal-extra.test.mjs

113 lines
5.1 KiB
JavaScript
Raw Permalink Normal View History

2026-09-13 16:29:03 +00:00
import { test } from 'node:test'
import assert from 'node:assert/strict'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { allStagesDone, lintGoal, loadGoal, syncGoalCheckbox, syncGoalDone, syncGoalStage } from '../src/goal.mjs'
function tmpProject() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'kilo-goal-'))
}
test('loadGoal parses verify/acceptance/model directives from a JSON stage', () => {
const project = tmpProject()
const file = path.join(project, 'goal.json')
fs.writeFileSync(
file,
JSON.stringify({
title: 'T',
stages: [{ id: 's1', title: 'A', verify: 'pnpm test', acceptance: 'green', model: 'p/m', agent: 'code', variant: 'high' }],
}),
)
const goal = loadGoal({ project, goal: 'goal.json', goalText: '' })
assert.equal(goal.stages[0].verify, 'pnpm test')
assert.equal(goal.stages[0].acceptance, 'green')
assert.equal(goal.stages[0].model, 'p/m')
assert.equal(goal.stages[0].agent, 'code')
assert.equal(goal.stages[0].variant, 'high')
})
test('loadGoal parses ":: verify: ..." directives from markdown checkboxes', () => {
const project = tmpProject()
const file = path.join(project, 'goal.md')
fs.writeFileSync(file, '# G\n\n- [ ] One :: verify: pnpm test :: acceptance: all green\n- [ ] Two\n')
const goal = loadGoal({ project, goal: 'goal.md', goalText: '' })
assert.equal(goal.stages[0].verify, 'pnpm test')
assert.equal(goal.stages[0].acceptance, 'all green')
assert.equal(goal.stages[0].title, 'One')
assert.equal(goal.stages[1].verify, '')
})
test('lintGoal warns when a stage has no verify command', () => {
const goal = { stages: [{ id: 's1', title: 'Rebuild the parser' }, { id: 's2', title: 'x', verify: 'true' }] }
const issues = lintGoal(goal, {})
assert.ok(issues.some((i) => i.stageId === 's1' && /no verify/.test(i.message)))
assert.ok(!issues.some((i) => i.stageId === 's2' && /no verify/.test(i.message)))
})
test('lintGoal reports duplicate ids as errors and vague titles as warnings', () => {
const goal = { stages: [{ id: 'dup', title: 'Fix stuff' }, { id: 'dup', title: 'Another thing' }] }
const issues = lintGoal(goal, { verify: 'true' })
assert.ok(issues.some((i) => i.level === 'error' && /duplicate stage id/.test(i.message)))
assert.ok(issues.some((i) => i.level === 'warn' && /vague/.test(i.message)))
})
test('syncGoalCheckbox ticks the matching markdown checkbox only', () => {
const project = tmpProject()
const file = path.join(project, 'plan.md')
fs.writeFileSync(file, '# G\n\n- [ ] First :: verify: t\n- [ ] Second\n')
const goal = { source: file, stages: [{ id: 's1', title: 'First' }, { id: 's2', title: 'Second' }] }
const res = syncGoalCheckbox(goal, goal.stages[0])
assert.equal(res.changed, true)
const text = fs.readFileSync(file, 'utf8')
assert.match(text, /- \[x\] First :: verify: t/)
assert.match(text, /- \[ \] Second/)
})
test('syncGoalCheckbox is a no-op for non-markdown goals', () => {
const res = syncGoalCheckbox({ source: '/tmp/goal.json' }, { title: 'x' })
assert.equal(res.ok, false)
assert.match(res.reason, /not a markdown file/)
})
test('syncGoalDone marks a JSON stage done by id and preserves formatting', () => {
const project = tmpProject()
const file = path.join(project, 'goal.json')
fs.writeFileSync(file, JSON.stringify({ title: 'G', stages: [{ id: 's1', title: 'One' }, { id: 's2', title: 'Two' }] }))
const res = syncGoalDone({ source: file }, { id: 's1', title: 'One' })
assert.equal(res.changed, true)
const saved = JSON.parse(fs.readFileSync(file, 'utf8'))
assert.equal(saved.stages[0].done, true)
assert.equal(saved.stages[1].done, undefined)
// Idempotent: a second call reports no change.
assert.equal(syncGoalDone({ source: file }, { id: 's1', title: 'One' }).changed, false)
})
test('syncGoalDone falls back to a title match', () => {
const project = tmpProject()
const file = path.join(project, 'goal.json')
fs.writeFileSync(file, JSON.stringify({ title: 'G', stages: [{ title: 'Only by title' }] }))
const res = syncGoalDone({ source: file }, { id: 'missing', title: 'Only by title' })
assert.equal(res.changed, true)
assert.equal(JSON.parse(fs.readFileSync(file, 'utf8')).stages[0].done, true)
})
test('syncGoalStage dispatches by file extension', () => {
const project = tmpProject()
const md = path.join(project, 'g.md')
fs.writeFileSync(md, '# G\n\n- [ ] One\n')
assert.equal(syncGoalStage({ source: md }, { title: 'One' }).ok, true)
const json = path.join(project, 'g.json')
fs.writeFileSync(json, JSON.stringify({ stages: [{ id: 's1', title: 'One' }] }))
assert.equal(syncGoalStage({ source: json }, { id: 's1', title: 'One' }).changed, true)
assert.equal(syncGoalStage({ source: '/tmp/other.txt' }, { title: 'One' }).ok, false)
})
test('allStagesDone requires a non-empty goal where every stage is done', () => {
assert.equal(allStagesDone({ stages: [{ done: true }, { done: true }] }), true)
assert.equal(allStagesDone({ stages: [{ done: true }, { done: false }] }), false)
assert.equal(allStagesDone({ stages: [] }), false)
assert.equal(allStagesDone({}), false)
})