import { describe, it, expect } from 'vitest'; import { classifyBrief, parseBriefFrontmatter, determineBriefClass, stagesForClass, } from '../src/brief-classifier.js'; describe('classifyBrief', () => { it('returns strategic when strategic keywords dominate', () => { expect(classifyBrief('We need a new security architecture for compliance')).toBe('strategic'); }); it('returns technical when technical keywords are present and dominate', () => { expect(classifyBrief('Fix the bugfix for CSS lint cleanup')).toBe('technical'); }); it('returns strategic when no keywords match (default)', () => { expect(classifyBrief('Implement a new notification system')).toBe('strategic'); }); it('returns strategic when strategic and technical are tied', () => { // 1 strategic (security) + 1 technical (bug) = strategic wins on > check expect(classifyBrief('security bug')).toBe('technical'); }); it('returns strategic for empty text', () => { expect(classifyBrief('')).toBe('strategic'); }); it('is case-insensitive', () => { expect(classifyBrief('MIGRATION and COMPLIANCE strategy')).toBe('strategic'); }); }); describe('parseBriefFrontmatter', () => { it('parses simple key-value frontmatter', () => { const text = '---\nclass: technical\ntitle: My Brief\n---\n\n# Body'; const fm = parseBriefFrontmatter(text); expect(fm).toEqual({ class: 'technical', title: 'My Brief' }); }); it('strips quotes from values', () => { const text = '---\nclass: "hotfix"\ntitle: \'Test\'\n---\n\n# Body'; const fm = parseBriefFrontmatter(text); expect(fm['class']).toBe('hotfix'); expect(fm['title']).toBe('Test'); }); it('returns empty object when no frontmatter', () => { expect(parseBriefFrontmatter('# Just a heading')).toEqual({}); }); it('returns empty object for malformed frontmatter', () => { expect(parseBriefFrontmatter('---\n---\n')).toEqual({}); }); }); describe('determineBriefClass', () => { it('CLI flag takes priority', () => { const result = determineBriefClass('security migration', 'hotfix'); expect(result).toEqual({ briefClass: 'hotfix', classSource: 'cli' }); }); it('frontmatter takes priority over auto', () => { const text = '---\nclass: technical\n---\n\nSecurity architecture compliance'; const result = determineBriefClass(text); expect(result).toEqual({ briefClass: 'technical', classSource: 'frontmatter' }); }); it('falls back to auto-classify', () => { const result = determineBriefClass('We need a migration plan'); expect(result).toEqual({ briefClass: 'strategic', classSource: 'auto' }); }); it('ignores invalid CLI class', () => { const result = determineBriefClass('bugfix cleanup', 'invalid'); expect(result).toEqual({ briefClass: 'technical', classSource: 'auto' }); }); it('ignores invalid frontmatter class', () => { const text = '---\nclass: banana\n---\n\nbugfix'; const result = determineBriefClass(text); expect(result).toEqual({ briefClass: 'technical', classSource: 'auto' }); }); }); describe('stagesForClass', () => { it('strategic includes all stages including board', () => { const stages = stagesForClass('strategic'); expect(stages).toContain('01-board'); expect(stages).toContain('01b-brief-analyzer'); expect(stages).toContain('00-intake'); expect(stages).toContain('09-deploy'); }); it('technical skips board', () => { const stages = stagesForClass('technical'); expect(stages).not.toContain('01-board'); expect(stages).toContain('01b-brief-analyzer'); }); it('hotfix skips board and brief analyzer', () => { const stages = stagesForClass('hotfix'); expect(stages).not.toContain('01-board'); expect(stages).not.toContain('01b-brief-analyzer'); expect(stages).toContain('05-coding'); }); it('forceBoard adds board back for technical', () => { const stages = stagesForClass('technical', true); expect(stages).toContain('01-board'); expect(stages).toContain('01b-brief-analyzer'); }); it('forceBoard adds board back for hotfix', () => { const stages = stagesForClass('hotfix', true); expect(stages).toContain('01-board'); expect(stages).toContain('01b-brief-analyzer'); }); it('stages are in canonical order', () => { const stages = stagesForClass('strategic'); for (let i = 1; i < stages.length; i++) { const prevIdx = stages.indexOf(stages[i - 1]!); const currIdx = stages.indexOf(stages[i]!); expect(prevIdx).toBeLessThan(currIdx); } }); });