import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { composeContract } from './launch.js'; /** * Composer unit test (R7/R8/R9): asserts the launcher-composed runtime contract * * - includes the per-tier anchors (CONSTITUTION / AGENTS / USER / runtime), * - keeps the CONSTITUTION block byte-equal to the on-disk file (Tier-3 * byte-equality — the bare-launch fallback read must match what is injected), * - merges `*.local.md` operator overlays as deltas-by-value, and omits them * entirely when absent (base-only), * - selects the correct per-harness RUNTIME.md. * * `composeContract` takes `mosaicHome` as a param, so each test runs against an * isolated fixture home. We also chdir to an empty temp cwd so the cwd-relative * mission/PRD blocks contribute nothing (deterministic output). */ const CONSTITUTION = '# CONSTITUTION\n\nGATE-1: the non-negotiable law.\n'; const AGENTS = '# Mosaic Agent Dispatcher\n\nLoad order + guide router.\n'; const USER = '# operator\n\nName: Test Operator\n'; const TOOLS = '# tools index\n'; function makeHome(): { home: string; root: string } { const root = mkdtempSync(join(tmpdir(), 'mosaic-compose-')); const home = join(root, 'mosaic-home'); for (const h of ['claude', 'codex', 'opencode', 'pi']) { mkdirSync(join(home, 'runtime', h), { recursive: true }); writeFileSync(join(home, 'runtime', h, 'RUNTIME.md'), `# ${h} runtime contract\n`); } writeFileSync(join(home, 'CONSTITUTION.md'), CONSTITUTION); writeFileSync(join(home, 'AGENTS.md'), AGENTS); writeFileSync(join(home, 'USER.md'), USER); writeFileSync(join(home, 'TOOLS.md'), TOOLS); return { home, root }; } describe('composeContract — overlay composer', () => { let fixture: ReturnType; let prevCwd: string; let cwdDir: string; beforeEach(() => { fixture = makeHome(); prevCwd = process.cwd(); cwdDir = mkdtempSync(join(tmpdir(), 'mosaic-cwd-')); process.chdir(cwdDir); // neutralize cwd-relative mission/PRD blocks }); afterEach(() => { process.chdir(prevCwd); rmSync(fixture.root, { recursive: true, force: true }); rmSync(cwdDir, { recursive: true, force: true }); }); it('includes the per-tier anchors and the selected harness runtime', () => { const out = composeContract('claude', fixture.home); expect(out).toContain('GATE-1: the non-negotiable law.'); // L0 expect(out).toContain('Mosaic Agent Dispatcher'); // AGENTS expect(out).toContain('# User Profile'); // USER header expect(out).toContain('Name: Test Operator'); // USER body expect(out).toContain('# Runtime-Specific Contract'); expect(out).toContain('# claude runtime contract'); }); it('keeps the CONSTITUTION block byte-equal to the on-disk file (Tier-3)', () => { const out = composeContract('pi', fixture.home); const onDisk = readFileSync(join(fixture.home, 'CONSTITUTION.md'), 'utf-8'); // The injected L0 must be a byte-equal substring of the composed blob, so a // bare-launch fallback read of CONSTITUTION.md matches what was injected. expect(out.includes(onDisk)).toBe(true); }); it('is base-only when no *.local overlays exist', () => { const out = composeContract('claude', fixture.home); expect(out).not.toContain('# Operator Overlays'); expect(out).not.toContain('Operator Overlay (USER.local.md)'); expect(out).not.toContain('Persona Overlay'); expect(out).not.toContain('Standards Overlay'); }); it('merges USER.local.md directly under the operator profile', () => { writeFileSync(join(fixture.home, 'USER.local.md'), 'Prefer terse status updates.\n'); const out = composeContract('claude', fixture.home); expect(out).toContain('## Operator Overlay (USER.local.md)'); expect(out).toContain('Prefer terse status updates.'); // Overlay appears AFTER its base profile. expect(out.indexOf('# User Profile')).toBeLessThan( out.indexOf('## Operator Overlay (USER.local.md)'), ); }); it('merges SOUL.local.md + STANDARDS.local.md as deltas in the Operator Overlays block', () => { writeFileSync(join(fixture.home, 'SOUL.local.md'), 'Tone: dry and direct.\n'); writeFileSync(join(fixture.home, 'STANDARDS.local.md'), 'Require 90% coverage on auth code.\n'); const out = composeContract('claude', fixture.home); expect(out).toContain('# Operator Overlays'); expect(out).toContain('## Persona Overlay (SOUL.local.md)'); expect(out).toContain('Tone: dry and direct.'); expect(out).toContain('## Standards Overlay (STANDARDS.local.md)'); expect(out).toContain('Require 90% coverage on auth code.'); }); it('ignores whitespace-only *.local overlays (no empty overlay section)', () => { writeFileSync(join(fixture.home, 'SOUL.local.md'), ' \n\n'); const out = composeContract('claude', fixture.home); expect(out).not.toContain('# Operator Overlays'); }); it('selects a different RUNTIME.md per harness', () => { expect(composeContract('codex', fixture.home)).toContain('# codex runtime contract'); expect(composeContract('pi', fixture.home)).toContain('# pi runtime contract'); expect(composeContract('codex', fixture.home)).not.toContain('# pi runtime contract'); }); });