Files
stack/packages/forge/__tests__/board-tasks.test.ts
Mos (Agent) 10689a30d2
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
feat: monorepo consolidation — forge pipeline, MACP protocol, framework plugin, profiles/guides/skills
Work packages completed:
- WP1: packages/forge — pipeline runner, stage adapter, board tasks, brief classifier,
  persona loader with project-level overrides. 89 tests, 95.62% coverage.
- WP2: packages/macp — credential resolver, gate runner, event emitter, protocol types.
  65 tests, 96.24% coverage. Full Python-to-TS port preserving all behavior.
- WP3: plugins/mosaic-framework — OC rails injection plugin (before_agent_start +
  subagent_spawning hooks for Mosaic contract enforcement).
- WP4: profiles/ (domains, tech-stacks, workflows), guides/ (17 docs),
  skills/ (5 universal skills), forge pipeline assets (48 markdown files).

Board deliberation: docs/reviews/consolidation-board-memo.md
Brief: briefs/monorepo-consolidation.md

Consolidates mosaic/stack (forge, MACP, bootstrap framework) into mosaic/mosaic-stack.
154 new tests total. Zero Python — all TypeScript/ESM.
2026-03-30 19:43:24 +00:00

200 lines
6.1 KiB
TypeScript

import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import {
buildPersonaBrief,
writePersonaBrief,
personaResultPath,
synthesisResultPath,
generateBoardTasks,
synthesizeReviews,
} from '../src/board-tasks.js';
import type { BoardPersona, PersonaReview } from '../src/types.js';
const testPersonas: BoardPersona[] = [
{ name: 'CEO', slug: 'ceo', description: 'The CEO sets direction.', path: 'agents/board/ceo.md' },
{
name: 'CTO',
slug: 'cto',
description: 'The CTO evaluates feasibility.',
path: 'agents/board/cto.md',
},
];
describe('buildPersonaBrief', () => {
it('includes persona name and description', () => {
const brief = buildPersonaBrief('Build feature X', testPersonas[0]!);
expect(brief).toContain('# Board Evaluation: CEO');
expect(brief).toContain('The CEO sets direction.');
expect(brief).toContain('Build feature X');
expect(brief).toContain('"persona": "CEO"');
});
});
describe('writePersonaBrief', () => {
let tmpDir: string;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'forge-board-'));
});
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});
it('writes brief file to disk', () => {
const briefPath = writePersonaBrief(tmpDir, 'BOARD', testPersonas[0]!, 'Test brief');
expect(fs.existsSync(briefPath)).toBe(true);
const content = fs.readFileSync(briefPath, 'utf-8');
expect(content).toContain('Board Evaluation: CEO');
});
});
describe('personaResultPath', () => {
it('builds correct path', () => {
const p = personaResultPath('/run/abc', 'BOARD-ceo');
expect(p).toContain('01-board/results/BOARD-ceo.board.json');
});
});
describe('synthesisResultPath', () => {
it('builds correct path', () => {
const p = synthesisResultPath('/run/abc', 'BOARD-SYNTHESIS');
expect(p).toContain('01-board/results/BOARD-SYNTHESIS.board.json');
});
});
describe('generateBoardTasks', () => {
let tmpDir: string;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'forge-board-tasks-'));
});
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});
it('generates one task per persona plus synthesis', () => {
const tasks = generateBoardTasks('Test brief', testPersonas, tmpDir);
expect(tasks).toHaveLength(3); // 2 personas + 1 synthesis
});
it('persona tasks have no dependsOn', () => {
const tasks = generateBoardTasks('Test brief', testPersonas, tmpDir);
expect(tasks[0]!.dependsOn).toBeUndefined();
expect(tasks[1]!.dependsOn).toBeUndefined();
});
it('synthesis task depends on all persona tasks', () => {
const tasks = generateBoardTasks('Test brief', testPersonas, tmpDir);
const synthesis = tasks[tasks.length - 1]!;
expect(synthesis.id).toBe('BOARD-SYNTHESIS');
expect(synthesis.dependsOn).toEqual(['BOARD-ceo', 'BOARD-cto']);
expect(synthesis.dependsOnPolicy).toBe('all_terminal');
});
it('persona tasks have correct metadata', () => {
const tasks = generateBoardTasks('Test brief', testPersonas, tmpDir);
expect(tasks[0]!.metadata['personaName']).toBe('CEO');
expect(tasks[0]!.metadata['personaSlug']).toBe('ceo');
});
it('uses custom base task ID', () => {
const tasks = generateBoardTasks('Brief', testPersonas, tmpDir, 'CUSTOM');
expect(tasks[0]!.id).toBe('CUSTOM-ceo');
expect(tasks[tasks.length - 1]!.id).toBe('CUSTOM-SYNTHESIS');
});
it('writes persona brief files to disk', () => {
generateBoardTasks('Test brief', testPersonas, tmpDir);
const briefDir = path.join(tmpDir, '01-board', 'briefs');
expect(fs.existsSync(briefDir)).toBe(true);
const files = fs.readdirSync(briefDir);
expect(files).toHaveLength(2);
});
});
describe('synthesizeReviews', () => {
const makeReview = (
persona: string,
verdict: PersonaReview['verdict'],
confidence: number,
): PersonaReview => ({
persona,
verdict,
confidence,
concerns: [`${persona} concern`],
recommendations: [`${persona} rec`],
keyRisks: [`${persona} risk`],
});
it('returns approve when all approve', () => {
const result = synthesizeReviews([
makeReview('CEO', 'approve', 0.8),
makeReview('CTO', 'approve', 0.9),
]);
expect(result.verdict).toBe('approve');
expect(result.confidence).toBe(0.85);
expect(result.persona).toBe('Board Synthesis');
});
it('returns reject when any reject', () => {
const result = synthesizeReviews([
makeReview('CEO', 'approve', 0.8),
makeReview('CTO', 'reject', 0.7),
]);
expect(result.verdict).toBe('reject');
});
it('returns conditional when any conditional (no reject)', () => {
const result = synthesizeReviews([
makeReview('CEO', 'approve', 0.8),
makeReview('CTO', 'conditional', 0.6),
]);
expect(result.verdict).toBe('conditional');
});
it('merges and deduplicates concerns', () => {
const reviews = [makeReview('CEO', 'approve', 0.8), makeReview('CTO', 'approve', 0.9)];
const result = synthesizeReviews(reviews);
expect(result.concerns).toEqual(['CEO concern', 'CTO concern']);
expect(result.recommendations).toEqual(['CEO rec', 'CTO rec']);
});
it('deduplicates identical items', () => {
const r1: PersonaReview = {
persona: 'CEO',
verdict: 'approve',
confidence: 0.8,
concerns: ['shared concern'],
recommendations: [],
keyRisks: [],
};
const r2: PersonaReview = {
persona: 'CTO',
verdict: 'approve',
confidence: 0.8,
concerns: ['shared concern'],
recommendations: [],
keyRisks: [],
};
const result = synthesizeReviews([r1, r2]);
expect(result.concerns).toEqual(['shared concern']);
});
it('includes original reviews', () => {
const reviews = [makeReview('CEO', 'approve', 0.8)];
const result = synthesizeReviews(reviews);
expect(result.reviews).toEqual(reviews);
});
it('handles empty reviews', () => {
const result = synthesizeReviews([]);
expect(result.verdict).toBe('approve');
expect(result.confidence).toBe(0);
});
});