a8b50f5109
---ci---
project: ci
phase: 6
milestone: v0.9
status: complete
artifacts:
tags: [v0.9.0]
decisions:
- id: D-047
decision: v0.9 theme = Distribution & Expansion
rationale: npm publish + OpenAI/Anthropic backends + agent flesh + parallel execution
confidence: 0.92
- id: D-049
decision: Feature milestone — patch tags v0.8.1-v0.8.6 then v0.9.0
rationale: OpenAI backend, agent flesh, npm publish all feat
confidence: 0.95
- id: D-059
decision: Rename OllamaBaseBackend to LLMBaseBackend + thin OllamaBaseBackend subclass
rationale: 15 of 17 methods backend-agnostic
confidence: 0.92
- id: D-060
decision: OpenAI/Anthropic backends use native fetch() not SDK packages
rationale: No dependency bloat; fetch native in Node 18+
confidence: 0.85
- id: D-066
decision: Concurrency limiter internal (no p-limit dependency)
rationale: 15 lines; avoids dependency for trivial feature
confidence: 0.90
- id: D-067
decision: Promise.allSettled for review agents at orchestrator lines 373-400
rationale: Current sequential loop replaced with parallel execution
confidence: 0.88
requirements:
covered: [PUBLISH-01, PUBLISH-02, PUBLISH-03, PUBLISH-04, OPENAI-01, OPENAI-02, OPENAI-03, OPENAI-04, OPENAI-05, FLESH-01, FLESH-02, FLESH-03, FLESH-04, FLESH-05, ANTHROPIC-01, ANTHROPIC-02, FLESH-06, FLESH-07, NPM-01, NPM-02, PARALLEL-01, PARALLEL-02, PARALLEL-03, INTEG-01, INTEG-02, INTEG-03, INTEG-04, INTEG-05]
---/ci---
6 phases, 28 tasks, 4077 net lines added, 57 test suites, 527 tests, zero stub agents
163 lines
7.0 KiB
TypeScript
163 lines
7.0 KiB
TypeScript
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<boolean> {
|
|
return true;
|
|
}
|
|
|
|
async execute(request: BackendRequest): Promise<BackendResult> {
|
|
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");
|
|
});
|
|
}); |