Files
ci/src/core/artifacts.test.ts
T
Jon Chery 04c4489e70 fix(P01): migrate audit trail to git-native and replace audit_file with commit_hash
---ci---
project: ci
phase: 1
milestone: v0.8
status: in_progress
decisions:
  - id: D-024
    decision: Audit trail reads from git log instead of .ciagent/audit/*.json
    rationale: Git-native context means audit data should come from commit history, not files
    confidence: 0.88
  - id: D-025
    decision: Replace audit_file with commit_hash in Escalation type
    rationale: Escalations are committed to git; reference by hash instead of deprecated file path
    confidence: 0.90
requirements:
  covered: [FIX-04, FIX-05]
---/ci---

FIX-04: audit.ts logDecision/logEscalation now emit deprecation warnings
and are no-ops (decisions/escalations live in ---ci--- blocks). readAudit()
and getAuditSummary() parse git log for ---ci--- blocks instead of reading
.ciagent/audit/*.json files. ArtifactManager no longer creates audit dir.

FIX-05: Escalation type replaces audit_file: string with commit_hash: string.
All consumers updated (escalation.ts, ollama-base.ts, opencode.ts).
Audit tests rewritten for git-native approach.
2026-05-29 20:02:07 +00:00

142 lines
4.6 KiB
TypeScript

import * as fs from "node:fs";
import * as path from "node:path";
import * as os from "node:os";
import { ArtifactManager, ProjectManifest } from "../core/artifacts.js";
describe("ArtifactManager", () => {
let tempDir: string;
let manager: ArtifactManager;
beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-artifact-test-"));
manager = new ArtifactManager(tempDir);
});
afterEach(() => {
fs.rmSync(tempDir, { recursive: true, force: true });
});
describe("ensureStructure", () => {
it("creates .ciagent directory structure", () => {
manager.ensureStructure();
expect(fs.existsSync(path.join(tempDir, ".ciagent"))).toBe(true);
expect(fs.existsSync(path.join(tempDir, ".ciagent", "phases"))).toBe(true);
});
it("is idempotent", () => {
manager.ensureStructure();
manager.ensureStructure();
expect(fs.existsSync(path.join(tempDir, ".ciagent"))).toBe(true);
});
});
describe("isInitialized", () => {
it("returns false before project is written", () => {
manager.ensureStructure();
expect(manager.isInitialized()).toBe(false);
});
it("returns true after project is written", () => {
manager.ensureStructure();
manager.writeProject({
name: "Test Project",
objective: "Build it",
created_at: new Date().toISOString(),
phases: [{ id: 1, name: "Phase 1", status: "pending" }],
current_phase: 1,
status: "initializing",
});
expect(manager.isInitialized()).toBe(true);
});
});
describe("writeProject / readState / writePhaseArtifact", () => {
it("writes and reads project artifacts", () => {
manager.ensureStructure();
const manifest: ProjectManifest = {
name: "Test Project",
objective: "Build a REST API",
created_at: new Date().toISOString(),
phases: [
{ id: 1, name: "Research", status: "pending" },
{ id: 2, name: "Plan & Execute", status: "pending" },
],
current_phase: 1,
status: "initializing",
};
manager.writeProject(manifest);
const projectPath = path.join(tempDir, ".ciagent", "PROJECT.md");
expect(fs.existsSync(projectPath)).toBe(true);
const content = fs.readFileSync(projectPath, "utf-8");
expect(content).toContain("Test Project");
expect(content).toContain("Build a REST API");
expect(content).toContain("Phase 1: Research");
});
it("writes phase artifacts", () => {
manager.ensureStructure();
manager.writePhaseArtifact(1, "PLAN.md", "# My Plan\n\nThis is the plan.");
const artifact = manager.readPhaseArtifact(1, "PLAN.md");
expect(artifact).not.toBeNull();
expect(artifact).toContain("My Plan");
});
it("returns null for non-existent artifact", () => {
manager.ensureStructure();
const artifact = manager.readPhaseArtifact(99, "NONEXISTENT.md");
expect(artifact).toBeNull();
});
it("writes and reads state", () => {
manager.ensureStructure();
manager.writeState({
current_phase: 1,
current_stage: "execute",
last_agent: "executor",
last_action: "Implemented feature X",
updated_at: new Date().toISOString(),
pipeline_progress: { specify: true, clarify: true, research: true, plan: true, execute: false, verify: false, complete: false },
});
const state = manager.readState();
expect(state).not.toBeNull();
expect(state!.current_phase).toBe(1);
expect(state!.current_stage).toBe("execute");
expect(state!.pipeline_progress.specify).toBe(true);
});
it("returns null for state when not written", () => {
manager.ensureStructure();
const state = manager.readState();
expect(state).toBeNull();
});
});
describe("writeDecisions", () => {
it("writes decisions to DECISIONS.md", () => {
manager.ensureStructure();
manager.writeDecisions({
decisions: [
{
id: "D-001",
decision: "Use PostgreSQL",
rationale: "ACID compliance",
confidence: 0.92,
category: "technology_choice",
timestamp: new Date().toISOString(),
},
],
});
const decisionsPath = path.join(tempDir, ".ciagent", "DECISIONS.md");
expect(fs.existsSync(decisionsPath)).toBe(true);
const content = fs.readFileSync(decisionsPath, "utf-8");
expect(content).toContain("D-001");
expect(content).toContain("Use PostgreSQL");
expect(content).toContain("92%");
});
});
});