feat(coder,server): audit engine — session audit, guideline compliance, user correction tracking
Implements audit-harness-inspired session lifecycle: audit session creation/end/recover/report-daily with JSONL buffer and graded context recovery (L0-L4). Guideline service for behavioral compliance rules (condition/action model with criticality). Correction service for persistent user correction tracking across agent sessions. 8 supporting skills: audit-start/end/report-daily/recover + command variants for slash-command integration.
This commit is contained in:
111
apps/server/src/services/audit/runs-dir.ts
Normal file
111
apps/server/src/services/audit/runs-dir.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
|
||||
export const INDEX_SCHEMA_VERSION = '1.1';
|
||||
export const GITIGNORE_CONTENT = `# boocode audit runs
|
||||
/*
|
||||
!index.json
|
||||
`;
|
||||
|
||||
export interface IndexEntry {
|
||||
id: string;
|
||||
type: string;
|
||||
status: string;
|
||||
task?: string;
|
||||
skill?: string;
|
||||
created?: string;
|
||||
last_updated?: string;
|
||||
record_count?: number;
|
||||
anomaly_count?: number;
|
||||
max_anomaly_level?: string;
|
||||
}
|
||||
|
||||
export interface IndexFile {
|
||||
schema_version: string;
|
||||
entries: IndexEntry[];
|
||||
}
|
||||
|
||||
function findRunsDirFrom(start: string): string {
|
||||
const explicit = process.env['AUDIT_DOT_DIR']?.trim();
|
||||
const candidates = explicit ? [explicit] : ['.boo'];
|
||||
let cur = resolve(start);
|
||||
while (true) {
|
||||
for (const basename of candidates) {
|
||||
const candidate = join(cur, basename, 'runs');
|
||||
if (existsSync(candidate)) return candidate;
|
||||
}
|
||||
const parent = resolve(cur, '..');
|
||||
if (parent === cur) break;
|
||||
cur = parent;
|
||||
}
|
||||
const defaultBasename = explicit || '.boo';
|
||||
return join(resolve(start), defaultBasename, 'runs');
|
||||
}
|
||||
|
||||
export function findRunsDir(projectRoot?: string): string {
|
||||
return findRunsDirFrom(projectRoot || process.cwd());
|
||||
}
|
||||
|
||||
export function ensureRunsDir(projectRoot?: string): string {
|
||||
const dir = findRunsDir(projectRoot);
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
const gitignorePath = join(dir, '.gitignore');
|
||||
if (!existsSync(gitignorePath)) {
|
||||
writeFileSync(gitignorePath, GITIGNORE_CONTENT, 'utf-8');
|
||||
}
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
export function readCurrentSession(projectRoot?: string): string | null {
|
||||
const path = join(ensureRunsDir(projectRoot), '.current_session');
|
||||
try {
|
||||
return readFileSync(path, 'utf-8').trim();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function writeCurrentSession(sessionId: string, projectRoot?: string): void {
|
||||
writeFileSync(join(ensureRunsDir(projectRoot), '.current_session'), sessionId, 'utf-8');
|
||||
}
|
||||
|
||||
export function clearCurrentSession(projectRoot?: string): void {
|
||||
const path = join(ensureRunsDir(projectRoot), '.current_session');
|
||||
try {
|
||||
writeFileSync(path, '', 'utf-8');
|
||||
} catch {
|
||||
// silent
|
||||
}
|
||||
}
|
||||
|
||||
export function readIndex(projectRoot?: string): IndexFile {
|
||||
const path = join(ensureRunsDir(projectRoot), 'index.json');
|
||||
try {
|
||||
return JSON.parse(readFileSync(path, 'utf-8')) as IndexFile;
|
||||
} catch {
|
||||
return { schema_version: INDEX_SCHEMA_VERSION, entries: [] };
|
||||
}
|
||||
}
|
||||
|
||||
export function writeIndex(index: IndexFile, projectRoot?: string): void {
|
||||
const runsDir = ensureRunsDir(projectRoot);
|
||||
writeFileSync(join(runsDir, 'index.json'), JSON.stringify(index, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
export function updateIndexEntry(entry: IndexEntry, projectRoot?: string): void {
|
||||
const idx = readIndex(projectRoot);
|
||||
const existing = idx.entries.find(e => e.id === entry.id);
|
||||
if (existing) {
|
||||
Object.assign(existing, entry);
|
||||
} else {
|
||||
idx.entries.push({ ...entry });
|
||||
}
|
||||
writeIndex(idx, projectRoot);
|
||||
}
|
||||
|
||||
export function findInProgressSessions(projectRoot?: string): IndexEntry[] {
|
||||
const idx = readIndex(projectRoot);
|
||||
return idx.entries.filter(e => e.status === 'in_progress');
|
||||
}
|
||||
Reference in New Issue
Block a user