{"version":3,"file":"sync-Btcb6xA4.mjs","names":[],"sources":["../src/memory/indexer/sync.ts"],"sourcesContent":["/**\n * Synchronous (SQLite) indexer for the PAI federation memory engine.\n *\n * Scans project memory/ and Notes/ directories, chunks markdown files, and\n * inserts the resulting chunks into federation.db for BM25 search.\n *\n * Change detection: files whose SHA-256 hash has not changed since the last\n * index run are skipped, keeping incremental re-indexing fast.\n *\n * Uses raw better-sqlite3 Database directly for maximum SQLite performance\n * (synchronous transactions, no serialisation overhead).\n */\n\nimport { readFileSync, statSync, existsSync } from \"node:fs\";\nimport { join, relative, basename } from \"node:path\";\nimport type { Database } from \"better-sqlite3\";\nimport { chunkMarkdown } from \"../chunker.js\";\nimport {\n  sha256File,\n  chunkId,\n  detectTier,\n  walkMdFiles,\n  walkContentFiles,\n  isPathTooBroadForContentScan,\n  parseSessionTitleChunk,\n  yieldToEventLoop,\n  INDEX_YIELD_EVERY,\n} from \"./helpers.js\";\nimport type { IndexResult, EmbedResult } from \"./types.js\";\n\nexport type { IndexResult, EmbedResult };\n\n// Re-export detectTier for backward-compatibility (consumers import it from indexer.js)\nexport { detectTier };\n\n// ---------------------------------------------------------------------------\n// Single-file indexing\n// ---------------------------------------------------------------------------\n\n/**\n * Index a single file into the federation database.\n *\n * @returns true if the file was re-indexed (changed or new), false if skipped.\n */\nexport function indexFile(\n  db: Database,\n  projectId: number,\n  rootPath: string,\n  relativePath: string,\n  source: string,\n  tier: string,\n): boolean {\n  const absPath = join(rootPath, relativePath);\n\n  // Read file content\n  let content: string;\n  let stat: ReturnType<typeof statSync>;\n  try {\n    content = readFileSync(absPath, \"utf8\");\n    stat = statSync(absPath);\n  } catch {\n    // File unreadable or missing — skip silently\n    return false;\n  }\n\n  const hash = sha256File(content);\n  const mtime = Math.floor(stat.mtimeMs);\n  const size = stat.size;\n\n  // Check if the file has changed since last index\n  const existing = db\n    .prepare(\n      \"SELECT hash FROM memory_files WHERE project_id = ? AND path = ?\",\n    )\n    .get(projectId, relativePath) as { hash: string } | undefined;\n\n  if (existing?.hash === hash) {\n    // Unchanged — skip\n    return false;\n  }\n\n  // Delete old chunks for this file from both tables\n  const oldChunkIds = db\n    .prepare(\n      \"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\",\n    )\n    .all(projectId, relativePath) as Array<{ id: string }>;\n\n  const deleteFts = db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n  const deleteChunk = db.prepare(\n    \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\",\n  );\n\n  db.transaction(() => {\n    for (const row of oldChunkIds) {\n      deleteFts.run(row.id);\n    }\n    deleteChunk.run(projectId, relativePath);\n  })();\n\n  // Chunk the new content\n  const chunks = chunkMarkdown(content);\n\n  // Insert new chunks into memory_chunks and memory_fts\n  const insertChunk = db.prepare(`\n    INSERT INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n  `);\n\n  const insertFts = db.prepare(`\n    INSERT INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n    VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n  `);\n\n  const upsertFile = db.prepare(`\n    INSERT INTO memory_files (project_id, path, source, tier, hash, mtime, size)\n    VALUES (?, ?, ?, ?, ?, ?, ?)\n    ON CONFLICT(project_id, path) DO UPDATE SET\n      source = excluded.source,\n      tier   = excluded.tier,\n      hash   = excluded.hash,\n      mtime  = excluded.mtime,\n      size   = excluded.size\n  `);\n\n  const updatedAt = Date.now();\n\n  db.transaction(() => {\n    for (let i = 0; i < chunks.length; i++) {\n      const chunk = chunks[i]!;\n      const id = chunkId(projectId, relativePath, i, chunk.startLine, chunk.endLine);\n      insertChunk.run(\n        id,\n        projectId,\n        source,\n        tier,\n        relativePath,\n        chunk.startLine,\n        chunk.endLine,\n        chunk.hash,\n        chunk.text,\n        updatedAt,\n      );\n      insertFts.run(\n        chunk.text,\n        id,\n        projectId,\n        relativePath,\n        source,\n        tier,\n        chunk.startLine,\n        chunk.endLine,\n      );\n    }\n    upsertFile.run(projectId, relativePath, source, tier, hash, mtime, size);\n  })();\n\n  return true;\n}\n\n// ---------------------------------------------------------------------------\n// Project-level indexing\n// ---------------------------------------------------------------------------\n\n/**\n * Index all memory, Notes, and content files for a single registered project.\n *\n * Scans:\n *  - {rootPath}/MEMORY.md    → source='memory', tier='evergreen'\n *  - {rootPath}/memory/      → source='memory', tier from detectTier()\n *  - {rootPath}/Notes/       → source='notes',  tier='session'\n *  - {rootPath}/**\\/*.md    → source='content', tier='topic'  (all other .md files, recursive)\n *  - {claudeNotesDir}/       → source='notes',  tier='session'  (if set and different)\n */\nexport async function indexProject(\n  db: Database,\n  projectId: number,\n  rootPath: string,\n  claudeNotesDir?: string | null,\n): Promise<IndexResult> {\n  const result: IndexResult = {\n    filesProcessed: 0,\n    chunksCreated: 0,\n    filesSkipped: 0,\n  };\n\n  const filesToIndex: Array<{ absPath: string; rootBase: string; source: string; tier: string }> = [];\n\n  // Root-level MEMORY.md\n  const rootMemoryMd = join(rootPath, \"MEMORY.md\");\n  if (existsSync(rootMemoryMd)) {\n    filesToIndex.push({ absPath: rootMemoryMd, rootBase: rootPath, source: \"memory\", tier: \"evergreen\" });\n  }\n\n  // memory/ directory\n  const memoryDir = join(rootPath, \"memory\");\n  for (const absPath of walkMdFiles(memoryDir)) {\n    const relPath = relative(rootPath, absPath);\n    const tier = detectTier(relPath);\n    filesToIndex.push({ absPath, rootBase: rootPath, source: \"memory\", tier });\n  }\n\n  // {rootPath}/Notes/ directory\n  const notesDir = join(rootPath, \"Notes\");\n  for (const absPath of walkMdFiles(notesDir)) {\n    filesToIndex.push({ absPath, rootBase: rootPath, source: \"notes\", tier: \"session\" });\n  }\n\n  // Synthetic session-title chunks for Notes files with the standard filename format:\n  // \"NNNN - YYYY-MM-DD - Descriptive Title.md\"\n  {\n    const titleInsertChunk = db.prepare(`\n      INSERT OR IGNORE INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n    `);\n    const titleInsertFts = db.prepare(`\n      INSERT OR IGNORE INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n      VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n    `);\n    const updatedAt = Date.now();\n    for (const absPath of walkMdFiles(notesDir)) {\n      const fileName = basename(absPath);\n      const text = parseSessionTitleChunk(fileName);\n      if (!text) continue;\n      const relPath = relative(rootPath, absPath);\n      const syntheticPath = `${relPath}::title`;\n      const id = chunkId(projectId, syntheticPath, 0, 0, 0);\n      const hash = sha256File(text);\n      db.transaction(() => {\n        titleInsertChunk.run(id, projectId, \"notes\", \"session\", syntheticPath, 0, 0, hash, text, updatedAt);\n        titleInsertFts.run(text, id, projectId, syntheticPath, \"notes\", \"session\", 0, 0);\n      })();\n    }\n  }\n\n  // {rootPath}/**/*.md — all other markdown content\n  if (!isPathTooBroadForContentScan(rootPath)) {\n    for (const absPath of walkContentFiles(rootPath)) {\n      filesToIndex.push({ absPath, rootBase: rootPath, source: \"content\", tier: \"topic\" });\n    }\n  }\n\n  // Claude Code session notes directory (~/.claude/projects/{encoded}/Notes/)\n  if (claudeNotesDir && claudeNotesDir !== notesDir) {\n    for (const absPath of walkMdFiles(claudeNotesDir)) {\n      filesToIndex.push({ absPath, rootBase: claudeNotesDir, source: \"notes\", tier: \"session\" });\n    }\n\n    // Synthetic title chunks for claude notes dir\n    {\n      const updatedAt = Date.now();\n      const titleInsertChunk2 = db.prepare(`\n        INSERT OR IGNORE INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n      `);\n      const titleInsertFts2 = db.prepare(`\n        INSERT OR IGNORE INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n        VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n      `);\n      for (const absPath of walkMdFiles(claudeNotesDir)) {\n        const fileName = basename(absPath);\n        const text = parseSessionTitleChunk(fileName);\n        if (!text) continue;\n        const relPath = relative(claudeNotesDir, absPath);\n        const syntheticPath = `${relPath}::title`;\n        const id = chunkId(projectId, syntheticPath, 0, 0, 0);\n        const hash = sha256File(text);\n        db.transaction(() => {\n          titleInsertChunk2.run(id, projectId, \"notes\", \"session\", syntheticPath, 0, 0, hash, text, updatedAt);\n          titleInsertFts2.run(text, id, projectId, syntheticPath, \"notes\", \"session\", 0, 0);\n        })();\n      }\n    }\n\n    // Derive the sibling memory/ directory: .../Notes/ → .../memory/\n    if (claudeNotesDir.endsWith(\"/Notes\")) {\n      const claudeProjectDir = claudeNotesDir.slice(0, -\"/Notes\".length);\n      const claudeMemoryDir = join(claudeProjectDir, \"memory\");\n\n      const claudeMemoryMd = join(claudeProjectDir, \"MEMORY.md\");\n      if (existsSync(claudeMemoryMd)) {\n        filesToIndex.push({\n          absPath: claudeMemoryMd,\n          rootBase: claudeProjectDir,\n          source: \"memory\",\n          tier: \"evergreen\",\n        });\n      }\n\n      for (const absPath of walkMdFiles(claudeMemoryDir)) {\n        const relPath = relative(claudeProjectDir, absPath);\n        const tier = detectTier(relPath);\n        filesToIndex.push({ absPath, rootBase: claudeProjectDir, source: \"memory\", tier });\n      }\n    }\n  }\n\n  // Yield after collection phase before processing\n  await yieldToEventLoop();\n\n  let filesSinceYield = 0;\n\n  for (const { absPath, rootBase, source, tier } of filesToIndex) {\n    if (filesSinceYield >= INDEX_YIELD_EVERY) {\n      await yieldToEventLoop();\n      filesSinceYield = 0;\n    }\n    filesSinceYield++;\n\n    const relPath = relative(rootBase, absPath);\n    const changed = indexFile(db, projectId, rootBase, relPath, source, tier);\n\n    if (changed) {\n      const count = db\n        .prepare(\n          \"SELECT COUNT(*) as n FROM memory_chunks WHERE project_id = ? AND path = ?\",\n        )\n        .get(projectId, relPath) as { n: number };\n\n      result.filesProcessed++;\n      result.chunksCreated += count.n;\n    } else {\n      result.filesSkipped++;\n    }\n  }\n\n  // Prune stale paths: remove DB entries for files that no longer exist on disk.\n  const livePaths = new Set<string>();\n  for (const { absPath, rootBase } of filesToIndex) {\n    livePaths.add(relative(rootBase, absPath));\n  }\n\n  const dbChunkPaths = db\n    .prepare(\"SELECT DISTINCT path FROM memory_chunks WHERE project_id = ?\")\n    .all(projectId) as Array<{ path: string }>;\n\n  const stalePaths: string[] = [];\n  for (const row of dbChunkPaths) {\n    const basePath = row.path.endsWith(\"::title\")\n      ? row.path.slice(0, -\"::title\".length)\n      : row.path;\n    if (!livePaths.has(basePath)) {\n      stalePaths.push(row.path);\n    }\n  }\n\n  if (stalePaths.length > 0) {\n    const deleteChunksFts = db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n    const deleteChunks = db.prepare(\n      \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\",\n    );\n    const deleteFile = db.prepare(\n      \"DELETE FROM memory_files WHERE project_id = ? AND path = ?\",\n    );\n\n    db.transaction(() => {\n      for (const stalePath of stalePaths) {\n        const chunkIds = db\n          .prepare(\"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\")\n          .all(projectId, stalePath) as Array<{ id: string }>;\n        for (const { id } of chunkIds) {\n          deleteChunksFts.run(id);\n        }\n        deleteChunks.run(projectId, stalePath);\n        deleteFile.run(projectId, stalePath);\n      }\n    })();\n  }\n\n  return result;\n}\n\n// ---------------------------------------------------------------------------\n// Global indexing (all registered projects)\n// ---------------------------------------------------------------------------\n\n/**\n * Index all active projects registered in the registry DB.\n *\n * Async: yields to the event loop between each project so that the daemon's\n * Unix socket server can process IPC requests (e.g. status) while indexing.\n */\nexport async function indexAll(\n  db: Database,\n  registryDb: Database,\n): Promise<{ projects: number; result: IndexResult }> {\n  const projects = registryDb\n    .prepare(\"SELECT id, root_path, claude_notes_dir FROM projects WHERE status = 'active'\")\n    .all() as Array<{ id: number; root_path: string; claude_notes_dir: string | null }>;\n\n  const totals: IndexResult = {\n    filesProcessed: 0,\n    chunksCreated: 0,\n    filesSkipped: 0,\n  };\n\n  for (const project of projects) {\n    await yieldToEventLoop();\n    const r = await indexProject(db, project.id, project.root_path, project.claude_notes_dir);\n    totals.filesProcessed += r.filesProcessed;\n    totals.chunksCreated += r.chunksCreated;\n    totals.filesSkipped += r.filesSkipped;\n  }\n\n  return { projects: projects.length, result: totals };\n}\n\n// ---------------------------------------------------------------------------\n// Embedding generation\n// ---------------------------------------------------------------------------\n\n/**\n * Generate and store embeddings for chunks that do not yet have one.\n *\n * Because better-sqlite3 is synchronous but the embedding pipeline is async,\n * we fetch all unembedded chunk texts first, generate embeddings in batches,\n * and then write them back in a transaction.\n *\n * @param db         Open federation database.\n * @param projectId  Optional — restrict to a specific project.\n * @param batchSize  Number of chunks to embed per round. Default 50.\n * @param onProgress Optional callback called after each batch with running totals.\n */\nexport async function embedChunks(\n  db: Database,\n  projectId?: number,\n  batchSize = 50,\n  onProgress?: (embedded: number, total: number) => void,\n): Promise<EmbedResult> {\n  // Dynamic import — keeps the heavy ML runtime out of the module load path\n  const { generateEmbedding, serializeEmbedding } = await import(\"../embeddings.js\");\n\n  const conditions = [\"embedding IS NULL\"];\n  const params: (string | number)[] = [];\n\n  if (projectId !== undefined) {\n    conditions.push(\"project_id = ?\");\n    params.push(projectId);\n  }\n\n  const where = \"WHERE \" + conditions.join(\" AND \");\n\n  const rows = db\n    .prepare(`SELECT id, text FROM memory_chunks ${where} ORDER BY id`)\n    .all(...params) as Array<{ id: string; text: string }>;\n\n  if (rows.length === 0) {\n    return { chunksEmbedded: 0, chunksSkipped: 0 };\n  }\n\n  const updateStmt = db.prepare(\n    \"UPDATE memory_chunks SET embedding = ? WHERE id = ?\",\n  );\n\n  let embedded = 0;\n  const total = rows.length;\n\n  for (let i = 0; i < rows.length; i += batchSize) {\n    const batch = rows.slice(i, i + batchSize);\n\n    // Generate embeddings for the batch (async — must happen OUTSIDE transaction)\n    const embeddings: Array<{ id: string; blob: Buffer }> = [];\n    for (const row of batch) {\n      const vec = await generateEmbedding(row.text);\n      const blob = serializeEmbedding(vec);\n      embeddings.push({ id: row.id, blob });\n    }\n\n    // Write the batch in a single transaction\n    db.transaction(() => {\n      for (const { id, blob } of embeddings) {\n        updateStmt.run(blob, id);\n      }\n    })();\n\n    embedded += embeddings.length;\n    onProgress?.(embedded, total);\n  }\n\n  return { chunksEmbedded: embedded, chunksSkipped: 0 };\n}\n"],"mappings":"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"}