/** * * Generalizes the original `decision-writer.ts` (which only knew how to * persist a `RedLineAudit` snapshot) into a polymorphic writer that * handles the 4 artifact types produced during a peaks-loop audit: * * 1. **decision** — `RedLineAudit` snapshot → audit-decisions/.md * 2. **prompt** — methodology input (LLM-facing text) → audit-prompts/.md * 3. **machine-output** — JSON audit result → audit--.md (envelope) * 4. **narrative** — free-form prose report → .md (top level) * * Each writer enforces canonical frontmatter (`name` / `description` / * `metadata.type` ∈ ProjectMemoryKind / `metadata.artifactType`) so the * `.peaks/memory/` shape stays compliant with `memory-shape-guard.test.ts`. * * Back-compat: `writeAuditDecision` in `decision-writer.ts` re-exports * `writeDecision` so existing callers of `peaks audit static --record` * keep working unchanged. * * Design notes (Karpathy #1 Think Before Coding): * * - **Why 4 writers instead of 1 polymorphic writer?** Each artifact * type has a distinct subdir + envelope semantics. A single writer * with a kind-flag would need conditional logic on every field. Four * named writers are 4 × ~30 lines each vs. 1 × ~150 line monolith * with nested if-trees. The cost of polymorphism here is lower * clarity; the cost of 4 small writers is one extra import line at * the call site. * * - **Why a strict frontmatter contract on every writer?** The memory * parser (`parseStoredMemoryFile` in `project-memory-service.ts`) * silently ignores unknown frontmatter keys. Adding `metadata.artifactType` * is therefore safe — it's additive, never breaks existing readers. * But adding it consistently across all 4 writers means new files * can be queried by artifactType in future tooling. * * - **Why JSON envelope for machine-output?** Raw `.json` files at the * top of `.peaks/memory/` are forbidden by the memory-shape guard. * Wrapping JSON in a `.md` envelope (fenced ```json block) keeps * the original bytes byte-for-byte intact while restoring compliance. * The envelope also adds a human-readable H1 + summary table. * * - **Out of scope:** index.json generation, warm-layer extraction, hot * layer routing — the existing memory service handles those. We only * write files; the service indexes them on next read. */ import type { RedLineAudit } from './types.js'; export type ArtifactKind = 'decision' | 'prompt' | 'machine-output' | 'narrative'; export type ArtifactType = 'decision' | 'prompt' | 'machine-output' | 'narrative'; export interface ArtifactWriterOptions { readonly projectRoot: string; /** ISO date (YYYY-MM-DD). Defaults to today (UTC). */ readonly date?: string; /** Optional request id, used to disambiguate multiple artifacts on the same day. */ readonly rid?: string; /** Override the auto-derived slug. Must be filename-safe (caller's responsibility). */ readonly slugOverride?: string; /** Skip writing the file — used by tests + --dry-run. */ readonly dryRun?: boolean; } export interface ArtifactWriteRecord { readonly kind: ArtifactKind; readonly slug: string; readonly title: string; readonly date: string; readonly filePath: string; readonly memoryDir: string; readonly indexPath: string; readonly indexSynced: boolean; } export declare function writeDecision(audit: RedLineAudit, options: ArtifactWriterOptions): ArtifactWriteRecord; export interface PromptInput { readonly name: string; readonly description: string; readonly body: string; } export declare function writePrompt(input: PromptInput, options: ArtifactWriterOptions): ArtifactWriteRecord; export interface MachineOutputInput { readonly name: string; readonly description: string; /** JSON string OR { path } to a JSON file on disk. */ readonly json: string | { path: string; }; } export declare function writeMachineOutput(input: MachineOutputInput, options: ArtifactWriterOptions): ArtifactWriteRecord; export interface NarrativeInput { readonly name: string; readonly description: string; readonly body: string; } export declare function writeNarrative(input: NarrativeInput, options: ArtifactWriterOptions): ArtifactWriteRecord;