feat(P01): implement git-native architecture
---ci---
phase: 1
milestone: v0.2.0
status: execute
decisions:
- id: D-001
decision: Git log as primary project memory, .ci/ for long-lived references only
rationale: Eliminates state drift, enables reconstruction from commit messages alone
confidence: 0.95
alternatives: [hybrid file+git, pure git with no .ci/]
- id: D-002
decision: ---ci--- YAML blocks in commit bodies for machine-parseable metadata
rationale: Structured and human-readable; grep-friendly; round-trips through parser
confidence: 0.92
alternatives: [JSON payload, conventional-commit-only]
- id: D-003
decision: Phase+milestone branch naming (phase/NN-slug, milestone/vX.X-slug)
rationale: Branch list immediately shows project state; merged equals complete
confidence: 0.88
alternatives: [trunk+tags, milestone-only branches]
requirements:
covered: [ARCH-01, ARCH-02, ARCH-03, ARCH-04, ARCH-05, ARCH-06]
lessons:
- Commit body YAML must round-trip through parser — tested before shipping
- .ci/audit/ removal required updating 4 test suites that depended on audit files
---/ci---
New modules: commit-parser, commit-builder, git-context, git-branch, ci-files
Core rewrites: DecisionEngine, EscalationProtocol, OrchestratorAgent
Removed: .ci/audit/, .planning/ directory support
Tests: 25 suites, 218 passing (up from 20/158)
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import * as fs from "node:fs";
|
||||
import { CiFiles, ProjectMd, RoadmapMd, RequirementsMd, ArchitectureMd } from "../core/ci-files.js";
|
||||
|
||||
function createTempDir(): string {
|
||||
return fs.mkdtempSync(path.join(os.tmpdir(), "ci-files-test-"));
|
||||
}
|
||||
|
||||
function cleanup(dir: string): void {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
describe("CiFiles", () => {
|
||||
let dir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
dir = createTempDir();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(dir);
|
||||
});
|
||||
|
||||
describe("ensureCIDir", () => {
|
||||
it("creates .ci directory", () => {
|
||||
const ciFiles = new CiFiles(dir);
|
||||
ciFiles.ensureCIDir();
|
||||
expect(fs.existsSync(path.join(dir, ".ci"))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isInitialized", () => {
|
||||
it("returns false when no config.json exists", () => {
|
||||
const ciFiles = new CiFiles(dir);
|
||||
expect(ciFiles.isInitialized()).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true when config.json exists", () => {
|
||||
const ciFiles = new CiFiles(dir);
|
||||
ciFiles.ensureCIDir();
|
||||
fs.writeFileSync(path.join(dir, ".ci", "config.json"), "{}");
|
||||
expect(ciFiles.isInitialized()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PROJECT.md", () => {
|
||||
const project: ProjectMd = {
|
||||
name: "Task API",
|
||||
coreValue: "Build a REST API for task management",
|
||||
requirements: {
|
||||
validated: ["User auth works"],
|
||||
active: ["Real-time notifications", "CRUD operations"],
|
||||
outOfScope: ["Admin dashboard"],
|
||||
},
|
||||
constraints: ["Must use Node.js", "Production-ready"],
|
||||
context: "This is a task management API",
|
||||
keyDecisions: [
|
||||
{ decision: "Use PostgreSQL", rationale: "ACID compliance", outcome: "✓ Good" },
|
||||
],
|
||||
};
|
||||
|
||||
it("writes and reads PROJECT.md", () => {
|
||||
const ciFiles = new CiFiles(dir);
|
||||
ciFiles.writeProjectMd(project, "initial creation");
|
||||
|
||||
const read = ciFiles.readProjectMd();
|
||||
expect(read).not.toBeNull();
|
||||
expect(read!.name).toBe("Task API");
|
||||
expect(read!.requirements.active).toContain("Real-time notifications");
|
||||
expect(read!.constraints).toContain("Must use Node.js");
|
||||
});
|
||||
|
||||
it("overwrites PROJECT.md on update", () => {
|
||||
const ciFiles = new CiFiles(dir);
|
||||
ciFiles.writeProjectMd(project, "initial");
|
||||
|
||||
const updated = { ...project, coreValue: "Updated description" };
|
||||
ciFiles.writeProjectMd(updated, "phase 1 complete");
|
||||
|
||||
const read = ciFiles.readProjectMd();
|
||||
expect(read!.coreValue).toBe("Updated description");
|
||||
});
|
||||
});
|
||||
|
||||
describe("ROADMAP.md", () => {
|
||||
const roadmap: RoadmapMd = {
|
||||
overview: "4-phase delivery",
|
||||
phases: [
|
||||
{
|
||||
number: 1,
|
||||
name: "auth",
|
||||
description: "Auth system",
|
||||
status: "in_progress",
|
||||
dependsOn: [],
|
||||
requirements: ["AUTH-01"],
|
||||
successCriteria: ["Users can sign up"],
|
||||
},
|
||||
{
|
||||
number: 2,
|
||||
name: "tasks",
|
||||
description: "Task CRUD",
|
||||
status: "not_started",
|
||||
dependsOn: [1],
|
||||
requirements: ["TASK-01"],
|
||||
successCriteria: ["Users can create tasks"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
it("writes and reads ROADMAP.md", () => {
|
||||
const ciFiles = new CiFiles(dir);
|
||||
ciFiles.writeRoadmapMd(roadmap);
|
||||
|
||||
const read = ciFiles.readRoadmapMd();
|
||||
expect(read).not.toBeNull();
|
||||
expect(read!.overview).toBe("4-phase delivery");
|
||||
});
|
||||
});
|
||||
|
||||
describe("REQUIREMENTS.md", () => {
|
||||
const requirements: RequirementsMd = {
|
||||
v1: [
|
||||
{
|
||||
category: "Auth",
|
||||
items: [
|
||||
{ id: "AUTH-01", description: "User can sign up" },
|
||||
{ id: "AUTH-02", description: "User can log in" },
|
||||
],
|
||||
},
|
||||
],
|
||||
v2: [
|
||||
{
|
||||
category: "Notifications",
|
||||
items: [{ id: "NOTIF-01", description: "Push notifications" }],
|
||||
},
|
||||
],
|
||||
outOfScope: [{ feature: "Admin dashboard", reason: "Not core value" }],
|
||||
traceability: [
|
||||
{ requirement: "AUTH-01", phase: 1, status: "pending" },
|
||||
{ requirement: "AUTH-02", phase: 1, status: "pending" },
|
||||
],
|
||||
};
|
||||
|
||||
it("writes and reads REQUIREMENTS.md", () => {
|
||||
const ciFiles = new CiFiles(dir);
|
||||
ciFiles.writeRequirementsMd(requirements);
|
||||
|
||||
const read = ciFiles.readRequirementsMd();
|
||||
expect(read).not.toBeNull();
|
||||
});
|
||||
|
||||
it("updates requirement status", () => {
|
||||
const ciFiles = new CiFiles(dir);
|
||||
ciFiles.writeRequirementsMd(requirements);
|
||||
|
||||
ciFiles.updateRequirementStatus("AUTH-01", "complete");
|
||||
|
||||
const read = ciFiles.readRequirementsMd();
|
||||
expect(read).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("ARCHITECTURE.md", () => {
|
||||
const arch: ArchitectureMd = {
|
||||
overview: "Monolith with modules",
|
||||
components: [
|
||||
{
|
||||
name: "API",
|
||||
description: "REST API server",
|
||||
boundaries: "HTTP layer only",
|
||||
dependsOn: ["Auth", "Tasks"],
|
||||
},
|
||||
],
|
||||
dataFlow: "Client -> API -> DB",
|
||||
buildOrder: ["Auth", "Tasks", "API"],
|
||||
};
|
||||
|
||||
it("writes and reads ARCHITECTURE.md", () => {
|
||||
const ciFiles = new CiFiles(dir);
|
||||
ciFiles.writeArchitectureMd(arch);
|
||||
|
||||
const read = ciFiles.readArchitectureMd();
|
||||
expect(read).not.toBeNull();
|
||||
expect(read!.overview).toBe("Monolith with modules");
|
||||
});
|
||||
});
|
||||
|
||||
describe("updatePhaseStatus", () => {
|
||||
it("updates phase status in roadmap", () => {
|
||||
const ciFiles = new CiFiles(dir);
|
||||
const roadmap: RoadmapMd = {
|
||||
overview: "test",
|
||||
phases: [
|
||||
{
|
||||
number: 1,
|
||||
name: "auth",
|
||||
description: "Auth",
|
||||
status: "not_started",
|
||||
dependsOn: [],
|
||||
requirements: [],
|
||||
successCriteria: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
ciFiles.writeRoadmapMd(roadmap);
|
||||
ciFiles.updatePhaseStatus(1, "complete");
|
||||
|
||||
const read = ciFiles.readRoadmapMd();
|
||||
expect(read).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user