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
167 lines
5.8 KiB
TypeScript
167 lines
5.8 KiB
TypeScript
import * as fs from "node:fs";
|
|
import * as path from "node:path";
|
|
import * as os from "node:os";
|
|
import { DocVerifierAgent } from "../agents/doc-verifier.js";
|
|
|
|
function setupValidProject(tempDir: string): void {
|
|
const srcDir = path.join(tempDir, "src");
|
|
const agentsDir = path.join(srcDir, "agents");
|
|
|
|
const agentFiles = [
|
|
"orchestrator.ts", "planner.ts", "executor.ts", "verifier.ts",
|
|
"researcher.ts", "challenger.ts", "security-auditor.ts", "debugger.ts",
|
|
"doc-writer.ts", "doc-verifier.ts", "code-reviewer.ts", "ideation-agent.ts",
|
|
"roadmapper.ts", "plan-checker.ts", "project-researcher.ts",
|
|
"research-synthesizer.ts", "solution-writer.ts", "phase-researcher.ts", "tester.ts",
|
|
];
|
|
|
|
for (const dir of ["agents", "backends", "cli", "core", "types", "utils", "verification"]) {
|
|
fs.mkdirSync(path.join(srcDir, dir), { recursive: true });
|
|
}
|
|
|
|
fs.writeFileSync(path.join(agentsDir, "base.ts"), "");
|
|
fs.writeFileSync(path.join(agentsDir, "index.ts"), "");
|
|
for (const f of agentFiles) {
|
|
fs.writeFileSync(path.join(agentsDir, f), "export class X {}");
|
|
}
|
|
|
|
fs.writeFileSync(path.join(srcDir, "version.ts"), 'export const VERSION = "0.8.0";');
|
|
fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ version: "0.8.0" }));
|
|
|
|
fs.writeFileSync(
|
|
path.join(tempDir, "AGENTS.md"),
|
|
"19 agent implementations\n44 test suites\n"
|
|
);
|
|
|
|
for (let i = 0; i < 44; i++) {
|
|
fs.writeFileSync(path.join(srcDir, `test-${i}.test.ts`), "test('x', () => {});");
|
|
}
|
|
}
|
|
|
|
describe("DocVerifierAgent", () => {
|
|
let tempDir: string;
|
|
|
|
beforeEach(() => {
|
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-doc-verifier-test-"));
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
});
|
|
|
|
it("valid project passes with no findings", () => {
|
|
setupValidProject(tempDir);
|
|
|
|
const agent = new DocVerifierAgent();
|
|
const findings = agent.mechanicalDocVerify(tempDir);
|
|
|
|
expect(findings).toHaveLength(0);
|
|
});
|
|
|
|
it("detects missing agent via agent_mismatch", () => {
|
|
const srcDir = path.join(tempDir, "src");
|
|
const agentsDir = path.join(srcDir, "agents");
|
|
fs.mkdirSync(agentsDir, { recursive: true });
|
|
|
|
const agentFiles = [
|
|
"orchestrator.ts", "planner.ts", "executor.ts", "verifier.ts",
|
|
"researcher.ts", "challenger.ts", "security-auditor.ts",
|
|
];
|
|
|
|
fs.writeFileSync(path.join(agentsDir, "base.ts"), "");
|
|
fs.writeFileSync(path.join(agentsDir, "index.ts"), "");
|
|
for (const f of agentFiles) {
|
|
fs.writeFileSync(path.join(agentsDir, f), "export class X {}");
|
|
}
|
|
|
|
fs.writeFileSync(path.join(srcDir, "version.ts"), 'export const VERSION = "0.8.0";');
|
|
fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ version: "0.8.0" }));
|
|
|
|
fs.writeFileSync(
|
|
path.join(tempDir, "AGENTS.md"),
|
|
"19 agent implementations\n44 test suites\n"
|
|
);
|
|
|
|
const agent = new DocVerifierAgent();
|
|
const findings = agent.mechanicalDocVerify(tempDir);
|
|
|
|
const mismatch = findings.find((f) => f.type === "agent_mismatch");
|
|
expect(mismatch).toBeDefined();
|
|
expect(mismatch!.severity).toBe("P1");
|
|
});
|
|
|
|
it("detects version drift between package.json and src/version.ts", () => {
|
|
const srcDir = path.join(tempDir, "src");
|
|
fs.mkdirSync(srcDir, { recursive: true });
|
|
|
|
fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ version: "0.8.0" }));
|
|
fs.writeFileSync(path.join(srcDir, "version.ts"), 'export const VERSION = "0.9.0";');
|
|
|
|
fs.writeFileSync(
|
|
path.join(tempDir, "AGENTS.md"),
|
|
"19 agent implementations\n44 test suites\n"
|
|
);
|
|
|
|
const agent = new DocVerifierAgent();
|
|
const findings = agent.mechanicalDocVerify(tempDir);
|
|
|
|
const drift = findings.find((f) => f.type === "version_drift");
|
|
expect(drift).toBeDefined();
|
|
expect(drift!.severity).toBe("P0");
|
|
expect(drift!.expected).toContain("0.8.0");
|
|
expect(drift!.actual).toContain("0.9.0");
|
|
});
|
|
|
|
it("detects architecture stale when expected directory missing", () => {
|
|
const srcDir = path.join(tempDir, "src");
|
|
const limitedDirs = ["agents", "types"];
|
|
for (const dir of limitedDirs) {
|
|
fs.mkdirSync(path.join(srcDir, dir), { recursive: true });
|
|
}
|
|
|
|
fs.writeFileSync(path.join(srcDir, "version.ts"), 'export const VERSION = "0.8.0";');
|
|
fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ version: "0.8.0" }));
|
|
|
|
fs.writeFileSync(
|
|
path.join(tempDir, "AGENTS.md"),
|
|
"19 agent implementations\n44 test suites\n"
|
|
);
|
|
|
|
const agent = new DocVerifierAgent();
|
|
const findings = agent.mechanicalDocVerify(tempDir);
|
|
|
|
const stale = findings.filter((f) => f.type === "architecture_stale");
|
|
expect(stale.length).toBeGreaterThan(0);
|
|
expect(stale.some((f) => f.expected.includes("backends"))).toBe(true);
|
|
});
|
|
|
|
it("agent name is doc-verifier", () => {
|
|
const agent = new DocVerifierAgent();
|
|
expect(agent.name).toBe("doc-verifier");
|
|
});
|
|
|
|
it("findings include type and severity fields", () => {
|
|
const srcDir = path.join(tempDir, "src");
|
|
fs.mkdirSync(srcDir, { recursive: true });
|
|
|
|
fs.writeFileSync(path.join(tempDir, "package.json"), JSON.stringify({ version: "1.0.0" }));
|
|
fs.writeFileSync(path.join(srcDir, "version.ts"), 'export const VERSION = "2.0.0";');
|
|
|
|
fs.writeFileSync(
|
|
path.join(tempDir, "AGENTS.md"),
|
|
"19 agent implementations\n99 test suites\n"
|
|
);
|
|
|
|
const agent = new DocVerifierAgent();
|
|
const findings = agent.mechanicalDocVerify(tempDir);
|
|
|
|
for (const f of findings) {
|
|
expect(f.type).toBeDefined();
|
|
expect(f.severity).toBeDefined();
|
|
expect(f.expected).toBeDefined();
|
|
expect(f.actual).toBeDefined();
|
|
expect(["P0", "P1", "P2"]).toContain(f.severity);
|
|
expect(["agent_mismatch", "version_drift", "architecture_stale", "test_count_drift"]).toContain(f.type);
|
|
}
|
|
});
|
|
}); |