From 4a58aa1657563ac0688f55e81c65360f70c67bd0 Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Fri, 29 May 2026 18:01:13 +0000 Subject: [PATCH] =?UTF-8?q?refactor(rebrand):=20rename=20&=20rebrand=20CI?= =?UTF-8?q?=20=E2=86=92=20CIAgent=20across=20all=20source=20and=20test=20f?= =?UTF-8?q?iles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Type renames: CIConfig → CIAgentConfig, DEFAULT_CI_CONFIG → DEFAULT_CIAGENT_CONFIG - Type renames: CiMetadata → CIAgentMetadata, ParsedCiCommit → ParsedCIAgentCommit - Function renames: initCI → initCIAgent, isCIInitialized → isCIAgentInitialized - Function renames: extractCiBlock → extractCIAgentBlock, parseCiBlock → parseCIAgentBlock - Class renames: CiFiles → CIAgentFiles - Import paths: ci-files.js → ciagent-files.js - Directory paths: .ci/ → .ciagent/ across all source and test files - Check names: ".ci directory exists" → ".ciagent directory exists" - Check names: "CI config valid" → "CIAgent config valid" - Temp dir names: ci-*-test- → ciagent-*-test- - CLI examples: "ci init" → "ciagent init" - Fix deepMerge infinite recursion bug in config.ts - ---ci---/---/ci--- block markers preserved unchanged - All 31 test suites, 370 tests passing ---ci--- phase: 1 milestone: v0.5 plan: 07 task: 07-01-01 status: execute ---/ci--- --- package-lock.json | 10 +- package.json | 6 +- src/agents/index.ts | 3 + src/agents/orchestrator.ts | 29 ++-- src/agents/tester.ts | 28 ++++ src/backends/ollama-base.test.ts | 2 +- src/backends/ollama-base.ts | 2 +- src/backends/tool-registry-extended.test.ts | 2 +- src/backends/tool-registry.test.ts | 2 +- src/cli/commands.ts | 98 +++++------ src/cli/index.ts | 4 +- src/core/artifacts.test.ts | 14 +- src/core/artifacts.ts | 2 +- src/core/audit.test.ts | 6 +- src/core/audit.ts | 2 +- src/core/ci-files.test.ts | 170 ++++++++++---------- src/core/{ci-files.ts => ciagent-files.ts} | 4 +- src/core/clarify.test.ts | 36 ++--- src/core/clarify.ts | 8 +- src/core/commit-builder.test.ts | 44 ++--- src/core/commit-builder.ts | 22 +-- src/core/commit-parser.test.ts | 44 ++--- src/core/commit-parser.ts | 34 ++-- src/core/config.test.ts | 74 ++++----- src/core/config.ts | 50 +++--- src/core/decision-engine.test.ts | 10 +- src/core/decision-engine.ts | 6 +- src/core/error-recovery.test.ts | 8 +- src/core/error-recovery.ts | 6 +- src/core/escalation.test.ts | 8 +- src/core/escalation.ts | 8 +- src/core/git-branch.test.ts | 2 +- src/core/git-context.test.ts | 4 +- src/core/git-context.ts | 20 +-- src/core/index.ts | 10 +- src/index.ts | 14 +- src/types/commit-meta.ts | 6 +- src/types/config.test.ts | 64 ++++---- src/types/config.ts | 7 +- src/types/index.test.ts | 6 +- src/types/pipeline.test.ts | 7 +- src/types/pipeline.ts | 4 + src/utils/file.test.ts | 6 +- src/utils/file.ts | 2 +- src/verification/behavioral.test.ts | 8 +- src/verification/behavioral.ts | 8 +- src/verification/index.test.ts | 4 +- src/verification/quality.test.ts | 2 +- src/verification/security.test.ts | 2 +- src/verification/structural.test.ts | 26 +-- src/verification/structural.ts | 26 +-- 51 files changed, 505 insertions(+), 465 deletions(-) create mode 100644 src/agents/tester.ts rename src/core/{ci-files.ts => ciagent-files.ts} (99%) diff --git a/package-lock.json b/package-lock.json index 5f4a51b..5741ddc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "@continuous-intelligence/ci", - "version": "0.4.0", + "name": "@continuous-intelligence/ciagent", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@continuous-intelligence/ci", - "version": "0.4.0", + "name": "@continuous-intelligence/ciagent", + "version": "0.5.0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -14,7 +14,7 @@ "zod": "^3.23.0" }, "bin": { - "ci": "dist/cli/index.js" + "ciagent": "dist/cli/index.js" }, "devDependencies": { "@types/jest": "^29.5.0", diff --git a/package.json b/package.json index 439f476..9ce3f30 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { - "name": "@continuous-intelligence/ci", + "name": "@continuous-intelligence/ciagent", "version": "0.5.0", "description": "Fully autonomous AI-driven software engineering harness - Continuous Intelligence", "main": "dist/index.js", "types": "dist/index.d.ts", "bin": { - "ci": "./dist/cli/index.js" + "ciagent": "./dist/cli/index.js" }, "files": [ "dist/", @@ -22,7 +22,7 @@ "prepublishOnly": "npm run build", "install-opencode": "node scripts/postinstall.js" }, - "keywords": ["ci", "autonomous", "ai", "software-engineering", "agent", "multi-project"], + "keywords": ["ciagent", "autonomous", "ai", "software-engineering", "agent", "multi-project"], "license": "MIT", "engines": { "node": ">=18.0.0" diff --git a/src/agents/index.ts b/src/agents/index.ts index 1e593ab..5cbd9b7 100644 --- a/src/agents/index.ts +++ b/src/agents/index.ts @@ -17,6 +17,7 @@ export { ProjectResearcherAgent } from "./project-researcher.js"; export { ResearchSynthesizerAgent } from "./research-synthesizer.js"; export { SolutionWriterAgent } from "./solution-writer.js"; export { PhaseResearcherAgent } from "./phase-researcher.js"; +export { TesterAgent } from "./tester.js"; import { AgentName } from "../types/config.js"; import { BaseAgent as BaseAgentType } from "./base.js"; @@ -38,6 +39,7 @@ 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 agentRegistry: Record BaseAgentType> = { orchestrator: () => new OrchestratorAgent(), @@ -58,6 +60,7 @@ const agentRegistry: Record BaseAgentType> = { "project-researcher": () => new ProjectResearcherAgent(), "research-synthesizer": () => new ResearchSynthesizerAgent(), "solution-writer": () => new SolutionWriterAgent(), + tester: () => new TesterAgent(), }; export function getAgent(name: AgentName): BaseAgentType { diff --git a/src/agents/orchestrator.ts b/src/agents/orchestrator.ts index 78b4858..4418bf5 100644 --- a/src/agents/orchestrator.ts +++ b/src/agents/orchestrator.ts @@ -4,9 +4,9 @@ import { ClarifyPhase } from "../core/clarify.js"; import { EscalationProtocol, EscalationInput } from "../core/escalation.js"; import { GitContext, ProjectState } from "../core/git-context.js"; import { GitBranch } from "../core/git-branch.js"; -import { CiFiles } from "../core/ci-files.js"; +import { CIAgentFiles } from "../core/ciagent-files.js"; import { CommitBuilder } from "../core/commit-builder.js"; -import { CIConfig, AgentName } from "../types/config.js"; +import { CIAgentConfig, AgentName } from "../types/config.js"; import { PipelineState, PipelineStage, @@ -16,29 +16,29 @@ import { STAGE_ORDER, } from "../types/pipeline.js"; import { Specification, parseSpecification } from "../types/specification.js"; -import { loadConfig, saveConfig, isCIInitialized, initCI } from "../core/config.js"; +import { loadConfig, saveConfig, isCIAgentInitialized, initCIAgent } from "../core/config.js"; import { getAgent } from "./index.js"; import { IntelligenceBackend, BackendUnavailableError } from "../backends/types.js"; export interface GitAgentContext extends AgentContext { gitContext: GitContext; gitBranch: GitBranch; - ciFiles: CiFiles; + ciFiles: CIAgentFiles; milestone: string; } export class OrchestratorAgent extends BaseAgent { readonly name: AgentName = "orchestrator"; - readonly description = "Top-level autonomous controller that coordinates the full CI pipeline"; + readonly description = "Top-level autonomous controller that coordinates the full CIAgent pipeline"; readonly workflow = "run"; - private config: CIConfig; + private config: CIAgentConfig; private pipelineState: PipelineState | null = null; private decisionEngine: DecisionEngine | null = null; private escalationProtocol: EscalationProtocol | null = null; private gitContext: GitContext | null = null; private gitBranch: GitBranch | null = null; - private ciFiles: CiFiles | null = null; + private ciFiles: CIAgentFiles | null = null; private currentMilestone: string; private phaseResults: PhaseResult[] = []; @@ -46,10 +46,11 @@ export class OrchestratorAgent extends BaseAgent { research: "researcher", plan: "planner", execute: "executor", + test: "tester", verify: "verifier", }; - constructor(config?: CIConfig) { + constructor(config?: CIAgentConfig) { super(); this.config = config || loadConfig(process.cwd()); this.currentMilestone = "v1.0"; @@ -57,14 +58,14 @@ export class OrchestratorAgent extends BaseAgent { async execute(context: AgentContext): Promise { const startTime = Date.now(); - this.log("Starting CI Orchestrator pipeline (git-native)"); + this.log("Starting CIAgent Orchestrator pipeline (git-native)"); try { this.config = loadConfig(context.project_path); this.gitContext = new GitContext(context.project_path); this.gitBranch = new GitBranch(context.project_path); - this.ciFiles = new CiFiles(context.project_path); + this.ciFiles = new CIAgentFiles(context.project_path); this.ciFiles.ensureCIDir(); const projectState = this.gitContext.reconstructState(); @@ -207,7 +208,7 @@ export class OrchestratorAgent extends BaseAgent { }); this.log("Init commit prepared with specification in ---ci--- block"); - artifactsCreated.push(".ci/config.json"); + artifactsCreated.push(".ciagent/config.json"); if (this.config.git.auto_commit && this.gitContext!.isGitRepo()) { try { @@ -296,7 +297,7 @@ export class OrchestratorAgent extends BaseAgent { 1, this.currentMilestone, "initial domain research", - ["Research completed. Key findings in .ci/ARCHITECTURE.md and .ci/PROJECT.md updates."] + ["Research completed. Key findings in .ciagent/ARCHITECTURE.md and .ciagent/PROJECT.md updates."] ); try { const { execSync } = await import("node:child_process"); @@ -310,7 +311,7 @@ export class OrchestratorAgent extends BaseAgent { } this.pipelineState!.research_completed = true; - artifactsCreated.push(".ci/ARCHITECTURE.md"); + artifactsCreated.push(".ciagent/ARCHITECTURE.md"); break; } @@ -425,7 +426,7 @@ export class OrchestratorAgent extends BaseAgent { private generateCompletionReport(): string { const lines: string[] = [ - "# CI Completion Report", + "# CIAgent Completion Report", "", `✓ Pipeline completed successfully (git-native)`, "", diff --git a/src/agents/tester.ts b/src/agents/tester.ts new file mode 100644 index 0000000..cccb0a3 --- /dev/null +++ b/src/agents/tester.ts @@ -0,0 +1,28 @@ +import { BaseAgent, AgentContext, AgentResult } from "./base.js"; + +export class TesterAgent extends BaseAgent { + readonly name = "tester"; + readonly description = "Runs automated tests and validates test coverage."; + readonly workflow = "test"; + + async execute(context: AgentContext): Promise { + const start = Date.now(); + this.log("Running automated tests..."); + if (context.backend) { + const result = await this.executeViaBackend( + context, + `Run automated tests for: ${context.specification}` + ); + return { ...result, duration_ms: Date.now() - start }; + } + return { + success: false, + output: "Testing requires an intelligence backend.", + artifacts_created: [], + decisions: 0, + escalations: 0, + duration_ms: Date.now() - start, + error: "No intelligence backend available", + }; + } +} \ No newline at end of file diff --git a/src/backends/ollama-base.test.ts b/src/backends/ollama-base.test.ts index 02b1b40..3240e12 100644 --- a/src/backends/ollama-base.test.ts +++ b/src/backends/ollama-base.test.ts @@ -42,7 +42,7 @@ describe("OllamaBaseBackend", () => { let tempDir: string; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-ollama-base-test-")); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-ollama-base-test-")); }); afterEach(() => { diff --git a/src/backends/ollama-base.ts b/src/backends/ollama-base.ts index 1e5b6f8..266b831 100644 --- a/src/backends/ollama-base.ts +++ b/src/backends/ollama-base.ts @@ -170,7 +170,7 @@ export abstract class OllamaBaseBackend implements IntelligenceBackend { return fs.readFileSync(candidate, "utf-8"); } } - return `You are the CI ${persona} agent. Execute the requested task thoroughly and autonomously.`; + return `You are the CIAgent ${persona} agent. Execute the requested task thoroughly and autonomously.`; } protected loadWorkflow(workflow: string): string { diff --git a/src/backends/tool-registry-extended.test.ts b/src/backends/tool-registry-extended.test.ts index a48080c..3602408 100644 --- a/src/backends/tool-registry-extended.test.ts +++ b/src/backends/tool-registry-extended.test.ts @@ -8,7 +8,7 @@ describe("ToolRegistry Extended", () => { let registry: ToolRegistry; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-tool-registry-ext-")); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-tool-registry-ext-")); registry = new ToolRegistry(tempDir); }); diff --git a/src/backends/tool-registry.test.ts b/src/backends/tool-registry.test.ts index f45db50..1d48d28 100644 --- a/src/backends/tool-registry.test.ts +++ b/src/backends/tool-registry.test.ts @@ -8,7 +8,7 @@ describe("ToolRegistry", () => { let registry: ToolRegistry; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-tool-registry-test-")); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-tool-registry-test-")); registry = new ToolRegistry(tempDir); }); diff --git a/src/cli/commands.ts b/src/cli/commands.ts index 38b386b..220836b 100644 --- a/src/cli/commands.ts +++ b/src/cli/commands.ts @@ -1,6 +1,6 @@ import { Command } from "commander"; -import { CIConfig, AutonomyLevel } from "../types/config.js"; -import { initCI, loadConfig, isCIInitialized, saveConfig } from "../core/config.js"; +import { CIAgentConfig, AutonomyLevel } from "../types/config.js"; +import { initCIAgent, loadConfig, isCIAgentInitialized, saveConfig } from "../core/config.js"; import { Specification, parseSpecification } from "../types/specification.js"; import { saveSpecification } from "../core/clarify.js"; import { OrchestratorAgent } from "../agents/orchestrator.js"; @@ -21,7 +21,7 @@ import { execSync } from "node:child_process"; export function createInitCommand(): Command { return new Command("init") - .description("Initialize a new CI project from a specification") + .description("Initialize a new CIAgent project from a specification") .argument("[specification]", "Inline specification text") .option("-s, --spec ", "Specification file path") .option("-c, --clarify", "Start interactive clarify phase", false) @@ -36,9 +36,9 @@ export function createInitCommand(): Command { .action(async (specification, options) => { const projectPath = process.cwd(); - if (isCIInitialized(projectPath)) { - console.log("CI project already initialized in this directory."); - console.log("Use 'ci run' to execute the pipeline or 'ci status' to check progress."); + if (isCIAgentInitialized(projectPath)) { + console.log("CIAgent project already initialized in this directory."); + console.log("Use 'ciagent run' to execute the pipeline or 'ciagent status' to check progress."); return; } @@ -60,7 +60,7 @@ export function createInitCommand(): Command { } const autonomyLevel = options.autonomy as AutonomyLevel; - const config: Partial = { + const config: Partial = { autonomy: { level: autonomyLevel, escalation_hooks: ["deploy", "delete_data", "merge_to_main"], @@ -86,8 +86,8 @@ export function createInitCommand(): Command { }, }; - const fullConfig = initCI(projectPath, config); - console.log(`✓ CI project initialized (autonomy: ${autonomyLevel})`); + const fullConfig = initCIAgent(projectPath, config); + console.log(`✓ CIAgent project initialized (autonomy: ${autonomyLevel})`); console.log(` Backend: ${options.backend || "auto"}`); if (specText) { @@ -115,15 +115,15 @@ export function createInitCommand(): Command { } } - console.log("\nConfiguration saved to .ci/config.json"); + console.log("\nConfiguration saved to .ciagent/config.json"); console.log("\nNext steps:"); - console.log(" ci run --all # Run full pipeline"); - console.log(" ci run research # Run specific phase"); + console.log(" ciagent run --all # Run full pipeline"); + console.log(" ciagent run research # Run specific phase"); console.log(" ci status # Check project status"); }); } -async function resolveBackendForCommand(config: CIConfig, overrideBackend?: string): Promise<{ backend: import("../backends/types.js").IntelligenceBackend | undefined; error?: string }> { +async function resolveBackendForCommand(config: CIAgentConfig, overrideBackend?: string): Promise<{ backend: import("../backends/types.js").IntelligenceBackend | undefined; error?: string }> { const backendConfig = { ...config.backend }; if (overrideBackend) { backendConfig.provider = overrideBackend as typeof backendConfig.provider; @@ -168,8 +168,8 @@ export function createRunCommand(): Command { .action(async (phase, options) => { const projectPath = process.cwd(); - if (!isCIInitialized(projectPath)) { - console.error("CI project not initialized. Run 'ci init' first."); + if (!isCIAgentInitialized(projectPath)) { + console.error("CIAgent project not initialized. Run 'ciagent init' first."); process.exit(1); } @@ -187,7 +187,7 @@ export function createRunCommand(): Command { phase: parseInt(options.phase) || 1, stage: phase || "all", specification: "", - config_path: path.join(projectPath, ".ci", "config.json"), + config_path: path.join(projectPath, ".ciagent", "config.json"), backend, }; @@ -196,7 +196,7 @@ export function createRunCommand(): Command { context.specification = spec.raw_content; } - console.log(`Running CI pipeline...`); + console.log(`Running CIAgent pipeline...`); if (options.all) { console.log(" Mode: Full pipeline (all phases)"); } else { @@ -226,16 +226,16 @@ export function createQuickCommand(): Command { const projectPath = process.cwd(); console.log(`Quick task: ${description}`); - if (!isCIInitialized(projectPath)) { - const config = initCI(projectPath); - console.log("Initialized temporary CI project"); + if (!isCIAgentInitialized(projectPath)) { + const config = initCIAgent(projectPath); + console.log("Initialized temporary CIAgent project"); } const config = loadConfig(projectPath); const { backend, error: backendError } = await resolveBackendForCommand(config, options.backend); if (!backend) { - console.error(`\n✗ "ci quick" requires an intelligence backend.`); + console.error(`\n✗ "ciagent quick" requires an intelligence backend.`); if (backendError) console.error(` ${backendError}`); process.exit(1); } @@ -249,7 +249,7 @@ export function createQuickCommand(): Command { phase: 0, stage: "all", specification: description, - config_path: path.join(projectPath, ".ci", "config.json"), + config_path: path.join(projectPath, ".ciagent", "config.json"), backend, }; @@ -274,8 +274,8 @@ export function createDebugCommand(): Command { .action(async (description, options) => { const projectPath = process.cwd(); - if (!isCIInitialized(projectPath)) { - console.error("CI project not initialized. Run 'ci init' first."); + if (!isCIAgentInitialized(projectPath)) { + console.error("CIAgent project not initialized. Run 'ciagent init' first."); process.exit(1); } @@ -283,7 +283,7 @@ export function createDebugCommand(): Command { const { backend, error: backendError } = await resolveBackendForCommand(config, options.backend); if (!backend) { - console.error(`\n✗ "ci debug" requires an intelligence backend.`); + console.error(`\n✗ "ciagent debug" requires an intelligence backend.`); if (backendError) console.error(` ${backendError}`); process.exit(1); } @@ -300,7 +300,7 @@ export function createDebugCommand(): Command { phase: 0, stage: "debug", specification: description || "", - config_path: path.join(projectPath, ".ci", "config.json"), + config_path: path.join(projectPath, ".ciagent", "config.json"), backend, }; @@ -324,8 +324,8 @@ export function createVerifyCommand(): Command { .action(async (phase, options) => { const projectPath = process.cwd(); - if (!isCIInitialized(projectPath)) { - console.error("CI project not initialized. Run 'ci init' first."); + if (!isCIAgentInitialized(projectPath)) { + console.error("CIAgent project not initialized. Run 'ciagent init' first."); process.exit(1); } @@ -371,8 +371,8 @@ export function createReviewCommand(): Command { .action(async (phase, options) => { const projectPath = process.cwd(); - if (!isCIInitialized(projectPath)) { - console.error("CI project not initialized. Run 'ci init' first."); + if (!isCIAgentInitialized(projectPath)) { + console.error("CIAgent project not initialized. Run 'ciagent init' first."); process.exit(1); } @@ -380,7 +380,7 @@ export function createReviewCommand(): Command { const { backend, error: backendError } = await resolveBackendForCommand(config, options.backend); if (!backend) { - console.error(`\n✗ "ci review" requires an intelligence backend.`); + console.error(`\n✗ "ciagent review" requires an intelligence backend.`); if (backendError) console.error(` ${backendError}`); process.exit(1); } @@ -394,7 +394,7 @@ export function createReviewCommand(): Command { phase: phaseNum, stage: "review", specification: "", - config_path: path.join(projectPath, ".ci", "config.json"), + config_path: path.join(projectPath, ".ciagent", "config.json"), backend, }; @@ -415,16 +415,16 @@ export function createStatusCommand(): Command { .action(() => { const projectPath = process.cwd(); - if (!isCIInitialized(projectPath)) { - console.log("CI project not initialized in this directory."); - console.log("Run 'ci init' to get started."); + if (!isCIAgentInitialized(projectPath)) { + console.log("CIAgent project not initialized in this directory."); + console.log("Run 'ciagent init' to get started."); return; } const config = loadConfig(projectPath); const artifacts = new ArtifactManager(projectPath); - console.log("─── CI Project Status ───"); + console.log("─── CIAgent Project Status ───"); console.log(`\nAutonomy: ${config.autonomy.level}`); console.log(`Model Profile: ${config.model_profile}`); console.log(`Backend: ${config.backend?.provider || "auto"}`); @@ -444,7 +444,7 @@ export function createStatusCommand(): Command { console.log(` ${icon} ${stage}`); } } else { - console.log("\nNo pipeline state found. Run 'ci run --all' to start."); + console.log("\nNo pipeline state found. Run 'ciagent run --all' to start."); } const summary = getAuditSummary(projectPath); @@ -464,15 +464,15 @@ export function createAuditCommand(): Command { .action((options) => { const projectPath = process.cwd(); - if (!isCIInitialized(projectPath)) { - console.error("CI project not initialized. Run 'ci init' first."); + if (!isCIAgentInitialized(projectPath)) { + console.error("CIAgent project not initialized. Run 'ciagent init' first."); process.exit(1); } const phase = options.phase ? parseInt(options.phase) : undefined; const summary = getAuditSummary(projectPath); - console.log("─── CI Audit Report ───"); + console.log("─── CIAgent Audit Report ───"); console.log(`\nTotal Decisions: ${summary.total_decisions}`); console.log(`Total Escalations: ${summary.total_escalations}`); console.log(`Phases Audited: ${summary.phases.join(", ") || "none"}`); @@ -517,8 +517,8 @@ export function createClarifyCommand(): Command { .action(async (options) => { const projectPath = process.cwd(); - if (!isCIInitialized(projectPath)) { - console.error("CI project not initialized. Run 'ci init' first."); + if (!isCIAgentInitialized(projectPath)) { + console.error("CIAgent project not initialized. Run 'ciagent init' first."); process.exit(1); } @@ -526,7 +526,7 @@ export function createClarifyCommand(): Command { const spec = loadSpec(projectPath); if (!spec) { - console.error("No specification found. Run 'ci init' first."); + console.error("No specification found. Run 'ciagent init' first."); process.exit(1); } @@ -557,8 +557,8 @@ export function createRollbackCommand(): Command { .action(async (target, options) => { const projectPath = process.cwd(); - if (!isCIInitialized(projectPath)) { - console.error("CI project not initialized. Run 'ci init' first."); + if (!isCIAgentInitialized(projectPath)) { + console.error("CIAgent project not initialized. Run 'ciagent init' first."); process.exit(1); } @@ -650,8 +650,8 @@ export function createShipCommand(): Command { .action(async (phase, options) => { const projectPath = process.cwd(); - if (!isCIInitialized(projectPath)) { - console.error("CI project not initialized. Run 'ci init' first."); + if (!isCIAgentInitialized(projectPath)) { + console.error("CIAgent project not initialized. Run 'ciagent init' first."); process.exit(1); } @@ -707,7 +707,7 @@ export function createShipCommand(): Command { cwd: projectPath, stdio: "pipe", }); - execSync(`git tag -a ${version.tag} -m "CI: Phase ${phaseNum} shipped"`, { + execSync(`git tag -a ${version.tag} -m "CIAgent: Phase ${phaseNum} shipped"`, { cwd: projectPath, stdio: "pipe", }); @@ -730,7 +730,7 @@ export function createShipCommand(): Command { function computeShipVersion( projectPath: string, phaseNum: number, - config: CIConfig + config: CIAgentConfig ): { tag: string; milestoneType: "nfr" | "feature" | "schema-breaking" } { const tags = execSync("git tag -l", { cwd: projectPath, encoding: "utf-8" }) .split("\n") diff --git a/src/cli/index.ts b/src/cli/index.ts index d42f701..90c095f 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -19,8 +19,8 @@ import { const program = new Command(); program - .name("ci") - .description("CI — Continuous Intelligence: autonomous AI-driven software engineering harness") + .name("ciagent") + .description("CIAgent — Continuous Intelligence: autonomous AI-driven software engineering harness") .version(VERSION) .addCommand(createInitCommand()) .addCommand(createRunCommand()) diff --git a/src/core/artifacts.test.ts b/src/core/artifacts.test.ts index ee8a79f..f5e8075 100644 --- a/src/core/artifacts.test.ts +++ b/src/core/artifacts.test.ts @@ -8,7 +8,7 @@ describe("ArtifactManager", () => { let manager: ArtifactManager; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-artifact-test-")); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-artifact-test-")); manager = new ArtifactManager(tempDir); }); @@ -17,16 +17,16 @@ describe("ArtifactManager", () => { }); describe("ensureStructure", () => { - it("creates .ci directory structure", () => { + it("creates .ciagent directory structure", () => { manager.ensureStructure(); - expect(fs.existsSync(path.join(tempDir, ".ci"))).toBe(true); - expect(fs.existsSync(path.join(tempDir, ".ci", "audit"))).toBe(true); + expect(fs.existsSync(path.join(tempDir, ".ciagent"))).toBe(true); + expect(fs.existsSync(path.join(tempDir, ".ciagent", "audit"))).toBe(true); }); it("is idempotent", () => { manager.ensureStructure(); manager.ensureStructure(); - expect(fs.existsSync(path.join(tempDir, ".ci"))).toBe(true); + expect(fs.existsSync(path.join(tempDir, ".ciagent"))).toBe(true); }); }); @@ -67,7 +67,7 @@ describe("ArtifactManager", () => { manager.writeProject(manifest); - const projectPath = path.join(tempDir, ".ci", "PROJECT.md"); + 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"); @@ -131,7 +131,7 @@ describe("ArtifactManager", () => { ], }); - const decisionsPath = path.join(tempDir, ".ci", "DECISIONS.md"); + 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"); diff --git a/src/core/artifacts.ts b/src/core/artifacts.ts index ee1a7bf..47f0672 100644 --- a/src/core/artifacts.ts +++ b/src/core/artifacts.ts @@ -2,7 +2,7 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { writeFile, readFile, ensureDir } from "../utils/file.js"; -const CI_DIR = ".ci"; +const CI_DIR = ".ciagent"; export interface ProjectManifest { name: string; diff --git a/src/core/audit.test.ts b/src/core/audit.test.ts index 43883fe..b97bcb7 100644 --- a/src/core/audit.test.ts +++ b/src/core/audit.test.ts @@ -9,8 +9,8 @@ describe("Audit", () => { let tempDir: string; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-audit-test-")); - fs.mkdirSync(path.join(tempDir, ".ci", "audit"), { recursive: true }); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-audit-test-")); + fs.mkdirSync(path.join(tempDir, ".ciagent", "audit"), { recursive: true }); }); afterEach(() => { @@ -40,7 +40,7 @@ describe("Audit", () => { ], default_option_id: "A", resolution: "pending", - audit_file: ".ci/audit/test.json", + audit_file: ".ciagent/audit/test.json", }; describe("logDecision", () => { diff --git a/src/core/audit.ts b/src/core/audit.ts index db46f73..5050abc 100644 --- a/src/core/audit.ts +++ b/src/core/audit.ts @@ -12,7 +12,7 @@ export interface AuditEntry { const AUDIT_DIR = "audit"; function getAuditDir(projectPath: string): string { - return path.join(projectPath, ".ci", AUDIT_DIR); + return path.join(projectPath, ".ciagent", AUDIT_DIR); } function getAuditFilePath(projectPath: string, phase: number): string { diff --git a/src/core/ci-files.test.ts b/src/core/ci-files.test.ts index e4ea628..31c0af4 100644 --- a/src/core/ci-files.test.ts +++ b/src/core/ci-files.test.ts @@ -1,17 +1,17 @@ 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"; +import { CIAgentFiles, ProjectMd, RoadmapMd, RequirementsMd, ArchitectureMd } from "../core/ciagent-files.js"; function createTempDir(): string { - return fs.mkdtempSync(path.join(os.tmpdir(), "ci-files-test-")); + return fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-files-test-")); } function cleanup(dir: string): void { fs.rmSync(dir, { recursive: true, force: true }); } -describe("CiFiles", () => { +describe("CIAgentFiles", () => { let dir: string; beforeEach(() => { @@ -22,41 +22,41 @@ describe("CiFiles", () => { cleanup(dir); }); - describe("ensureCIDir", () => { - it("creates .ci directory", () => { - const ciFiles = new CiFiles(dir); + describe("ensureCIAgentDir", () => { + it("creates .ciagent directory", () => { + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - expect(fs.existsSync(path.join(dir, ".ci"))).toBe(true); + expect(fs.existsSync(path.join(dir, ".ciagent"))).toBe(true); }); }); describe("isInitialized", () => { it("returns false when no config.json exists", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); expect(ciFiles.isInitialized()).toBe(false); }); it("returns true when config.json exists", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), "{}"); + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), "{}"); expect(ciFiles.isInitialized()).toBe(true); }); }); describe("projectSlug", () => { it("defaults to empty string", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); expect(ciFiles.getProjectSlug()).toBe(""); }); it("uses provided project slug", () => { - const ciFiles = new CiFiles(dir, "task-api"); + const ciFiles = new CIAgentFiles(dir, "task-api"); expect(ciFiles.getProjectSlug()).toBe("task-api"); }); it("setProjectSlug updates slug", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.setProjectSlug("auth-svc"); expect(ciFiles.getProjectSlug()).toBe("auth-svc"); }); @@ -64,14 +64,14 @@ describe("CiFiles", () => { describe("multi-project support", () => { it("isMultiProject returns false when not initialized", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); expect(ciFiles.isMultiProject()).toBe(false); }); it("isMultiProject returns false for single-project config", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [{ slug: "default", name: "Default" }], active_project: "default", })); @@ -79,59 +79,59 @@ describe("CiFiles", () => { }); it("isMultiProject returns false for config without projects array", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({})); + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({})); expect(ciFiles.isMultiProject()).toBe(false); }); it("addProject adds a project to config", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [], active_project: "", })); ciFiles.addProject("task-api", "Task API", true); - const config = JSON.parse(fs.readFileSync(path.join(dir, ".ci", "config.json"), "utf-8")); + const config = JSON.parse(fs.readFileSync(path.join(dir, ".ciagent", "config.json"), "utf-8")); expect(config.projects).toHaveLength(1); expect(config.projects[0].slug).toBe("task-api"); expect(config.active_project).toBe("task-api"); }); it("addProject does not duplicate existing project", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [{ slug: "task-api", name: "Task API" }], active_project: "task-api", })); ciFiles.addProject("task-api", "Task API V2"); - const config = JSON.parse(fs.readFileSync(path.join(dir, ".ci", "config.json"), "utf-8")); + const config = JSON.parse(fs.readFileSync(path.join(dir, ".ciagent", "config.json"), "utf-8")); expect(config.projects).toHaveLength(1); }); it("addProject creates project subdirectory", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [], active_project: "", })); ciFiles.addProject("task-api", "Task API", true); - expect(fs.existsSync(path.join(dir, ".ci", "task-api"))).toBe(true); + expect(fs.existsSync(path.join(dir, ".ciagent", "task-api"))).toBe(true); }); it("getActiveProject returns from config", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [{ slug: "task-api", name: "Task API", default: true }], active_project: "task-api", })); @@ -140,9 +140,9 @@ describe("CiFiles", () => { }); it("setActiveProject updates config", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [ { slug: "task-api", name: "Task API" }, { slug: "auth-svc", name: "Auth Service" }, @@ -152,14 +152,14 @@ describe("CiFiles", () => { ciFiles.setActiveProject("auth-svc"); - const config = JSON.parse(fs.readFileSync(path.join(dir, ".ci", "config.json"), "utf-8")); + const config = JSON.parse(fs.readFileSync(path.join(dir, ".ciagent", "config.json"), "utf-8")); expect(config.active_project).toBe("auth-svc"); }); it("listProjects returns projects from config", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [ { slug: "task-api", name: "Task API", default: true }, { slug: "auth-svc", name: "Auth Service" }, @@ -176,71 +176,71 @@ describe("CiFiles", () => { describe("needsMigration", () => { it("returns false when not initialized", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); expect(ciFiles.needsMigration()).toBe(false); }); it("returns false when already multi-project", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [{ slug: "default", name: "Default" }], })); - fs.writeFileSync(path.join(dir, ".ci", "PROJECT.md"), "# Test"); + fs.writeFileSync(path.join(dir, ".ciagent", "PROJECT.md"), "# Test"); expect(ciFiles.needsMigration()).toBe(false); }); it("returns true when flat files exist without subdirs or multi-project config", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({})); - fs.writeFileSync(path.join(dir, ".ci", "PROJECT.md"), "# Test"); + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({})); + fs.writeFileSync(path.join(dir, ".ciagent", "PROJECT.md"), "# Test"); expect(ciFiles.needsMigration()).toBe(true); }); it("returns false when flat files exist but subdirs also exist", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({})); - fs.writeFileSync(path.join(dir, ".ci", "PROJECT.md"), "# Test"); - fs.mkdirSync(path.join(dir, ".ci", "task-api")); - fs.writeFileSync(path.join(dir, ".ci", "task-api", "PROJECT.md"), "# Task API"); + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({})); + fs.writeFileSync(path.join(dir, ".ciagent", "PROJECT.md"), "# Test"); + fs.mkdirSync(path.join(dir, ".ciagent", "task-api")); + fs.writeFileSync(path.join(dir, ".ciagent", "task-api", "PROJECT.md"), "# Task API"); expect(ciFiles.needsMigration()).toBe(false); }); }); describe("migrateFlatToProject", () => { it("moves flat files to project subdirectory", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({})); - fs.writeFileSync(path.join(dir, ".ci", "PROJECT.md"), "# Test Project"); - fs.writeFileSync(path.join(dir, ".ci", "ARCHITECTURE.md"), "# Architecture"); - fs.writeFileSync(path.join(dir, ".ci", "ROADMAP.md"), "# Roadmap"); - fs.writeFileSync(path.join(dir, ".ci", "REQUIREMENTS.md"), "# Requirements"); + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({})); + fs.writeFileSync(path.join(dir, ".ciagent", "PROJECT.md"), "# Test Project"); + fs.writeFileSync(path.join(dir, ".ciagent", "ARCHITECTURE.md"), "# Architecture"); + fs.writeFileSync(path.join(dir, ".ciagent", "ROADMAP.md"), "# Roadmap"); + fs.writeFileSync(path.join(dir, ".ciagent", "REQUIREMENTS.md"), "# Requirements"); ciFiles.migrateFlatToProject("my-app"); - expect(fs.existsSync(path.join(dir, ".ci", "my-app", "PROJECT.md"))).toBe(true); - expect(fs.existsSync(path.join(dir, ".ci", "my-app", "ARCHITECTURE.md"))).toBe(true); - expect(fs.existsSync(path.join(dir, ".ci", "my-app", "ROADMAP.md"))).toBe(true); - expect(fs.existsSync(path.join(dir, ".ci", "my-app", "REQUIREMENTS.md"))).toBe(true); + expect(fs.existsSync(path.join(dir, ".ciagent", "my-app", "PROJECT.md"))).toBe(true); + expect(fs.existsSync(path.join(dir, ".ciagent", "my-app", "ARCHITECTURE.md"))).toBe(true); + expect(fs.existsSync(path.join(dir, ".ciagent", "my-app", "ROADMAP.md"))).toBe(true); + expect(fs.existsSync(path.join(dir, ".ciagent", "my-app", "REQUIREMENTS.md"))).toBe(true); - const config = JSON.parse(fs.readFileSync(path.join(dir, ".ci", "config.json"), "utf-8")); + const config = JSON.parse(fs.readFileSync(path.join(dir, ".ciagent", "config.json"), "utf-8")); expect(config.projects).toHaveLength(1); expect(config.active_project).toBe("my-app"); }); it("does not migrate when not needed", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [{ slug: "existing", name: "Existing" }], })); ciFiles.migrateFlatToProject("new-proj"); - const config = JSON.parse(fs.readFileSync(path.join(dir, ".ci", "config.json"), "utf-8")); + const config = JSON.parse(fs.readFileSync(path.join(dir, ".ciagent", "config.json"), "utf-8")); expect(config.projects).toHaveLength(1); expect(config.projects[0].slug).toBe("existing"); }); @@ -248,14 +248,14 @@ describe("CiFiles", () => { describe("isNfrMilestone", () => { it("returns true when no roadmap exists", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); expect(ciFiles.isNfrMilestone()).toBe(true); }); it("returns true when phases are all NFR types", () => { - const ciFiles = new CiFiles(dir, "nfr-proj"); + const ciFiles = new CIAgentFiles(dir, "nfr-proj"); ciFiles.ensureProjectDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [{ slug: "nfr-proj", name: "NFR Project", default: true }], active_project: "nfr-proj", })); @@ -271,9 +271,9 @@ describe("CiFiles", () => { }); it("returns false when phases include feature work", () => { - const ciFiles = new CiFiles(dir, "feat-proj"); + const ciFiles = new CIAgentFiles(dir, "feat-proj"); ciFiles.ensureProjectDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [{ slug: "feat-proj", name: "Feature Project", default: true }], active_project: "feat-proj", })); @@ -291,14 +291,14 @@ describe("CiFiles", () => { describe("getMilestoneType", () => { it("returns nfr when no roadmap exists", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); expect(ciFiles.getMilestoneType()).toBe("nfr"); }); it("returns nfr when phases are all NFR types", () => { - const ciFiles = new CiFiles(dir, "nfr-proj2"); + const ciFiles = new CIAgentFiles(dir, "nfr-proj2"); ciFiles.ensureProjectDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [{ slug: "nfr-proj2", name: "NFR Project 2", default: true }], active_project: "nfr-proj2", })); @@ -313,9 +313,9 @@ describe("CiFiles", () => { }); it("returns feature when phases include feat work", () => { - const ciFiles = new CiFiles(dir, "feat-proj2"); + const ciFiles = new CIAgentFiles(dir, "feat-proj2"); ciFiles.ensureProjectDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [{ slug: "feat-proj2", name: "Feature Project 2", default: true }], active_project: "feat-proj2", })); @@ -330,9 +330,9 @@ describe("CiFiles", () => { }); it("returns schema-breaking when phases include refactor/rewrite/migrate", () => { - const ciFiles = new CiFiles(dir, "schema-proj"); + const ciFiles = new CIAgentFiles(dir, "schema-proj"); ciFiles.ensureProjectDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [{ slug: "schema-proj", name: "Schema Project", default: true }], active_project: "schema-proj", })); @@ -349,9 +349,9 @@ describe("CiFiles", () => { describe("multi-project file paths", () => { it("writes PROJECT.md to project subdirectory when slug is set", () => { - const ciFiles = new CiFiles(dir, "my-app"); + const ciFiles = new CIAgentFiles(dir, "my-app"); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({ + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({ projects: [{ slug: "my-app", name: "My App", default: true }], active_project: "my-app", })); @@ -367,13 +367,13 @@ describe("CiFiles", () => { ciFiles.writeProjectMd(project, "initial"); - expect(fs.existsSync(path.join(dir, ".ci", "my-app", "PROJECT.md"))).toBe(true); + expect(fs.existsSync(path.join(dir, ".ciagent", "my-app", "PROJECT.md"))).toBe(true); }); it("writes PROJECT.md to .ci root when no slug is set", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.ensureCIDir(); - fs.writeFileSync(path.join(dir, ".ci", "config.json"), JSON.stringify({})); + fs.writeFileSync(path.join(dir, ".ciagent", "config.json"), JSON.stringify({})); const project: ProjectMd = { name: "Default App", @@ -386,7 +386,7 @@ describe("CiFiles", () => { ciFiles.writeProjectMd(project, "initial"); - expect(fs.existsSync(path.join(dir, ".ci", "PROJECT.md"))).toBe(true); + expect(fs.existsSync(path.join(dir, ".ciagent", "PROJECT.md"))).toBe(true); }); }); @@ -407,7 +407,7 @@ describe("CiFiles", () => { }; it("writes and reads PROJECT.md", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.writeProjectMd(project, "initial creation"); const read = ciFiles.readProjectMd(); @@ -418,7 +418,7 @@ describe("CiFiles", () => { }); it("overwrites PROJECT.md on update", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.writeProjectMd(project, "initial"); const updated = { ...project, coreValue: "Updated description" }; @@ -455,7 +455,7 @@ describe("CiFiles", () => { }; it("writes and reads ROADMAP.md", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.writeRoadmapMd(roadmap); const read = ciFiles.readRoadmapMd(); @@ -489,7 +489,7 @@ describe("CiFiles", () => { }; it("writes and reads REQUIREMENTS.md", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.writeRequirementsMd(requirements); const read = ciFiles.readRequirementsMd(); @@ -497,7 +497,7 @@ describe("CiFiles", () => { }); it("updates requirement status", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.writeRequirementsMd(requirements); ciFiles.updateRequirementStatus("AUTH-01", "complete"); @@ -523,7 +523,7 @@ describe("CiFiles", () => { }; it("writes and reads ARCHITECTURE.md", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); ciFiles.writeArchitectureMd(arch); const read = ciFiles.readArchitectureMd(); @@ -534,7 +534,7 @@ describe("CiFiles", () => { describe("updatePhaseStatus", () => { it("updates phase status in roadmap", () => { - const ciFiles = new CiFiles(dir); + const ciFiles = new CIAgentFiles(dir); const roadmap: RoadmapMd = { overview: "test", phases: [ diff --git a/src/core/ci-files.ts b/src/core/ciagent-files.ts similarity index 99% rename from src/core/ci-files.ts rename to src/core/ciagent-files.ts index 38f5017..73614c5 100644 --- a/src/core/ci-files.ts +++ b/src/core/ciagent-files.ts @@ -4,7 +4,7 @@ import { writeFile, readFile, ensureDir, fileExists } from "../utils/file.js"; import { PipelineStage } from "../types/pipeline.js"; import { MilestoneType } from "../types/config.js"; -const CI_DIR = ".ci"; +const CI_DIR = ".ciagent"; export interface ProjectMd { name: string; @@ -71,7 +71,7 @@ export interface ProjectEntry { default?: boolean; } -export class CiFiles { +export class CIAgentFiles { private projectPath: string; private projectSlug: string; diff --git a/src/core/clarify.test.ts b/src/core/clarify.test.ts index f75a40f..a3dfefd 100644 --- a/src/core/clarify.test.ts +++ b/src/core/clarify.test.ts @@ -2,15 +2,15 @@ import * as fs from "node:fs"; import * as path from "node:path"; import * as os from "node:os"; import { ClarifyPhase, saveSpecification, loadSpecification } from "../core/clarify.js"; -import { DEFAULT_CI_CONFIG } from "../types/config.js"; +import { DEFAULT_CIAGENT_CONFIG } from "../types/config.js"; import { Specification, parseSpecification } from "../types/specification.js"; describe("ClarifyPhase", () => { let tempDir: string; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-clarify-test-")); - fs.mkdirSync(path.join(tempDir, ".ci"), { recursive: true }); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-clarify-test-")); + fs.mkdirSync(path.join(tempDir, ".ciagent"), { recursive: true }); }); afterEach(() => { @@ -41,7 +41,7 @@ describe("ClarifyPhase", () => { describe("generateQuestions", () => { it("generates questions for missing requirements", () => { - const clarify = new ClarifyPhase(DEFAULT_CI_CONFIG, tempDir); + const clarify = new ClarifyPhase(DEFAULT_CIAGENT_CONFIG, tempDir); const questions = clarify.generateQuestions(specWithoutRequirements); expect(questions.length).toBeGreaterThan(0); const reqQuestion = questions.find((q) => q.category === "requirements"); @@ -50,7 +50,7 @@ describe("ClarifyPhase", () => { }); it("generates questions for missing constraints", () => { - const clarify = new ClarifyPhase(DEFAULT_CI_CONFIG, tempDir); + const clarify = new ClarifyPhase(DEFAULT_CIAGENT_CONFIG, tempDir); const questions = clarify.generateQuestions(specWithoutRequirements); const constraintQuestion = questions.find((q) => q.category === "constraints"); expect(constraintQuestion).toBeDefined(); @@ -58,7 +58,7 @@ describe("ClarifyPhase", () => { }); it("generates deployment question when deploy is mentioned without deploy constraint", () => { - const clarify = new ClarifyPhase(DEFAULT_CI_CONFIG, tempDir); + const clarify = new ClarifyPhase(DEFAULT_CIAGENT_CONFIG, tempDir); const questions = clarify.generateQuestions(specWithRequirements); const deployQuestion = questions.find((q) => q.category === "deployment"); expect(deployQuestion).toBeDefined(); @@ -66,8 +66,8 @@ describe("ClarifyPhase", () => { it("respects clarify_budget", () => { const limitedConfig = { - ...DEFAULT_CI_CONFIG, - autonomy: { ...DEFAULT_CI_CONFIG.autonomy, clarify_budget: 1 }, + ...DEFAULT_CIAGENT_CONFIG, + autonomy: { ...DEFAULT_CIAGENT_CONFIG.autonomy, clarify_budget: 1 }, }; const clarify = new ClarifyPhase(limitedConfig, tempDir); const questions = clarify.generateQuestions(specWithoutRequirements); @@ -75,7 +75,7 @@ describe("ClarifyPhase", () => { }); it("assigns sequential question IDs", () => { - const clarify = new ClarifyPhase(DEFAULT_CI_CONFIG, tempDir); + const clarify = new ClarifyPhase(DEFAULT_CIAGENT_CONFIG, tempDir); const questions = clarify.generateQuestions(specWithoutRequirements); for (let i = 0; i < questions.length; i++) { expect(questions[i].id).toBe(`Q-${String(i + 1).padStart(3, "0")}`); @@ -83,7 +83,7 @@ describe("ClarifyPhase", () => { }); it("sorts questions by impact priority", () => { - const clarify = new ClarifyPhase(DEFAULT_CI_CONFIG, tempDir); + const clarify = new ClarifyPhase(DEFAULT_CIAGENT_CONFIG, tempDir); const questions = clarify.generateQuestions(specWithoutRequirements); const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 }; for (let i = 1; i < questions.length; i++) { @@ -96,7 +96,7 @@ describe("ClarifyPhase", () => { describe("answerQuestion", () => { it("records an answer to a question", () => { - const clarify = new ClarifyPhase(DEFAULT_CI_CONFIG, tempDir); + const clarify = new ClarifyPhase(DEFAULT_CIAGENT_CONFIG, tempDir); const questions = clarify.generateQuestions(specWithoutRequirements); expect(questions.length).toBeGreaterThan(0); @@ -107,7 +107,7 @@ describe("ClarifyPhase", () => { }); it("returns null for unknown question ID", () => { - const clarify = new ClarifyPhase(DEFAULT_CI_CONFIG, tempDir); + const clarify = new ClarifyPhase(DEFAULT_CIAGENT_CONFIG, tempDir); const result = clarify.answerQuestion("Q-999", "answer"); expect(result).toBeNull(); }); @@ -115,7 +115,7 @@ describe("ClarifyPhase", () => { describe("acceptDefaults", () => { it("accepts defaults for all unanswered questions", () => { - const clarify = new ClarifyPhase(DEFAULT_CI_CONFIG, tempDir); + const clarify = new ClarifyPhase(DEFAULT_CIAGENT_CONFIG, tempDir); clarify.generateQuestions(specWithoutRequirements); const result = clarify.acceptDefaults(); @@ -125,7 +125,7 @@ describe("ClarifyPhase", () => { }); it("preserves manually answered questions", () => { - const clarify = new ClarifyPhase(DEFAULT_CI_CONFIG, tempDir); + const clarify = new ClarifyPhase(DEFAULT_CIAGENT_CONFIG, tempDir); const questions = clarify.generateQuestions(specWithoutRequirements); if (questions.length > 0) { clarify.answerQuestion(questions[0].id, "My answer"); @@ -138,11 +138,11 @@ describe("ClarifyPhase", () => { }); it("saves clarify responses file", () => { - const clarify = new ClarifyPhase(DEFAULT_CI_CONFIG, tempDir); + const clarify = new ClarifyPhase(DEFAULT_CIAGENT_CONFIG, tempDir); clarify.generateQuestions(specWithoutRequirements); clarify.acceptDefaults(); - const responsesPath = path.join(tempDir, ".ci", "clarify-responses.md"); + const responsesPath = path.join(tempDir, ".ciagent", "clarify-responses.md"); expect(fs.existsSync(responsesPath)).toBe(true); const content = fs.readFileSync(responsesPath, "utf-8"); expect(content).toContain("Clarify Phase Responses"); @@ -154,8 +154,8 @@ describe("saveSpecification / loadSpecification", () => { let tempDir: string; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-spec-test-")); - fs.mkdirSync(path.join(tempDir, ".ci"), { recursive: true }); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-spec-test-")); + fs.mkdirSync(path.join(tempDir, ".ciagent"), { recursive: true }); }); afterEach(() => { diff --git a/src/core/clarify.ts b/src/core/clarify.ts index 4b06ae7..f91a471 100644 --- a/src/core/clarify.ts +++ b/src/core/clarify.ts @@ -2,22 +2,22 @@ import * as fs from "node:fs"; import * as path from "node:path"; import { ClarifyQuestion, ClarifyResult } from "../types/clarify.js"; import { Specification, parseSpecification } from "../types/specification.js"; -import { CIConfig } from "../types/config.js"; +import { CIAgentConfig } from "../types/config.js"; const CLARIFY_RESPONSES_FILE = "clarify-responses.md"; const SPECIFICATION_FILE = "specification.md"; function getCIDir(projectPath: string): string { - return path.join(projectPath, ".ci"); + return path.join(projectPath, ".ciagent"); } export class ClarifyPhase { - private config: CIConfig; + private config: CIAgentConfig; private projectPath: string; private questions: ClarifyQuestion[]; private questionCounter: number; - constructor(config: CIConfig, projectPath: string) { + constructor(config: CIAgentConfig, projectPath: string) { this.config = config; this.projectPath = projectPath; this.questions = []; diff --git a/src/core/commit-builder.test.ts b/src/core/commit-builder.test.ts index 19978b1..31fabb5 100644 --- a/src/core/commit-builder.test.ts +++ b/src/core/commit-builder.test.ts @@ -1,11 +1,11 @@ import { CommitBuilder } from "../core/commit-builder.js"; -import { extractCiBlock, parseCiBlock } from "../core/commit-parser.js"; -import { CiMetadata } from "../types/commit-meta.js"; +import { extractCIAgentBlock, parseCIAgentBlock } from "../core/commit-parser.js"; +import { CIAgentMetadata } from "../types/commit-meta.js"; describe("CommitBuilder", () => { describe("buildCiBlock", () => { it("builds minimal ci block", () => { - const ci: CiMetadata = { phase: 1, milestone: "v1.0", status: "execute" }; + const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "execute" }; const block = CommitBuilder.buildCiBlock(ci); expect(block).toContain("phase: 1"); @@ -14,19 +14,19 @@ describe("CommitBuilder", () => { }); it("builds ci block with project", () => { - const ci: CiMetadata = { phase: 1, milestone: "v1.0", status: "execute", project: "task-api" }; + const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "execute", project: "task-api" }; const block = CommitBuilder.buildCiBlock(ci); expect(block).toContain("project: task-api"); }); it("builds ci block without project when not set", () => { - const ci: CiMetadata = { phase: 1, milestone: "v1.0", status: "execute" }; + const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "execute" }; const block = CommitBuilder.buildCiBlock(ci); expect(block).not.toContain("project:"); }); it("builds ci block with decisions", () => { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "execute", @@ -49,7 +49,7 @@ describe("CommitBuilder", () => { }); it("builds ci block with lessons", () => { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "complete", @@ -63,7 +63,7 @@ describe("CommitBuilder", () => { }); it("builds ci block with compound", () => { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "complete", @@ -82,7 +82,7 @@ describe("CommitBuilder", () => { }); it("builds ci block with escalations", () => { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: 3, milestone: "v1.0", status: "execute", @@ -103,7 +103,7 @@ describe("CommitBuilder", () => { }); it("builds ci block with requirements", () => { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "complete", @@ -122,12 +122,12 @@ describe("CommitBuilder", () => { describe("round-trip: build then parse", () => { it("round-trips a simple ci block", () => { - const ci: CiMetadata = { phase: 1, milestone: "v1.0", status: "execute" }; + const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "execute" }; const block = CommitBuilder.buildCiBlock(ci); const fullMessage = `feat(P01): test\n\n---ci---\n${block}\n---/ci---\n\nBody text`; - const extracted = extractCiBlock(fullMessage)!; - const parsed = parseCiBlock(extracted)!; + const extracted = extractCIAgentBlock(fullMessage)!; + const parsed = parseCIAgentBlock(extracted)!; expect(parsed.phase).toBe(1); expect(parsed.milestone).toBe("v1.0"); @@ -135,7 +135,7 @@ describe("CommitBuilder", () => { }); it("round-trips decisions", () => { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "execute", @@ -152,8 +152,8 @@ describe("CommitBuilder", () => { const block = CommitBuilder.buildCiBlock(ci); const fullMessage = `feat(P01): test\n\n---ci---\n${block}\n---/ci---`; - const extracted = extractCiBlock(fullMessage)!; - const parsed = parseCiBlock(extracted)!; + const extracted = extractCIAgentBlock(fullMessage)!; + const parsed = parseCIAgentBlock(extracted)!; expect(parsed.decisions).toHaveLength(1); expect(parsed.decisions![0].id).toBe("D-001"); @@ -163,7 +163,7 @@ describe("CommitBuilder", () => { }); it("round-trips compound with lessons", () => { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: 2, milestone: "v1.0", status: "complete", @@ -177,8 +177,8 @@ describe("CommitBuilder", () => { const block = CommitBuilder.buildCiBlock(ci); const fullMessage = `compound(P02): test\n\n---ci---\n${block}\n---/ci---`; - const extracted = extractCiBlock(fullMessage)!; - const parsed = parseCiBlock(extracted)!; + const extracted = extractCIAgentBlock(fullMessage)!; + const parsed = parseCIAgentBlock(extracted)!; expect(parsed.compound!.category).toBe("auth"); expect(parsed.compound!.problem).toBe("Token replay attacks"); @@ -186,11 +186,11 @@ describe("CommitBuilder", () => { }); it("round-trips project field", () => { - const ci: CiMetadata = { phase: 1, milestone: "v1.0", status: "execute", project: "task-api" }; + const ci: CIAgentMetadata = { phase: 1, milestone: "v1.0", status: "execute", project: "task-api" }; const block = CommitBuilder.buildCiBlock(ci); const fullMessage = `feat(task-api/P01): test\n\n---ci---\n${block}\n---/ci---`; - const extracted = extractCiBlock(fullMessage)!; - const parsed = parseCiBlock(extracted)!; + const extracted = extractCIAgentBlock(fullMessage)!; + const parsed = parseCIAgentBlock(extracted)!; expect(parsed.project).toBe("task-api"); }); diff --git a/src/core/commit-builder.ts b/src/core/commit-builder.ts index 77e326e..de0584c 100644 --- a/src/core/commit-builder.ts +++ b/src/core/commit-builder.ts @@ -1,5 +1,5 @@ import { - CiMetadata, + CIAgentMetadata, CommitType, CommitScope, CommitDecision, @@ -17,7 +17,7 @@ export interface CommitMessageInput { type: CommitType; scope: CommitScope; subject: string; - ci: CiMetadata; + ci: CIAgentMetadata; body?: string; } @@ -92,7 +92,7 @@ export interface VerifyCommitInput { } export class CommitBuilder { - static buildCiBlock(ci: CiMetadata): string { + static buildCiBlock(ci: CIAgentMetadata): string { const lines: string[] = []; lines.push(`phase: ${ci.phase}`); lines.push(`milestone: ${ci.milestone}`); @@ -162,7 +162,7 @@ export class CommitBuilder { } static buildInitCommit(input: InitCommitInput): string { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: 0, milestone: input.milestone, project: input.project, @@ -194,7 +194,7 @@ export class CommitBuilder { } static buildTaskCommit(input: TaskCommitInput): string { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: input.phase, milestone: input.milestone, project: input.project, @@ -224,7 +224,7 @@ export class CommitBuilder { } static buildPhaseCompletionCommit(input: PhaseCompletionInput): string { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: input.phase, milestone: input.milestone, status: "complete", @@ -253,7 +253,7 @@ export class CommitBuilder { } static buildDecisionCommit(input: DecisionCommitInput): string { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: input.phase, milestone: input.milestone, status: "plan", @@ -271,7 +271,7 @@ export class CommitBuilder { } static buildEscalationCommit(input: EscalationCommitInput): string { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: input.phase, milestone: input.milestone, status: "execute", @@ -289,7 +289,7 @@ export class CommitBuilder { } static buildCompoundCommit(input: CompoundCommitInput): string { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: input.phase, milestone: input.milestone, status: "complete", @@ -313,7 +313,7 @@ export class CommitBuilder { } static buildVerifyCommit(input: VerifyCommitInput): string { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase: input.phase, milestone: input.milestone, status: "verify", @@ -338,7 +338,7 @@ export class CommitBuilder { findings: string[], decisions?: CommitDecision[] ): string { - const ci: CiMetadata = { + const ci: CIAgentMetadata = { phase, milestone, status: "research", diff --git a/src/core/commit-parser.test.ts b/src/core/commit-parser.test.ts index 91382dc..5d086a2 100644 --- a/src/core/commit-parser.test.ts +++ b/src/core/commit-parser.test.ts @@ -1,5 +1,5 @@ import { - CiMetadata, + CIAgentMetadata, CommitDecision, CommitEscalation, CommitRequirements, @@ -9,8 +9,8 @@ import { CommitScope, } from "../types/commit-meta.js"; import { - extractCiBlock, - parseCiBlock, + extractCIAgentBlock, + parseCIAgentBlock, parseCommitMessage, } from "./commit-parser.js"; @@ -128,29 +128,29 @@ status: execute Registration endpoint for task-api project.`; -describe("extractCiBlock", () => { +describe("extractCIAgentBlock", () => { it("extracts ---ci--- block from commit message", () => { - const block = extractCiBlock(SAMPLE_INIT_COMMIT); + const block = extractCIAgentBlock(SAMPLE_INIT_COMMIT); expect(block).toBeTruthy(); expect(block).toContain("phase: 0"); expect(block).toContain("milestone: v1.0"); }); it("returns null when no ---ci--- block exists", () => { - const block = extractCiBlock("docs: some regular commit\n\nNo CI block here"); + const block = extractCIAgentBlock("docs: some regular commit\n\nNo CI block here"); expect(block).toBeNull(); }); it("returns null for unclosed ---ci--- block", () => { - const block = extractCiBlock("docs: bad\n---ci---\nphase: 1\nno end marker"); + const block = extractCIAgentBlock("docs: bad\n---ci---\nphase: 1\nno end marker"); expect(block).toBeNull(); }); }); -describe("parseCiBlock", () => { +describe("parseCIAgentBlock", () => { it("parses init commit ci block", () => { - const block = extractCiBlock(SAMPLE_INIT_COMMIT)!; - const meta = parseCiBlock(block)!; + const block = extractCIAgentBlock(SAMPLE_INIT_COMMIT)!; + const meta = parseCIAgentBlock(block)!; expect(meta.phase).toBe(0); expect(meta.milestone).toBe("v1.0"); @@ -163,8 +163,8 @@ describe("parseCiBlock", () => { }); it("parses task commit ci block", () => { - const block = extractCiBlock(SAMPLE_TASK_COMMIT)!; - const meta = parseCiBlock(block)!; + const block = extractCIAgentBlock(SAMPLE_TASK_COMMIT)!; + const meta = parseCIAgentBlock(block)!; expect(meta.phase).toBe(1); expect(meta.plan).toBe("01-01"); @@ -177,8 +177,8 @@ describe("parseCiBlock", () => { }); it("parses phase completion with lessons", () => { - const block = extractCiBlock(SAMPLE_PHASE_COMPLETE_COMMIT)!; - const meta = parseCiBlock(block)!; + const block = extractCIAgentBlock(SAMPLE_PHASE_COMPLETE_COMMIT)!; + const meta = parseCIAgentBlock(block)!; expect(meta.phase).toBe(1); expect(meta.status).toBe("complete"); @@ -188,8 +188,8 @@ describe("parseCiBlock", () => { }); it("parses compound commit", () => { - const block = extractCiBlock(SAMPLE_COMPOUND_COMMIT)!; - const meta = parseCiBlock(block)!; + const block = extractCIAgentBlock(SAMPLE_COMPOUND_COMMIT)!; + const meta = parseCIAgentBlock(block)!; expect(meta.compound).toBeDefined(); expect(meta.compound!.category).toBe("auth"); @@ -199,8 +199,8 @@ describe("parseCiBlock", () => { }); it("parses escalation commit", () => { - const block = extractCiBlock(SAMPLE_ESCALATION_COMMIT)!; - const meta = parseCiBlock(block)!; + const block = extractCIAgentBlock(SAMPLE_ESCALATION_COMMIT)!; + const meta = parseCIAgentBlock(block)!; expect(meta.escalations).toHaveLength(1); expect(meta.escalations![0].id).toBe("E-001"); @@ -209,20 +209,20 @@ describe("parseCiBlock", () => { }); it("parses project field", () => { - const block = extractCiBlock(SAMPLE_PROJECT_COMMIT)!; - const meta = parseCiBlock(block)!; + const block = extractCIAgentBlock(SAMPLE_PROJECT_COMMIT)!; + const meta = parseCIAgentBlock(block)!; expect(meta.project).toBe("task-api"); expect(meta.phase).toBe(1); expect(meta.plan).toBe("01-01"); }); it("returns null for empty block", () => { - const meta = parseCiBlock(""); + const meta = parseCIAgentBlock(""); expect(meta).toBeNull(); }); it("returns null for block missing required fields", () => { - const meta = parseCiBlock("something: true\nother: false"); + const meta = parseCIAgentBlock("something: true\nother: false"); expect(meta).toBeNull(); }); }); diff --git a/src/core/commit-parser.ts b/src/core/commit-parser.ts index 35a792d..9975d10 100644 --- a/src/core/commit-parser.ts +++ b/src/core/commit-parser.ts @@ -1,8 +1,8 @@ import { - CiMetadata, + CIAgentMetadata, CommitType, CommitEscalation, - ParsedCiCommit, + ParsedCIAgentCommit, parseCommitType, parseCommitScope, } from "../types/commit-meta.js"; @@ -10,7 +10,7 @@ import { const CI_BLOCK_START = "---ci---"; const CI_BLOCK_END = "---/ci---"; -export function extractCiBlock(message: string): string | null { +export function extractCIAgentBlock(message: string): string | null { const startIdx = message.indexOf(CI_BLOCK_START); if (startIdx < 0) return null; @@ -20,10 +20,10 @@ export function extractCiBlock(message: string): string | null { return message.slice(startIdx + CI_BLOCK_START.length, endIdx).trim(); } -export function parseCiBlock(yaml: string): CiMetadata | null { +export function parseCIAgentBlock(yaml: string): CIAgentMetadata | null { if (!yaml) return null; - const result: Partial = {}; + const result: Partial = {}; const phaseMatch = yaml.match(/^phase:\s*(.+)$/m); if (phaseMatch) result.phase = parseInt(phaseMatch[1], 10) || 0; @@ -38,7 +38,7 @@ export function parseCiBlock(yaml: string): CiMetadata | null { if (taskMatch) result.task = taskMatch[1].trim(); const statusMatch = yaml.match(/^status:\s*(.+)$/m); - if (statusMatch) result.status = statusMatch[1].trim() as CiMetadata["status"]; + if (statusMatch) result.status = statusMatch[1].trim() as CIAgentMetadata["status"]; const projectMatch = yaml.match(/^project:\s*(.+)$/m); if (projectMatch) result.project = projectMatch[1].trim(); @@ -50,14 +50,14 @@ export function parseCiBlock(yaml: string): CiMetadata | null { result.compound = parseCompoundFromYaml(yaml); if (result.phase !== undefined && result.milestone !== undefined && result.status !== undefined) { - return result as CiMetadata; + return result as CIAgentMetadata; } return null; } -function parseDecisionsFromYaml(yaml: string): CiMetadata["decisions"] { - const decisions: NonNullable = []; +function parseDecisionsFromYaml(yaml: string): CIAgentMetadata["decisions"] { + const decisions: NonNullable = []; const decisionRegex = /- id: (.+)\n\s+decision: (.+)\n\s+rationale: (.+)\n\s+confidence: (.+)\n\s+alternatives: \[([^\]]*)\]/g; let match; @@ -74,8 +74,8 @@ function parseDecisionsFromYaml(yaml: string): CiMetadata["decisions"] { return decisions.length > 0 ? decisions : undefined; } -function parseEscalationsFromYaml(yaml: string): CiMetadata["escalations"] { - const escalations: NonNullable = []; +function parseEscalationsFromYaml(yaml: string): CIAgentMetadata["escalations"] { + const escalations: NonNullable = []; const escalationRegex = /- id: (.+)\n\s+type: (.+)\n\s+description: (.+)\n\s+resolution: (.+)/g; let match; @@ -91,7 +91,7 @@ function parseEscalationsFromYaml(yaml: string): CiMetadata["escalations"] { return escalations.length > 0 ? escalations : undefined; } -function parseRequirementsFromYaml(yaml: string): CiMetadata["requirements"] { +function parseRequirementsFromYaml(yaml: string): CIAgentMetadata["requirements"] { const coveredMatch = yaml.match(/^\s+covered: \[([^\]]*)\]/m); const partialMatch = yaml.match(/^\s+partial: \[([^\]]*)\]/m); @@ -106,7 +106,7 @@ function parseRequirementsFromYaml(yaml: string): CiMetadata["requirements"] { return { covered, partial }; } -function parseLessonsFromYaml(yaml: string): CiMetadata["lessons"] { +function parseLessonsFromYaml(yaml: string): CIAgentMetadata["lessons"] { const lessonRegex = /^ - (.+)$/gm; const lessons: string[] = []; let inLessonsSection = false; @@ -126,7 +126,7 @@ function parseLessonsFromYaml(yaml: string): CiMetadata["lessons"] { return lessons.length > 0 ? lessons : undefined; } -function parseCompoundFromYaml(yaml: string): CiMetadata["compound"] { +function parseCompoundFromYaml(yaml: string): CIAgentMetadata["compound"] { const categoryMatch = yaml.match(/^\s+category: (.+)$/m); const problemMatch = yaml.match(/^\s+problem: (.+)$/m); const solutionMatch = yaml.match(/^\s+solution: (.+)$/m); @@ -143,7 +143,7 @@ function parseCompoundFromYaml(yaml: string): CiMetadata["compound"] { export function parseCommitMessage( hash: string, message: string -): ParsedCiCommit { +): ParsedCIAgentCommit { const firstLine = message.split("\n")[0] || ""; const subjectMatch = firstLine.match(/^(\w+)(?:\(([^)]+)\))?: (.+)$/); @@ -157,8 +157,8 @@ export function parseCommitMessage( subject = subjectMatch[3] || firstLine; } - const ciBlock = extractCiBlock(message); - const ci = ciBlock ? parseCiBlock(ciBlock) : null; + const ciBlock = extractCIAgentBlock(message); + const ci = ciBlock ? parseCIAgentBlock(ciBlock) : null; const bodyStart = message.indexOf("\n"); let body = bodyStart >= 0 ? message.slice(bodyStart + 1).trim() : ""; diff --git a/src/core/config.test.ts b/src/core/config.test.ts index b11c8d3..611a012 100644 --- a/src/core/config.test.ts +++ b/src/core/config.test.ts @@ -1,45 +1,45 @@ import * as fs from "node:fs"; import * as path from "node:path"; import * as os from "node:os"; -import { initCI, loadConfig, saveConfig, isCIInitialized, ensureCIDir } from "../core/config.js"; -import { DEFAULT_CI_CONFIG } from "../types/config.js"; +import { initCIAgent, loadConfig, saveConfig, isCIAgentInitialized, ensureCIDir } from "../core/config.js"; +import { DEFAULT_CIAGENT_CONFIG } from "../types/config.js"; -describe("CI Config", () => { +describe("CIAgent Config", () => { let tempDir: string; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-config-test-")); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-config-test-")); }); afterEach(() => { fs.rmSync(tempDir, { recursive: true, force: true }); }); - describe("initCI", () => { - it("initializes a new CI project with default config", () => { - const config = initCI(tempDir); + describe("initCIAgent", () => { + it("initializes a new CIAgent project with default config", () => { + const config = initCIAgent(tempDir); expect(config.autonomy.level).toBe("full"); - expect(isCIInitialized(tempDir)).toBe(true); + expect(isCIAgentInitialized(tempDir)).toBe(true); }); it("initializes with custom config merged on top of defaults", () => { - const config = initCI(tempDir, { - autonomy: { ...DEFAULT_CI_CONFIG.autonomy, level: "guided" }, + const config = initCIAgent(tempDir, { + autonomy: { ...DEFAULT_CIAGENT_CONFIG.autonomy, level: "guided" }, }); expect(config.autonomy.level).toBe("guided"); expect(config.autonomy.clarify_budget).toBe(10); expect(config.model_profile).toBe("quality"); }); - it("creates .ci/ directory structure", () => { - initCI(tempDir); - expect(fs.existsSync(path.join(tempDir, ".ci"))).toBe(true); - expect(fs.existsSync(path.join(tempDir, ".ci", "config.json"))).toBe(true); + it("creates .ciagent/ directory structure", () => { + initCIAgent(tempDir); + expect(fs.existsSync(path.join(tempDir, ".ciagent"))).toBe(true); + expect(fs.existsSync(path.join(tempDir, ".ciagent", "config.json"))).toBe(true); }); it("deep merges nested config", () => { - const config = initCI(tempDir, { - autonomy: { ...DEFAULT_CI_CONFIG.autonomy, level: "supervised" }, + const config = initCIAgent(tempDir, { + autonomy: { ...DEFAULT_CIAGENT_CONFIG.autonomy, level: "supervised" }, }); expect(config.autonomy.level).toBe("supervised"); expect(config.autonomy.max_revision_iterations).toBe(3); @@ -47,7 +47,7 @@ describe("CI Config", () => { }); it("initializes with project slug", () => { - const config = initCI(tempDir, undefined, "task-api", "Task API"); + const config = initCIAgent(tempDir, undefined, "task-api", "Task API"); expect(config.projects).toHaveLength(1); expect(config.projects[0].slug).toBe("task-api"); expect(config.projects[0].name).toBe("Task API"); @@ -56,20 +56,20 @@ describe("CI Config", () => { }); it("does not re-add existing project slug", () => { - initCI(tempDir, undefined, "task-api", "Task API"); - const config = initCI(tempDir, undefined, "task-api", "Task API V2"); + initCIAgent(tempDir, undefined, "task-api", "Task API"); + const config = initCIAgent(tempDir, undefined, "task-api", "Task API V2"); expect(config.projects).toHaveLength(1); }); it("defaults projects and active_project when no slug provided", () => { - const config = initCI(tempDir); + const config = initCIAgent(tempDir); expect(config.projects).toEqual([]); expect(config.active_project).toBe(""); }); it("preserves existing projects when adding new one", () => { - const config1 = initCI(tempDir, undefined, "task-api", "Task API"); - const config2 = initCI(tempDir, { + const config1 = initCIAgent(tempDir, undefined, "task-api", "Task API"); + const config2 = initCIAgent(tempDir, { ...config1, projects: [...config1.projects, { slug: "auth-svc", name: "Auth Service" }], }, "auth-svc", "Auth Service"); @@ -81,11 +81,11 @@ describe("CI Config", () => { describe("loadConfig", () => { it("returns default config when no config file exists", () => { const config = loadConfig(tempDir); - expect(config).toEqual(DEFAULT_CI_CONFIG); + expect(config).toEqual(DEFAULT_CIAGENT_CONFIG); }); it("loads and deep merges config from file", () => { - initCI(tempDir, { autonomy: { ...DEFAULT_CI_CONFIG.autonomy, decision_confidence_threshold: 0.85 } }); + initCIAgent(tempDir, { autonomy: { ...DEFAULT_CIAGENT_CONFIG.autonomy, decision_confidence_threshold: 0.85 } }); const config = loadConfig(tempDir); expect(config.autonomy.decision_confidence_threshold).toBe(0.85); expect(config.autonomy.level).toBe("full"); @@ -93,7 +93,7 @@ describe("CI Config", () => { }); it("preserves nested objects that are not overridden", () => { - initCI(tempDir, { git: { ...DEFAULT_CI_CONFIG.git, auto_push: true } }); + initCIAgent(tempDir, { git: { ...DEFAULT_CIAGENT_CONFIG.git, auto_push: true } }); const config = loadConfig(tempDir); expect(config.git.auto_push).toBe(true); expect(config.git.auto_commit).toBe(true); @@ -101,7 +101,7 @@ describe("CI Config", () => { }); it("loads projects array from config", () => { - initCI(tempDir, undefined, "task-api", "Task API"); + initCIAgent(tempDir, undefined, "task-api", "Task API"); const config = loadConfig(tempDir); expect(config.projects).toHaveLength(1); expect(config.active_project).toBe("task-api"); @@ -112,8 +112,8 @@ describe("CI Config", () => { it("saves and reloads config correctly", () => { ensureCIDir(tempDir); const customConfig = { - ...DEFAULT_CI_CONFIG, - autonomy: { ...DEFAULT_CI_CONFIG.autonomy, level: "guided" as const }, + ...DEFAULT_CIAGENT_CONFIG, + autonomy: { ...DEFAULT_CIAGENT_CONFIG.autonomy, level: "guided" as const }, }; saveConfig(tempDir, customConfig); const loaded = loadConfig(tempDir); @@ -123,7 +123,7 @@ describe("CI Config", () => { it("saves and reloads config with projects", () => { ensureCIDir(tempDir); const config = { - ...DEFAULT_CI_CONFIG, + ...DEFAULT_CIAGENT_CONFIG, projects: [{ slug: "my-app", name: "My App", default: true }], active_project: "my-app", }; @@ -134,27 +134,27 @@ describe("CI Config", () => { }); }); - describe("isCIInitialized", () => { + describe("isCIAgentInitialized", () => { it("returns false for uninitialized directory", () => { - expect(isCIInitialized(tempDir)).toBe(false); + expect(isCIAgentInitialized(tempDir)).toBe(false); }); - it("returns true after initCI", () => { - initCI(tempDir); - expect(isCIInitialized(tempDir)).toBe(true); + it("returns true after initCIAgent", () => { + initCIAgent(tempDir); + expect(isCIAgentInitialized(tempDir)).toBe(true); }); }); describe("ensureCIDir", () => { - it("creates .ci directory", () => { + it("creates .ciagent directory", () => { ensureCIDir(tempDir); - expect(fs.existsSync(path.join(tempDir, ".ci"))).toBe(true); + expect(fs.existsSync(path.join(tempDir, ".ciagent"))).toBe(true); }); it("is idempotent", () => { ensureCIDir(tempDir); ensureCIDir(tempDir); - expect(fs.existsSync(path.join(tempDir, ".ci"))).toBe(true); + expect(fs.existsSync(path.join(tempDir, ".ciagent"))).toBe(true); }); }); }); \ No newline at end of file diff --git a/src/core/config.ts b/src/core/config.ts index 36298bb..d8db9be 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -1,11 +1,11 @@ import * as fs from "node:fs"; import * as path from "node:path"; -import { CIConfig, DEFAULT_CI_CONFIG } from "../types/config.js"; +import { CIAgentConfig, DEFAULT_CIAGENT_CONFIG } from "../types/config.js"; -const CI_DIR = ".ci"; +const CI_DIR = ".ciagent"; const CONFIG_FILE = "config.json"; -export function getCIConfigPath(projectPath: string): string { +export function getCIAgentConfigPath(projectPath: string): string { return path.join(projectPath, CI_DIR, CONFIG_FILE); } @@ -20,7 +20,7 @@ export function ensureCIDir(projectPath: string): void { } } -function deepMerge(base: CIConfig, override: Record): CIConfig { +function deepMerge(base: CIAgentConfig, override: Record): CIAgentConfig { const result = { ...base } as Record; for (const key of Object.keys(override)) { const baseVal = result[key]; @@ -30,43 +30,43 @@ function deepMerge(base: CIConfig, override: Record): CIConfig overrideVal && typeof overrideVal === "object" && !Array.isArray(overrideVal) ) { result[key] = deepMerge( - baseVal as unknown as CIConfig, + baseVal as unknown as CIAgentConfig, overrideVal as Record ) as unknown; } else if (overrideVal !== undefined) { result[key] = overrideVal; } } - return result as unknown as CIConfig; + return result as unknown as CIAgentConfig; } -export function loadConfig(projectPath: string): CIConfig { - const configPath = getCIConfigPath(projectPath); +export function loadConfig(projectPath: string): CIAgentConfig { + const configPath = getCIAgentConfigPath(projectPath); if (!fs.existsSync(configPath)) { - return { ...DEFAULT_CI_CONFIG }; + return { ...DEFAULT_CIAGENT_CONFIG }; } const raw = fs.readFileSync(configPath, "utf-8"); const parsed = JSON.parse(raw); - return deepMerge(DEFAULT_CI_CONFIG, parsed); + return deepMerge(DEFAULT_CIAGENT_CONFIG, parsed); } -export function saveConfig(projectPath: string, config: CIConfig): void { +export function saveConfig(projectPath: string, config: CIAgentConfig): void { ensureCIDir(projectPath); - const configPath = getCIConfigPath(projectPath); + const configPath = getCIAgentConfigPath(projectPath); fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8"); } -export function isCIInitialized(projectPath: string): boolean { +export function isCIAgentInitialized(projectPath: string): boolean { const ciDir = getCIDir(projectPath); - const configPath = getCIConfigPath(projectPath); + const configPath = getCIAgentConfigPath(projectPath); return fs.existsSync(ciDir) && fs.existsSync(configPath); } -export function initCI(projectPath: string, config?: Partial, projectSlug?: string, projectName?: string): CIConfig { +export function initCIAgent(projectPath: string, config?: Partial, projectSlug?: string, projectName?: string): CIAgentConfig { ensureCIDir(projectPath); - let projects = config?.projects || DEFAULT_CI_CONFIG.projects; - let activeProject = config?.active_project || DEFAULT_CI_CONFIG.active_project; + let projects = config?.projects || DEFAULT_CIAGENT_CONFIG.projects; + let activeProject = config?.active_project || DEFAULT_CIAGENT_CONFIG.active_project; if (projectSlug) { if (!projects.some((p) => p.slug === projectSlug)) { @@ -75,20 +75,20 @@ export function initCI(projectPath: string, config?: Partial, projectS activeProject = projectSlug; } - const fullConfig: CIConfig = { - ...DEFAULT_CI_CONFIG, + const fullConfig: CIAgentConfig = { + ...DEFAULT_CIAGENT_CONFIG, ...config, projects, active_project: activeProject, - autonomy: { ...DEFAULT_CI_CONFIG.autonomy, ...config?.autonomy }, + autonomy: { ...DEFAULT_CIAGENT_CONFIG.autonomy, ...config?.autonomy }, parallelization: { - ...DEFAULT_CI_CONFIG.parallelization, + ...DEFAULT_CIAGENT_CONFIG.parallelization, ...config?.parallelization, }, - verification: { ...DEFAULT_CI_CONFIG.verification, ...config?.verification }, - security: { ...DEFAULT_CI_CONFIG.security, ...config?.security }, - git: { ...DEFAULT_CI_CONFIG.git, ...config?.git }, - backend: { ...DEFAULT_CI_CONFIG.backend, ...config?.backend }, + verification: { ...DEFAULT_CIAGENT_CONFIG.verification, ...config?.verification }, + security: { ...DEFAULT_CIAGENT_CONFIG.security, ...config?.security }, + git: { ...DEFAULT_CIAGENT_CONFIG.git, ...config?.git }, + backend: { ...DEFAULT_CIAGENT_CONFIG.backend, ...config?.backend }, }; saveConfig(projectPath, fullConfig); return fullConfig; diff --git a/src/core/decision-engine.test.ts b/src/core/decision-engine.test.ts index 76fb21b..658502e 100644 --- a/src/core/decision-engine.test.ts +++ b/src/core/decision-engine.test.ts @@ -2,15 +2,15 @@ import * as fs from "node:fs"; import * as path from "node:path"; import * as os from "node:os"; import { DecisionEngine, DecisionInput } from "../core/decision-engine.js"; -import { DEFAULT_CI_CONFIG } from "../types/config.js"; +import { DEFAULT_CIAGENT_CONFIG } from "../types/config.js"; describe("DecisionEngine", () => { let tempDir: string; let engine: DecisionEngine; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-decision-test-")); - engine = new DecisionEngine(DEFAULT_CI_CONFIG, tempDir); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-decision-test-")); + engine = new DecisionEngine(DEFAULT_CIAGENT_CONFIG, tempDir); }); afterEach(() => { @@ -106,8 +106,8 @@ describe("DecisionEngine", () => { it("escalates if threshold is raised above 0.7", () => { const strictConfig = { - ...DEFAULT_CI_CONFIG, - autonomy: { ...DEFAULT_CI_CONFIG.autonomy, decision_confidence_threshold: 0.8 }, + ...DEFAULT_CIAGENT_CONFIG, + autonomy: { ...DEFAULT_CIAGENT_CONFIG.autonomy, decision_confidence_threshold: 0.8 }, }; const strictEngine = new DecisionEngine(strictConfig, tempDir); const result = strictEngine.makeMediumConfidenceDecision( diff --git a/src/core/decision-engine.ts b/src/core/decision-engine.ts index c32b298..c68f609 100644 --- a/src/core/decision-engine.ts +++ b/src/core/decision-engine.ts @@ -1,6 +1,6 @@ import { execSync } from "node:child_process"; import { Decision, DecisionCategory, Alternative, confidenceToLevel } from "../types/decisions.js"; -import { CIConfig } from "../types/config.js"; +import { CIAgentConfig } from "../types/config.js"; import { CommitBuilder, DecisionCommitInput } from "./commit-builder.js"; import { CommitDecision } from "../types/commit-meta.js"; @@ -22,13 +22,13 @@ export interface DecisionResult { } export class DecisionEngine { - private config: CIConfig; + private config: CIAgentConfig; private projectPath: string; private currentPhase: number; private currentMilestone: string; private decisionCounter: number; - constructor(config: CIConfig, projectPath: string, milestone: string = "v1.0") { + constructor(config: CIAgentConfig, projectPath: string, milestone: string = "v1.0") { this.config = config; this.projectPath = projectPath; this.currentPhase = 0; diff --git a/src/core/error-recovery.test.ts b/src/core/error-recovery.test.ts index 5d99983..de41176 100644 --- a/src/core/error-recovery.test.ts +++ b/src/core/error-recovery.test.ts @@ -2,16 +2,16 @@ import * as fs from "node:fs"; import * as path from "node:path"; import * as os from "node:os"; import { ErrorRecovery } from "../core/error-recovery.js"; -import { DEFAULT_CI_CONFIG } from "../types/config.js"; +import { DEFAULT_CIAGENT_CONFIG } from "../types/config.js"; describe("ErrorRecovery", () => { let tempDir: string; let recovery: ErrorRecovery; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-recovery-test-")); - fs.mkdirSync(path.join(tempDir, ".ci"), { recursive: true }); - recovery = new ErrorRecovery(DEFAULT_CI_CONFIG, tempDir); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-recovery-test-")); + fs.mkdirSync(path.join(tempDir, ".ciagent"), { recursive: true }); + recovery = new ErrorRecovery(DEFAULT_CIAGENT_CONFIG, tempDir); }); afterEach(() => { diff --git a/src/core/error-recovery.ts b/src/core/error-recovery.ts index 822ea92..f384099 100644 --- a/src/core/error-recovery.ts +++ b/src/core/error-recovery.ts @@ -1,5 +1,5 @@ import { execSync } from "node:child_process"; -import { CIConfig } from "../types/config.js"; +import { CIAgentConfig } from "../types/config.js"; export interface RetryConfig { max_retries: number; @@ -15,11 +15,11 @@ export interface RecoveryResult { } export class ErrorRecovery { - private config: CIConfig; + private config: CIAgentConfig; private projectPath: string; private revisionCount: number; - constructor(config: CIConfig, projectPath: string) { + constructor(config: CIAgentConfig, projectPath: string) { this.config = config; this.projectPath = projectPath; this.revisionCount = 0; diff --git a/src/core/escalation.test.ts b/src/core/escalation.test.ts index 204570d..df01c0b 100644 --- a/src/core/escalation.test.ts +++ b/src/core/escalation.test.ts @@ -2,17 +2,17 @@ import * as fs from "node:fs"; import * as path from "node:path"; import * as os from "node:os"; import { EscalationProtocol, EscalationInput } from "../core/escalation.js"; -import { DEFAULT_CI_CONFIG } from "../types/config.js"; +import { DEFAULT_CIAGENT_CONFIG } from "../types/config.js"; describe("EscalationProtocol", () => { let tempDir: string; let protocol: EscalationProtocol; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-escalation-test-")); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-escalation-test-")); const noAutoCommitConfig = { - ...DEFAULT_CI_CONFIG, - git: { ...DEFAULT_CI_CONFIG.git, auto_commit: false }, + ...DEFAULT_CIAGENT_CONFIG, + git: { ...DEFAULT_CIAGENT_CONFIG.git, auto_commit: false }, }; protocol = new EscalationProtocol(noAutoCommitConfig, tempDir); }); diff --git a/src/core/escalation.ts b/src/core/escalation.ts index 721519f..a8c9157 100644 --- a/src/core/escalation.ts +++ b/src/core/escalation.ts @@ -6,7 +6,7 @@ import { EscalationResolution, ESCALATION_TYPES, } from "../types/escalation.js"; -import { CIConfig } from "../types/config.js"; +import { CIAgentConfig } from "../types/config.js"; import { CommitBuilder, EscalationCommitInput } from "./commit-builder.js"; import { CommitEscalation } from "../types/commit-meta.js"; @@ -22,7 +22,7 @@ export interface EscalationInput { } export class EscalationProtocol { - private config: CIConfig; + private config: CIAgentConfig; private projectPath: string; private currentMilestone: string; private counter: number; @@ -31,7 +31,7 @@ export class EscalationProtocol { private timers: NodeJS.Timeout[]; constructor( - config: CIConfig, + config: CIAgentConfig, projectPath: string, milestone: string = "v1.0", timeoutCallback: (escalation: Escalation, chosenOption: string) => void = () => {} @@ -64,7 +64,7 @@ export class EscalationProtocol { options: input.options, default_option_id: input.default_option_id, resolution: "pending", - audit_file: `.ci/audit/deprecated`, + audit_file: `.ciagent/audit/deprecated`, }; this.pendingEscalations.set(id, escalation); diff --git a/src/core/git-branch.test.ts b/src/core/git-branch.test.ts index 1b1fe4d..f0606ca 100644 --- a/src/core/git-branch.test.ts +++ b/src/core/git-branch.test.ts @@ -5,7 +5,7 @@ import * as fs from "node:fs"; import { GitBranch } from "../core/git-branch.js"; function createTempRepo(): string { - const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-branch-test-")); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-branch-test-")); execSync("git init", { cwd: dir, stdio: "pipe" }); execSync('git config user.email "test@test.com"', { cwd: dir, stdio: "pipe" }); execSync('git config user.name "Test"', { cwd: dir, stdio: "pipe" }); diff --git a/src/core/git-context.test.ts b/src/core/git-context.test.ts index 5b25892..1455868 100644 --- a/src/core/git-context.test.ts +++ b/src/core/git-context.test.ts @@ -5,7 +5,7 @@ import * as fs from "node:fs"; import { GitContext } from "../core/git-context.js"; function createTempRepo(): string { - const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-test-")); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-test-")); execSync("git init", { cwd: dir, stdio: "pipe" }); execSync('git config user.email "test@test.com"', { cwd: dir, stdio: "pipe" }); execSync('git config user.name "Test"', { cwd: dir, stdio: "pipe" }); @@ -41,7 +41,7 @@ describe("GitContext", () => { }); it("returns false for non-git directory", () => { - const nonGit = fs.mkdtempSync(path.join(os.tmpdir(), "ci-nongit-")); + const nonGit = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-nongit-")); const ctx = new GitContext(nonGit); expect(ctx.isGitRepo()).toBe(false); cleanup(nonGit); diff --git a/src/core/git-context.ts b/src/core/git-context.ts index c43b55e..b7b684e 100644 --- a/src/core/git-context.ts +++ b/src/core/git-context.ts @@ -1,7 +1,7 @@ import { execSync } from "node:child_process"; import { - ParsedCiCommit, - CiMetadata, + ParsedCIAgentCommit, + CIAgentMetadata, CommitDecision, } from "../types/commit-meta.js"; import { parseCommitMessage } from "./commit-parser.js"; @@ -16,7 +16,7 @@ export interface ProjectState { phasesCompleted: number[]; phaseBranches: BranchInfo[]; milestoneBranches: string[]; - lastCommit: ParsedCiCommit | null; + lastCommit: ParsedCIAgentCommit | null; } export interface BranchInfo { @@ -69,13 +69,13 @@ export class GitContext { return this.git("rev-parse --abbrev-ref HEAD"); } - getRecentCommits(count: number = 20): ParsedCiCommit[] { + getRecentCommits(count: number = 20): ParsedCIAgentCommit[] { const format = "%H%x00%s%x00%B%x01"; const raw = this.git(`log --max-count=${count} --format="${format}"`); if (!raw) return []; - const commits: ParsedCiCommit[] = []; + const commits: ParsedCIAgentCommit[] = []; const entries = raw.split("\x01").filter(Boolean); for (const entry of entries) { @@ -93,7 +93,7 @@ export class GitContext { return commits; } - getLatestCiCommit(): ParsedCiCommit | null { + getLatestCiCommit(): ParsedCIAgentCommit | null { const commits = this.getRecentCommits(1); return commits.length > 0 ? commits[0] : null; } @@ -207,7 +207,7 @@ export class GitContext { return decisions; } - getDecisionsFromCommits(commits: ParsedCiCommit[], phase?: number): CommitDecision[] { + getDecisionsFromCommits(commits: ParsedCIAgentCommit[], phase?: number): CommitDecision[] { const decisions: CommitDecision[] = []; for (const commit of commits) { if (commit.ci?.decisions) { @@ -300,20 +300,20 @@ export class GitContext { }; } - getCommitsForPhase(phase: number): ParsedCiCommit[] { + getCommitsForPhase(phase: number): ParsedCIAgentCommit[] { const commits = this.getRecentCommits(200); return commits.filter( (c) => c.scope === `P${String(phase).padStart(2, "0")}` || c.ci?.phase === phase ); } - getCommitsForBranch(branch: string): ParsedCiCommit[] { + getCommitsForBranch(branch: string): ParsedCIAgentCommit[] { const format = "%H%x00%s%x00%B%x01"; const raw = this.git(`log ${branch} --max-count=100 --format="${format}"`); if (!raw) return []; - const commits: ParsedCiCommit[] = []; + const commits: ParsedCIAgentCommit[] = []; const entries = raw.split("\x01").filter(Boolean); for (const entry of entries) { diff --git a/src/core/index.ts b/src/core/index.ts index ee46471..f47fc8e 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,12 +1,12 @@ -export { initCI, loadConfig, saveConfig, isCIInitialized, getCIConfigPath, getCIDir, ensureCIDir } from "./config.js"; +export { initCIAgent, loadConfig, saveConfig, isCIAgentInitialized, getCIAgentConfigPath, getCIDir, ensureCIDir } from "./config.js"; export { DecisionEngine } from "./decision-engine.js"; export { EscalationProtocol } from "./escalation.js"; export { ClarifyPhase } from "./clarify.js"; -export { CiFiles } from "./ci-files.js"; +export { CIAgentFiles } from "./ciagent-files.js"; export { ErrorRecovery } from "./error-recovery.js"; export { GitContext } from "./git-context.js"; export { GitBranch } from "./git-branch.js"; export { CommitBuilder } from "./commit-builder.js"; -export { extractCiBlock, parseCiBlock, parseCommitMessage } from "./commit-parser.js"; -export type { CIConfig } from "../types/config.js"; -export { DEFAULT_CI_CONFIG } from "../types/config.js"; \ No newline at end of file +export { extractCIAgentBlock, parseCIAgentBlock, parseCommitMessage } from "./commit-parser.js"; +export type { CIAgentConfig } from "../types/config.js"; +export { DEFAULT_CIAGENT_CONFIG } from "../types/config.js"; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 9784ea3..4a73991 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,20 +2,20 @@ export { OrchestratorAgent } from "./agents/orchestrator.js"; export { DecisionEngine } from "./core/decision-engine.js"; export { EscalationProtocol } from "./core/escalation.js"; export { ClarifyPhase } from "./core/clarify.js"; -export { CiFiles } from "./core/ci-files.js"; +export { CIAgentFiles } from "./core/ciagent-files.js"; export { ErrorRecovery } from "./core/error-recovery.js"; export { GitContext } from "./core/git-context.js"; export { GitBranch } from "./core/git-branch.js"; export { CommitBuilder } from "./core/commit-builder.js"; -export { extractCiBlock, parseCiBlock, parseCommitMessage } from "./core/commit-parser.js"; +export { extractCIAgentBlock, parseCIAgentBlock, parseCommitMessage } from "./core/commit-parser.js"; export { VerificationPipeline } from "./verification/index.js"; export { StructuralVerification } from "./verification/structural.js"; export { BehavioralVerification } from "./verification/behavioral.js"; export { SecurityVerification } from "./verification/security.js"; export { QualityVerification } from "./verification/quality.js"; export { getAgent, getAvailableAgents } from "./agents/index.js"; -export { initCI, loadConfig, saveConfig, isCIInitialized } from "./core/config.js"; -export { DEFAULT_CI_CONFIG } from "./types/config.js"; +export { initCIAgent, loadConfig, saveConfig, isCIAgentInitialized } from "./core/config.js"; +export { DEFAULT_CIAGENT_CONFIG } from "./types/config.js"; export { confidenceToLevel, shouldEscalate } from "./types/decisions.js"; export { ESCALATION_TYPES } from "./types/escalation.js"; export { createClarifyQuestion } from "./types/clarify.js"; @@ -28,7 +28,7 @@ export { OllamaLocalBackend } from "./backends/ollama-local.js"; export { OllamaCloudBackend } from "./backends/ollama-cloud.js"; export { ToolRegistry } from "./backends/tool-registry.js"; -export type { CIConfig, AutonomyLevel, ModelProfile } from "./types/config.js"; +export type { CIAgentConfig, AutonomyLevel, ModelProfile } from "./types/config.js"; export type { Decision, DecisionCategory } from "./types/decisions.js"; export type { Escalation, EscalationType } from "./types/escalation.js"; export type { PipelineState, PhaseResult, OrchestratorResult } from "./types/pipeline.js"; @@ -38,9 +38,9 @@ export type { AgentContext, AgentResult } from "./agents/base.js"; export type { LayeredVerificationResult } from "./verification/index.js"; export type { VerificationResult, VerificationCheck } from "./verification/types.js"; export type { AgentName } from "./types/config.js"; -export type { CiMetadata, ParsedCiCommit, CommitType, CommitScope, CommitDecision, CommitEscalation, CommitRequirements, CommitCompoundMeta } from "./types/commit-meta.js"; +export type { CIAgentMetadata, ParsedCIAgentCommit, CommitType, CommitScope, CommitDecision, CommitEscalation, CommitRequirements, CommitCompoundMeta } from "./types/commit-meta.js"; export type { ProjectState, BranchInfo } from "./core/git-context.js"; export type { PhaseBranchInfo, MilestoneBranchInfo, BranchCreateResult, BranchMergeResult } from "./core/git-branch.js"; -export type { ProjectMd, RoadmapMd, RequirementsMd, ArchitectureMd } from "./core/ci-files.js"; +export type { ProjectMd, RoadmapMd, RequirementsMd, ArchitectureMd } from "./core/ciagent-files.js"; export type { IntelligenceBackend, BackendRequest, BackendResult, BackendConfigSection, BackendUnavailableError, Artifact, TokenUsage } from "./backends/types.js"; export type { ToolDefinition, ToolCall, ToolResult } from "./backends/tool-registry.js"; \ No newline at end of file diff --git a/src/types/commit-meta.ts b/src/types/commit-meta.ts index 039fa7c..3a9db96 100644 --- a/src/types/commit-meta.ts +++ b/src/types/commit-meta.ts @@ -51,7 +51,7 @@ export interface CommitCompoundMeta { solution: string; } -export interface CiMetadata { +export interface CIAgentMetadata { phase: number; milestone: string; project?: string; @@ -65,12 +65,12 @@ export interface CiMetadata { compound?: CommitCompoundMeta; } -export interface ParsedCiCommit { +export interface ParsedCIAgentCommit { hash: string; type: CommitType; scope: string; subject: string; - ci: CiMetadata | null; + ci: CIAgentMetadata | null; body: string; } diff --git a/src/types/config.test.ts b/src/types/config.test.ts index ec09ce3..683bbcf 100644 --- a/src/types/config.test.ts +++ b/src/types/config.test.ts @@ -1,33 +1,33 @@ -import { CIConfig, DEFAULT_CI_CONFIG, AutonomyLevel, ModelProfile, ProjectEntry } from "../types/config.js"; +import { CIAgentConfig, DEFAULT_CIAGENT_CONFIG, AutonomyLevel, ModelProfile, ProjectEntry } from "../types/config.js"; -describe("CIConfig", () => { - it("DEFAULT_CI_CONFIG has all required fields", () => { - expect(DEFAULT_CI_CONFIG.autonomy.level).toBe("full"); - expect(DEFAULT_CI_CONFIG.autonomy.clarify_budget).toBe(10); - expect(DEFAULT_CI_CONFIG.autonomy.decision_confidence_threshold).toBe(0.6); - expect(DEFAULT_CI_CONFIG.autonomy.max_revision_iterations).toBe(3); - expect(DEFAULT_CI_CONFIG.autonomy.max_verification_retries).toBe(2); - expect(DEFAULT_CI_CONFIG.autonomy.escalation_timeout_ms).toBe(300000); - expect(DEFAULT_CI_CONFIG.autonomy.escalation_hooks).toContain("deploy"); - expect(DEFAULT_CI_CONFIG.model_profile).toBe("quality"); - expect(DEFAULT_CI_CONFIG.parallelization.enabled).toBe(true); - expect(DEFAULT_CI_CONFIG.verification.automated_only).toBe(true); - expect(DEFAULT_CI_CONFIG.security.auto_accept_low_severity).toBe(true); - expect(DEFAULT_CI_CONFIG.git.auto_commit).toBe(true); - expect(DEFAULT_CI_CONFIG.git.auto_push).toBe(false); +describe("CIAgentConfig", () => { + it("DEFAULT_CIAGENT_CONFIG has all required fields", () => { + expect(DEFAULT_CIAGENT_CONFIG.autonomy.level).toBe("full"); + expect(DEFAULT_CIAGENT_CONFIG.autonomy.clarify_budget).toBe(10); + expect(DEFAULT_CIAGENT_CONFIG.autonomy.decision_confidence_threshold).toBe(0.6); + expect(DEFAULT_CIAGENT_CONFIG.autonomy.max_revision_iterations).toBe(3); + expect(DEFAULT_CIAGENT_CONFIG.autonomy.max_verification_retries).toBe(2); + expect(DEFAULT_CIAGENT_CONFIG.autonomy.escalation_timeout_ms).toBe(300000); + expect(DEFAULT_CIAGENT_CONFIG.autonomy.escalation_hooks).toContain("deploy"); + expect(DEFAULT_CIAGENT_CONFIG.model_profile).toBe("quality"); + expect(DEFAULT_CIAGENT_CONFIG.parallelization.enabled).toBe(true); + expect(DEFAULT_CIAGENT_CONFIG.verification.automated_only).toBe(true); + expect(DEFAULT_CIAGENT_CONFIG.security.auto_accept_low_severity).toBe(true); + expect(DEFAULT_CIAGENT_CONFIG.git.auto_commit).toBe(true); + expect(DEFAULT_CIAGENT_CONFIG.git.auto_push).toBe(false); }); - it("DEFAULT_CI_CONFIG has multi-project fields", () => { - expect(DEFAULT_CI_CONFIG.projects).toEqual([]); - expect(DEFAULT_CI_CONFIG.active_project).toBe(""); + it("DEFAULT_CIAGENT_CONFIG has multi-project fields", () => { + expect(DEFAULT_CIAGENT_CONFIG.projects).toEqual([]); + expect(DEFAULT_CIAGENT_CONFIG.active_project).toBe(""); }); it("AutonomyLevel accepts all valid levels", () => { const levels: AutonomyLevel[] = ["full", "supervised", "guided"]; for (const level of levels) { - const config: CIConfig = { - ...DEFAULT_CI_CONFIG, - autonomy: { ...DEFAULT_CI_CONFIG.autonomy, level }, + const config: CIAgentConfig = { + ...DEFAULT_CIAGENT_CONFIG, + autonomy: { ...DEFAULT_CIAGENT_CONFIG.autonomy, level }, }; expect(config.autonomy.level).toBe(level); } @@ -36,8 +36,8 @@ describe("CIConfig", () => { it("ModelProfile accepts all valid profiles", () => { const profiles: ModelProfile[] = ["quality", "speed", "balanced"]; for (const profile of profiles) { - const config: CIConfig = { - ...DEFAULT_CI_CONFIG, + const config: CIAgentConfig = { + ...DEFAULT_CIAGENT_CONFIG, model_profile: profile, }; expect(config.model_profile).toBe(profile); @@ -45,7 +45,7 @@ describe("CIConfig", () => { }); it("escalation_hooks defaults include expected items", () => { - expect(DEFAULT_CI_CONFIG.autonomy.escalation_hooks).toEqual([ + expect(DEFAULT_CIAGENT_CONFIG.autonomy.escalation_hooks).toEqual([ "deploy", "delete_data", "merge_to_main", @@ -66,10 +66,10 @@ describe("CIConfig", () => { }); }); - describe("CIConfig with projects", () => { + describe("CIAgentConfig with projects", () => { it("supports multiple projects", () => { - const config: CIConfig = { - ...DEFAULT_CI_CONFIG, + const config: CIAgentConfig = { + ...DEFAULT_CIAGENT_CONFIG, projects: [ { slug: "task-api", name: "Task API", default: true }, { slug: "auth-svc", name: "Auth Service" }, @@ -82,8 +82,8 @@ describe("CIConfig", () => { }); it("supports single project", () => { - const config: CIConfig = { - ...DEFAULT_CI_CONFIG, + const config: CIAgentConfig = { + ...DEFAULT_CIAGENT_CONFIG, projects: [{ slug: "my-app", name: "My App", default: true }], active_project: "my-app", }; @@ -92,8 +92,8 @@ describe("CIConfig", () => { }); it("defaults to empty projects array and empty active_project", () => { - expect(DEFAULT_CI_CONFIG.projects).toEqual([]); - expect(DEFAULT_CI_CONFIG.active_project).toBe(""); + expect(DEFAULT_CIAGENT_CONFIG.projects).toEqual([]); + expect(DEFAULT_CIAGENT_CONFIG.active_project).toBe(""); }); }); }); \ No newline at end of file diff --git a/src/types/config.ts b/src/types/config.ts index fe148b3..06083b0 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -28,7 +28,8 @@ export type AgentName = | "plan-checker" | "project-researcher" | "research-synthesizer" - | "solution-writer"; + | "solution-writer" + | "tester"; export interface AutonomyConfig { level: AutonomyLevel; @@ -71,7 +72,7 @@ export interface ProjectEntry { default?: boolean; } -export interface CIConfig { +export interface CIAgentConfig { projects: ProjectEntry[]; active_project: string; autonomy: AutonomyConfig; @@ -83,7 +84,7 @@ export interface CIConfig { backend: BackendConfigSection; } -export const DEFAULT_CI_CONFIG: CIConfig = { +export const DEFAULT_CIAGENT_CONFIG: CIAgentConfig = { projects: [], active_project: "", autonomy: { diff --git a/src/types/index.test.ts b/src/types/index.test.ts index 8ee40b5..ece11cd 100644 --- a/src/types/index.test.ts +++ b/src/types/index.test.ts @@ -3,11 +3,11 @@ import { confidenceToLevel, shouldEscalate } from "../types/decisions.js"; import { ESCALATION_TYPES } from "../types/escalation.js"; import { parseSpecification } from "../types/specification.js"; import { createClarifyQuestion } from "../types/clarify.js"; -import { DEFAULT_CI_CONFIG } from "../types/config.js"; +import { DEFAULT_CIAGENT_CONFIG } from "../types/config.js"; describe("Type exports", () => { it("pipeline types are importable and functional", () => { - expect(STAGE_ORDER).toHaveLength(7); + expect(STAGE_ORDER).toHaveLength(8); expect(getNextStage("specify")).toBe("clarify"); const state = createInitialPipelineState("/tmp/test"); expect(state.current_stage).toBe("specify"); @@ -40,6 +40,6 @@ describe("Type exports", () => { }); it("config defaults are importable", () => { - expect(DEFAULT_CI_CONFIG.autonomy.level).toBe("full"); + expect(DEFAULT_CIAGENT_CONFIG.autonomy.level).toBe("full"); }); }); \ No newline at end of file diff --git a/src/types/pipeline.test.ts b/src/types/pipeline.test.ts index f9fd9ae..cc6eebb 100644 --- a/src/types/pipeline.test.ts +++ b/src/types/pipeline.test.ts @@ -8,13 +8,14 @@ import { } from "../types/pipeline.js"; describe("STAGE_ORDER", () => { - it("has 7 stages in correct order", () => { + it("has 8 stages in correct order", () => { expect(STAGE_ORDER).toEqual([ "specify", "clarify", "research", "plan", "execute", + "test", "verify", "complete", ]); @@ -27,7 +28,8 @@ describe("getNextStage", () => { expect(getNextStage("clarify")).toBe("research"); expect(getNextStage("research")).toBe("plan"); expect(getNextStage("plan")).toBe("execute"); - expect(getNextStage("execute")).toBe("verify"); + expect(getNextStage("execute")).toBe("test"); + expect(getNextStage("test")).toBe("verify"); expect(getNextStage("verify")).toBe("complete"); }); @@ -51,6 +53,7 @@ describe("createInitialPipelineState", () => { expect(state.research_completed).toBe(false); expect(state.plan_completed).toBe(false); expect(state.execute_completed).toBe(false); + expect(state.test_completed).toBe(false); expect(state.verify_completed).toBe(false); expect(state.errors).toHaveLength(0); expect(state.started_at).toBeTruthy(); diff --git a/src/types/pipeline.ts b/src/types/pipeline.ts index b80e1c2..2e60dfd 100644 --- a/src/types/pipeline.ts +++ b/src/types/pipeline.ts @@ -6,6 +6,7 @@ export type PipelineStage = | "research" | "plan" | "execute" + | "test" | "verify" | "complete"; @@ -19,6 +20,7 @@ export interface PipelineState { research_completed: boolean; plan_completed: boolean; execute_completed: boolean; + test_completed: boolean; verify_completed: boolean; errors: PipelineError[]; started_at: string; @@ -61,6 +63,7 @@ export const STAGE_ORDER: PipelineStage[] = [ "research", "plan", "execute", + "test", "verify", "complete", ]; @@ -84,6 +87,7 @@ export function createInitialPipelineState( research_completed: false, plan_completed: false, execute_completed: false, + test_completed: false, verify_completed: false, errors: [], started_at: new Date().toISOString(), diff --git a/src/utils/file.test.ts b/src/utils/file.test.ts index eceaed9..9311e74 100644 --- a/src/utils/file.test.ts +++ b/src/utils/file.test.ts @@ -7,7 +7,7 @@ describe("file utilities", () => { let tempDir: string; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-file-test-")); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-file-test-")); }); afterEach(() => { @@ -115,8 +115,8 @@ describe("file utilities", () => { expect(getProjectRoot(path.join(tempDir, "subdir"))).toBe(tempDir); }); - it("finds project root with .ci directory", () => { - fs.mkdirSync(path.join(tempDir, ".ci")); + it("finds project root with .ciagent directory", () => { + fs.mkdirSync(path.join(tempDir, ".ciagent")); expect(getProjectRoot(path.join(tempDir, "nested", "dir"))).toBe(tempDir); }); diff --git a/src/utils/file.ts b/src/utils/file.ts index 93cc869..81d9dbe 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -47,7 +47,7 @@ export function copyFile(src: string, dest: string): void { export function getProjectRoot(startPath?: string): string { let current = startPath || process.cwd(); while (current !== path.dirname(current)) { - if (fs.existsSync(path.join(current, ".ci"))) return current; + if (fs.existsSync(path.join(current, ".ciagent"))) return current; if (fs.existsSync(path.join(current, ".git"))) return current; if (fs.existsSync(path.join(current, "package.json"))) return current; current = path.dirname(current); diff --git a/src/verification/behavioral.test.ts b/src/verification/behavioral.test.ts index 6595be0..a694ce4 100644 --- a/src/verification/behavioral.test.ts +++ b/src/verification/behavioral.test.ts @@ -7,7 +7,7 @@ describe("BehavioralVerification", () => { let tempDir: string; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-behavioral-test-")); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-behavioral-test-")); }); afterEach(() => { @@ -50,7 +50,7 @@ describe("BehavioralVerification", () => { }); it("passes with REQUIREMENTS.md", async () => { - const ciDir = path.join(tempDir, ".ci"); + const ciDir = path.join(tempDir, ".ciagent"); fs.mkdirSync(ciDir, { recursive: true }); fs.writeFileSync(path.join(ciDir, "REQUIREMENTS.md"), "# Requirements\n\n| REQ-ID | Requirement | Priority | Phase | Status |\n|--------|-------------|----------|-------|--------|\n| REQ-01 | Must have auth | P0 | 1 | pending |\n"); @@ -62,7 +62,7 @@ describe("BehavioralVerification", () => { }); it("skips when no REQUIREMENTS.md or PROJECT.md", async () => { - const ciDir = path.join(tempDir, ".ci"); + const ciDir = path.join(tempDir, ".ciagent"); fs.mkdirSync(ciDir, { recursive: true }); const verifier = new BehavioralVerification(); @@ -73,7 +73,7 @@ describe("BehavioralVerification", () => { }); it("passes with PROJECT.md when no REQUIREMENTS.md", async () => { - const ciDir = path.join(tempDir, ".ci"); + const ciDir = path.join(tempDir, ".ciagent"); fs.mkdirSync(ciDir, { recursive: true }); fs.writeFileSync(path.join(ciDir, "PROJECT.md"), "# Test\n\n## What This Is\nBuild it\n\n## Requirements\n\n### Active\n\n- [ ] Must have auth\n- [ ] Shall support CRUD\n"); diff --git a/src/verification/behavioral.ts b/src/verification/behavioral.ts index 7475aa1..c8f89f3 100644 --- a/src/verification/behavioral.ts +++ b/src/verification/behavioral.ts @@ -108,8 +108,8 @@ export class BehavioralVerification extends VerificationLayer { } private checkSpecificationRequirements(projectPath: string): VerificationCheck { - const reqPath = path.join(projectPath, ".ci", "REQUIREMENTS.md"); - const projectPath_md = path.join(projectPath, ".ci", "PROJECT.md"); + const reqPath = path.join(projectPath, ".ciagent", "REQUIREMENTS.md"); + const projectPath_md = path.join(projectPath, ".ciagent", "PROJECT.md"); const specPath = reqPath; if (!fs.existsSync(specPath)) { @@ -189,7 +189,7 @@ export class BehavioralVerification extends VerificationLayer { private checkPlanMustHaves(projectPath: string, phase: number): VerificationCheck { const roadmapPath = path.join( projectPath, - ".ci", + ".ciagent", "ROADMAP.md" ); @@ -252,7 +252,7 @@ export class BehavioralVerification extends VerificationLayer { ciBlockRegex.lastIndex = 0; } - const reqPath = path.join(projectPath, ".ci", "REQUIREMENTS.md"); + const reqPath = path.join(projectPath, ".ciagent", "REQUIREMENTS.md"); if (!fs.existsSync(reqPath)) { return this.check( "Requirement test coverage via git log", diff --git a/src/verification/index.test.ts b/src/verification/index.test.ts index f670f7a..f03263b 100644 --- a/src/verification/index.test.ts +++ b/src/verification/index.test.ts @@ -7,8 +7,8 @@ describe("VerificationPipeline", () => { let tempDir: string; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-pipeline-test-")); - const ciDir = path.join(tempDir, ".ci"); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-pipeline-test-")); + const ciDir = path.join(tempDir, ".ciagent"); fs.mkdirSync(ciDir, { recursive: true }); fs.writeFileSync(path.join(ciDir, "config.json"), JSON.stringify({ autonomy: { level: "full" } })); fs.writeFileSync(path.join(ciDir, "specification.md"), "# Test\n## Objective\nBuild it\n\n## Requirements\n- Feature A\n"); diff --git a/src/verification/quality.test.ts b/src/verification/quality.test.ts index ba8b00c..ac4b645 100644 --- a/src/verification/quality.test.ts +++ b/src/verification/quality.test.ts @@ -7,7 +7,7 @@ describe("QualityVerification", () => { let tempDir: string; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-quality-test-")); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-quality-test-")); }); afterEach(() => { diff --git a/src/verification/security.test.ts b/src/verification/security.test.ts index 85e0fe9..912a771 100644 --- a/src/verification/security.test.ts +++ b/src/verification/security.test.ts @@ -7,7 +7,7 @@ describe("SecurityVerification", () => { let tempDir: string; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-security-test-")); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-security-test-")); }); afterEach(() => { diff --git a/src/verification/structural.test.ts b/src/verification/structural.test.ts index 11de540..b07c5de 100644 --- a/src/verification/structural.test.ts +++ b/src/verification/structural.test.ts @@ -7,28 +7,28 @@ describe("StructuralVerification", () => { let tempDir: string; beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ci-structural-test-")); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ciagent-structural-test-")); }); afterEach(() => { fs.rmSync(tempDir, { recursive: true, force: true }); }); - function setupProjectStructure(hasCIDir = true, hasRoadmap = true, hasCIConfig = true, hasSpec = true) { + function setupProjectStructure(hasCIDir = true, hasRoadmap = true, hasCIAgentConfig = true, hasSpec = true) { if (hasCIDir) { - const ciDir = path.join(tempDir, ".ci"); + const ciDir = path.join(tempDir, ".ciagent"); fs.mkdirSync(ciDir, { recursive: true }); if (hasRoadmap) { fs.writeFileSync(path.join(ciDir, "ROADMAP.md"), "# Roadmap\n\n## Phases\n\n### Phase 1: Init\n**Goal**: Set up project\n**Status**: not_started\n"); } } - if (hasCIConfig) { - const ciDir = path.join(tempDir, ".ci"); + if (hasCIAgentConfig) { + const ciDir = path.join(tempDir, ".ciagent"); fs.mkdirSync(ciDir, { recursive: true }); fs.writeFileSync(path.join(ciDir, "config.json"), JSON.stringify({ autonomy: { level: "full" } }, null, 2)); } if (hasSpec) { - const specDir = path.join(tempDir, ".ci"); + const specDir = path.join(tempDir, ".ciagent"); if (!fs.existsSync(specDir)) fs.mkdirSync(specDir, { recursive: true }); fs.writeFileSync(path.join(specDir, "specification.md"), "# Project\n## Objective\nBuild a REST API for task management\n\n## Requirements\n- User auth\n- CRUD\n"); } @@ -43,13 +43,13 @@ describe("StructuralVerification", () => { expect(result.name).toBe("Structural"); expect(result.checks.length).toBeGreaterThan(0); - const phaseDirCheck = result.checks.find((c) => c.name === ".ci directory exists"); + const phaseDirCheck = result.checks.find((c) => c.name === ".ciagent directory exists"); expect(phaseDirCheck?.status).toBe("pass"); const planCheck = result.checks.find((c) => c.name === "ROADMAP.md exists"); expect(planCheck?.status).toBe("pass"); - const configCheck = result.checks.find((c) => c.name === "CI config valid"); + const configCheck = result.checks.find((c) => c.name === "CIAgent config valid"); expect(configCheck?.status).toBe("pass"); const specCheck = result.checks.find((c) => c.name === "Specification exists"); @@ -61,7 +61,7 @@ describe("StructuralVerification", () => { const verifier = new StructuralVerification(); const result = await verifier.verify(tempDir, 1); - const phaseDirCheck = result.checks.find((c) => c.name === ".ci directory exists"); + const phaseDirCheck = result.checks.find((c) => c.name === ".ciagent directory exists"); expect(phaseDirCheck?.status).toBe("fail"); }); @@ -75,15 +75,15 @@ describe("StructuralVerification", () => { }); it("fails when CI config has invalid JSON", async () => { - const ciDir = path.join(tempDir, ".ci"); + const ciDir = path.join(tempDir, ".ciagent"); fs.mkdirSync(ciDir, { recursive: true }); fs.writeFileSync(path.join(ciDir, "config.json"), "not valid json{{{"); - fs.mkdirSync(path.join(tempDir, ".ci"), { recursive: true }); + fs.mkdirSync(path.join(tempDir, ".ciagent"), { recursive: true }); const verifier = new StructuralVerification(); const result = await verifier.verify(tempDir, 1); - const configCheck = result.checks.find((c) => c.name === "CI config valid"); + const configCheck = result.checks.find((c) => c.name === "CIAgent config valid"); expect(configCheck?.status).toBe("fail"); }); @@ -91,7 +91,7 @@ describe("StructuralVerification", () => { const srcDir = path.join(tempDir, "src"); fs.mkdirSync(srcDir, { recursive: true }); fs.writeFileSync(path.join(srcDir, "app.ts"), "export function main() { /* TODO: implement */ }"); - const ciDir = path.join(tempDir, ".ci"); + const ciDir = path.join(tempDir, ".ciagent"); fs.mkdirSync(ciDir, { recursive: true }); fs.writeFileSync(path.join(ciDir, "config.json"), "{}"); fs.writeFileSync(path.join(ciDir, "specification.md"), "# Test\n## Objective\nBuild it"); diff --git a/src/verification/structural.ts b/src/verification/structural.ts index 083bf31..4db3452 100644 --- a/src/verification/structural.ts +++ b/src/verification/structural.ts @@ -23,7 +23,7 @@ export class StructuralVerification extends VerificationLayer { checks.push(this.checkPhaseDir(projectPath, phase)); checks.push(this.checkPlanExists(projectPath, phase)); - checks.push(this.checkCIConfig(projectPath)); + checks.push(this.checkCIAgentConfig(projectPath)); checks.push(this.checkSpecification(projectPath)); checks.push(this.checkNoStubs(projectPath)); checks.push(this.checkImportsWired(projectPath)); @@ -41,47 +41,47 @@ export class StructuralVerification extends VerificationLayer { } private checkPhaseDir(projectPath: string, phase: number) { - const ciDir = path.join(projectPath, ".ci"); + const ciDir = path.join(projectPath, ".ciagent"); const exists = fs.existsSync(ciDir); return this.check( - ".ci directory exists", + ".ciagent directory exists", exists ? "pass" : "fail", - exists ? ".ci directory found" : ".ci directory not found", + exists ? ".ciagent directory found" : ".ciagent directory not found", ciDir ); } private checkPlanExists(projectPath: string, phase: number) { - const roadmapPath = path.join(projectPath, ".ci", "ROADMAP.md"); + const roadmapPath = path.join(projectPath, ".ciagent", "ROADMAP.md"); const exists = fs.existsSync(roadmapPath); return this.check( "ROADMAP.md exists", exists ? "pass" : "warning", - exists ? "ROADMAP.md found" : "ROADMAP.md not found (run 'ci init' first)", + exists ? "ROADMAP.md found" : "ROADMAP.md not found (run 'ciagent init' first)", roadmapPath ); } - private checkCIConfig(projectPath: string) { - const configPath = path.join(projectPath, ".ci", "config.json"); + private checkCIAgentConfig(projectPath: string) { + const configPath = path.join(projectPath, ".ciagent", "config.json"); const exists = fs.existsSync(configPath); if (!exists) { - return this.check("CI config exists", "fail", ".ci/config.json not found", configPath); + return this.check("CIAgent config exists", "fail", ".ciagent/config.json not found", configPath); } try { const content = fs.readFileSync(configPath, "utf-8"); JSON.parse(content); - return this.check("CI config valid", "pass", ".ci/config.json is valid JSON"); + return this.check("CIAgent config valid", "pass", ".ciagent/config.json is valid JSON"); } catch (e) { - return this.check("CI config valid", "fail", `.ci/config.json has invalid JSON: ${(e as Error).message}`); + return this.check("CIAgent config valid", "fail", `.ciagent/config.json has invalid JSON: ${(e as Error).message}`); } } private checkSpecification(projectPath: string) { - const specPath = path.join(projectPath, ".ci", "specification.md"); + const specPath = path.join(projectPath, ".ciagent", "specification.md"); const exists = fs.existsSync(specPath); if (!exists) { - return this.check("Specification exists", "warning", ".ci/specification.md not found — specification may not be loaded yet"); + return this.check("Specification exists", "warning", ".ciagent/specification.md not found — specification may not be loaded yet"); } const content = fs.readFileSync(specPath, "utf-8"); if (content.trim().length < 10) {