import { Database, Database as Database$1, Database as Database$2 } from "better-sqlite3"; //#region src/registry/schema.d.ts declare const SCHEMA_VERSION = 4; declare const CREATE_TABLES_SQL = "\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS projects (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n slug TEXT NOT NULL UNIQUE,\n display_name TEXT NOT NULL,\n root_path TEXT NOT NULL UNIQUE,\n encoded_dir TEXT NOT NULL UNIQUE,\n type TEXT NOT NULL DEFAULT 'local'\n CHECK(type IN ('local','central','obsidian-linked','external')),\n status TEXT NOT NULL DEFAULT 'active'\n CHECK(status IN ('active','archived','migrating')),\n parent_id INTEGER,\n obsidian_link TEXT,\n claude_notes_dir TEXT,\n session_config TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n archived_at INTEGER,\n FOREIGN KEY (parent_id) REFERENCES projects(id)\n);\n\nCREATE TABLE IF NOT EXISTS sessions (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project_id INTEGER NOT NULL,\n number INTEGER NOT NULL,\n date TEXT NOT NULL,\n slug TEXT NOT NULL,\n title TEXT NOT NULL,\n filename TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'open'\n CHECK(status IN ('open','completed','compacted')),\n claude_session_id TEXT,\n token_count INTEGER,\n created_at INTEGER NOT NULL,\n closed_at INTEGER,\n UNIQUE (project_id, number),\n FOREIGN KEY (project_id) REFERENCES projects(id)\n);\n\nCREATE TABLE IF NOT EXISTS tags (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n name TEXT NOT NULL UNIQUE\n);\n\nCREATE TABLE IF NOT EXISTS project_tags (\n project_id INTEGER NOT NULL,\n tag_id INTEGER NOT NULL,\n PRIMARY KEY (project_id, tag_id),\n FOREIGN KEY (project_id) REFERENCES projects(id),\n FOREIGN KEY (tag_id) REFERENCES tags(id)\n);\n\nCREATE TABLE IF NOT EXISTS session_tags (\n session_id INTEGER NOT NULL,\n tag_id INTEGER NOT NULL,\n PRIMARY KEY (session_id, tag_id),\n FOREIGN KEY (session_id) REFERENCES sessions(id),\n FOREIGN KEY (tag_id) REFERENCES tags(id)\n);\n\nCREATE TABLE IF NOT EXISTS aliases (\n alias TEXT PRIMARY KEY,\n project_id INTEGER NOT NULL,\n FOREIGN KEY (project_id) REFERENCES projects(id)\n);\n\nCREATE TABLE IF NOT EXISTS compaction_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project_id INTEGER NOT NULL,\n session_id INTEGER,\n trigger TEXT NOT NULL\n CHECK(trigger IN ('precompact','manual','end-session')),\n files_written TEXT NOT NULL,\n token_count INTEGER,\n created_at INTEGER NOT NULL,\n FOREIGN KEY (project_id) REFERENCES projects(id),\n FOREIGN KEY (session_id) REFERENCES sessions(id)\n);\n\nCREATE TABLE IF NOT EXISTS links (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id INTEGER NOT NULL,\n target_project_id INTEGER NOT NULL,\n link_type TEXT NOT NULL DEFAULT 'related'\n CHECK(link_type IN ('related','follow-up','reference')),\n created_at INTEGER NOT NULL,\n UNIQUE (session_id, target_project_id),\n FOREIGN KEY (session_id) REFERENCES sessions(id),\n FOREIGN KEY (target_project_id) REFERENCES projects(id)\n);\n\nCREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY,\n applied_at INTEGER NOT NULL\n);\n\n-- Indexes\nCREATE INDEX IF NOT EXISTS idx_projects_slug ON projects(slug);\nCREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);\nCREATE INDEX IF NOT EXISTS idx_projects_type ON projects(type);\nCREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);\nCREATE INDEX IF NOT EXISTS idx_sessions_date ON sessions(date);\nCREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);\nCREATE INDEX IF NOT EXISTS idx_sessions_claude ON sessions(claude_session_id);\nCREATE INDEX IF NOT EXISTS idx_pc_project ON project_tags(project_id);\n"; /** * Run the full DDL against an open database connection. * * The function is idempotent — every statement uses IF NOT EXISTS so it is * safe to call on an already-initialised database. After creating the tables * it inserts the current SCHEMA_VERSION into schema_version if no row exists * yet. */ declare function initializeSchema(db: Database): void; //#endregion //#region src/registry/db.d.ts /** * Open (or create) the PAI registry database. * * @param path Absolute path to registry.db. Defaults to PAI_HOME/registry.db * (falling back to the pre-2026-09-19 ~/.pai/registry.db). * @returns An open better-sqlite3 Database instance. * * Side effects on first call: * - Creates the parent directory if it does not exist. * - Enables WAL journal mode. * - Runs initializeSchema() if schema_version is empty. */ declare function openRegistry(path?: string): Database$2; //#endregion //#region src/registry/migrate.d.ts /** * Reverse Claude Code's directory encoding. * * Claude Code's actual encoding rules: * - `/` (path separator) → `-` * - ` ` (space) → `--` (escaped) * - `.` (dot) → `--` (escaped) * - `-` (literal hyphen) → `--` (escaped) * * Because space, dot, and hyphen all encode to `--`, the encoding is * **lossy** — you cannot unambiguously reverse it. This function therefore * provides a *best-effort* heuristic decode (treating `--` as a literal `-` * which gives wrong results for paths with spaces or dots). * * PREFER using {@link buildEncodedDirMap} to get the authoritative mapping * from session-registry.json instead of calling this function directly. * * Examples (best-effort, may be wrong for paths with spaces/dots): * `-Users-alice-dev-apps-MyProject` → `/Users/alice/dev/apps/MyProject` * `-Users-alice--ssh` → `/Users/alice/-ssh` ← WRONG (actually .ssh) * * @param encoded The Claude-encoded directory name. * @param lookupMap Optional authoritative map from {@link buildEncodedDirMap}. * If provided and the key is found, that value is returned * instead of the heuristic result. */ declare function decodeEncodedDir(encoded: string, lookupMap?: Map): string; /** * Derive a URL-safe kebab-case slug from an arbitrary string. * * Uses the last path component so that `/Users/alice/dev/my-app` → `my-app`. */ declare function slugify(value: string): string; interface ParsedSession { number: number; date: string; slug: string; title: string; filename: string; } /** * Attempt to parse a session note filename into its structured parts. * * Returns `null` if the filename does not match either known format. */ declare function parseSessionFilename(filename: string): ParsedSession | null; interface MigrationResult { projectsInserted: number; projectsSkipped: number; sessionsInserted: number; errors: string[]; } /** * Migrate the existing JSON session-registry into the SQLite registry. * * @param db Open better-sqlite3 Database (target). * @param registryPath Path to session-registry.json. * Defaults to ~/.claude/session-registry.json. * * The migration is idempotent: projects and sessions that already exist * (matched by slug / project_id+number) are silently skipped. */ declare function migrateFromJson(db: Database, registryPath?: string): MigrationResult; //#endregion //#region src/registry/pai-marker.d.ts interface PaiMarker { /** Absolute path to the PAI.md file */ path: string; /** The `slug` value from the `pai:` frontmatter block */ slug: string; /** Absolute path to the project root (parent of Notes/) */ projectRoot: string; } /** * Create or update `/Notes/PAI.md`. * * - File absent: creates `Notes/` if needed, writes from template. * - File present: updates only the `pai:` frontmatter block; body and all * other frontmatter keys are preserved verbatim. * * @param projectRoot Absolute path to the project root directory. * @param slug PAI slug for this project. * @param displayName Human-readable name (defaults to slug if omitted). */ declare function ensurePaiMarker(projectRoot: string, slug: string, displayName?: string): void; /** * Read PAI marker data from `/Notes/PAI.md`. * Returns null if the file does not exist or contains no `pai:` block. */ declare function readPaiMarker(projectRoot: string): { slug: string; registered: string; status: string; } | null; /** * Scan a list of parent directories for `/Notes/PAI.md` marker files. * Each directory in `searchDirs` is scanned one level deep — its immediate * child directories are checked for a `Notes/PAI.md` file. * * Returns an array of PaiMarker objects for every valid marker found. * Invalid or malformed markers are silently skipped. * * @param searchDirs Absolute paths to parent directories. */ declare function discoverPaiMarkers(searchDirs: string[]): PaiMarker[]; //#endregion //#region src/memory/schema.d.ts declare const FEDERATION_SCHEMA_SQL = "\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS memory_files (\n project_id INTEGER NOT NULL,\n path TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT 'memory',\n tier TEXT NOT NULL DEFAULT 'topic',\n hash TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n size INTEGER NOT NULL,\n PRIMARY KEY (project_id, path)\n);\n\nCREATE TABLE IF NOT EXISTS memory_chunks (\n id TEXT PRIMARY KEY,\n project_id INTEGER NOT NULL,\n source TEXT NOT NULL DEFAULT 'memory',\n tier TEXT NOT NULL DEFAULT 'topic',\n path TEXT NOT NULL,\n start_line INTEGER NOT NULL,\n end_line INTEGER NOT NULL,\n hash TEXT NOT NULL,\n text TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n last_accessed_at INTEGER,\n relevance_score REAL DEFAULT 0.5,\n embedding BLOB\n);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(\n text,\n id UNINDEXED,\n project_id UNINDEXED,\n path UNINDEXED,\n source UNINDEXED,\n tier UNINDEXED,\n start_line UNINDEXED,\n end_line UNINDEXED\n);\n\nCREATE INDEX IF NOT EXISTS idx_mc_project ON memory_chunks(project_id);\nCREATE INDEX IF NOT EXISTS idx_mc_source ON memory_chunks(project_id, source);\nCREATE INDEX IF NOT EXISTS idx_mc_tier ON memory_chunks(tier);\nCREATE INDEX IF NOT EXISTS idx_mf_project ON memory_files(project_id);\n\nCREATE TABLE IF NOT EXISTS kg_entities (\n entity_id TEXT PRIMARY KEY,\n tenant_id TEXT NOT NULL DEFAULT 'default',\n name TEXT NOT NULL,\n type TEXT NOT NULL DEFAULT 'unknown',\n description TEXT,\n first_seen INTEGER,\n last_seen INTEGER,\n mention_count INTEGER NOT NULL DEFAULT 1,\n feedback_weight REAL NOT NULL DEFAULT 0.5,\n UNIQUE(tenant_id, entity_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_kge_tenant ON kg_entities(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_kge_name ON kg_entities(tenant_id, name);\nCREATE INDEX IF NOT EXISTS idx_kge_type ON kg_entities(tenant_id, type);\n"; /** * Apply the full federation schema to an open database. * * Idempotent — all statements use IF NOT EXISTS so calling this on an * already-initialised database is safe. * * Also runs any necessary migrations for existing databases (e.g. adding the * embedding column to an older schema that was created without it). */ declare function initializeFederationSchema(db: Database): void; //#endregion //#region src/memory/db.d.ts /** * Open (or create) the PAI federation database. * * @param path Absolute path to federation.db. Defaults to PAI_HOME/federation.db * (falling back to the pre-2026-09-19 ~/.pai/federation.db). * @returns An open better-sqlite3 Database instance. * * Side effects on first call: * - Creates the parent directory if it does not exist. * - Enables WAL journal mode. * - Runs initializeFederationSchema() to ensure tables exist. */ declare function openFederation(path?: string): Database$1; //#endregion //#region src/memory/chunker.d.ts /** * Markdown text chunker for the PAI memory engine. * * Splits markdown files into overlapping text segments suitable for BM25 * full-text indexing. Respects heading boundaries where possible, falling * back to paragraph and sentence splitting when sections are large. */ interface Chunk { text: string; startLine: number; endLine: number; hash: string; } interface ChunkOptions { /** Approximate maximum tokens per chunk. Default 400. */ maxTokens?: number; /** Overlap in tokens from the previous chunk. Default 80. */ overlap?: number; } /** * Approximate token count using a words * 1.3 heuristic. * Matches the OpenClaw estimate approach. */ declare function estimateTokens(text: string): number; declare function chunkMarkdown(content: string, opts?: ChunkOptions): Chunk[]; //#endregion //#region src/memory/indexer/helpers.d.ts /** * Shared helpers for the PAI memory indexers. * * Contains utilities used by both the sync (SQLite) and async (StorageBackend) * indexer paths: hashing, chunk ID generation, directory walking, and path guards. */ /** * Classify a relative file path into one of the four memory tiers. * * Rules (in priority order): * - MEMORY.md anywhere in memory/ → 'evergreen' * - YYYY-MM-DD.md in memory/ → 'daily' * - anything else in memory/ → 'topic' * - anything in Notes/ → 'session' */ declare function detectTier(relativePath: string): "evergreen" | "daily" | "topic" | "session"; //#endregion //#region src/memory/indexer/types.d.ts /** * Shared types for the PAI memory indexer. */ interface IndexResult { filesProcessed: number; chunksCreated: number; filesSkipped: number; } //#endregion //#region src/memory/indexer/sync.d.ts /** * Index a single file into the federation database. * * @returns true if the file was re-indexed (changed or new), false if skipped. */ declare function indexFile(db: Database, projectId: number, rootPath: string, relativePath: string, source: string, tier: string): boolean; /** * Index all memory, Notes, and content files for a single registered project. * * Scans: * - {rootPath}/MEMORY.md → source='memory', tier='evergreen' * - {rootPath}/memory/ → source='memory', tier from detectTier() * - {rootPath}/Notes/ → source='notes', tier='session' * - {rootPath}/**\/*.md → source='content', tier='topic' (all other .md files, recursive) * - {claudeNotesDir}/ → source='notes', tier='session' (if set and different) */ declare function indexProject(db: Database, projectId: number, rootPath: string, claudeNotesDir?: string | null): Promise; /** * Index all active projects registered in the registry DB. * * Async: yields to the event loop between each project so that the daemon's * Unix socket server can process IPC requests (e.g. status) while indexing. */ declare function indexAll(db: Database, registryDb: Database): Promise<{ projects: number; result: IndexResult; }>; //#endregion //#region src/memory/search.d.ts interface SearchResult { projectId: number; projectSlug?: string; path: string; startLine: number; endLine: number; snippet: string; score: number; tier: string; source: string; updatedAt?: number; lastAccessedAt?: number; chunkId?: string; } interface SearchOptions { /** Restrict search to these project IDs. */ projectIds?: number[]; /** Restrict to 'memory' or 'notes' sources. */ sources?: string[]; /** Restrict to specific tier(s): 'evergreen' | 'daily' | 'topic' | 'session' */ tiers?: string[]; /** Maximum number of results to return. Default 10. */ maxResults?: number; /** Minimum BM25 score threshold (FTS5 scores are negative; 0.0 means no filter). */ minScore?: number; } declare function buildFtsQuery(query: string): string; /** * Search across all indexed memory using FTS5 BM25 ranking. * * Results are ordered by BM25 score (most relevant first). * FTS5 bm25() returns negative values; closer to 0 = more relevant. * We negate the score so callers get positive values where higher = better. * * Multilingual note: SQLite FTS5 uses the `unicode61` tokenizer by default, * which handles Unicode correctly (German umlauts, French accents, etc.) without * language-specific stemming. No changes needed here — it is already * multilingual-safe. */ declare function searchMemory(db: Database, query: string, opts?: SearchOptions): SearchResult[]; /** * Populate the projectSlug field on search results by looking up project IDs * in the registry database. */ declare function populateSlugs(results: SearchResult[], registryDb: Database): SearchResult[]; //#endregion //#region src/memory/reranker.d.ts /** * Configure the reranker model. * Must be called before the first rerank() call if you want a non-default model. */ declare function configureRerankerModel(model?: string): void; interface RerankOptions { /** Maximum number of results to return after reranking. */ topK?: number; /** * Maximum number of candidates to rerank. * Cross-encoders are O(n) per candidate, so we cap to keep latency * reasonable. Default: 50. */ maxCandidates?: number; } /** * Rerank search results using a cross-encoder model. * * Takes the top `maxCandidates` results from a first-stage retriever, * scores each (query, snippet) pair through the cross-encoder, and * returns them sorted by cross-encoder relevance score. * * The original retrieval score is replaced with the cross-encoder score. */ declare function rerankResults(query: string, results: SearchResult[], opts?: RerankOptions): Promise; //#endregion export { CREATE_TABLES_SQL, type Chunk, type ChunkOptions, FEDERATION_SCHEMA_SQL, type IndexResult, type MigrationResult, type PaiMarker, type RerankOptions, SCHEMA_VERSION, type SearchOptions, type SearchResult, buildFtsQuery, chunkMarkdown, configureRerankerModel, decodeEncodedDir, detectTier, discoverPaiMarkers, ensurePaiMarker, estimateTokens, indexAll, indexFile, indexProject, initializeFederationSchema, initializeSchema, migrateFromJson, openFederation, openRegistry, parseSessionFilename, populateSlugs, readPaiMarker, rerankResults, searchMemory, slugify }; //# sourceMappingURL=index.d.mts.map