import fs from 'fs'; import os from 'os'; import path from 'path'; import { fileURLToPath } from 'url'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { AGENT_LANGUAGE } from './config.js'; import { getPairedRoomPromptPath, getPlatformPromptPath, readPairedRoomPrompt, readPlatformPrompt, } from './platform-prompts.js'; describe('platform-prompts', () => { let tempDir: string; beforeEach(() => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ejclaw-prompts-')); vi.spyOn(process, 'cwd').mockReturnValue(tempDir); }); afterEach(() => { vi.restoreAllMocks(); fs.rmSync(tempDir, { recursive: true, force: true }); }); it('returns undefined when the prompt file is missing', () => { expect(readPlatformPrompt('claude-code')).toBeUndefined(); }); it('reads and trims provider-specific prompt files', () => { const promptsDir = path.join(tempDir, 'prompts'); fs.mkdirSync(promptsDir, { recursive: true }); fs.writeFileSync( path.join(promptsDir, 'codex-platform.md'), '\nCodex platform prompt\n', ); expect(getPlatformPromptPath('codex')).toBe( path.join(promptsDir, 'codex-platform.md'), ); expect(readPlatformPrompt('codex')).toBe('Codex platform prompt'); }); it('reads and trims paired-room prompt files', () => { const promptsDir = path.join(tempDir, 'prompts'); fs.mkdirSync(promptsDir, { recursive: true }); fs.writeFileSync( path.join(promptsDir, 'claude-paired-room.md'), '\nClaude paired prompt\n', ); expect(getPairedRoomPromptPath('claude-code')).toBe( path.join(promptsDir, 'claude-paired-room.md'), ); const expected = AGENT_LANGUAGE ? `Claude paired prompt\n\n## Language\n\nAlways respond in ${AGENT_LANGUAGE}.` : 'Claude paired prompt'; expect(readPairedRoomPrompt('claude-code')).toBe(expected); }); it('maps Codex paired-room prompts to the shared reviewer prompt while preserving failover identity wording', () => { const repoRoot = path.resolve( path.dirname(fileURLToPath(import.meta.url)), '..', ); expect(getPairedRoomPromptPath('codex', repoRoot)).toBe( path.join(repoRoot, 'prompts', 'claude-paired-room.md'), ); const codexPairedPrompt = readPairedRoomPrompt('codex', repoRoot); expect(codexPairedPrompt).toContain('reviewer'); expect(codexPairedPrompt).toContain('the output is invalid'); expect(codexPairedPrompt).toContain('EJCLAW_WORK_DIR'); expect(codexPairedPrompt).toContain( 'canonical verification root for this turn', ); expect(codexPairedPrompt).toContain( 'suggest 1-2 better alternatives with the reason and tradeoff for each', ); expect(codexPairedPrompt).toContain( 'Separate correctness issues from improvement ideas', ); expect(codexPairedPrompt).toContain( 'Do not present static analysis as completed verification', ); expect(codexPairedPrompt).toContain('Keep reviewer output owner-facing'); expect(codexPairedPrompt).toContain('prefer 3-6 lines'); expect(codexPairedPrompt).not.toContain('owner-side paired agent'); const failoverPlatformPrompt = fs.readFileSync( path.join(repoRoot, 'prompts', 'codex-review-failover-platform.md'), 'utf-8', ); expect(failoverPlatformPrompt).toContain('acting as `클코`'); }); it('keeps the superpowers-derived debugging guidance compressed and role-scoped', () => { const repoRoot = path.resolve( path.dirname(fileURLToPath(import.meta.url)), '..', ); const promptsDir = path.join(repoRoot, 'prompts'); const ownerPrompt = fs.readFileSync( path.join(promptsDir, 'owner-common-paired-room.md'), 'utf-8', ); const reviewerPrompt = fs.readFileSync( path.join(promptsDir, 'claude-paired-room.md'), 'utf-8', ); for (const prompt of [ownerPrompt, reviewerPrompt]) { expect(prompt).toContain('## Debugging discipline'); expect(prompt).toContain('root-cause'); expect(prompt).toContain('component-boundary data'); expect(prompt).toContain('symptom'); expect(prompt).toContain('same failed fix path'); expect(prompt).toContain('3 times'); expect(prompt).not.toContain('superpowers'); expect(prompt).not.toContain('NO FIXES WITHOUT ROOT CAUSE'); expect(prompt).not.toContain('The Four Phases'); } }); it('keeps file-backed note guidance optional and lightweight', () => { const repoRoot = path.resolve( path.dirname(fileURLToPath(import.meta.url)), '..', ); const promptsDir = path.join(repoRoot, 'prompts'); const ownerPrompt = fs.readFileSync( path.join(promptsDir, 'owner-common-paired-room.md'), 'utf-8', ); const reviewerPrompt = fs.readFileSync( path.join(promptsDir, 'claude-paired-room.md'), 'utf-8', ); for (const prompt of [ownerPrompt, reviewerPrompt]) { expect(prompt).toContain('## Durable work notes'); expect(prompt).toContain('multi-step plans'); expect(prompt).toContain('long debugging evidence'); expect(prompt).toContain('existing docs/plans location'); expect(prompt).toContain('tradeoff'); expect(prompt).toContain('pasted'); expect(prompt).not.toContain('docs/superpowers'); expect(prompt).not.toContain('Every plan MUST'); expect(prompt).not.toContain('Task-size gate'); } }); });