import type { IdentifiedFinding, Severity } from "./findings.js"; /** * Program memory: the durable record of what the pipeline concluded and why. * * Two layers with opposite growth behavior: * * - The **journal** (`docs/programs/{id}-memory.jsonl`) is append-only and is * never placed in a prompt. It is the source of truth; every read reduces * it from scratch, so a corrupted or hand-edited view file costs nothing. * - The **view** (`docs/programs/{id}-memory.json`) is the derived current * state — one entry per finding fingerprint regardless of how many runs * re-raised it. It exists for humans and for agents that pull context on * demand; the runner itself always reduces the journal. * * Both live under `docs/programs/` and are committed, deliberately: memory * that dies with a machine or a gitignored directory is what made every run * start blind. Only the runner writes here — agents receive projections in * their briefs and read the view file, exactly the separation the runner * already applies to commits and summaries. * * Model output recorded here is **context, never precedent**: a writer's * decline is a position the next critic must engage, not a settlement. * Only human decisions carry authority, and even those are revocable. */ export declare const MEMORY_SCHEMA_VERSION = 1; export type FindingStatus = "open" | "fix-applied" | "declined" | "resolved" | "waived" | "superseded"; export interface HumanDecision { decision: "waived" | "upheld"; rationale: string; at: string; /** * The convergence input hash at decision time. A waiver passes the gate * mechanically only while this matches the current hash — any spec, plan, * or config edit lapses it, exactly like criteria approval. Durable never * means irreversible: a later decision replaces it. */ scopeHash?: string; } /** One entry in a finding's raise/decline conversation, oldest first. */ export interface Exchange { runId: string; round?: number; action: "raised" | "applied" | "declined" | "decline-accepted" | "downgraded" | "waived" | "human-decision"; severity?: Severity; rationale?: string; at: string; } export interface FindingMemory { /** The latest version raised — re-raises update it, unlike the loop's first-wins ledger. */ finding: IdentifiedFinding; status: FindingStatus; firstRaised: { runId: string; round?: number; at: string; }; exchanges: Exchange[]; raiseCount: number; declineCount: number; lastDeclineReason?: string; humanDecision?: HumanDecision; /** The models deadlocked across runs; a human owns the next move. */ pendingDecision?: { reason: string; runId: string; at: string; }; } export interface RunSummary { runId: string; stage: string; startedAt: string; outcome?: string; result?: string; reason?: string; } export interface CheckpointMemory { status: "safe" | "unsafe"; reason: string; runId: string; at: string; } /** One attempt at a unit of work — `build:WS-03`, `replan`, `author:WS-07`. */ export interface AttemptRecord { runId: string; attempt: number; outcome: "failed" | "succeeded"; reason?: string; /** Bounded output excerpt — the evidence that mattered, never a log path. */ excerpt?: string; failedCommand?: string; at: string; } /** A stage-level structural diagnosis (cycle, unmet requirement, oversize). */ export interface DiagnosisRecord { stage: string; outcome: string; reason: string; detail?: string; runId: string; at: string; } export interface ProgramMemoryView { schemaVersion: typeof MEMORY_SCHEMA_VERSION; programId: string; updatedAt: string; runs: RunSummary[]; findings: Record; checkpoints: Record; attempts: Record; diagnoses: DiagnosisRecord[]; /** Latest plan-audit verdict per success criterion — kept even on PASS. */ criteria: Record; } interface EventBase { at: string; runId: string; } export type MemoryEvent = (EventBase & { kind: "run-started"; stage: string; }) | (EventBase & { kind: "finding-raised"; round?: number; finding: IdentifiedFinding; }) | (EventBase & { kind: "finding-applied"; round?: number; id: string; }) | (EventBase & { kind: "finding-declined"; round?: number; id: string; reason: string; }) | (EventBase & { kind: "decline-accepted"; round?: number; id: string; }) | (EventBase & { kind: "severity-downgraded"; id: string; from: Severity; reason: string; }) | (EventBase & { kind: "checkpoint-assessed"; workstreamId: string; status: "safe" | "unsafe"; reason: string; }) | (EventBase & { kind: "round-completed"; round: number; critic: string; writer?: string; raised: number; fresh: number; applied: number; rejected: number; criticSummary?: string; writerSummary?: string; }) | (EventBase & { kind: "loop-finished"; stage: string; outcome: string; result: string; reason?: string; waivedFindings?: string[]; }) | (EventBase & { kind: "human-decision"; id: string; decision: "waived" | "upheld"; rationale: string; scopeHash?: string; }) | (EventBase & { kind: "decision-requested"; id: string; reason: string; }) | (EventBase & { kind: "criterion-assessed"; criterionId: string; status: string; reason: string; }) | (EventBase & { kind: "attempt-recorded"; unit: string; attempt: number; outcome: "failed" | "succeeded"; reason?: string; excerpt?: string; failedCommand?: string; }) | (EventBase & { kind: "stage-diagnosis"; stage: string; outcome: string; reason: string; detail?: string; }); type DistributeInput = T extends unknown ? Omit : never; /** A memory event minus the envelope the recorder stamps on every entry. */ export type MemoryEventInput = DistributeInput; export declare function memoryJournalPath(root: string, programId: string): string; export declare function memoryViewPath(root: string, programId: string): string; /** * Fold the journal into the current state. Deliberately dumb: status * transitions only, no gate semantics — the loop decides what passes, memory * records what the loop decided. */ export declare function reduceMemoryEvents(programId: string, events: readonly MemoryEvent[]): ProgramMemoryView; /** Parse the journal, skipping malformed lines rather than failing the read. */ export declare function readMemoryJournal(root: string, programId: string): Promise; /** The current state, reduced from the journal (the view file is never trusted). */ export declare function readProgramMemory(root: string, programId: string): Promise; /** * Append events to the journal and refresh the derived view. The view write * is best-effort — the journal is the record; the view can always be rebuilt. */ export declare function appendMemoryEvents(root: string, programId: string, events: readonly MemoryEvent[]): Promise; /** * The brief projection: findings from prior runs a critic must know about. * Constraints are pushed, evidence is pulled — this carries the conclusion * and rationale of each unsettled or human-decided finding, and the brief * points at the view file for everything else. */ export interface PriorRunFinding { finding: IdentifiedFinding; status: FindingStatus; raiseCount: number; declineCount: number; lastDeclineReason?: string; humanDecision?: HumanDecision; } export declare function priorRunFindings(view: ProgramMemoryView, options?: { excludeRunId?: string; }): PriorRunFinding[]; export interface PendingDecision { id: string; finding: IdentifiedFinding; reason: string; lastDeclineReason?: string; } /** Findings waiting on a human `decide` call. */ export declare function pendingDecisions(view: ProgramMemoryView): PendingDecision[]; /** The unit's most recent attempt, when that attempt failed — the state a * resumed run must not rediscover from scratch. */ export declare function lastFailedAttempt(view: ProgramMemoryView, unit: string): AttemptRecord | undefined; export declare function countByStatus(view: ProgramMemoryView): Record; export {}; //# sourceMappingURL=program-memory.d.ts.map