import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import * as fs from 'node:fs'; import * as path from 'node:path'; import * as os from 'node:os'; import { createSkillTools, type SkillToolsDeps } from '@waggle/agent'; import { loadSkills, needsConfirmation } from '@waggle/agent'; /** * Integration tests for the Capability Acquisition Loop MVP. * * Tests the full flow: detect gap → search → propose → install → runtime availability. * These tests use real filesystem operations to prove the loop is end-to-end real. */ describe('Capability Acquisition — Integration', () => { let tmpDir: string; let waggleHome: string; let skillsDir: string; let starterDir: string; let tools: ReturnType; let skillsReloaded: boolean; beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-acq-int-')); waggleHome = path.join(tmpDir, '.waggle'); skillsDir = path.join(waggleHome, 'skills'); starterDir = path.join(tmpDir, 'starter-skills'); fs.mkdirSync(skillsDir, { recursive: true }); fs.mkdirSync(starterDir, { recursive: true }); // Create starter skills fs.writeFileSync( path.join(starterDir, 'risk-assessment.md'), '# Risk Assessment — Project Risk Identification and Ranking\n\nSystematically identify, evaluate, and plan mitigations for project risks.\n\n## What to do\n1. Identify risks\n2. Evaluate likelihood and impact\n3. Plan mitigations', ); fs.writeFileSync( path.join(starterDir, 'daily-plan.md'), '# Daily Plan — Structured Day Planning\n\nCreate a focused daily plan.\n\n## What to do\n1. Review tasks\n2. Prioritize\n3. Time-block', ); // Pre-install one skill fs.writeFileSync( path.join(skillsDir, 'catch-up.md'), '# Catch-up — Workspace Restart Summary\n\nGenerate a catch-up summary.', ); skillsReloaded = false; const deps: SkillToolsDeps = { waggleHome, starterSkillsDir: starterDir, nativeToolNames: ['web_search', 'read_file', 'bash', 'search_memory'], getInstalledSkills: () => loadSkills(waggleHome), onSkillsChanged: () => { skillsReloaded = true; }, }; tools = createSkillTools(deps); }); afterEach(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); function getTool(name: string) { const tool = tools.find(t => t.name === name); if (!tool) throw new Error(`Tool "${name}" not found`); return tool; } // ── Full acquisition flow ───────────────────────────────────────── it('full flow: acquire → find starter skill → install → runtime available', async () => { // Step 1: Detect gap — user needs risk assessment const acquireTool = getTool('acquire_capability'); const proposal = await acquireTool.execute({ need: 'risk assessment for my project' }); // Proposal should identify the gap and recommend installation. // The summary uses an inline-install marker (parsed by // CapabilityRequestCard in the UI) as the modern action-of-record // instead of naming the tool 'install_capability' inline. expect(proposal).toContain('risk-assessment'); expect(proposal).toContain('