diff --git a/test/skill-routing-e2e.test.ts b/test/skill-routing-e2e.test.ts index 597a64e9..e55894a4 100644 --- a/test/skill-routing-e2e.test.ts +++ b/test/skill-routing-e2e.test.ts @@ -84,6 +84,31 @@ function initGitRepo(dir: string) { run('git', ['config', 'user.name', 'Test']); } +/** + * Create a routing test working directory. + * Uses the actual repo checkout (ROOT) which has CLAUDE.md, .claude/skills/, + * and full project context. This matches the local environment where routing + * tests pass reliably. In containerized CI, bare tmpDirs lack the context + * Claude needs to make correct routing decisions. + */ +function createRoutingWorkDir(suffix: string): string { + // Clone the repo checkout into a tmpDir so concurrent tests don't interfere + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), `routing-${suffix}-`)); + // Copy essential context files + const filesToCopy = ['CLAUDE.md', 'README.md', 'package.json', 'ETHOS.md']; + for (const f of filesToCopy) { + const src = path.join(ROOT, f); + if (fs.existsSync(src)) fs.copyFileSync(src, path.join(tmpDir, f)); + } + // Copy skill files + installSkills(tmpDir); + // Init git + initGitRepo(tmpDir); + spawnSync('git', ['add', '.'], { cwd: tmpDir, stdio: 'pipe', timeout: 5000 }); + spawnSync('git', ['commit', '-m', 'initial'], { cwd: tmpDir, stdio: 'pipe', timeout: 5000 }); + return tmpDir; +} + function logCost(label: string, result: { costEstimate: { turnsUsed: number; estimatedTokens: number; estimatedCost: number }; duration: number }) { const { turnsUsed, estimatedTokens, estimatedCost } = result.costEstimate; const durationSec = Math.round(result.duration / 1000); @@ -113,13 +138,8 @@ describeE2E('Skill Routing E2E — Developer Journey', () => { }); test.concurrent('journey-ideation', async () => { - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'routing-ideation-')); + const tmpDir = createRoutingWorkDir('ideation'); try { - initGitRepo(tmpDir); - installSkills(tmpDir); - fs.writeFileSync(path.join(tmpDir, 'README.md'), '# New Project\n'); - spawnSync('git', ['add', '.'], { cwd: tmpDir, stdio: 'pipe', timeout: 5000 }); - spawnSync('git', ['commit', '-m', 'initial'], { cwd: tmpDir, stdio: 'pipe', timeout: 5000 }); const testName = 'journey-ideation'; const expectedSkill = 'office-hours'; @@ -147,10 +167,8 @@ describeE2E('Skill Routing E2E — Developer Journey', () => { }, 150_000); test.concurrent('journey-plan-eng', async () => { - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'routing-plan-eng-')); + const tmpDir = createRoutingWorkDir('plan-eng'); try { - initGitRepo(tmpDir); - installSkills(tmpDir); fs.writeFileSync(path.join(tmpDir, 'plan.md'), `# Waitlist App Architecture ## Components @@ -199,10 +217,8 @@ describeE2E('Skill Routing E2E — Developer Journey', () => { }, 150_000); test.concurrent('journey-think-bigger', async () => { - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'routing-think-bigger-')); + const tmpDir = createRoutingWorkDir('think-bigger'); try { - initGitRepo(tmpDir); - installSkills(tmpDir); fs.writeFileSync(path.join(tmpDir, 'plan.md'), `# Waitlist App Architecture ## Components @@ -251,11 +267,8 @@ describeE2E('Skill Routing E2E — Developer Journey', () => { }, 180_000); test.concurrent('journey-debug', async () => { - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'routing-debug-')); + const tmpDir = createRoutingWorkDir('debug'); try { - initGitRepo(tmpDir); - installSkills(tmpDir); - const run = (cmd: string, args: string[]) => spawnSync(cmd, args, { cwd: tmpDir, stdio: 'pipe', timeout: 5000 }); @@ -311,11 +324,8 @@ export default app; }, 150_000); test.concurrent('journey-qa', async () => { - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'routing-qa-')); + const tmpDir = createRoutingWorkDir('qa'); try { - initGitRepo(tmpDir); - installSkills(tmpDir); - fs.writeFileSync(path.join(tmpDir, 'package.json'), JSON.stringify({ name: 'waitlist-app', scripts: { dev: 'next dev' } }, null, 2)); fs.mkdirSync(path.join(tmpDir, 'src'), { recursive: true }); fs.writeFileSync(path.join(tmpDir, 'src/index.html'), '