import { Database } from 'bun:sqlite'; import { SchemaManager } from '../schema.js'; import type { ContextualLinkResolution } from '../link-health/types.js'; import type { NoteKind, NoteStatus, Lifecycle } from '../types.js'; import type { ConformanceRecord, ConformanceAggregates } from '../template-handler.js'; import { type VisibilityOptions } from '../knowledge-scope.js'; import { type ScreeningSnapshot } from '../reviewed-storage.js'; import { TOOL_DEFINITIONS } from '../tool-meta.js'; export declare function normalizeWikilinkPath(relativePath: string): string; export declare class LifecycleViolationError extends Error { constructor(message: string); } export interface NoteMetadata { id: string; path: string; title: string; kind: NoteKind; status: NoteStatus; lifecycle: Lifecycle; type: 'atomic' | 'moc'; tags: string[]; content: string; summary?: string; guidance?: string; context?: string; updated_at: number; created_at: number; word_count: number; access_count?: number; last_accessed_at?: number; related_notes?: string[]; backlinks_count?: number; } export interface NoteLink { source_id: string; target_id: string; link_text: string; created_at: number; } export interface StoreResult { action: 'created' | 'updated' | 'archived' | 'removed'; path: string; id: string; previousPath?: string; } export interface KnowledgeMutationContext { getScreeningSnapshot(visibility: VisibilityOptions): ScreeningSnapshot; hydrateScreeningCanonicalHashes(snapshot: ScreeningSnapshot, noteIds: readonly string[]): ScreeningSnapshot; getByIdVisible(id: string, visibility: VisibilityOptions): NoteMetadata | null; getDomainNote(project: string): NoteMetadata | null; store(contentOrOptions: string | (StoreOptions & { content?: string; }), optionsArg?: StoreOptions): StoreResult; } export interface StoreOptions { title?: string; kind?: NoteKind; status?: NoteStatus; lifecycle?: Lifecycle; type?: 'atomic' | 'moc'; tags?: string[]; summary?: string; guidance?: string; context?: string; existingId?: string; expectedCanonicalFileHash?: string; related?: string[]; extraFrontmatter?: Record; } type CanonicalToolName = typeof TOOL_DEFINITIONS[number]['name']; export type TelemetryToolName = CanonicalToolName extends `knowledge-${infer Name}` ? Name : never; export declare const TELEMETRY_TOOL_NAMES: readonly TelemetryToolName[]; export declare const CANONICAL_TELEMETRY_CLIENTS: readonly ["pi", "claude-code", "opencode", "cursor", "windsurf", "zed", "omp", "other"]; export type CanonicalTelemetryClient = typeof CANONICAL_TELEMETRY_CLIENTS[number]; export declare function normalizeTelemetryClient(client: string | undefined | null): CanonicalTelemetryClient; export declare function normalizeTelemetryClientVersion(version: string | null | undefined): string | null; /** Keep model dimensions useful without retaining arbitrary caller-provided strings. */ export declare function normalizeTelemetryModel(model: string | undefined): string | undefined; export interface UnreportedSession { session_id: string; client: string; client_version: string | null; started_at: number; ended_at: number | null; vault_size: number; version: string; os_platform: string; tool_counts: Record; total_invocations: number; models: string[]; } export interface TelemetryAggregates { sessions: number; searches: number; stores: number; maintains: number; mines: number; storesByKind: Record; maintainByAction: Record; sessionDurations: number[]; /** * Contextual link-health scan usage: `runs` counts `unlinked`, * `broken-links`, and `link-health` maintain rows with a non-null * `result_count`; `excludedCandidates` sums that `result_count` (excluded * contextual candidates). No note-level data is aggregated. */ contextualLinkScans: { runs: number; excludedCandidates: number; }; } export interface TelemetryRow { session_id: string; tool_name: TelemetryToolName; arg_kind: string | null; timestamp: number; result_count: number | null; model: string | null; } export declare function shouldRecoverStaleLock(input: { pidAlive: boolean; recordedIdentity?: string; currentIdentity?: string; }): boolean; export declare function processStartIdentity(pid: number, options?: { platform?: NodeJS.Platform; execFile?: (file: string, args: readonly string[]) => string; }): string | undefined; export declare class KnowledgeMutationBusyError extends Error { constructor(); } /** Why canonical inventory cannot safely account for a file — never a path or content. */ export type UnindexedCanonicalReason = 'unindexed' | 'unreadable' | 'metadata-drift' | 'traversal-incomplete'; export interface DuplicateAuditSnapshot { readonly notes: Array; readonly indexedSnapshotUnsafe: boolean; readonly omissions: Readonly>; readonly uncertaintyReasons: Readonly>; } export declare class NoteRepository { protected db: Database; protected docsPath: string; protected dbPath: string; protected schemaManager: SchemaManager; private readonly sessionId; private readonly telemetryEnabled; private readonly mutationLockOwners; private readonly mutationLockContext; private indexedCanonicalMetadata; private baselineState; private canonicalMetadataVaultKey; constructor(docsPath?: string, options?: { telemetryEnabled?: boolean; readonly?: boolean; }); private attachCanonicalMetadataState; private detachCanonicalMetadataState; private canonicalMetadata; private canonicalBaselinePath; private markBaselineUnavailable; private writeCanonicalBaseline; private updateCanonicalMetadataBaseline; private removeCanonicalMetadataBaseline; private refreshIndexedCanonicalMetadata; /** * Identity-only inventory of canonical vault Markdown files that no indexed * row accounts for, plus whether traversal itself was incomplete. Real * filesystem identities are compared so a symlink alias of an indexed note * is not a false positive, and generated structural files without * identifiers are excluded, matching rebuild's convention. No path, title, * or file content ever leaves this method. */ private scanUnindexedCanonicalFiles; /** * Query-only identity list of canonical Markdown files the index does not * account for, so a reader can surface them as read failures instead of * silently reviewing an incomplete document set. An incomplete traversal is * itself reported as one entry. */ getUnindexedCanonicalDocuments(): Array<{ id: string; reason: UnindexedCanonicalReason; }>; private initializeSchema; private selfHealIfNeeded; private generateId; private incrementTimestamp; private formatTimestamp; private slugify; private countWords; private extractTitle; protected buildFrontmatter(metadata: Partial & { extraFrontmatter?: Record; }): string; private static readonly MANAGED_SECTIONS; private static readonly REWRITE_MANAGED_FRONTMATTER_KEYS; protected buildNoteBody(options: { content: string; guidance?: string; context?: string; relatedIds?: string[]; relatedContent?: string; }): string; /** * Wiki-link for a relation target. Unresolvable IDs keep a bare link so an * unreadable or removed target never drops the recorded relation. */ private renderRelationLink; protected parseBodySections(bodyAfterTitle: string): { summary: string; guidance: string; context: string; related: string; content: string; }; private isManagedSection; private computeUpLink; private computeAlias; protected buildNavBreadcrumb(kind: string, tags: string[]): string; private static readonly NAV_PATTERN; private static readonly TITLE_PATTERN; private extraFrontmatterForRewrite; protected stripNavBreadcrumb(body: string): string; protected parseFrontmatter(content: string): { frontmatter: Record; body: string; }; protected sanitizeFTS5Query(query: string): string; private ftsInsert; private ftsDelete; private ftsUpdate; store(contentOrOptions: string | (StoreOptions & { content?: string; }), optionsArg?: StoreOptions): StoreResult; /** * Runs final reviewed validation and its canonical write under one vault-wide lock. * Synchronous callers fail fast when another same-process operation owns the lock. */ withKnowledgeMutationLock(operation: (context: KnowledgeMutationContext) => T): T; withKnowledgeMutationLockAsync(operation: (context: KnowledgeMutationContext) => Promise): Promise; private knowledgeMutationContext; private assertLeaseActive; private mutationLockGateKey; private lockState; private acquireInProcessSyncOrFail; private acquireInProcessAsync; private releaseInProcess; private storeUnlocked; private visibilityPredicate; search(query: string, options?: { status?: NoteStatus; kind?: NoteKind; tags?: string[]; context?: string; lifecycle?: string; excludeStructuralKinds?: boolean; limit?: number; visibility?: VisibilityOptions; }): NoteMetadata[]; /** * Search notes by vector similarity using cosine distance. * Loads embeddings from DB and computes similarity in pure TS. * Returns notes sorted by similarity score (highest first). */ searchVector(queryEmbedding: number[], options?: { status?: NoteStatus; kind?: NoteKind; tags?: string[]; lifecycle?: string; excludeStructuralKinds?: boolean; limit?: number; visibility?: VisibilityOptions; }): Array; /** Counts the exact hybrid candidate union without hydrating note content or embeddings. */ countHybridMatches(query: string, hasQueryEmbedding: boolean, options?: { status?: NoteStatus; kind?: NoteKind; tags?: string[]; lifecycle?: string; excludeStructuralKinds?: boolean; excludeId?: string; visibility?: VisibilityOptions; }): number; /** * Hybrid search: combines FTS5 keyword results with vector similarity results. * Uses Reciprocal Rank Fusion (RRF) to merge rankings. */ searchHybrid(query: string, queryEmbedding: number[] | null, options?: { status?: NoteStatus; kind?: NoteKind; tags?: string[]; context?: string; lifecycle?: string; excludeStructuralKinds?: boolean; limit?: number; visibility?: VisibilityOptions; }): NoteMetadata[]; /** Copies all persisted screening inputs in one SQLite read transaction. */ getScreeningSnapshot(visibility: VisibilityOptions): ScreeningSnapshot; /** Reads canonical bytes only for notes selected by DB-based screening. */ hydrateScreeningCanonicalHashes(snapshot: ScreeningSnapshot, noteIds: readonly string[]): ScreeningSnapshot; private acquireMutationLock; private acquireMutationLockAsync; private recoverStaleMutationLock; private releaseMutationLock; /** * Store an embedding for a note. Called after store() when embedding is available. */ storeEmbedding(noteId: string, embedding: number[], model: string): boolean; private storeEmbeddingUnlocked; updateContentHash(noteId: string, hash: string): void; /** Persist semantic metadata only if the note still has the expected source. */ persistSemanticMetadataIfCurrent(noteId: string, expected: { title: string; summary: string; content: string; }, contentHash: string, embedding?: { values: number[]; model: string; }): boolean; /** Parse only canonical eligibility metadata, failing closed on malformed frontmatter. */ private canonicalDedupeEligibility; /** Query-only canonical input and coverage evidence for one duplicate audit invocation. */ getDuplicateAuditResult(): DuplicateAuditSnapshot; /** Query-only indexed rows retained for callers that do not need coverage evidence. */ getDuplicateAuditSnapshot(): Array; getNotesWithoutContentHash(limit?: number): NoteMetadata[]; findNearDuplicates(hash: string, threshold?: number, visibility?: VisibilityOptions): NoteMetadata[]; findSimHashDuplicates(threshold?: number): Map; getAllContentHashes(): Array<{ id: string; hash: string; }>; /** * Get IDs of notes that don't have embeddings yet. */ getNotesWithoutEmbeddings(limit?: number): Array<{ id: string; title: string; summary: string; content: string; }>; /** * Get embedding stats for maintenance reporting. */ getEmbeddingStats(project?: string, client?: string): { total: number; withEmbedding: number; withoutEmbedding: number; models: Record; }; /** * Look up notes by exact tag match using SQL LIKE on the JSON tags column. * More reliable than FTS5 search + post-filter for tag-based lookups. */ getByTag(tag: string, limit?: number, visibility?: VisibilityOptions): NoteMetadata[]; getDomainNote(project: string): NoteMetadata | null; getIndexNote(project: string): NoteMetadata | null; getLogNote(project: string): NoteMetadata | null; getProjectNotes(project: string): NoteMetadata[]; getAllProjects(): string[]; getProjectStats(): Array<{ project: string; noteCount: number; lastActive: number; }>; /** Count unique notes that have at least one project tag (non-archived). */ getScopedNoteCount(): number; getGeneralGlobalNotes(): NoteMetadata[]; getPersonalizationNotes(): NoteMetadata[]; getFleetingNotes(): NoteMetadata[]; getByStatus(status: NoteStatus, limit?: number): NoteMetadata[]; getByKind(kind: NoteKind, limit?: number): NoteMetadata[]; getById(id: string): NoteMetadata | null; getByIdVisible(id: string, visibility: VisibilityOptions): NoteMetadata | null; getByPath(filePath: string): NoteMetadata | null; getStaleNotes(reviewAfterDays: number, promotionThreshold: number, excludeKinds: NoteKind[]): NoteMetadata[]; getAll(limit?: number): NoteMetadata[]; findByUrl(url: string): Array<{ id: string; title: string; }>; /** * Get the most frequently accessed notes, prioritizing permanent notes. */ getTopAccessedNotes(limit?: number): NoteMetadata[]; /** * Get notes accessed within the last N days. * Captures "hot" notes from recent sessions. */ getRecentlyAccessedNotes(days?: number, limit?: number, visibility?: VisibilityOptions): NoteMetadata[]; /** * Get relevant notes by balancing recency, frequency, and importance. */ getRelevantNotesForContext(maxNotes?: number): NoteMetadata[]; remove(id: string): boolean; private removeUnlocked; archive(id: string): boolean; private archiveUnlocked; promoteToPermanent(id: string): boolean; private promoteToPermanentUnlocked; updatePath(id: string, newPath: string): boolean; private updatePathUnlocked; updateTags(id: string, tags: string[]): boolean; private updateTagsUnlocked; assignProject(id: string, project: string, tags: string[]): { oldPath: string; newPath: string; } | null; private assignProjectUnlocked; private updateFrontmatterStatus; private rewriteNoteFile; getStats(project?: string, client?: string): { total: number; fleeting: number; permanent: number; archived: number; other: number; }; getStatsByKind(): Record; /** * Get notes created within a time window, grouped by kind. * Used by knowledge-health for growth rate reporting. */ getGrowthByKind(sinceMs: number, project?: string, client?: string): Record; /** * Get staleness distribution across buckets: 0-7d, 7-30d, 30-90d, 90d+. * Staleness = days since last access (or creation if never accessed). */ getStalenessDistribution(project?: string, client?: string): { fresh: number; recent: number; aging: number; stale: number; }; recordToolInvocation(toolName: TelemetryToolName, argKind?: string, resultCount?: number, model?: string): void; updateLastAccessed(noteIds: string[]): void; getTelemetryAggregates(days?: number): TelemetryAggregates; recordAccess(id: string): void; getTelemetryRows(): TelemetryRow[]; getSessionId(): string; /** Record session start. When sharingEnabled is false, the session is * pre-marked as reported so it won't be uploaded if sharing is enabled later. */ recordSessionStart(client: string, clientVersion: string | null, vaultSize: number, version: string, sharingEnabled?: boolean): void; recordSessionEnd(): void; /** * Atomically claim and return unreported sessions. * Sets reported = 2 ("claiming") inside a transaction so concurrent * startups cannot read the same rows. Use markSessionsReported() on * success or releaseClaimedSessions() on failure. */ getUnreportedSessions(limit?: number): UnreportedSession[]; markSessionsReported(sessionIds: string[]): void; /** Reset sessions stuck in claimed state (reported=2) whose claim has * expired. Uses a 60-second TTL so live reporters' in-flight claims * are not disturbed by concurrent startups. */ recoverAbandonedClaims(): void; /** Release claimed sessions back to unreported on send failure. */ releaseClaimedSessions(sessionIds: string[]): void; recordConformance(record: ConformanceRecord): void; getConformanceAggregates(days?: number): ConformanceAggregates; rebuildFromFiles(): { indexed: number; errors: number; warnings: string[]; }; private rebuildFromFilesUnlocked; formatAllFiles(): { formatted: number; skipped: number; errors: number; }; private formatAllFilesUnlocked; private extractWikiLinks; /** Query-only exhaustive contextual resolution. Existing unindexed Markdown * files and directory-index notes are valid targets but do not participate * in the active graph, so they resolve to a neutral `vault-target` outcome * rather than a document identity. */ resolveContextualLink(linkText: string): ContextualLinkResolution; resolveLink(linkText: string): string | null; /** * Relation IDs from a note's marked system-generated Related section, read from * its canonical file. Unmarked authored Related sections yield no relations. */ getGeneratedRelatedIds(id: string): string[]; syncLinks(noteId: string, content: string): void; private syncLinksUnlocked; getBacklinks(noteId: string, visibility?: VisibilityOptions): Array<{ note: NoteMetadata; link_text: string; }>; getOutgoingLinks(noteId: string, visibility?: VisibilityOptions): Array<{ note: NoteMetadata; link_text: string; }>; getUnlinkedNotes(project?: string, client?: string): NoteMetadata[]; getOneWayLinks(project?: string, client?: string): Array<{ sourceId: string; sourceTitle: string; targetId: string; targetTitle: string; }>; getBrokenLinks(project?: string, client?: string): Array<{ sourceId: string; sourceTitle: string; brokenTarget: string; line: number; }>; /** * Query-only source list for the internal contextual link-health * evaluator: active, non-structural note identity/metadata plus the file * path a production reader needs to load raw source bytes. Never exposes * content, a database handle, or a mutation capability. */ getContextualLinkDocuments(): Array<{ id: string; title: string; kind: NoteKind; status: NoteStatus; tags: string[]; path: string; }>; getUpgradeStatus(): { total: number; needsSummary: number; needsGuidance: number; }; getNotesMissingFields(): NoteMetadata[]; getByIds(ids: string[]): NoteMetadata[]; updateSummaryGuidance(id: string, summary: string, guidance: string): boolean; private updateSummaryGuidanceUnlocked; private updateNoteBodyFields; getPermanentPersonalizations(): NoteMetadata[]; getRecentNotes(limit?: number, visibility?: VisibilityOptions): NoteMetadata[]; /** * Query-only snapshot for the internal vault-review core (src/review/). * Returns every note, any status, ordered like `getAll` (updated_at DESC). * When `visibility` is omitted this is unrestricted full-vault maintenance * scope; when provided it delegates to the canonical `visibilityPredicate` * (global/universal-client semantics; unclassified notes fail closed). * No mutation, telemetry, or filesystem access. */ getReviewNotes(visibility?: VisibilityOptions): NoteMetadata[]; /** * Batch backlink counts for the internal vault-review core: incoming links * from non-archived source notes, keyed by target note id. When * `visibility` is provided, source notes are additionally restricted to * the canonical visibility predicate (scoped evaluation); omitted means * unrestricted across projects (full-vault evaluation). A single * aggregate query — not per-note lookups — so callers can batch across an * entire snapshot. */ getReviewBacklinkCounts(visibility?: VisibilityOptions): Map; getReviewQueue(filter?: 'fleeting' | 'permanent', daysThreshold?: number, limit?: number, exemptKinds?: NoteKind[], staleCutoff?: number): { fleeting: { notes: NoteMetadata[]; total: number; }; permanent: { notes: NoteMetadata[]; total: number; }; }; findDuplicates(): Map; clearAll(): void; private clearAllUnlocked; private _closed; close(): void; getAllGlobalNotes(limit?: number): NoteMetadata[]; getAllActiveNotes(): NoteMetadata[]; /** Backlinks safe for display on a global note: global sources only. */ getGlobalBacklinks(noteId: string): Array<{ note: NoteMetadata; link_text: string; }>; validateGlobalEdge(sourceId: string, targetId: string): void; addLocalToGlobalRelation(sourceId: string, globalId: string): void; private addLocalToGlobalRelationUnlocked; } export declare function createNoteRepository(docsPath?: string, options?: { telemetryEnabled?: boolean; readonly?: boolean; }): NoteRepository; export default createNoteRepository; //# sourceMappingURL=NoteRepository.d.ts.map