import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync, } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { clearCommandsCache } from '../../../commands.js' import { getTurnZeroSkillDiscovery } from '../prefetch.js' import { clearSkillIndexCache } from '../localSearch.js' let root: string let previousCwd: string const originalEnv = { ...process.env } beforeEach(() => { root = mkdtempSync(join(tmpdir(), 'skill-search-prefetch-')) previousCwd = process.cwd() process.chdir(root) process.env = { ...originalEnv } process.env.CLAUDE_CONFIG_DIR = join(root, 'config') process.env.CLAUDE_SKILL_LEARNING_HOME = join(root, 'learning') process.env.SKILL_SEARCH_ENABLED = '1' process.env.SKILL_LEARNING_ENABLED = '1' process.env.NODE_ENV = 'test' process.env.ANTHROPIC_API_KEY = 'test-key' clearCommandsCache() clearSkillIndexCache() }) afterEach(() => { process.chdir(previousCwd) process.env = { ...originalEnv } clearCommandsCache() clearSkillIndexCache() try { rmSync(root, { recursive: true, force: true, maxRetries: 10, retryDelay: 100, }) } catch { // Windows can keep transient handles after dynamic command loading. } }) describe('skill search prefetch', () => { test('auto-loads high-confidence project skill content', async () => { const skillDir = join(root, '.claude', 'skills', 'feature-audit') mkdirSync(skillDir, { recursive: true }) writeFileSync( join(skillDir, 'SKILL.md'), [ '---', 'name: feature-audit', 'description: Audit feature flags and classify minimal implementations', '---', '', '# Feature Audit', '', 'Use the feature flag audit workflow and classify flags as stub, shell, MVP, or thin-toggle.', ].join('\n'), ) const attachment = await getTurnZeroSkillDiscovery( 'audit feature flags for minimal implementation stubs', [], { agentId: undefined } as any, ) expect(attachment?.type).toBe('skill_discovery') if (attachment?.type !== 'skill_discovery') { throw new Error('expected skill_discovery attachment') } expect(attachment.skills[0]?.name).toBe('feature-audit') expect(attachment.skills[0]?.autoLoaded).toBe(true) expect(attachment.skills[0]?.content).toContain( 'feature flag audit workflow', ) }) test('records a pending skill gap on the first unmatched prompt (no draft file yet)', async () => { const attachment = await getTurnZeroSkillDiscovery( 'frobnicate zephyr ledger workflow', [], { agentId: undefined } as any, ) expect(attachment?.type).toBe('skill_discovery') if (attachment?.type !== 'skill_discovery') { throw new Error('expected skill_discovery attachment') } expect(attachment.skills).toEqual([]) expect(attachment.gap?.status).toBe('pending') expect(attachment.gap?.draftPath).toBeUndefined() }) })