import * as fs from "node:fs"; import * as path from "node:path"; import * as os from "node:os"; import { execSync } from "node:child_process"; import { IntelligenceBackend, BackendRequest, BackendResult, BackendType, emptyTokenUsage, OpenAIConfig, AnthropicConfig } from "./backends/types.js"; import { OpenAIBackend } from "./backends/openai.js"; import { AnthropicBackend } from "./backends/anthropic.js"; import { PlannerAgent } from "./agents/planner.js"; import { ResearcherAgent } from "./agents/researcher.js"; import { VerifierAgent } from "./agents/verifier.js"; import { SecurityAuditorAgent } from "./agents/security-auditor.js"; import { CodeReviewerAgent } from "./agents/code-reviewer.js"; import { AgentContext } from "./agents/base.js"; class MockBackend implements IntelligenceBackend { readonly name = "mock"; readonly type: BackendType = "llm"; async isAvailable(): Promise { return true; } async execute(request: BackendRequest): Promise { return { success: true, output: `Mock backend executed task for ${request.persona}: ${request.task.substring(0, 80)}`, artifacts: [], decisions: [], escalations: [], usage: emptyTokenUsage(), }; } } describe("E2E v0.9 — Integration with mock backend", () => { let tempDir: string; const mockBackend = new MockBackend(); beforeEach(() => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-e2e-v09-")); fs.mkdirSync(path.join(tempDir, ".ciagent"), { recursive: true }); fs.mkdirSync(path.join(tempDir, "src"), { recursive: true }); fs.writeFileSync( path.join(tempDir, ".ciagent", "config.json"), JSON.stringify({ autonomy: { level: "full", escalation_hooks: [], clarify_budget: 10, decision_confidence_threshold: 0.6, max_revision_iterations: 3, max_verification_retries: 2, escalation_timeout_ms: 300000 }, model_profile: "quality", parallelization: { enabled: true, max_concurrent_agents: 5, min_plans_for_parallel: 2 }, verification: { automated_only: true, escalate_visual: true, escalate_external_integration: true, test_first: false }, security: { auto_accept_low_severity: true, auto_mitigate_medium_severity: true, escalate_high_severity: true }, git: { branching_strategy: "phase", auto_commit: false, auto_push: false }, backend: { provider: "auto", agent_backends: { opencode: { enabled: false } }, llm_backends: {} }, }, null, 2) ); fs.writeFileSync( path.join(tempDir, ".ciagent", "PROJECT.md"), "# Project: E2E Test\n\n## Core Value\nTest CIAgent v0.9 integration\n\n## Requirements\n### Active\n- TEST-01: E2E pipeline completes\n\n## Key Decisions\n\n## Constraints\n- Test environment only" ); fs.writeFileSync( path.join(tempDir, ".ciagent", "REQUIREMENTS.md"), "# Requirements\n\n## V1\n### Functional\n| ID | Description | Priority |\n|------|------|------|\n| REQ-01 | E2E test completes | high |\n\n## Traceability\n| Requirement | Phase | Status |\n|------|------|------|\n| REQ-01 | 1 | in_progress |" ); fs.writeFileSync( path.join(tempDir, ".ciagent", "ROADMAP.md"), "# Roadmap\n\n## Phases\n\n| # | Name | Description | Requirements | Depends On | Status |\n|------|------|------|------|------|------|\n| 1 | Test Phase | E2E test phase | REQ-01 | | in_progress |" ); fs.writeFileSync( path.join(tempDir, ".ciagent", "ARCHITECTURE.md"), "# Architecture\n\n## Overview\nE2E test architecture\n\n## Components\n| Name | Description | Boundaries | Depends On |\n|------|------|------|------|\n| core | Core module | src/core/ — test support | | \n\n## Build Order\n1. Build core\n\n## Data Flow\nSimple test flow" ); fs.writeFileSync( path.join(tempDir, "package.json"), JSON.stringify({ name: "e2e-test", version: "0.1.0", scripts: { test: "echo 'no tests'" } }) ); fs.writeFileSync(path.join(tempDir, "tsconfig.json"), "{}"); fs.writeFileSync(path.join(tempDir, "src", "app.ts"), "export function main() { return 1; }"); execSync("git init", { cwd: tempDir, stdio: "pipe" }); execSync("git add -A", { cwd: tempDir, stdio: "pipe" }); execSync('git commit -m "init: E2E test project"', { cwd: tempDir, stdio: "pipe" }); }); afterEach(() => { try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch {} }); it("runs a multi-agent pipeline with mock backend and collects artifacts", async () => { const context: AgentContext = { project_path: tempDir, phase: 1, stage: "research", specification: "Build an E2E test project that validates CIAgent v0.9 integration", config_path: path.join(tempDir, ".ciagent", "config.json"), backend: mockBackend as unknown as IntelligenceBackend, }; const researcher = new ResearcherAgent(); const researcherResult = await researcher.execute(context); expect(researcherResult).toBeDefined(); expect(typeof researcherResult.success).toBe("boolean"); expect(researcherResult.output.length).toBeGreaterThan(0); const planner = new PlannerAgent(); const plannerResult = await planner.execute({ ...context, stage: "plan" }); expect(plannerResult).toBeDefined(); expect(typeof plannerResult.success).toBe("boolean"); const auditor = new SecurityAuditorAgent(); const auditorResult = await auditor.execute({ ...context, stage: "verify" }); expect(auditorResult).toBeDefined(); expect(typeof auditorResult.success).toBe("boolean"); const reviewer = new CodeReviewerAgent(); const reviewerResult = await reviewer.execute({ ...context, stage: "review" }); expect(reviewerResult).toBeDefined(); expect(typeof reviewerResult.success).toBe("boolean"); const verifier = new VerifierAgent(); const verifierResult = await verifier.execute({ ...context, stage: "verify" }); expect(verifierResult).toBeDefined(); expect(typeof verifierResult.success).toBe("boolean"); }); it("loads OpenAI and Anthropic config types without runtime errors", () => { const openaiConfig: OpenAIConfig = { base_url: "https://api.openai.com/v1", api_key_env: "OPENAI_API_KEY", model: "gpt-4o", model_profile: "quality", timeout_ms: 60000, }; expect(openaiConfig.model).toBe("gpt-4o"); expect(openaiConfig.api_key_env).toBe("OPENAI_API_KEY"); const anthropicConfig: AnthropicConfig = { base_url: "https://api.anthropic.com", api_key_env: "ANTHROPIC_API_KEY", model: "claude-sonnet-4-20250514", model_profile: "quality", timeout_ms: 60000, api_version: "2023-06-01", }; expect(anthropicConfig.model).toBe("claude-sonnet-4-20250514"); expect(anthropicConfig.api_key_env).toBe("ANTHROPIC_API_KEY"); const openaiBackend = new OpenAIBackend(openaiConfig); expect(openaiBackend.name).toBe("openai"); expect(openaiBackend.type).toBe("llm"); const anthropicBackend = new AnthropicBackend(anthropicConfig); expect(anthropicBackend.name).toBe("anthropic"); expect(anthropicBackend.type).toBe("llm"); }); });