/**
* memory-file-projection.ts, standing memory records as GIT-BACKED markdown
* files that ROUND-TRIP through the confirmation-gated mutation path.
*
* The platform's memory should be inspectable and correctable as files. This
* module projects standing (project/team-scope) MemoryRecords to one markdown
* file per record under a git-backed directory, and reads user edits BACK, but
* never as a silent write to the store. A user edit or deletion becomes a
* PROPOSAL (a review-queue entry); the store is mutated only for proposals the
* caller explicitly confirms, through the registry's own update/delete. The disk
* file is a correction surface, not a second source of truth.
*
* Temporal validity (validFrom/validUntil) is projected into each file's
* front-matter and its live status is labelled, an expired record is shown as
* `status: expired`, not silently dropped from the projection.
*/
import type { MemoryClass, MemoryRecord, MemoryScope, MemoryTemporalStatus } from './memory-store.js';
export interface MemoryProjectionOptions {
/** Restrict projection to these scopes; defaults to project + team. */
readonly scopes?: readonly MemoryScope[];
}
/**
* Optional git seam: durably commit the projected files. Injectable for tests.
*
* The projection only ever commits to a repository it OWNS, one whose
* toplevel is the projection directory itself, never a repository the
* directory merely sits inside. `projectMemoryToFiles` enforces that policy:
* it resolves the enclosing toplevel first, and when the directory is not its
* own repository root (nested in a foreign checkout, or not in any repository
* at all) it initializes a repository AT the projection directory before
* staging. Without this, a projection under a temp/scratch path nested inside
* a real checkout would commit into that checkout.
*/
export interface MemoryProjectionGit {
/**
* The toplevel directory git would resolve for `dir` (`git -C
* rev-parse --show-toplevel`), or null when no repository encloses it.
*/
resolveToplevel(dir: string): string | null;
/** Initialize a repository whose root is exactly `dir`. */
init(dir: string): void;
add(dir: string): void;
commit(dir: string, message: string): void;
}
/**
* Production git seam for the memory projection: shells out to the system
* git, commits with a neutral projection identity (never the operator's),
* and tolerates an unchanged tree.
*/
export declare function createMemoryProjectionGit(): MemoryProjectionGit;
/** The parsed content of one projected markdown file. */
export interface MemoryProjectionFile {
readonly id: string;
readonly path: string;
readonly scope?: MemoryScope | undefined;
readonly cls?: MemoryClass | undefined;
readonly summary: string;
readonly detail?: string | undefined;
readonly tags: readonly string[];
readonly validFrom?: number | undefined;
readonly validUntil?: number | undefined;
}
export interface MemoryProjectionWriteReport {
readonly dir: string;
readonly written: readonly string[];
readonly committed: boolean;
}
/** Render one record as a markdown file body (front-matter + content). */
export declare function projectMemoryRecordToMarkdown(record: MemoryRecord, now?: number): string;
/**
* Project standing records to `/.md`. Writes one file per in-scope
* record. When a git seam is supplied, stages + commits the directory so the
* projection is durable and diffable. Never deletes existing files (a store
* deletion is surfaced as a proposal on the next diff, not a silent unlink).
*/
export declare function projectMemoryToFiles(records: readonly MemoryRecord[], dir: string, options?: MemoryProjectionOptions & {
readonly now?: number;
readonly git?: MemoryProjectionGit;
}): MemoryProjectionWriteReport;
/** Parse one projected markdown file. Returns null when it has no `id` front-matter key. */
export declare function parseProjectedMemoryFile(path: string, content: string): MemoryProjectionFile | null;
/**
* One entry in the LIVE memory projection (computed from the store's standing
* records, not read from disk), the shape the memory.projections.* wire verbs
* expose. `status` is the record's live temporal status (active / expired /
* pending), so an expired record is visibly labelled rather than silently
* dropped, exactly as the file projection labels it.
*/
export interface MemoryProjectionEntry {
readonly id: string;
/** The `.md` filename the file projection would use, a stable per-record handle. */
readonly filename: string;
readonly scope: MemoryScope;
readonly cls: MemoryClass;
readonly summary: string;
readonly tags: readonly string[];
readonly confidence: number;
readonly reviewState: string;
readonly validFrom?: number | undefined;
readonly validUntil?: number | undefined;
readonly status: MemoryTemporalStatus;
}
/**
* The live projection of standing (project/team) memory records, one metadata
* entry per record, oldest first. Does not touch disk. Session-scope records are
* excluded (they are not standing memory), matching the file projection's own
* scope selection.
*/
export declare function listMemoryProjections(records: readonly MemoryRecord[], options?: MemoryProjectionOptions & {
readonly now?: number;
}): MemoryProjectionEntry[];
/**
* The live projection of ONE standing record by id: its metadata entry plus the
* exact markdown the file projection would write. Returns null when no standing
* record has that id (a session-scope or unknown id is an honest miss, not an
* empty projection).
*/
export declare function getMemoryProjection(records: readonly MemoryRecord[], id: string, options?: MemoryProjectionOptions & {
readonly now?: number;
}): {
readonly entry: MemoryProjectionEntry;
readonly markdown: string;
} | null;
/** Read + parse every `*.md` in the projection directory. */
export declare function readProjectedMemoryFiles(dir: string): MemoryProjectionFile[];
export type MemoryProjectionProposalKind = 'update' | 'delete';
/**
* A proposed change from the projection round-trip, a review-queue entry, NOT a
* write. The caller confirms (or not) each entry; only confirmed entries mutate
* the store, through the registry's own update/delete.
*/
export interface MemoryProjectionProposal {
readonly kind: MemoryProjectionProposalKind;
readonly id: string;
/** Honest human-readable reason this change is proposed. */
readonly reason: string;
/** For 'update': the record fields the edited file would change. */
readonly changedFields?: readonly string[] | undefined;
/** The desired field values parsed from the file (for 'update'). */
readonly desired?: {
readonly scope?: MemoryScope | undefined;
readonly summary?: string | undefined;
readonly detail?: string | undefined;
readonly tags?: readonly string[] | undefined;
readonly validFrom?: number | null | undefined;
readonly validUntil?: number | null | undefined;
} | undefined;
}
/**
* Diff the current record set against the projected files and produce proposals.
* PURE, no I/O. An edited file whose fields differ from its record yields an
* `update` proposal; an in-scope record with NO file yields a `delete` proposal
* (the user removed the file); a file with no matching record is ignored (its
* record is already gone). Nothing here mutates the store.
*/
export declare function diffProjectionToProposals(records: readonly MemoryRecord[], files: readonly MemoryProjectionFile[], options?: MemoryProjectionOptions): MemoryProjectionProposal[];
/** The registry surface the projection apply needs, update + delete only. */
export interface MemoryProjectionRegistry {
update(id: string, patch: {
scope?: MemoryScope;
summary?: string;
detail?: string;
tags?: string[];
validFrom?: number | null;
validUntil?: number | null;
}): MemoryRecord | null;
delete(id: string): boolean;
}
export interface MemoryProjectionApplyReceipt {
readonly applied: readonly MemoryProjectionProposal[];
/** Proposals the caller did NOT confirm, left untouched (the gate). */
readonly skipped: readonly MemoryProjectionProposal[];
/** Proposals confirmed but whose store mutation returned no record / false. */
readonly failed: readonly MemoryProjectionProposal[];
}
/**
* Apply ONLY the proposals the caller confirms, through the registry's own
* update/delete. This is the confirmation gate: a proposal is never applied
* unless `confirm(proposal)` returns true, a file edit can never become a
* silent store write. Unconfirmed proposals are recorded as skipped.
*/
export declare function applyMemoryProjectionProposals(registry: MemoryProjectionRegistry, proposals: readonly MemoryProjectionProposal[], options: {
readonly confirm: (proposal: MemoryProjectionProposal) => boolean;
}): MemoryProjectionApplyReceipt;
//# sourceMappingURL=memory-file-projection.d.ts.map