/** * E2 project_brief first-class object * (docs/plans/2026-05-30-e2-project-brief-object.md). * * A `project_brief` is the living, repo-scoped summary of a repository's state: a * `summary` body scoped to a `repo`, evolving via the supersede delta lifecycle. * "Auto-refreshes from receipts" is scoped to a DETERMINISTIC (no-LLM) assembler: * `refreshBrief` gathers the repo's recent receipts (memory rows tagged * `path:`) and assembles them into the brief body. The distinguishing * capability is therefore the refresh assembler (analog of skill's export * renderer), not an LLM/async pipeline (deferred). * * Reuses the skill/process supersede machinery verbatim (superseded_by self-FK + * CAS + INSERT-preflight + server-derived version + change_summary + supersede * tenant-match trigger). It DROPS skill's `skill_name`/`trigger_text` and ADDS * `repo` (the repo-scoping dimension) + `summary` (the brief body). * * The `project_briefs` table is the source of truth (survives memory decay); the * memory mirror is for recall. memory_id is NULLABLE with ON DELETE SET NULL. * * Lifecycle: active -> superseded (a newer version replaces it) or active -> * closed (retired). */ export type BriefStatus = 'active' | 'superseded' | 'closed'; export declare const VALID_BRIEF_STATES: ReadonlySet; /** Field caps (untrusted at the HTTP/SDK boundary). summary is a body, so a larger * cap than the 4096 short-field convention. */ export declare const MAX_REPO_LEN = 256; export declare const MAX_BRIEF_SUMMARY_LEN = 8192; export declare const MAX_CHANGE_SUMMARY_LEN = 4096; /** Bound the receipts gathered per refresh (a refresh reads memories; cap the scan * + the rendered body). Realistic repos have far fewer recent receipts than this. */ export declare const MAX_BRIEF_RECEIPTS = 50; /** Truncate each receipt's headline in the assembled digest. */ export declare const MAX_RECEIPT_HEADLINE_LEN = 200; export interface ProjectBrief { id: number; /** Nullable: ON DELETE SET NULL lets memory deletion proceed without breaking * the brief row. */ memoryId: string | null; tenantId: string; /** The repo identifier this brief is scoped to (e.g. `hippo`). */ repo: string; /** The brief body. */ summary: string; /** Server-derived: 1 on a fresh create, predecessor.version + 1 on supersede. */ version: number; status: BriefStatus; supersededBy: number | null; supersededAt: string | null; /** The per-version delta note; set on a successor row only (NULL on a v1). */ changeSummary: string | null; closedAt: string | null; createdAt: string; } export interface SaveProjectBriefOpts { repo: string; summary: string; /** The delta note for a supersession; ignored (stored NULL) on a fresh create. */ changeSummary?: string; /** Table id of an ACTIVE brief this new version supersedes. */ supersedesBriefId?: number; /** Extra memory tags merged after ['project_brief']. */ extraTags?: string[]; /** Internal: set by refreshBrief to the receipt count so the audit metadata can * mark the write as an auto-refresh (vs a manual supersede) WITHOUT a 4th audit * op. Not part of the public CLI/HTTP surface. */ refreshReceiptCount?: number; } export interface ListProjectBriefsOpts { status?: BriefStatus; /** Filter to a single repo. */ repo?: string; limit?: number; } /** * Create a project_brief (or a new version that supersedes an existing one). Writes * the memory mirror + the project_briefs row atomically inside writeEntry's * SAVEPOINT. When supersedesBriefId is given, the referenced ACTIVE row is * preflighted (status + version) BEFORE the INSERT, then CAS-UPDATEd -> superseded * in the same SAVEPOINT; the new version = predecessor.version + 1 (server-derived). */ export declare function saveProjectBrief(hippoRoot: string, tenantId: string, opts: SaveProjectBriefOpts, actor?: string): ProjectBrief; /** * Close (retire) an active brief. CAS guard WHERE status='active'; 0 changes * distinguishes not-found from not-active. A superseded row is terminal. */ export declare function closeProjectBrief(hippoRoot: string, tenantId: string, id: number, actor?: string): ProjectBrief; export declare function loadProjectBriefById(hippoRoot: string, tenantId: string, id: number): ProjectBrief | null; export declare function loadProjectBriefs(hippoRoot: string, tenantId: string, opts?: ListProjectBriefsOpts): ProjectBrief[]; /** * The repo's CURRENT active brief, or null. By convention there is one active brief * per (tenant, repo); if an operator created more than one (the DB does not prevent * it, consistent with every other E2 object), the MOST-RECENT active row wins. */ export declare function loadActiveBriefForRepo(hippoRoot: string, tenantId: string, repo: string): ProjectBrief | null; /** * Assemble the repo's recent receipts into a deterministic markdown digest, and * return it WITH the receipt count (the count feeds refreshBrief's change_summary + * audit metadata). NO LLM. Always returns a non-empty, valid summary (a brief * `summary` is NOT NULL), including the zero-receipts case. * * A "receipt" = a tenant memory row carrying the repo's `path:` tag. The * brief's OWN memory mirror (source='project_brief') is excluded so a brief never * becomes its own receipt on the next refresh. The match is against the JSON-array * serialization (each element is a double-quoted string `"path:hippo"`); the * surrounding quotes are load-bearing — they stop `hip` matching `path:hippo`. * `repo` is LIKE-escaped + parameterized (operator-supplied; security.md). */ export declare function assembleBriefFromReceipts(hippoRoot: string, tenantId: string, repo: string): { markdown: string; receiptCount: number; }; /** * Auto-refresh the repo's brief from its receipts: assemble the digest, then create * a new version. If the repo already has an active brief it is superseded (the * change_summary records the auto-refresh + the audit metadata carries * `refreshed: true`); otherwise a v1 is created. Returns the new brief. * * The assemble (a read of `memories`) happens BEFORE writeEntry opens its SAVEPOINT; * a concurrent receipt write landing between the read and the brief write simply * appears in the NEXT refresh — the brief is a derived snapshot, not a transactional * aggregate, so no consistency invariant is violated. */ export declare function refreshBrief(hippoRoot: string, tenantId: string, repo: string, actor?: string): ProjectBrief; //# sourceMappingURL=project-briefs.d.ts.map