import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { ensureRunsDir } from './runs-dir.js'; export type GuidelineId = string; export type TagId = string; export type Criticality = 'low' | 'medium' | 'high'; export type GuidelineDocumentVersion = string; export interface GuidelineContent { condition: string; action: string | null; description: string | null; } export interface Guideline { id: GuidelineId; creationUtc: string; content: GuidelineContent; enabled: boolean; tags: TagId[]; labels: string[]; metadata: Record; criticality: Criticality; title: string | null; priority: number; } export interface GuidelineDocument { id: string; version: GuidelineDocumentVersion; creation_utc: string; condition: string; action: string | null; description: string | null; title: string | null; criticality: string; enabled: boolean; metadata: Record; labels: string[]; priority: number; } export interface GuidelineUpdateParams { condition?: string; action?: string | null; description?: string | null; title?: string | null; criticality?: Criticality; enabled?: boolean; priority?: number; } function generateId(): string { const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; let result = ''; for (let i = 0; i < 10; i++) { result += chars[Math.floor(Math.random() * chars.length)]; } return result; } function dbPath(projectRoot?: string): string { const dir = join(ensureRunsDir(projectRoot), '..', 'guidelines'); if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); return join(dir, 'guidelines.json'); } function readDb(projectRoot?: string): GuidelineDocument[] { const path = dbPath(projectRoot); try { return JSON.parse(readFileSync(path, 'utf-8')) as GuidelineDocument[]; } catch { return []; } } function writeDb(docs: GuidelineDocument[], projectRoot?: string): void { writeFileSync(dbPath(projectRoot), JSON.stringify(docs, null, 2), 'utf-8'); } function toDocument(g: Guideline): GuidelineDocument { return { id: g.id, version: '0.11.0', creation_utc: g.creationUtc, condition: g.content.condition, action: g.content.action, description: g.content.description, title: g.title, criticality: g.criticality, enabled: g.enabled, metadata: g.metadata, labels: g.labels, priority: g.priority, }; } function fromDocument(d: GuidelineDocument): Guideline { return { id: d.id, creationUtc: d.creation_utc, content: { condition: d.condition, action: d.action ?? null, description: d.description ?? null, }, title: d.title ?? null, criticality: (d.criticality || 'medium') as Criticality, enabled: d.enabled ?? true, tags: [], labels: d.labels ?? [], metadata: d.metadata ?? {}, priority: d.priority ?? 0, }; } export class GuidelineDocumentStore { createGuideline(params: { condition: string; action?: string | null; description?: string | null; title?: string | null; criticality?: Criticality; enabled?: boolean; labels?: string[]; priority?: number; id?: GuidelineId; }, projectRoot?: string): Guideline { const docs = readDb(projectRoot); const id = params.id || `gl_${generateId()}`; if (docs.find(d => d.id === id)) { throw new Error(`Guideline with id '${id}' already exists`); } const guideline: Guideline = { id, creationUtc: new Date().toISOString(), content: { condition: params.condition, action: params.action ?? null, description: params.description ?? null, }, title: params.title ?? null, criticality: params.criticality ?? 'medium', enabled: params.enabled ?? true, tags: [], labels: params.labels ?? [], metadata: {}, priority: params.priority ?? 0, }; docs.push(toDocument(guideline)); writeDb(docs, projectRoot); return guideline; } listGuidelines(params?: { tags?: TagId[]; labels?: string[]; }, projectRoot?: string): Guideline[] { let docs = readDb(projectRoot); if (params?.tags && params.tags.length > 0) { const tagSet = new Set(params.tags); docs = docs.filter(d => d.metadata['tags'] && Array.isArray(d.metadata['tags']) && (d.metadata['tags'] as string[]).some(t => tagSet.has(t))); } if (params?.labels && params.labels.length > 0) { const labelSet = new Set(params.labels); docs = docs.filter(d => { const gl = fromDocument(d); return params.labels!.every(l => gl.labels.includes(l)); }); } return docs.map(fromDocument); } readGuideline(id: GuidelineId, projectRoot?: string): Guideline { const docs = readDb(projectRoot); const doc = docs.find(d => d.id === id); if (!doc) throw new Error(`Guideline '${id}' not found`); return fromDocument(doc); } updateGuideline(id: GuidelineId, params: GuidelineUpdateParams, projectRoot?: string): Guideline { const docs = readDb(projectRoot); const idx = docs.findIndex(d => d.id === id); if (idx === -1) throw new Error(`Guideline '${id}' not found`); const doc = docs[idx]!; if (params.condition !== undefined) doc.condition = params.condition; if (params.action !== undefined) doc.action = params.action; if (params.description !== undefined) doc.description = params.description; if (params.title !== undefined) doc.title = params.title; if (params.criticality !== undefined) doc.criticality = params.criticality; if (params.enabled !== undefined) doc.enabled = params.enabled; if (params.priority !== undefined) doc.priority = params.priority; docs[idx] = doc; writeDb(docs, projectRoot); return fromDocument(doc); } deleteGuideline(id: GuidelineId, projectRoot?: string): void { const docs = readDb(projectRoot); const idx = docs.findIndex(d => d.id === id); if (idx === -1) throw new Error(`Guideline '${id}' not found`); docs.splice(idx, 1); writeDb(docs, projectRoot); } findGuideline(content: GuidelineContent, projectRoot?: string): Guideline { const docs = readDb(projectRoot); const doc = docs.find(d => d.condition === content.condition && (content.action === undefined || d.action === content.action), ); if (!doc) throw new Error(`Guideline not found for condition='${content.condition}'`); return fromDocument(doc); } upsertLabels(id: GuidelineId, labels: string[], projectRoot?: string): Guideline { const docs = readDb(projectRoot); const idx = docs.findIndex(d => d.id === id); if (idx === -1) throw new Error(`Guideline '${id}' not found`); const doc = docs[idx]!; const current = new Set(doc.labels || []); for (const l of labels) current.add(l); doc.labels = [...current]; writeDb(docs, projectRoot); return fromDocument(doc); } removeLabels(id: GuidelineId, labels: string[], projectRoot?: string): Guideline { const docs = readDb(projectRoot); const idx = docs.findIndex(d => d.id === id); if (idx === -1) throw new Error(`Guideline '${id}' not found`); const doc = docs[idx]!; const removeSet = new Set(labels); doc.labels = (doc.labels || []).filter(l => !removeSet.has(l)); writeDb(docs, projectRoot); return fromDocument(doc); } }