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.
112 lines
3.1 KiB
TypeScript
112 lines
3.1 KiB
TypeScript
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');
|
|
}
|