import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { readPersonaContractBlock } from './persona-contract.js'; /** * Persona-contract launch injection (A3b). Asserts the override-aware resolver * is wired so a customized persona in roles.local/ wins at launch (AC-NS-7), and * that any miss (unset/empty/unknown class, missing file) no-ops silently — * never throws — mirroring readFleetCommsBlock's tolerant contract. */ const BASELINE_CODER = `# Coder — fleet role definition The **coder** persona (\`class: coder\`, \`domain: engineering\`). ## Mandate BASELINE-MANDATE: implement the assigned lane. `; const OVERRIDE_CODER = `# Coder — fleet role definition (override) The **coder** persona (\`class: coder\`). ## Mandate OVERRIDE-MANDATE: implement the assigned lane, the user's way. `; function makeHome(): string { const root = mkdtempSync(join(tmpdir(), 'mosaic-persona-')); return join(root, 'mosaic-home'); } function seedBaseline(home: string, klass: string, body: string): void { const dir = join(home, 'fleet', 'roles'); mkdirSync(dir, { recursive: true }); writeFileSync(join(dir, `${klass}.md`), body); } function seedOverride(home: string, klass: string, body: string): void { const dir = join(home, 'fleet', 'roles.local'); mkdirSync(dir, { recursive: true }); writeFileSync(join(dir, `${klass}.md`), body); } describe('readPersonaContractBlock — launch-time persona injection (A3b)', () => { let home: string; beforeEach(() => { home = makeHome(); }); afterEach(() => { // root is the parent of mosaic-home rmSync(join(home, '..'), { recursive: true, force: true }); }); it('injects the baseline persona when the class has a fleet/roles/.md', () => { seedBaseline(home, 'coder', BASELINE_CODER); const block = readPersonaContractBlock(home, 'coder'); expect(block).toContain('# Persona Contract (coder)'); expect(block).toContain('BASELINE-MANDATE'); expect(block).toContain('baseline `fleet/roles/` layer'); }); it('OVERRIDE WINS: roles.local/.md content is injected over the baseline (AC-NS-7)', () => { seedBaseline(home, 'coder', BASELINE_CODER); seedOverride(home, 'coder', OVERRIDE_CODER); const block = readPersonaContractBlock(home, 'coder'); expect(block).toContain('# Persona Contract (coder)'); expect(block).toContain('OVERRIDE-MANDATE'); // override body present expect(block).not.toContain('BASELINE-MANDATE'); // baseline NOT used expect(block).toContain('roles.local'); // layer note names the override layer }); it('injects an override-only (user-added) persona with no baseline at all', () => { seedOverride(home, 'reviewer', '# Reviewer\n\n(`class: reviewer`)\n\nCUSTOM-ROLE.\n'); const block = readPersonaContractBlock(home, 'reviewer'); expect(block).toContain('# Persona Contract (reviewer)'); expect(block).toContain('CUSTOM-ROLE'); }); it('no-ops (empty string) when the class is undefined', () => { seedBaseline(home, 'coder', BASELINE_CODER); expect(readPersonaContractBlock(home, undefined)).toBe(''); }); it('no-ops (empty string) when the class is empty/whitespace', () => { seedBaseline(home, 'coder', BASELINE_CODER); expect(readPersonaContractBlock(home, '')).toBe(''); expect(readPersonaContractBlock(home, ' ')).toBe(''); }); it('no-ops (empty string) for an unknown class with no role file', () => { seedBaseline(home, 'coder', BASELINE_CODER); expect(readPersonaContractBlock(home, 'nonexistent')).toBe(''); }); it('no-ops (empty string, no throw) when no roles directories exist at all', () => { expect(() => readPersonaContractBlock(home, 'coder')).not.toThrow(); expect(readPersonaContractBlock(home, 'coder')).toBe(''); }); });