feat(ultraplan-local): extend brief-validator to accept type:ultrareview

This commit is contained in:
Kjell Tore Guttormsen 2026-05-01 13:31:39 +02:00
commit 1c22452e81
2 changed files with 80 additions and 3 deletions

View file

@ -6,9 +6,15 @@ import { parseDocument } from '../util/frontmatter.mjs';
import { issue, ok, fail } from '../util/result.mjs';
export const BRIEF_REQUIRED_FRONTMATTER = ['type', 'brief_version', 'task', 'slug', 'research_topics', 'research_status'];
export const REVIEW_AS_BRIEF_REQUIRED_FRONTMATTER = ['type', 'task', 'slug', 'project_dir', 'findings'];
export const BRIEF_TYPE_VALUES = Object.freeze(['ultrabrief', 'ultrareview']);
export const BRIEF_RESEARCH_STATUS_VALUES = ['pending', 'in_progress', 'complete', 'skipped'];
export const BRIEF_BODY_SECTIONS = ['Intent', 'Goal', 'Success Criteria'];
function getRequiredFields(type) {
return type === 'ultrareview' ? REVIEW_AS_BRIEF_REQUIRED_FRONTMATTER : BRIEF_REQUIRED_FRONTMATTER;
}
export function validateBriefContent(text, opts = {}) {
const strict = opts.strict !== false;
const doc = parseDocument(text);
@ -19,14 +25,25 @@ export function validateBriefContent(text, opts = {}) {
const errors = [];
const warnings = [];
for (const k of BRIEF_REQUIRED_FRONTMATTER) {
for (const k of getRequiredFields(fm.type)) {
if (!(k in fm)) {
errors.push(issue('BRIEF_MISSING_FIELD', `Required frontmatter field missing: ${k}`));
}
}
if (fm.type !== undefined && fm.type !== 'ultrabrief') {
errors.push(issue('BRIEF_WRONG_TYPE', `frontmatter.type must be "ultrabrief", got "${fm.type}"`));
if (fm.type !== undefined && !BRIEF_TYPE_VALUES.includes(fm.type)) {
errors.push(issue(
'BRIEF_WRONG_TYPE',
`frontmatter.type must be one of [${BRIEF_TYPE_VALUES.join(', ')}], got "${fm.type}"`,
));
}
if (fm.type === 'ultrareview' && fm.findings !== undefined && !Array.isArray(fm.findings)) {
errors.push(issue(
'BRIEF_BAD_FINDINGS_TYPE',
'Field "findings" must be an array of finding-IDs for type:ultrareview',
'Use block-style YAML: `findings:\\n - <id1>\\n - <id2>`',
));
}
if (fm.research_status !== undefined && !BRIEF_RESEARCH_STATUS_VALUES.includes(fm.research_status)) {

View file

@ -92,3 +92,63 @@ test('validateBrief — missing frontmatter is hard error', () => {
assert.equal(r.valid, false);
assert.ok(r.errors.find(e => e.code === 'FM_MISSING'));
});
const REVIEW_AS_BRIEF = `---
type: ultrareview
task: "Review delivered ultrareview-local v1.0"
slug: ultrareview-local
project_dir: .claude/projects/2026-05-01-ultrareview-local/
findings:
- 0123456789abcdef0123456789abcdef01234567
- fedcba9876543210fedcba9876543210fedcba98
---
# Review brief
## Intent
Adversarial review of delivered ultrareview-local v1.0.
## Goal
Find what was missed.
## Success Criteria
- All BLOCKER findings get a fix-plan.
`;
test('validateBrief — ultrareview type accepted with findings array', () => {
const r = validateBriefContent(REVIEW_AS_BRIEF, { strict: true });
assert.equal(r.valid, true, JSON.stringify(r.errors));
});
test('validateBrief — ultrareview without findings rejected (BRIEF_MISSING_FIELD)', () => {
const t = REVIEW_AS_BRIEF.replace(/findings:\n - 0123[\s\S]*?- fedcba[0-9a-f]+\n/, '');
const r = validateBriefContent(t);
assert.equal(r.valid, false);
assert.ok(
r.errors.find(e => e.code === 'BRIEF_MISSING_FIELD' && /findings/.test(e.message)),
`expected BRIEF_MISSING_FIELD for findings; got ${JSON.stringify(r.errors)}`,
);
});
test('validateBrief — ultrareview with findings as scalar (not array) rejected (BRIEF_BAD_FINDINGS_TYPE)', () => {
const t = REVIEW_AS_BRIEF.replace(
/findings:\n - 0123[\s\S]*?- fedcba[0-9a-f]+/,
'findings: not-an-array',
);
const r = validateBriefContent(t);
assert.equal(r.valid, false);
assert.ok(r.errors.find(e => e.code === 'BRIEF_BAD_FINDINGS_TYPE'));
});
test('validateBrief — wrong-type error message includes accepted set', () => {
const t = REVIEW_AS_BRIEF.replace('type: ultrareview', 'type: somethingelse');
const r = validateBriefContent(t);
assert.equal(r.valid, false);
const wrongType = r.errors.find(e => e.code === 'BRIEF_WRONG_TYPE');
assert.ok(wrongType);
assert.ok(/ultrabrief/.test(wrongType.message));
assert.ok(/ultrareview/.test(wrongType.message));
});