From 1c22452e81f916ea72b6d55e6af66f7f57832a3c Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Fri, 1 May 2026 13:31:39 +0200 Subject: [PATCH] feat(ultraplan-local): extend brief-validator to accept type:ultrareview --- .../lib/validators/brief-validator.mjs | 23 ++++++- .../tests/validators/brief-validator.test.mjs | 60 +++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/plugins/ultraplan-local/lib/validators/brief-validator.mjs b/plugins/ultraplan-local/lib/validators/brief-validator.mjs index 0fb3199..ad90cd8 100644 --- a/plugins/ultraplan-local/lib/validators/brief-validator.mjs +++ b/plugins/ultraplan-local/lib/validators/brief-validator.mjs @@ -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 - \\n - `', + )); } if (fm.research_status !== undefined && !BRIEF_RESEARCH_STATUS_VALUES.includes(fm.research_status)) { diff --git a/plugins/ultraplan-local/tests/validators/brief-validator.test.mjs b/plugins/ultraplan-local/tests/validators/brief-validator.test.mjs index 481eb2a..56f02d0 100644 --- a/plugins/ultraplan-local/tests/validators/brief-validator.test.mjs +++ b/plugins/ultraplan-local/tests/validators/brief-validator.test.mjs @@ -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)); +});