Roo-Code/tests/phase3-trace-logging.test.ts

177 lines
5.9 KiB
TypeScript

import fs from "fs"
import path from "path"
import { describe, it, expect, beforeEach, afterEach } from "vitest"
import { TraceLogger, type MutationClass } from "../src/core/intent/TraceLogger"
const orchestrationDir = path.join(process.cwd(), ".orchestration")
const tracePath = path.join(orchestrationDir, "agent_trace.jsonl")
beforeEach(() => {
if (fs.existsSync(orchestrationDir)) {
fs.rmSync(orchestrationDir, { recursive: true, force: true })
}
})
afterEach(() => {
if (fs.existsSync(orchestrationDir)) {
fs.rmSync(orchestrationDir, { recursive: true, force: true })
}
})
describe("Phase 3: AI-Native Git Layer - Semantic Tracking", () => {
it("generates SHA-256 hashes for content", () => {
const content = "export const foo = () => {}"
const hash = TraceLogger.hashContent(content)
// Verify hash is a valid SHA-256 (64 hex characters)
expect(hash).toMatch(/^[a-f0-9]{64}$/)
// Verify same content produces same hash
const hash2 = TraceLogger.hashContent(content)
expect(hash).toBe(hash2)
// Verify different content produces different hash
const hash3 = TraceLogger.hashContent("different content")
expect(hash).not.toBe(hash3)
})
it("classifies mutations as AST_REFACTOR for syntax-only changes", () => {
const original = "function hello() {\n console.log('hello');\n}\n"
// Minor formatting change: add semicolon (< 20% change)
const updated = "function hello() {\n console.log('hello');\n};\n"
const classification = TraceLogger.classifyMutation(updated, original, false)
expect(classification).toBe("AST_REFACTOR")
})
it("classifies mutations as INTENT_EVOLUTION for new files", () => {
const content = "export const newFeature = () => {}"
const classification = TraceLogger.classifyMutation(content, undefined, true)
expect(classification).toBe("INTENT_EVOLUTION")
})
it("classifies mutations as INTENT_EVOLUTION for significant changes (>20%)", () => {
const original = "function original() {\n return 'hello';\n}"
// Much longer content
const updated = `function refactored() {
// New implementation with additional features
return 'hello world with new features';
}
export const newExport = () => {}
export const anotherExport = () => {}`
const classification = TraceLogger.classifyMutation(updated, original, false)
expect(classification).toBe("INTENT_EVOLUTION")
})
it("logs trace entries to agent_trace.jsonl with intent_id and content_hash", () => {
const logger = new TraceLogger()
const content = "console.log('test')"
const intentId = "INT-001"
const filePath = "src/test.ts"
logger.logTrace(intentId, filePath, content, "AST_REFACTOR")
// Verify file was created
expect(fs.existsSync(tracePath)).toBe(true)
// Verify entry was logged
const traces = logger.readTraces()
expect(traces).toHaveLength(1)
const entry = traces[0]
expect(entry.intent_id).toBe(intentId)
expect(entry.path).toBe(filePath)
expect(entry.mutation_class).toBe("AST_REFACTOR")
expect(entry.content_hash).toBe(TraceLogger.hashContent(content))
expect(entry.timestamp).toBeDefined()
})
it("logs trace entries with req_id when provided", () => {
const logger = new TraceLogger()
const content = "new feature code"
const intentId = "INT-001"
const reqId = "REQ-12345"
logger.logTrace(intentId, "src/feature.ts", content, "INTENT_EVOLUTION", reqId)
const traces = logger.readTraces()
expect(traces).toHaveLength(1)
const entry = traces[0]
expect(entry.req_id).toBe(reqId)
expect(entry.mutation_class).toBe("INTENT_EVOLUTION")
})
it("appends multiple trace entries to agent_trace.jsonl", () => {
const logger = new TraceLogger()
// Log first entry
logger.logTrace("INT-001", "src/auth.ts", "auth code", "AST_REFACTOR")
// Log second entry
logger.logTrace("INT-002", "src/feature.ts", "feature code", "INTENT_EVOLUTION", "REQ-789")
const traces = logger.readTraces()
expect(traces).toHaveLength(2)
// Verify first entry
expect(traces[0].intent_id).toBe("INT-001")
expect(traces[0].path).toBe("src/auth.ts")
expect(traces[0].mutation_class).toBe("AST_REFACTOR")
// Verify second entry
expect(traces[1].intent_id).toBe("INT-002")
expect(traces[1].path).toBe("src/feature.ts")
expect(traces[1].mutation_class).toBe("INTENT_EVOLUTION")
expect(traces[1].req_id).toBe("REQ-789")
})
it("queries traces by intent_id", () => {
const logger = new TraceLogger()
// Log traces for different intents
logger.logTrace("INT-001", "src/auth.ts", "auth code", "AST_REFACTOR")
logger.logTrace("INT-001", "src/auth-utils.ts", "utils code", "AST_REFACTOR")
logger.logTrace("INT-002", "src/feature.ts", "feature code", "INTENT_EVOLUTION")
const int001Traces = logger.getTracesByIntent("INT-001")
expect(int001Traces).toHaveLength(2)
expect(int001Traces.every((e) => e.intent_id === "INT-001")).toBe(true)
const int002Traces = logger.getTracesByIntent("INT-002")
expect(int002Traces).toHaveLength(1)
expect(int002Traces[0].path).toBe("src/feature.ts")
})
it("handles missing intent_id (null) in traces", () => {
const logger = new TraceLogger()
// Log without intent_id (pre-intent phase)
logger.logTrace(null, "src/setup.ts", "setup code", "INTENT_EVOLUTION")
const traces = logger.readTraces()
expect(traces).toHaveLength(1)
expect(traces[0].intent_id).toBeNull()
})
it("serializes trace entries as valid JSON lines format", () => {
const logger = new TraceLogger()
logger.logTrace("INT-001", "src/file.ts", "code", "AST_REFACTOR", "REQ-123")
const rawContent = fs.readFileSync(tracePath, "utf8")
const lines = rawContent.trim().split("\n")
expect(lines).toHaveLength(1)
// Verify each line is valid JSON
const parsed = JSON.parse(lines[0])
expect(parsed).toHaveProperty("intent_id")
expect(parsed).toHaveProperty("mutation_class")
expect(parsed).toHaveProperty("content_hash")
expect(parsed).toHaveProperty("timestamp")
expect(parsed).toHaveProperty("req_id")
})
})