feat(ci): v0.9.0 — Distribution & Expansion milestone complete
---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
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as os from "node:os";
|
||||
import { AgentContext, AgentResult } from "./base.js";
|
||||
import { PlannerAgent } from "./planner.js";
|
||||
import { ExecutorAgent } from "./executor.js";
|
||||
import { VerifierAgent } from "./verifier.js";
|
||||
import { ResearcherAgent } from "./researcher.js";
|
||||
import { ChallengerAgent } from "./challenger.js";
|
||||
import { SecurityAuditorAgent } from "./security-auditor.js";
|
||||
import { DebuggerAgent } from "./debugger.js";
|
||||
import { DocWriterAgent } from "./doc-writer.js";
|
||||
import { DocVerifierAgent } from "./doc-verifier.js";
|
||||
import { CodeReviewerAgent } from "./code-reviewer.js";
|
||||
import { IdeationAgent } from "./ideation-agent.js";
|
||||
import { RoadmapperAgent } from "./roadmapper.js";
|
||||
import { PlanCheckerAgent } from "./plan-checker.js";
|
||||
import { ProjectResearcherAgent } from "./project-researcher.js";
|
||||
import { ResearchSynthesizerAgent } from "./research-synthesizer.js";
|
||||
import { SolutionWriterAgent } from "./solution-writer.js";
|
||||
import { PhaseResearcherAgent } from "./phase-researcher.js";
|
||||
import { TesterAgent } from "./tester.js";
|
||||
|
||||
const NON_ORCHESTRATOR_AGENTS: Array<{ name: string; factory: () => { execute(ctx: AgentContext): Promise<AgentResult>; name: string } }> = [
|
||||
{ name: "planner", factory: () => new PlannerAgent() },
|
||||
{ name: "executor", factory: () => new ExecutorAgent() },
|
||||
{ name: "verifier", factory: () => new VerifierAgent() },
|
||||
{ name: "researcher", factory: () => new ResearcherAgent() },
|
||||
{ name: "challenger", factory: () => new ChallengerAgent() },
|
||||
{ name: "security-auditor", factory: () => new SecurityAuditorAgent() },
|
||||
{ name: "debugger", factory: () => new DebuggerAgent() },
|
||||
{ name: "doc-writer", factory: () => new DocWriterAgent() },
|
||||
{ name: "doc-verifier", factory: () => new DocVerifierAgent() },
|
||||
{ name: "code-reviewer", factory: () => new CodeReviewerAgent() },
|
||||
{ name: "ideation-agent", factory: () => new IdeationAgent() },
|
||||
{ name: "roadmapper", factory: () => new RoadmapperAgent() },
|
||||
{ name: "plan-checker", factory: () => new PlanCheckerAgent() },
|
||||
{ name: "project-researcher", factory: () => new ProjectResearcherAgent() },
|
||||
{ name: "research-synthesizer", factory: () => new ResearchSynthesizerAgent() },
|
||||
{ name: "solution-writer", factory: () => new SolutionWriterAgent() },
|
||||
{ name: "phase-researcher", factory: () => new PhaseResearcherAgent() },
|
||||
{ name: "tester", factory: () => new TesterAgent() },
|
||||
];
|
||||
|
||||
describe("All agents have intrinsic mechanical logic", () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-mechanical-test-"));
|
||||
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: false, 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: Mechanical Test\n\n## Core Value\nValidate mechanical agent logic\n\n## Requirements\n### Active\n- REQ-01: Agent runs mechanically\n\n## Key Decisions\n\n## Constraints\n- Test only"
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, ".ciagent", "REQUIREMENTS.md"),
|
||||
"# Requirements\n\n## V1\n### Functional\n| ID | Description | Priority |\n|------|------|------|\n| REQ-01 | Agent test | 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 | Agent test phase | REQ-01 | | in_progress |"
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, ".ciagent", "ARCHITECTURE.md"),
|
||||
"# Architecture\n\n## Overview\nTest architecture\n\n## Components\n| Name | Description | Boundaries | Depends On |\n|------|------|------|------|\n| core | Core | src/core/ | | \n\n## Build Order\n1. Build core\n\n## Data Flow\nTest flow"
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, "package.json"),
|
||||
JSON.stringify({ name: "mech-test", version: "0.1.0", scripts: { test: "echo ok" } })
|
||||
);
|
||||
|
||||
fs.writeFileSync(path.join(tempDir, "tsconfig.json"), "{}");
|
||||
fs.writeFileSync(path.join(tempDir, "src", "app.ts"), "export function main() { return 1; }");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
try {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
} catch {}
|
||||
});
|
||||
|
||||
it("every non-orchestrator agent produces meaningful output without backend", async () => {
|
||||
const context: AgentContext = {
|
||||
project_path: tempDir,
|
||||
phase: 1,
|
||||
stage: "plan",
|
||||
specification: "Test mechanical agent logic execution",
|
||||
config_path: path.join(tempDir, ".ciagent", "config.json"),
|
||||
};
|
||||
|
||||
expect(NON_ORCHESTRATOR_AGENTS.length).toBe(18);
|
||||
|
||||
const results: Record<string, { success: boolean; error?: string; hasStubError: boolean }> = {};
|
||||
|
||||
for (const { name, factory } of NON_ORCHESTRATOR_AGENTS) {
|
||||
const agent = factory();
|
||||
expect(agent.name).toBe(name);
|
||||
|
||||
let result: AgentResult;
|
||||
|
||||
try {
|
||||
result = await agent.execute(context);
|
||||
} catch (err) {
|
||||
result = {
|
||||
success: false,
|
||||
output: "",
|
||||
artifacts_created: [],
|
||||
decisions: 0,
|
||||
escalations: 0,
|
||||
duration_ms: 0,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
};
|
||||
}
|
||||
|
||||
const errorText = (result.error || "").toLowerCase();
|
||||
const hasStubError =
|
||||
errorText.includes("requires an intelligence backend") ||
|
||||
errorText.includes("no intelligence backend available");
|
||||
|
||||
results[name] = {
|
||||
success: result.success,
|
||||
error: result.error,
|
||||
hasStubError,
|
||||
};
|
||||
}
|
||||
|
||||
const agentsWithStubErrors = Object.entries(results)
|
||||
.filter(([, r]) => r.hasStubError)
|
||||
.map(([name]) => name);
|
||||
|
||||
expect(agentsWithStubErrors).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user