{"version":3,"sources":["../src/index.ts","../src/filters.ts","../src/git.ts","../src/logger.ts","../src/indexer.ts","../src/search-matches.ts","../src/name-stores.ts","../src/tags.ts","../src/session-name.ts","../src/parser.ts","../src/persistent/metadata-reducer.ts","../src/providers/provider.ts","../src/persistent/claude-name-slots.ts","../src/persistent/conversation-reducer.ts","../src/persistent/sidecar.ts","../src/profiles.ts","../src/providers/codex-cli.ts","../src/canonical-path.ts","../src/providers/copilot.ts","../src/providers/cursor.ts","../src/discovery.ts","../src/providers/threadbase.ts","../src/scanner.ts","../src/cache.ts","../src/persistent/conversation-stream.ts","../src/persistent/paged-reader.ts","../src/persistent/jsonl-tail-reader.ts","../src/persistent/cursor.ts","../src/persistent/index-engine.ts","../src/providers/parse.ts","../src/search-document.ts","../src/tiers.ts","../src/persistent/db.ts","../src/persistent/schema.ts","../src/persistent/migrations.ts","../src/persistent/dir-watermark.ts","../src/persistent/repositories/checkpoints.repo.ts","../src/persistent/repositories/conversation-files.repo.ts","../src/persistent/repositories/conversations.repo.ts","../src/persistent/repositories/fts.repo.ts","../src/persistent/repositories/scanned-dirs.repo.ts","../src/watcher/file-watcher.ts","../src/watcher/index-queue.ts","../src/types.ts"],"sourcesContent":["export {\n  applyAccountFilter,\n  applyIncludeFilter,\n  applyPagination,\n  applyProjectFilter,\n  applySinceFilter,\n  applySort,\n} from \"./filters\";\nexport { readGitBranch } from \"./git\";\nexport { SearchIndexer } from \"./indexer\";\nexport type { Logger, LoggerOptions } from \"./logger\";\nexport { createLogger, getLogger, setLogger } from \"./logger\";\nexport {\n  readCodexNameSlots,\n  readCopilotNameSlots,\n  readCursorCliNameSlots,\n  readCursorIdeNameSlots,\n  setCursorStateDb,\n} from \"./name-stores\";\nexport type { ConvReducerState as JsonlParseState } from \"./persistent/conversation-reducer\";\nexport {\n  initialConvState as createJsonlParseState,\n  parseJsonlLine,\n} from \"./persistent/conversation-reducer\";\nexport type { Sidecar } from \"./persistent/sidecar\";\nexport { readSidecar, sidecarPath } from \"./persistent/sidecar\";\nexport {\n  detectDefaultProfile,\n  getProjectsDir,\n  loadProfiles,\n  resolveConfigDir,\n  saveProfiles,\n} from \"./profiles\";\nexport {\n  CodexCliProvider,\n  isCodexInjectedContext,\n  parseCodexJsonlLine,\n} from \"./providers/codex-cli\";\nexport { CopilotProvider, parseCopilotJsonlLine } from \"./providers/copilot\";\nexport {\n  CursorProvider,\n  parseCursorJsonlLine,\n  unwrapCursorUserText,\n} from \"./providers/cursor\";\nexport type {\n  DiscoveredConversationFile,\n  ScannerProvider,\n  ScannerProviderName,\n} from \"./providers/provider\";\nexport {\n  CLAUDE_CODE_PROVIDER,\n  CODEX_CLI_PROVIDER,\n  COPILOT_PROVIDER,\n  CURSOR_PROVIDER,\n  canonicalizeProviderList,\n  canonicalizeProviderName,\n  LEGACY_CURSOR_PROVIDER,\n  providerMatches,\n} from \"./providers/provider\";\nexport { ThreadbaseProvider } from \"./providers/threadbase\";\nexport type {\n  ConversationScannerOptions,\n  PersistentConfig,\n  ScannerChangeEvent,\n  WatchOptions,\n} from \"./scanner\";\nexport { ConversationScanner } from \"./scanner\";\nexport type { NameSlot, NameSlots, ResolvedSessionName } from \"./session-name\";\nexport {\n  applyNameCandidate,\n  isValidNameCandidate,\n  NAME_PRECEDENCE,\n  resolveSessionName,\n} from \"./session-name\";\nexport { cleanSystemTags } from \"./tags\";\nexport { DEFAULT_TIERS, resolveTier } from \"./tiers\";\nexport * from \"./types\";\n\n// ─── Standalone Convenience Functions ───────────────────────────────\n\nimport { ConversationScanner } from \"./scanner\";\nimport type {\n  Conversation,\n  GetConversationOptions,\n  ScanOptions,\n  ScanResult,\n  SearchOptions,\n  SearchResult,\n} from \"./types\";\n\nlet defaultScanner: ConversationScanner | undefined;\n\nfunction getDefaultScanner(): ConversationScanner {\n  if (!defaultScanner) {\n    defaultScanner = new ConversationScanner();\n  }\n  return defaultScanner;\n}\n\nexport function resetDefaultScanner(): void {\n  defaultScanner = undefined;\n}\n\nexport async function scan(\n  options?: ScanOptions,\n  scanner?: ConversationScanner,\n): Promise<ScanResult> {\n  return (scanner ?? getDefaultScanner()).scan(options);\n}\n\nexport async function search(\n  query: string,\n  options?: SearchOptions,\n  scanner?: ConversationScanner,\n): Promise<SearchResult[]> {\n  return (scanner ?? getDefaultScanner()).search(query, options);\n}\n\nexport async function getConversation(\n  id: string,\n  options?: GetConversationOptions,\n  scanner?: ConversationScanner,\n): Promise<Conversation | null> {\n  return (scanner ?? getDefaultScanner()).getConversation(id, options);\n}\n","import type { ConversationMeta, Include, SortOrder } from \"./types\";\n\nexport function applySort(metas: ConversationMeta[], order: SortOrder): ConversationMeta[] {\n  const out = [...metas];\n  // Tie-break by id (the file path) so the order is fully deterministic and does\n  // not depend on input order. This matters because metas can arrive in\n  // different orders (filesystem discovery vs. an SQLite query), and a tie on\n  // the primary key would otherwise resolve differently between them.\n  const tie = (a: ConversationMeta, b: ConversationMeta) => a.id.localeCompare(b.id);\n  switch (order) {\n    case \"recent\":\n      out.sort((a, b) => b.timestamp.localeCompare(a.timestamp) || tie(a, b));\n      break;\n    case \"oldest\":\n      out.sort((a, b) => a.timestamp.localeCompare(b.timestamp) || tie(a, b));\n      break;\n    case \"messages-desc\":\n      out.sort((a, b) => b.messageCount - a.messageCount || tie(a, b));\n      break;\n    case \"messages-asc\":\n      out.sort((a, b) => a.messageCount - b.messageCount || tie(a, b));\n      break;\n    case \"alpha\":\n      out.sort((a, b) => {\n        const cmp = a.projectName.localeCompare(b.projectName);\n        if (cmp !== 0) return cmp;\n        return a.preview.localeCompare(b.preview) || tie(a, b);\n      });\n      break;\n  }\n  return out;\n}\n\nexport function applySinceFilter(metas: ConversationMeta[], since: string): ConversationMeta[] {\n  const cutoff = parseSinceCutoff(since);\n  return metas.filter((m) => new Date(m.timestamp).getTime() >= cutoff.getTime());\n}\n\nexport function applyIncludeFilter(\n  metas: ConversationMeta[],\n  include: Include,\n): ConversationMeta[] {\n  switch (include) {\n    case \"all\":\n      return metas;\n    case \"conversations\":\n      return metas.filter((m) => !m.isSubagent && !m.isTeammate);\n    case \"subagents\":\n      return metas.filter((m) => m.isSubagent);\n    case \"teammates\":\n      return metas.filter((m) => m.isTeammate);\n  }\n}\n\nexport function applyProjectFilter(metas: ConversationMeta[], project: string): ConversationMeta[] {\n  const lower = project.toLowerCase();\n  return metas.filter(\n    (m) =>\n      m.projectPath.toLowerCase().includes(lower) || m.projectName.toLowerCase().includes(lower),\n  );\n}\n\nexport function applyAccountFilter(metas: ConversationMeta[], account: string): ConversationMeta[] {\n  return metas.filter((m) => m.account === account);\n}\n\nexport function applyPagination<T>(\n  items: T[],\n  limit: number,\n  offset: number,\n): { items: T[]; total: number } {\n  return {\n    items: items.slice(offset, offset + limit),\n    total: items.length,\n  };\n}\n\nexport function parseSinceCutoff(value: string): Date {\n  const s = value.trim();\n  if (!s) throw new Error(\"Empty --since value\");\n\n  // Try ISO date\n  const isoMatch = s.match(/^\\d{4}-\\d{2}-\\d{2}$/);\n  if (isoMatch) {\n    const d = new Date(`${s}T00:00:00Z`);\n    if (!Number.isNaN(d.getTime())) return d;\n  }\n\n  // Try duration: digits + unit\n  const durationMatch = s.match(/^(\\d+)([hdw])$/);\n  if (!durationMatch) {\n    throw new Error(\n      `Invalid --since value \"${s}\": expected duration like \"7d\", \"24h\", \"2w\" or ISO date \"2006-01-02\"`,\n    );\n  }\n\n  const n = parseInt(durationMatch[1], 10);\n  const unit = durationMatch[2];\n  let ms: number;\n  switch (unit) {\n    case \"h\":\n      ms = n * 60 * 60 * 1000;\n      break;\n    case \"d\":\n      ms = n * 24 * 60 * 60 * 1000;\n      break;\n    case \"w\":\n      ms = n * 7 * 24 * 60 * 60 * 1000;\n      break;\n    default:\n      throw new Error(`Invalid unit \"${unit}\"`);\n  }\n\n  return new Date(Date.now() - ms);\n}\n","import { readFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { getLogger } from \"./logger\";\n\nconst REF_PREFIX = \"ref: refs/heads/\";\nconst MAX_DEPTH = 6;\n\nexport function readGitBranch(projectPath: string): string | null {\n  if (!projectPath) return null;\n  const log = getLogger();\n\n  let dir = projectPath;\n  let depth = 0;\n\n  while (depth < MAX_DEPTH) {\n    const headPath = join(dir, \".git\", \"HEAD\");\n    try {\n      const content = readFileSync(headPath, \"utf-8\").trim();\n      if (content.startsWith(REF_PREFIX)) {\n        const branch = content.slice(REF_PREFIX.length);\n        log.trace({ projectPath, dir, branch }, \"git: branch resolved\");\n        return branch;\n      }\n      // Detached HEAD: raw commit SHA\n      if (content.length >= 7) {\n        log.trace({ projectPath, dir }, \"git: detached HEAD\");\n        return \"(detached)\";\n      }\n      return null;\n    } catch {\n      // .git/HEAD not found at this level\n    }\n\n    const parent = dirname(dir);\n    if (parent === dir) return null; // filesystem root\n    dir = parent;\n    depth++;\n  }\n\n  log.trace({ projectPath }, \"git: no .git found within depth\");\n  return null;\n}\n","import pino, { type Logger, type LoggerOptions } from \"pino\";\n\nlet currentLogger: Logger = pino({ level: \"silent\" });\n\nexport function createLogger(options?: LoggerOptions | Logger): Logger {\n  if (options && typeof (options as Logger).child === \"function\") {\n    return options as Logger;\n  }\n  return pino((options as LoggerOptions) ?? { level: \"silent\" });\n}\n\nexport function setLogger(logger: Logger): void {\n  currentLogger = logger;\n}\n\nexport function getLogger(): Logger {\n  return currentLogger;\n}\n\nexport type { Logger, LoggerOptions };\n","import FlexSearchModule from \"flexsearch\";\n\n// FlexSearch has inconsistent default export across ESM/CJS\nconst FlexSearch = (FlexSearchModule as any).default ?? FlexSearchModule;\n\nimport { getLogger } from \"./logger\";\nimport { buildContentMatch, generateMatches } from \"./search-matches\";\nimport type { ConversationMeta, SearchMatch, SearchResult } from \"./types\";\n\nexport class SearchIndexer {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  private index: any;\n  private documents = new Map<string, ConversationMeta>();\n  // The indexed body per conversation, kept so a hit can produce a real excerpt\n  // instead of falling back to an unrelated preview. Sized by the caller (the\n  // scanner tail-caps it to the content tier) — see the note on addDocument.\n  private searchContents = new Map<string, string>();\n\n  constructor() {\n    this.index = this.createIndex();\n  }\n\n  private createIndex() {\n    return new (FlexSearch as any).Document({\n      document: {\n        id: \"id\",\n        index: [\n          \"content\",\n          \"projectName\",\n          \"projectPath\",\n          \"sessionId\",\n          \"sessionName\",\n          \"account\",\n          \"model\",\n          \"gitBranch\",\n          \"toolNames\",\n        ],\n        store: [\"id\"],\n      },\n      tokenize: \"forward\",\n      resolution: 9,\n      cache: 100,\n    });\n  }\n\n  // `searchContent` is the combined search document (text + thinking + tools),\n  // NOT meta.contentSnippet.\n  //\n  // It arrives pre-capped. This index is `tokenize: \"forward\"` at resolution 9,\n  // which stores every prefix of every token, so it is a resident-memory\n  // structure whose cost is very different from the on-disk FTS index. Feeding\n  // it the full ~128 KB budget across a few hundred conversations would be a\n  // multi-hundred-MB-to-GB index. The scanner therefore caps this to the active\n  // tier's snippetMax and accepts that `persistent: false` has lower recall than\n  // SQLite — an honest, documented divergence rather than a silent one.\n  addDocument(meta: ConversationMeta, searchContent = \"\"): void {\n    this.documents.set(meta.id, meta);\n    this.searchContents.set(meta.id, searchContent);\n    this.index.add(toIndexDoc(meta, searchContent));\n  }\n\n  buildIndex(metas: ConversationMeta[], searchContents?: Map<string, string>): void {\n    this.clear();\n    for (const meta of metas) {\n      this.addDocument(meta, searchContents?.get(meta.id) ?? \"\");\n    }\n    getLogger().debug({ docCount: metas.length }, \"indexer: built\");\n  }\n\n  search(query: string, options?: { fields?: string[]; limit?: number }): SearchResult[] {\n    const limit = options?.limit ?? 50;\n\n    if (!query.trim()) {\n      return this.getRecent(limit);\n    }\n\n    const results = this.index.search(query, { limit: limit * 2, enrich: true });\n\n    const seen = new Set<string>();\n    const searchResults: SearchResult[] = [];\n\n    for (const fieldResult of results) {\n      if (!fieldResult.result) continue;\n      for (const item of fieldResult.result) {\n        const id = typeof item === \"object\" ? (item as { id: string }).id : String(item);\n        if (seen.has(id)) continue;\n        seen.add(id);\n\n        const meta = this.documents.get(id);\n        if (!meta) continue;\n\n        searchResults.push({\n          meta,\n          score: 1,\n          matches: this.matchesFor(meta, query),\n        });\n        if (searchResults.length >= limit) break;\n      }\n      if (searchResults.length >= limit) break;\n    }\n\n    return searchResults;\n  }\n\n  // Body context first (that is what explains why the result appeared), then any\n  // metadata matches. Only when neither hits does generateMatches' preview\n  // fallback stand in.\n  private matchesFor(meta: ConversationMeta, query: string): SearchMatch[] {\n    const contentMatch = buildContentMatch(this.searchContents.get(meta.id) ?? \"\", query);\n    const metaMatches = generateMatches(meta, query);\n    if (!contentMatch) return metaMatches;\n    return [contentMatch, ...metaMatches.filter((m) => m.field !== \"preview\")];\n  }\n\n  private getRecent(limit: number): SearchResult[] {\n    return Array.from(this.documents.values())\n      .sort((a, b) => b.timestamp.localeCompare(a.timestamp))\n      .slice(0, limit)\n      .map((meta) => ({\n        meta,\n        score: 1,\n        matches: [{ field: \"timestamp\", snippet: meta.preview }],\n      }));\n  }\n\n  getDocumentCount(): number {\n    return this.documents.size;\n  }\n\n  // Replace an already-indexed document in place. FlexSearch's `add` does not\n  // overwrite an existing id, so a single-file refresh must go through\n  // `update` to avoid stale matches lingering in the index.\n  updateDocument(meta: ConversationMeta, searchContent = \"\"): void {\n    this.documents.set(meta.id, meta);\n    this.searchContents.set(meta.id, searchContent);\n    this.index.update(toIndexDoc(meta, searchContent));\n  }\n\n  removeDocument(id: string): void {\n    this.documents.delete(id);\n    this.searchContents.delete(id);\n    this.index.remove(id);\n  }\n\n  clear(): void {\n    this.documents.clear();\n    this.searchContents.clear();\n    this.index = this.createIndex();\n    getLogger().trace(\"indexer: cleared\");\n  }\n}\n\nfunction toIndexDoc(meta: ConversationMeta, searchContent: string) {\n  return {\n    id: meta.id,\n    content: searchContent,\n    projectName: meta.projectName,\n    projectPath: meta.projectPath,\n    sessionId: meta.sessionId,\n    sessionName: meta.sessionName,\n    account: meta.account,\n    model: meta.model || \"\",\n    gitBranch: meta.gitBranch || \"\",\n    toolNames: meta.toolNames.join(\" \"),\n  };\n}\n","import type { ConversationMeta, SearchHighlight, SearchMatch } from \"./types\";\n\n// Delimiters handed to FTS5 snippet() to mark matched tokens, stripped again\n// before the excerpt reaches a caller. Control characters (STX/ETX) rather than\n// a textual sentinel like [[HIT]]: a transcript can legitimately contain wiki\n// link syntax, and a collision would silently shift every highlight offset.\nexport const FTS_HIT_OPEN = \"\\u0002\";\nexport const FTS_HIT_CLOSE = \"\\u0003\";\n\n// Tokens of context FTS5 puts around a hit. Clients decide final layout.\nexport const FTS_SNIPPET_TOKENS = 16;\n\nexport const FTS_ELLIPSIS = \"…\";\n\n// Field name used for any conversation-body hit, whichever body column matched.\n// Column identity (text/thinking/tools) is an indexing detail; consumers only\n// need to distinguish \"body\" from \"metadata\".\nexport const CONTENT_FIELD = \"content\";\n\n// Convert one raw FTS5 snippet() string into plain text plus highlight ranges.\n// Returns null when the column carried no marked hit — snippet() returns the\n// head of a column even when the match was in a different column, so marker\n// presence is the only reliable \"did THIS column match\" signal.\nexport function parseFtsSnippet(\n  raw: string | null | undefined,\n): { snippet: string; highlights: SearchHighlight[] } | null {\n  if (!raw?.includes(FTS_HIT_OPEN)) return null;\n\n  const highlights: SearchHighlight[] = [];\n  let snippet = \"\";\n  let openAt = -1;\n\n  for (const ch of raw) {\n    if (ch === FTS_HIT_OPEN) {\n      openAt = snippet.length;\n      continue;\n    }\n    if (ch === FTS_HIT_CLOSE) {\n      if (openAt >= 0 && snippet.length > openAt) {\n        highlights.push({ start: openAt, end: snippet.length });\n      }\n      openAt = -1;\n      continue;\n    }\n    snippet += ch;\n  }\n\n  return highlights.length > 0 ? { snippet, highlights } : null;\n}\n\n// Per-field match snippets for a METADATA hit. Body context no longer comes from\n// here: it comes from FTS5 snippet() (persistent) or buildContentMatch()\n// (in-memory), because meta.contentSnippet is a 5 KB head-biased preview, not\n// the search corpus.\n//\n// Field ids are camelCase, matching ConversationMeta — never the snake_case FTS\n// column names. Consumers switch on matches[].field, so the two vocabularies\n// must not mix. Order follows the documented precedence.\nexport function generateMatches(meta: ConversationMeta, query: string): SearchMatch[] {\n  const matches: SearchMatch[] = [];\n  const lowerQuery = query.toLowerCase();\n\n  const fields: [string, string][] = [\n    [\"sessionName\", meta.sessionName],\n    [\"projectName\", meta.projectName],\n    [\"gitBranch\", meta.gitBranch || \"\"],\n    [\"toolNames\", meta.toolNames.join(\" \")],\n    [\"model\", meta.model || \"\"],\n    [\"account\", meta.account],\n    [\"sessionId\", meta.sessionId],\n  ];\n\n  for (const [field, value] of fields) {\n    const idx = value.toLowerCase().indexOf(lowerQuery);\n    if (idx !== -1) {\n      const start = Math.max(0, idx - 80);\n      const end = Math.min(value.length, idx + query.length + 120);\n      let snippet = value.slice(start, end);\n      if (start > 0) snippet = `...${snippet}`;\n      if (end < value.length) snippet = `${snippet}...`;\n      matches.push({ field, snippet });\n    }\n  }\n\n  return matches.length > 0 ? matches : [{ field: \"preview\", snippet: meta.preview }];\n}\n\n// Body context for the in-memory path, which has no FTS5 snippet(). Locates the\n// query literally in the combined search document and returns the same\n// {field, snippet, highlights} shape the persistent path produces.\nexport function buildContentMatch(searchContent: string, query: string): SearchMatch | null {\n  if (!searchContent || !query.trim()) return null;\n  const idx = searchContent.toLowerCase().indexOf(query.toLowerCase());\n  if (idx === -1) return null;\n\n  const start = Math.max(0, idx - 80);\n  const end = Math.min(searchContent.length, idx + query.length + 120);\n  const body = searchContent.slice(start, end).replace(/\\s+/g, \" \").trim();\n\n  // Measure the hit AFTER the whitespace collapse, so the offsets describe the\n  // string actually being returned.\n  const hitAt = body.toLowerCase().indexOf(query.toLowerCase());\n  const prefix = start > 0 ? FTS_ELLIPSIS : \"\";\n  const suffix = end < searchContent.length ? FTS_ELLIPSIS : \"\";\n  const snippet = `${prefix}${body}${suffix}`;\n\n  const highlights: SearchHighlight[] =\n    hitAt === -1\n      ? []\n      : [{ start: hitAt + prefix.length, end: hitAt + prefix.length + query.length }];\n\n  return { field: CONTENT_FIELD, snippet, highlights };\n}\n","import Database from \"better-sqlite3\";\nimport { closeSync, existsSync, openSync, readdirSync, readFileSync, readSync, statSync } from \"fs\";\nimport { basename, dirname, join } from \"path\";\nimport { getLogger } from \"./logger\";\nimport type { ScannerProviderName } from \"./providers/provider\";\nimport { applyNameCandidate, type NameSlot, type NameSlots } from \"./session-name\";\n\n// Name sources that live beside a transcript instead of inside it. Every reader\n// returns the slots it found for one session id and never throws: a missing,\n// locked or corrupt store yields {} so the transcript's own slots still resolve.\n\nfunction slotsFrom(\n  provider: ScannerProviderName,\n  candidates: Array<[NameSlot, unknown]>,\n): NameSlots {\n  return candidates.reduce<NameSlots>(\n    (slots, [slot, raw]) =>\n      applyNameCandidate(slots, slot, typeof raw === \"string\" ? raw : undefined, provider),\n    {},\n  );\n}\n\nfunction readJson(path: string): unknown {\n  try {\n    return JSON.parse(readFileSync(path, \"utf8\"));\n  } catch {\n    return undefined;\n  }\n}\n\n// Read-only, existing files only: never creates, locks for writing, or waits\n// long on a database another process (Cursor's 3.9 GB state.vscdb) keeps open.\nfunction queryOne<T>(dbPath: string, sql: string, ...params: unknown[]): T | undefined {\n  let db: Database.Database | undefined;\n  try {\n    db = new Database(dbPath, { readonly: true, fileMustExist: true, timeout: 250 });\n    return db.prepare(sql).get(...params) as T | undefined;\n  } catch (err) {\n    getLogger().debug({ dbPath, err }, \"name store: query failed\");\n    return undefined;\n  } finally {\n    db?.close();\n  }\n}\n\n// id → value from the LAST line carrying that id, cached until the file changes\n// (a Codex index/history can be large and is read once per session otherwise).\nconst lastByIdCache = new Map<string, { sig: string; map: Map<string, string> }>();\n\nfunction lastValueById(path: string, idField: string, valueField: string): Map<string, string> {\n  let sig: string;\n  try {\n    const s = statSync(path);\n    sig = `${s.mtimeMs}:${s.size}`;\n  } catch {\n    return new Map();\n  }\n  const key = `${path}\\0${idField}\\0${valueField}`;\n  const cached = lastByIdCache.get(key);\n  if (cached?.sig === sig) return cached.map;\n\n  const map = new Map<string, string>();\n  try {\n    for (const line of readFileSync(path, \"utf8\").split(\"\\n\")) {\n      if (!line.trim()) continue;\n      try {\n        const row = JSON.parse(line) as Record<string, unknown>;\n        const id = row[idField];\n        const value = row[valueField];\n        if (typeof id === \"string\" && typeof value === \"string\") map.set(id, value);\n      } catch {}\n    }\n  } catch {}\n  lastByIdCache.set(key, { sig, map });\n  return map;\n}\n\n// ── Codex ────────────────────────────────────────────────────────────────────\n\n// `codexHome` is the directory holding session_index.jsonl, history.jsonl and\n// state_5.sqlite (normally ~/.codex).\nexport function readCodexNameSlots(codexHome: string, id: string): NameSlots {\n  const index = lastValueById(join(codexHome, \"session_index.jsonl\"), \"id\", \"thread_name\");\n  const history = lastValueById(join(codexHome, \"history.jsonl\"), \"session_id\", \"text\");\n\n  let title = index.get(id);\n  // The sqlite title is only consulted when the index has no line for the id.\n  if (!index.has(id)) {\n    title = queryOne<{ name: string | null }>(\n      join(codexHome, \"state_5.sqlite\"),\n      \"SELECT name FROM threads WHERE id = ?\",\n      id,\n    )?.name as string | undefined;\n  }\n  return slotsFrom(\"codex-cli\", [\n    [\"toolTitle\", title],\n    [\"lastPrompt\", history.get(id)],\n  ]);\n}\n\n// ── Cursor ───────────────────────────────────────────────────────────────────\n\n// `chatsDir` is ~/.cursor/chats; a chat is <chatsDir>/<workspace hash>/<id>/.\nexport function readCursorCliNameSlots(chatsDir: string, id: string): NameSlots {\n  let chat: string | undefined;\n  try {\n    chat = readdirSync(chatsDir)\n      .map((hash) => join(chatsDir, hash, id))\n      .find((p) => existsSync(p));\n  } catch {}\n  if (!chat) return {};\n\n  const meta = readJson(join(chat, \"meta.json\")) as { title?: unknown } | undefined;\n  const history = readJson(join(chat, \"prompt_history.json\"));\n  let title = meta?.title;\n  if (typeof title !== \"string\" || !title.trim()) title = storeDbName(join(chat, \"store.db\"));\n\n  // prompt_history.json is newest-first.\n  return slotsFrom(\"cursor\", [\n    [\"toolTitle\", title],\n    [\"lastPrompt\", Array.isArray(history) ? history[0] : undefined],\n  ]);\n}\n\n// store.db keeps the chat's metadata as hex-encoded JSON under meta key \"0\".\nfunction storeDbName(dbPath: string): string | undefined {\n  const row = queryOne<{ value: string | Buffer }>(\n    dbPath,\n    \"SELECT value FROM meta WHERE key = '0'\",\n  );\n  if (!row) return undefined;\n  try {\n    const text = row.value.toString();\n    const json = /^(?:[0-9a-f]{2})+$/i.test(text)\n      ? Buffer.from(text, \"hex\").toString(\"utf8\")\n      : text;\n    const name = (JSON.parse(json) as { name?: unknown }).name;\n    return typeof name === \"string\" ? name : undefined;\n  } catch {\n    return undefined;\n  }\n}\n\n// One primary-key lookup, never a table scan: state.vscdb is multi-GB and Cursor\n// has it open.\nexport function readCursorIdeNameSlots(stateDbPath: string, id: string): NameSlots {\n  const row = queryOne<{ name: string | null }>(\n    stateDbPath,\n    \"SELECT json_extract(CAST(value AS TEXT), '$.name') AS name FROM cursorDiskKV WHERE key = ?\",\n    `composerData:${id}`,\n  );\n  return slotsFrom(\"cursor\", [[\"toolTitle\", row?.name]]);\n}\n\n// ── Copilot ──────────────────────────────────────────────────────────────────\n\nfunction yamlScalar(text: string, key: string): string | undefined {\n  const m = text.match(new RegExp(`^${key}:[ \\\\t]*(.*)$`, \"m\"));\n  const v = m?.[1].trim().replace(/^([\"'])(.*)\\1$/, \"$2\");\n  return v || undefined;\n}\n\n// workspace.yaml and session-store.db: the title slots only. `sessionDir` is\n// <copilot home>/session-state/<id>.\nexport function readCopilotTitleSlots(sessionDir: string): NameSlots {\n  const id = basename(sessionDir);\n  let yaml = \"\";\n  try {\n    yaml = readFileSync(join(sessionDir, \"workspace.yaml\"), \"utf8\");\n  } catch {}\n\n  const name = yamlScalar(yaml, \"name\");\n  if (name) {\n    return slotsFrom(\"copilot\", [\n      [yamlScalar(yaml, \"user_named\") === \"true\" ? \"userTitle\" : \"toolTitle\", name],\n    ]);\n  }\n  const row = queryOne<{ summary: string | null }>(\n    join(dirname(dirname(sessionDir)), \"session-store.db\"),\n    \"SELECT summary FROM sessions WHERE id = ?\",\n    id,\n  );\n  return slotsFrom(\"copilot\", [[\"toolTitle\", row?.summary]]);\n}\n\n// The title slots plus the first user.message `.data.content` of events.jsonl\n// (never `.data.transformedContent`, the prompt wrapped for the model).\nexport function readCopilotNameSlots(sessionDir: string): NameSlots {\n  return {\n    ...readCopilotTitleSlots(sessionDir),\n    ...slotsFrom(\"copilot\", [\n      [\"firstUserMessage\", firstCopilotUserMessage(join(sessionDir, \"events.jsonl\"))],\n    ]),\n  };\n}\n\nfunction firstCopilotUserMessage(path: string): string | undefined {\n  let fd: number;\n  try {\n    fd = openSync(path, \"r\");\n  } catch {\n    return undefined;\n  }\n  try {\n    const buf = Buffer.alloc(64 * 1024);\n    let pending = \"\";\n    for (\n      let n = readSync(fd, buf, 0, buf.length, null);\n      n > 0;\n      n = readSync(fd, buf, 0, buf.length, null)\n    ) {\n      pending += buf.toString(\"utf8\", 0, n);\n      const lines = pending.split(\"\\n\");\n      pending = lines.pop() ?? \"\";\n      for (const line of lines) {\n        try {\n          const e = JSON.parse(line) as { type?: string; data?: { content?: unknown } };\n          if (e.type === \"user.message\" && typeof e.data?.content === \"string\")\n            return e.data.content;\n        } catch {}\n      }\n    }\n    return undefined;\n  } catch {\n    return undefined;\n  } finally {\n    closeSync(fd);\n  }\n}\n\n// ── Locating the stores from a transcript path ───────────────────────────────\n\n// The stores are not under the roots a caller passes, they sit next to them\n// (~/.codex/sessions vs ~/.codex/session_index.jsonl). Walk up from the\n// transcript a bounded number of levels, so only ancestors of a file the caller\n// already pointed at are touched.\nfunction findUp(start: string, hasStore: (dir: string) => boolean, levels: number): string | null {\n  let dir = start;\n  for (let i = 0; i < levels; i++) {\n    if (hasStore(dir)) return dir;\n    const parent = dirname(dir);\n    if (parent === dir) break;\n    dir = parent;\n  }\n  return null;\n}\n\nexport function codexNameSlotsFor(rolloutPath: string, id: string): NameSlots {\n  const home = findUp(\n    dirname(rolloutPath),\n    (d) =>\n      [\"session_index.jsonl\", \"history.jsonl\", \"state_5.sqlite\"].some((f) =>\n        existsSync(join(d, f)),\n      ),\n    6,\n  );\n  return home ? readCodexNameSlots(home, id) : {};\n}\n\n// The IDE store is global to a Cursor install, not next to the transcripts, so\n// it is configured once (ScanOptions.cursorStateDb) instead of located.\n// ponytail: module state, so every parse path (scan, getConversation, a host\n// tailing a file) sees it without threading an option through each one.\nlet cursorStateDb: string | undefined;\n\nexport function setCursorStateDb(path: string | undefined): void {\n  cursorStateDb = path;\n}\n\nexport function cursorNameSlotsFor(transcriptPath: string, id: string): NameSlots {\n  const home = findUp(dirname(transcriptPath), (d) => existsSync(join(d, \"chats\")), 6);\n  const cli = home ? readCursorCliNameSlots(join(home, \"chats\"), id) : {};\n  // A CLI chat's own title wins; the (multi-GB) IDE db is only asked without one.\n  if (cli.toolTitle || !cursorStateDb) return cli;\n  return { ...cli, ...readCursorIdeNameSlots(cursorStateDb, id) };\n}\n\nexport function copilotNameSlotsFor(eventsPath: string): NameSlots {\n  return readCopilotTitleSlots(dirname(eventsPath));\n}\n\n// What the side stores say about one provider transcript right now. The engine\n// keeps the JSON of this beside the file's cursor, so a rename that changes no\n// transcript byte still shows up as a difference and triggers a reparse.\nexport function nameStoreSlotsFor(\n  provider: ScannerProviderName,\n  filePath: string,\n  sessionId: string,\n): NameSlots {\n  switch (provider) {\n    case \"codex-cli\":\n      return codexNameSlotsFor(filePath, sessionId);\n    case \"cursor\":\n      return cursorNameSlotsFor(filePath, basename(filePath, \".jsonl\"));\n    case \"copilot\":\n      return copilotNameSlotsFor(filePath);\n    default:\n      return {};\n  }\n}\n","const SYSTEM_TAGS = [\n  \"system-reminder\",\n  \"command-name\",\n  \"command-message\",\n  \"command-args\",\n  \"ide_selection\",\n  \"ide_opened_file\",\n  \"local-command-stdout\",\n  \"local-command-caveat\",\n  \"retrieval_status\",\n  \"task_id\",\n  \"task_type\",\n  \"task-id\",\n  \"task-notification\",\n  \"fast_mode_info\",\n  \"persisted-output\",\n  \"tool_use_error\",\n  \"user-prompt-submit-hook\",\n  \"thinking\",\n  \"ask_user\",\n  \"teammate-message\",\n];\n\nconst SYSTEM_TAG_RE = new RegExp(`<(${SYSTEM_TAGS.join(\"|\")})[^>]*>[\\\\s\\\\S]*?<\\\\/\\\\1>`, \"g\");\n\n// The CLI wraps an oversized paste in <pasted_content id=\"XXXX\"> /\n// </pasted_content id=\"XXXX\"> (the closing tag repeats the id, so it isn't a\n// SYSTEM_TAGS-style `<\\/\\1>` pair) or a plain </pasted_content>. Unlike the\n// system tags above, this wraps the user's real prompt, so only the tag lines\n// are dropped and the inner text is kept.\nconst PASTED_CONTENT_RE =\n  /<pasted_content id=\"[0-9a-f]{4}\">([\\s\\S]*?)<\\/pasted_content(?: id=\"[0-9a-f]{4}\")?>/g;\n\nexport function cleanSystemTags(text: string): string {\n  return text\n    .replace(SYSTEM_TAG_RE, \"\")\n    .replace(PASTED_CONTENT_RE, \"$1\")\n    .replace(/[^\\S\\n]+/g, \" \")\n    .replace(/\\n{3,}/g, \"\\n\\n\")\n    .trim();\n}\n","import type { ScannerProviderName } from \"./providers/provider\";\nimport { cleanSystemTags } from \"./tags\";\n\nexport type NameSlot = \"userTitle\" | \"toolTitle\" | \"lastPrompt\" | \"firstUserMessage\" | \"slug\";\n\n// Highest first: the first non-empty slot is the session's name.\nexport const NAME_PRECEDENCE: readonly NameSlot[] = [\n  \"userTitle\",\n  \"toolTitle\",\n  \"lastPrompt\",\n  \"firstUserMessage\",\n  \"slug\",\n];\n\nexport interface NameSlots {\n  userTitle?: string;\n  toolTitle?: string;\n  lastPrompt?: string;\n  firstUserMessage?: string; // set once, never replaced\n  slug?: string;\n}\n\nexport interface ResolvedSessionName {\n  name: string | null; // full, untruncated\n  source: NameSlot | null;\n}\n\n// Text the host injects as a \"user\" turn. Matched against the trimmed text.\nconst CLAUDE_SYSTEM_PREFIXES = [\n  \"<command-name>\",\n  \"<command-message>\",\n  \"<command-args>\",\n  \"<local-command-stdout>\",\n  \"<local-command-stderr>\",\n  \"<local-command-caveat>\",\n  \"<bash-input>\",\n  \"<bash-stdout>\",\n  \"<bash-stderr>\",\n  \"<system-reminder>\",\n  \"<task-notification>\",\n  \"<teammate-message\",\n  \"Another Claude session sent a message:\",\n  \"[Request interrupted by user\",\n];\n\nconst CODEX_SYSTEM_PREFIXES = [\n  \"<environment_context>\",\n  \"# AGENTS.md instructions\",\n  \"<user_instructions>\",\n  \"<INSTRUCTIONS>\",\n  \"<external_codex_apps_open\",\n  \"<user_shell_command>\",\n  \"<turn_aborted>\",\n];\n\n/**\n * Boilerplate Codex injects as a `user`-role turn. Codex-generic only — prompts a\n * host passes as argv (Codex has no --system-prompt flag) also land as role:user,\n * but recognising those is the host's business, not the scanner's.\n */\nexport function isCodexInjectedContext(text: string): boolean {\n  // AGENTS.md / instruction dumps prepended as the opening turn.\n  if (text.startsWith(\"# AGENTS.md\") || text.includes(\"<INSTRUCTIONS>\")) return true;\n  // Permissions / sandbox preamble, sometimes a giant user-role blob.\n  return (\n    text.startsWith(\"<permissions instructions>\") || text.includes(\"Filesystem sandboxing defines\")\n  );\n}\n\nconst CODEX_IMAGE_TAG_RE = /<\\/?image\\b[^>]*>/g;\n\n// The text to store for a candidate, or null when it cannot name a session.\nfunction normalizeCandidate(text: string, provider: ScannerProviderName): string | null {\n  let t = text.trim();\n  if (provider === \"codex-cli\") t = t.replace(CODEX_IMAGE_TAG_RE, \"\").trim();\n  const prefixes =\n    provider === \"claude-code\"\n      ? CLAUDE_SYSTEM_PREFIXES\n      : provider === \"codex-cli\"\n        ? CODEX_SYSTEM_PREFIXES\n        : [];\n  if (prefixes.some((p) => t.startsWith(p))) return null;\n  if (provider === \"codex-cli\" && isCodexInjectedContext(t)) return null;\n  return cleanSystemTags(t) || null;\n}\n\nexport function isValidNameCandidate(text: string, provider: ScannerProviderName): boolean {\n  return normalizeCandidate(text, provider) !== null;\n}\n\n// Pure: same slots in, same name out.\nexport function resolveSessionName(slots: NameSlots): ResolvedSessionName {\n  for (const slot of NAME_PRECEDENCE) {\n    const value = slots[slot];\n    if (value?.trim()) return { name: value, source: slot };\n  }\n  return { name: null, source: null };\n}\n\n// The single write path for every slot: the last valid occurrence wins, an\n// invalid candidate never overwrites a valid value, and firstUserMessage is set\n// once. Returns a new object, or the input unchanged when nothing applies.\nexport function applyNameCandidate(\n  slots: NameSlots,\n  slot: NameSlot,\n  raw: string | undefined,\n  provider: ScannerProviderName,\n): NameSlots {\n  if (typeof raw !== \"string\") return slots;\n  if (slot === \"firstUserMessage\" && slots.firstUserMessage !== undefined) return slots;\n  const text = normalizeCandidate(raw, provider);\n  return text === null ? slots : { ...slots, [slot]: text };\n}\n","import { createReadStream } from \"fs\";\nimport { basename } from \"path\";\nimport { createInterface } from \"readline\";\nimport { getLogger } from \"./logger\";\nimport { applyTeamInfo, initialConvState, reduceConvLine } from \"./persistent/conversation-reducer\";\nimport { finalizeMeta, initialReducerState, reduceLine } from \"./persistent/metadata-reducer\";\nimport { resolveSessionName } from \"./session-name\";\nimport { cleanSystemTags } from \"./tags\";\nimport type {\n  ContentTier,\n  Conversation,\n  ConversationMessage,\n  ConversationMeta,\n  TeamInfo,\n  ToolResultBlock,\n  ToolUseBlock,\n  TurnDuration,\n} from \"./types\";\n\n// `onEntry` lets a caller fold a second accumulator (the search document) over\n// the same lines without re-reading the file.\nexport async function parseMeta(\n  filePath: string,\n  account: string,\n  tier: ContentTier,\n  onEntry?: (entry: Record<string, unknown>) => void,\n): Promise<ConversationMeta | null> {\n  const log = getLogger();\n  log.trace({ filePath, account, tier: tier.name }, \"parseMeta: start\");\n\n  // Stream the whole file through the same per-line fold the incremental\n  // indexer uses, so a full parse and an append-then-resume produce identical\n  // metadata by construction.\n  const state = initialReducerState();\n  const fileStream = createReadStream(filePath);\n  const rl = createInterface({ input: fileStream, crlfDelay: Infinity });\n\n  try {\n    for await (const line of rl) {\n      if (!line.trim()) continue;\n      let entry: Record<string, unknown>;\n      try {\n        entry = JSON.parse(line);\n      } catch {\n        state.badJsonLines++;\n        continue;\n      }\n      reduceLine(state, entry, tier);\n      onEntry?.(entry);\n    }\n  } catch (err) {\n    log.warn({ filePath, err }, \"parseMeta: read failed\");\n    return null;\n  }\n\n  if (state.badJsonLines > 0) {\n    log.warn(\n      { filePath, badJsonLines: state.badJsonLines },\n      \"parseMeta: skipped malformed JSON lines\",\n    );\n  }\n\n  const meta = finalizeMeta(state, filePath, account, tier);\n  if (!meta) log.trace({ filePath }, \"parseMeta: no messages\");\n  return meta;\n}\n\nexport async function parseConversation(\n  filePath: string,\n  account: string,\n): Promise<Conversation | null> {\n  const log = getLogger();\n  log.trace({ filePath, account }, \"parseConversation: start\");\n  const messages: ConversationMessage[] = [];\n  let badJsonLines = 0;\n  const textParts: string[] = [];\n  const turnDurations: TurnDuration[] = [];\n\n  // The message line→message reduction lives in reduceConvLine (shared with the\n  // bounded page reader). turn_duration collection is parseConversation-only\n  // (pages don't need it), so it stays inline here.\n  const state = initialConvState();\n\n  const fileStream = createReadStream(filePath);\n  const rl = createInterface({ input: fileStream, crlfDelay: Infinity });\n\n  try {\n    for await (const line of rl) {\n      if (!line.trim()) continue;\n\n      let entry: Record<string, unknown>;\n      try {\n        entry = JSON.parse(line);\n      } catch {\n        badJsonLines++;\n        continue;\n      }\n\n      if (\n        entry.type === \"system\" &&\n        entry.subtype === \"turn_duration\" &&\n        typeof entry.durationMs === \"number\"\n      ) {\n        turnDurations.push({\n          durationMs: entry.durationMs as number,\n          messageCount: (entry.messageCount as number) || 0,\n          uuid: entry.uuid as string | undefined,\n        });\n        continue;\n      }\n\n      const message = reduceConvLine(state, entry);\n      if (message) {\n        messages.push(message);\n        if (message.text && !message.isCompactSummary) textParts.push(message.text);\n      }\n    }\n  } catch (err) {\n    log.warn({ filePath, err }, \"parseConversation: read failed\");\n    return null;\n  }\n\n  if (badJsonLines > 0) {\n    log.warn({ filePath, badJsonLines }, \"parseConversation: skipped malformed JSON lines\");\n  }\n\n  if (messages.length === 0) {\n    log.trace({ filePath }, \"parseConversation: no messages\");\n    return null;\n  }\n\n  log.debug({ filePath, messageCount: messages.length }, \"parseConversation: complete\");\n  applyTeamInfo(messages, state);\n\n  return {\n    id: filePath,\n    filePath,\n    projectPath: state.cwd,\n    projectName: getShortProjectName(state.cwd),\n    sessionId: state.sessionId || basename(filePath, \".jsonl\"),\n    sessionName: resolveSessionName(state.nameSlots).name ?? \"\",\n    messages,\n    fullText: textParts.join(\" \"),\n    timestamp: state.latestTimestamp || new Date().toISOString(),\n    messageCount: messages.length,\n    account,\n    turnDurations: turnDurations.length > 0 ? turnDurations : undefined,\n    lastPrompt: state.lastPrompt || undefined,\n  };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nexport function extractTextContent(content: unknown): string {\n  if (!content) return \"\";\n  if (typeof content === \"string\") return cleanSystemTags(content);\n  if (Array.isArray(content)) {\n    return content\n      .map((item) => {\n        if (typeof item === \"string\") return item;\n        if (item?.type === \"text\" && item?.text) return item.text;\n        if (item?.type === \"tool_result\" && typeof item?.content === \"string\") return item.content;\n        return \"\";\n      })\n      .filter(Boolean)\n      .map(cleanSystemTags)\n      .join(\" \");\n  }\n  return \"\";\n}\n\nexport function extractToolUseNames(content: unknown): string[] {\n  if (!Array.isArray(content)) return [];\n  return content\n    .filter((item) => item?.type === \"tool_use\" && item?.name)\n    .map((item) => item.name as string);\n}\n\nexport function extractToolUseBlocks(content: unknown): ToolUseBlock[] {\n  if (!Array.isArray(content)) return [];\n  return content\n    .filter((item) => item?.type === \"tool_use\" && item?.name && item?.id)\n    .map((item) => ({\n      id: item.id as string,\n      name: item.name as string,\n      input: (item.input as Record<string, unknown>) || {},\n    }));\n}\n\nconst TOOL_NAME_TO_TYPE: Record<string, ToolResultBlock[\"type\"]> = {\n  Edit: \"edit\",\n  Write: \"write\",\n  Read: \"read\",\n  Bash: \"bash\",\n  Grep: \"grep\",\n  Glob: \"glob\",\n  Agent: \"taskAgent\",\n  TaskCreate: \"taskCreate\",\n  TaskUpdate: \"taskUpdate\",\n};\n\nexport function extractToolResultBlocks(\n  content: unknown,\n  pendingToolUses: Map<string, ToolUseBlock>,\n): ToolResultBlock[] {\n  if (!Array.isArray(content)) return [];\n  return content\n    .filter((item) => item?.type === \"tool_result\" && item?.tool_use_id)\n    .map((item) => {\n      const toolName = pendingToolUses.get(item.tool_use_id as string)?.name ?? \"\";\n      return {\n        toolUseId: item.tool_use_id as string,\n        type: TOOL_NAME_TO_TYPE[toolName] ?? \"generic\",\n        content:\n          typeof item.content === \"string\"\n            ? { text: item.content as string }\n            : ((item.content as Record<string, unknown>) ?? {}),\n        isError: typeof item.is_error === \"boolean\" ? item.is_error : undefined,\n      };\n    });\n}\n\nexport function collectToolNames(content: unknown, toolSet: Set<string>): void {\n  if (!Array.isArray(content)) return;\n  for (const item of content) {\n    if (item?.type === \"tool_use\" && item?.name) {\n      toolSet.add(item.name as string);\n    }\n  }\n}\n\nexport function isOnlyToolResultContent(content: unknown): boolean {\n  if (!Array.isArray(content)) return false;\n  return content.length > 0 && content.every((item) => item?.type === \"tool_result\");\n}\n\nexport function isTeammateContent(content: unknown): boolean {\n  const raw =\n    typeof content === \"string\"\n      ? content\n      : Array.isArray(content)\n        ? content\n            .map((item) =>\n              typeof item === \"string\" ? item : item?.type === \"text\" ? (item.text ?? \"\") : \"\",\n            )\n            .join(\"\")\n        : \"\";\n  return raw.includes(\"<teammate-message\");\n}\n\nexport function extractThinking(content: unknown): { content: string; signature: string } {\n  if (!Array.isArray(content)) return { content: \"\", signature: \"\" };\n  const blocks = content.filter((item) => item?.type === \"thinking\");\n  return {\n    content: blocks\n      .map((b) => b.thinking as string)\n      .filter(Boolean)\n      .join(\"\\n\\n\"),\n    signature: blocks\n      .map((b) => b.signature as string)\n      .filter(Boolean)\n      .join(\"\"),\n  };\n}\n\nexport function hasImageBlocks(content: unknown): boolean {\n  if (!Array.isArray(content)) return false;\n  return content.some(\n    (item) =>\n      item?.type === \"image\" &&\n      (item?.source?.type === \"base64\" || item?.file?.base64 !== undefined),\n  );\n}\n\nexport function parseTeammateMessageTag(content: string): TeamInfo | null {\n  const match = content.match(/<teammate-message\\s+([^>]*)>/);\n  if (!match) return null;\n  const attrs = match[1];\n  const id = attrs.match(/teammate_id=\"([^\"]*)\"/)?.[1];\n  if (!id) return null;\n  const summary = attrs.match(/summary=\"([^\"]*)\"/)?.[1];\n  const color = attrs.match(/color=\"([^\"]*)\"/)?.[1];\n  return { teammateId: id, summary, color };\n}\n\nexport function getShortProjectName(fullPath: string): string {\n  const parts = fullPath.split(\"/\").filter(Boolean);\n  return parts.slice(-3).join(\"/\");\n}\n","import { basename, dirname, join } from \"path\";\nimport {\n  collectToolNames,\n  extractTextContent,\n  extractThinking,\n  extractToolUseBlocks,\n  isOnlyToolResultContent,\n  isTeammateContent,\n} from \"../parser\";\nimport { CLAUDE_CODE_PROVIDER } from \"../providers/provider\";\nimport { type NameSlots, resolveSessionName } from \"../session-name\";\nimport type { ContentTier, ConversationMeta, MessageSender, MessageSnapshot } from \"../types\";\nimport { applyClaudeNameEntry } from \"./claude-name-slots\";\n\n// Serializable fold state for incremental metadata indexing. parseMeta()'s\n// per-line loop is expressed as reduceLine(state, entry); finalizeMeta(state)\n// produces the ConversationMeta. Because the state is plain JSON, it can be\n// persisted in conversation_files.reducer_state and resumed when a file grows,\n// so an append only needs to fold the newly-read lines.\nexport interface ReducerState {\n  sessionId: string;\n  nameSlots: NameSlots;\n  latestTimestamp: string;\n  cwd: string;\n  teamName: string;\n  model: string | null;\n  messageCount: number;\n  // Count of messages parseConversation() would produce — broader than\n  // messageCount (also counts tool_use-only and thinking-only lines). Used as\n  // the page total for bounded reads, since metadata messageCount differs.\n  pageMessageCount: number;\n  lastMessageSender: MessageSender;\n  // Provider-created child (\"sidechain\") identity, lifted from the explicit\n  // per-line JSONL fields. A sidechain line carries its PARENT's id in\n  // `sessionId`, so `agentId` is the only thing that distinguishes siblings.\n  agentId: string;\n  isSidechain: boolean;\n  isTeammate: boolean;\n  firstUserSeen: boolean;\n  firstMessage: MessageSnapshot | null;\n  lastMessage: MessageSnapshot | null;\n  lastPrompt: string;\n  toolNames: string[];\n  previewParts: string[];\n  snippetParts: string[];\n  previewLength: number;\n  snippetLength: number;\n  badJsonLines: number;\n}\n\nexport function initialReducerState(): ReducerState {\n  return {\n    sessionId: \"\",\n    nameSlots: {},\n    latestTimestamp: \"\",\n    cwd: \"\",\n    teamName: \"\",\n    model: null,\n    messageCount: 0,\n    lastMessageSender: \"user\",\n    agentId: \"\",\n    isSidechain: false,\n    isTeammate: false,\n    firstUserSeen: false,\n    firstMessage: null,\n    lastMessage: null,\n    lastPrompt: \"\",\n    pageMessageCount: 0,\n    toolNames: [],\n    previewParts: [],\n    snippetParts: [],\n    previewLength: 0,\n    snippetLength: 0,\n    badJsonLines: 0,\n  };\n}\n\n// Fold one already-parsed JSONL entry into the state, mutating it in place.\n// Mirrors parseMeta()'s loop body exactly so a full reduce equals a streamed\n// parseMeta. `entry` is the JSON.parse() of one non-empty line.\nexport function reduceLine(\n  state: ReducerState,\n  entry: Record<string, unknown>,\n  tier: ContentTier,\n): void {\n  if (entry.cwd && !state.cwd) state.cwd = entry.cwd as string;\n  if (entry.sessionId && !state.sessionId) state.sessionId = entry.sessionId as string;\n  state.nameSlots = applyClaudeNameEntry(state.nameSlots, entry);\n  if (entry.teamName && !state.teamName) state.teamName = entry.teamName as string;\n  if (entry.agentId && !state.agentId) state.agentId = entry.agentId as string;\n  if (entry.isSidechain === true) state.isSidechain = true;\n  if (entry.timestamp) {\n    const ts = entry.timestamp as string;\n    if (!state.latestTimestamp || ts > state.latestTimestamp) state.latestTimestamp = ts;\n  }\n\n  const type = entry.type as string;\n\n  if (type === \"last-prompt\") {\n    if (entry.lastPrompt && !state.lastPrompt) state.lastPrompt = entry.lastPrompt as string;\n    return;\n  }\n\n  // file-history-snapshot and other non-message entries are excluded.\n  if (type !== \"user\" && type !== \"assistant\") return;\n  if (entry.isMeta) return;\n\n  const msg = entry.message as Record<string, unknown> | undefined;\n\n  // A compaction summary is generated text, not a prompt: keep it out of the\n  // counts, previews and snippets. It is still a message in the conversation\n  // reducer, so the page total must keep counting it.\n  if (entry.isCompactSummary === true) {\n    if (extractTextContent(msg?.content)) state.pageMessageCount++;\n    return;\n  }\n\n  if (state.model === null && msg?.model) state.model = msg.model as string;\n\n  if (type === \"user\" && !state.firstUserSeen) {\n    state.firstUserSeen = true;\n    if (isTeammateContent(msg?.content)) state.isTeammate = true;\n  }\n\n  const content = extractTextContent(msg?.content);\n  const hasToolUseResult = type === \"user\" && entry.toolUseResult != null;\n  const isOnlyToolResult = hasToolUseResult && isOnlyToolResultContent(msg?.content);\n\n  const toolSet = new Set(state.toolNames);\n  collectToolNames(msg?.content, toolSet);\n  state.toolNames = Array.from(toolSet);\n\n  // Count messages the same way parseConversation does, so bounded paging has a\n  // correct total. (Broader than messageCount: includes tool_use-only and\n  // thinking-only lines.)\n  const toolUseBlocks = extractToolUseBlocks(msg?.content);\n  const thinking = type === \"assistant\" ? extractThinking(msg?.content) : null;\n  const hasThinking = !!(thinking?.content || thinking?.signature);\n  if (content || isOnlyToolResult || toolUseBlocks.length > 0 || hasThinking) {\n    state.pageMessageCount++;\n  }\n\n  if (content || isOnlyToolResult) {\n    state.messageCount++;\n    state.lastMessageSender = type as MessageSender;\n\n    if (content) {\n      const ts = (entry.timestamp as string) || \"\";\n      if (!state.firstMessage) state.firstMessage = { text: content.slice(0, 200), timestamp: ts };\n      state.lastMessage = { text: content.slice(0, 200), timestamp: ts };\n\n      if (state.previewLength < tier.previewMax) {\n        state.previewParts.push(content);\n        state.previewLength += content.length;\n      }\n      if (state.snippetLength < tier.snippetMax) {\n        const remaining = tier.snippetMax - state.snippetLength;\n        const chunk = content.length > remaining ? content.slice(0, remaining) : content;\n        state.snippetParts.push(chunk);\n        state.snippetLength += chunk.length;\n      }\n    }\n  }\n}\n\n// Build the ConversationMeta from accumulated state, or null when no messages\n// were seen — matching parseMeta()'s final shape exactly.\nexport function finalizeMeta(\n  state: ReducerState,\n  filePath: string,\n  account: string,\n  tier: ContentTier,\n): ConversationMeta | null {\n  if (state.messageCount === 0) return null;\n\n  // The explicit JSONL fields are authoritative and platform-independent. The\n  // path shape is only a fallback for a transcript that carries neither.\n  const isSidechain = state.isSidechain && !!state.agentId;\n  const isSubagentPath = SUBAGENT_PATH_RE.test(filePath);\n  const isSubagent = isSidechain || isSubagentPath;\n\n  let parentSessionId: string | null = null;\n  if (isSubagentPath) {\n    const uuidDir = dirname(dirname(filePath));\n    parentSessionId = join(dirname(uuidDir), `${basename(uuidDir)}.jsonl`);\n  }\n\n  const projectPath = state.cwd;\n  return {\n    id: filePath,\n    filePath,\n    provider: CLAUDE_CODE_PROVIDER,\n    sessionId: state.sessionId || basename(filePath, \".jsonl\"),\n    sessionName: resolveSessionName(state.nameSlots).name ?? \"\",\n    nameSlots: state.nameSlots,\n    projectPath,\n    projectName: getShortProjectName(projectPath),\n    account,\n    timestamp: state.latestTimestamp || new Date().toISOString(),\n    messageCount: state.messageCount,\n    lastMessageSender: state.lastMessageSender,\n    preview: state.previewParts.join(\" \").slice(0, tier.previewMax),\n    contentSnippet: state.snippetParts.join(\" \"),\n    gitBranch: null,\n    model: state.model,\n    isSubagent,\n    parentSessionId,\n    subagentId: isSidechain ? state.agentId : undefined,\n    // A sidechain line's `sessionId` IS the parent's id — that is exactly the\n    // conflation `subagentId` exists to undo, so it is the parent UUID here.\n    parentSessionUuid: isSidechain && state.sessionId ? state.sessionId : undefined,\n    isTeammate: state.isTeammate,\n    teamName: state.teamName || null,\n    toolNames: state.toolNames,\n    firstMessage: state.firstMessage,\n    lastMessage: state.lastMessage,\n    lastPrompt: state.lastPrompt || undefined,\n  };\n}\n\n// Both separators: the scanner is handed NATIVE paths, so a hardcoded \"/\" makes\n// this test silently false on Windows — a clean false negative that disables\n// path-based subagent detection there without any error.\nconst SUBAGENT_PATH_RE = /[\\\\/]subagents[\\\\/]/;\n\nfunction getShortProjectName(fullPath: string): string {\n  const parts = fullPath.split(\"/\").filter(Boolean);\n  return parts.slice(-3).join(\"/\");\n}\n","import type { ContentTier, ConversationMeta } from \"../types\";\n\n// A scanner provider knows how to discover and parse one local conversation-log\n// format into the shared ConversationMeta model. The reducer triplet\n// (createEmptyAccumulator / reduceEntry / finalize) is the same fold used by a\n// full streamed parse and — by design — a future offset-resumed incremental\n// parse, since the accumulator is plain serializable state. See\n// metadata-reducer.ts for the Threadbase implementation this mirrors.\nexport interface ScannerProvider<Acc = unknown> {\n  name: ScannerProviderName;\n\n  // Discover candidate files for this provider under the given roots. Roots are\n  // already absolute. Returns one entry per file worth parsing.\n  discover(roots: string[]): Promise<DiscoveredConversationFile[]>;\n\n  // Cheap structural sniff: does this file look like this provider's format?\n  // `sample` is the first few lines (newline-joined). Used so a single roots\n  // list can be shared across providers without misclassifying files.\n  canParse(filePath: string, sample: string): boolean;\n\n  createEmptyAccumulator(): Acc;\n\n  // Fold one already-JSON.parsed line into the accumulator, in place. Must never\n  // throw on an unknown entry shape — ignore what it doesn't understand.\n  reduceEntry(acc: Acc, entry: Record<string, unknown>, tier: ContentTier): void;\n\n  // Build the ConversationMeta from the accumulator, or null if no messages.\n  finalize(acc: Acc, filePath: string, account: string, tier: ContentTier): ConversationMeta | null;\n}\n\nexport type ScannerProviderName = \"claude-code\" | \"codex-cli\" | \"cursor\" | \"copilot\";\n\nexport const CLAUDE_CODE_PROVIDER = \"claude-code\" as const;\nexport const CODEX_CLI_PROVIDER = \"codex-cli\" as const;\nexport const CURSOR_PROVIDER = \"cursor\" as const;\nexport const COPILOT_PROVIDER = \"copilot\" as const;\n/** Live PTY on main shipped this wire name; accept it and emit `cursor`. */\nexport const LEGACY_CURSOR_PROVIDER = \"cursor-cli\" as const;\n\nexport function canonicalizeProviderName(value: string): ScannerProviderName | undefined {\n  if (value === LEGACY_CURSOR_PROVIDER) return CURSOR_PROVIDER;\n  if (\n    value === CLAUDE_CODE_PROVIDER ||\n    value === CODEX_CLI_PROVIDER ||\n    value === CURSOR_PROVIDER ||\n    value === COPILOT_PROVIDER\n  ) {\n    return value;\n  }\n  return undefined;\n}\n\nexport function canonicalizeProviderList(\n  providers: readonly string[] | undefined,\n): ScannerProviderName[] {\n  const src = providers ?? [CLAUDE_CODE_PROVIDER];\n  const out: ScannerProviderName[] = [];\n  for (const p of src) {\n    const n = canonicalizeProviderName(p);\n    if (n && !out.includes(n)) out.push(n);\n  }\n  return out;\n}\n\nexport function providerMatches(stored: string | undefined, wanted: string): boolean {\n  const a = canonicalizeProviderName(stored ?? CLAUDE_CODE_PROVIDER) ?? stored;\n  const b = canonicalizeProviderName(wanted) ?? wanted;\n  return a === b;\n}\n\nexport interface DiscoveredConversationFile {\n  filePath: string;\n  account: string;\n}\n","import { applyNameCandidate, type NameSlots } from \"../session-name\";\n\n// Prompt text of a user entry, or \"\" when it can't name a session: injected\n// meta/compaction rows, and entries with no `text` block (tool results only).\n// Raw on purpose: the prefix checks in isValidNameCandidate run before tags are\n// cleaned, so a `<command-name>` row is rejected rather than emptied.\nfunction userPromptText(entry: Record<string, unknown>): string {\n  if (entry.isMeta || entry.isCompactSummary === true) return \"\";\n  const content = (entry.message as Record<string, unknown> | undefined)?.content;\n  if (typeof content === \"string\") return content;\n  if (!Array.isArray(content)) return \"\";\n  return content\n    .map((b) => (typeof b === \"string\" ? b : b?.type === \"text\" ? b.text : \"\"))\n    .filter((t): t is string => typeof t === \"string\" && t !== \"\")\n    .join(\" \");\n}\n\n// Maps one Claude JSONL entry to its name-slot candidates. Shared by the\n// metadata and conversation reducers so neither keeps its own naming logic.\nexport function applyClaudeNameEntry(slots: NameSlots, entry: Record<string, unknown>): NameSlots {\n  const apply = (slot: Parameters<typeof applyNameCandidate>[1], raw: unknown) =>\n    applyNameCandidate(slots, slot, typeof raw === \"string\" ? raw : undefined, \"claude-code\");\n\n  // slug rides on every entry of a session, so skip the re-normalising when it\n  // is the value already held — this runs once per line of a 100k-line file.\n  if (entry.slug !== slots.slug) slots = apply(\"slug\", entry.slug);\n  switch (entry.type) {\n    case \"custom-title\":\n      return apply(\"userTitle\", entry.customTitle);\n    case \"ai-title\":\n      return apply(\"toolTitle\", entry.aiTitle);\n    case \"last-prompt\":\n      return apply(\"lastPrompt\", entry.lastPrompt);\n    case \"user\":\n      return slots.firstUserMessage === undefined\n        ? apply(\"firstUserMessage\", userPromptText(entry))\n        : slots;\n    default:\n      return slots;\n  }\n}\n","import {\n  extractTextContent,\n  extractThinking,\n  extractToolResultBlocks,\n  extractToolUseBlocks,\n  extractToolUseNames,\n  hasImageBlocks,\n  isOnlyToolResultContent,\n  parseTeammateMessageTag,\n} from \"../parser\";\nimport type { NameSlots } from \"../session-name\";\nimport type {\n  AttachmentSidecar,\n  CompactionInfo,\n  ConversationMessage,\n  MessageMetadata,\n  MessageSender,\n  TeamInfo,\n  ToolUseBlock,\n} from \"../types\";\nimport { applyClaudeNameEntry } from \"./claude-name-slots\";\n\n// Resumable, serializable state for the conversation line→message reduction.\n// Mirrors parseConversation()'s loop so a full reduce equals a streamed parse,\n// and so a bounded page read can resume an *equivalent* parse from a checkpoint\n// by restoring the cross-line state (pending tool_use blocks + team info).\nexport interface ConvReducerState {\n  cwd: string;\n  sessionId: string;\n  nameSlots: NameSlots;\n  latestTimestamp: string;\n  lastPrompt: string;\n  // id -> tool_use block, so a later tool_result line resolves its tool type.\n  pendingToolUses: Record<string, ToolUseBlock>;\n  // teamName -> info, collected as lines are read and applied to messages.\n  teamInfo: Record<string, TeamInfo>;\n  // The last compact_boundary row seen, held until its summary row (a few\n  // lines later) picks it up. Absent in checkpoints written before this field.\n  pendingCompaction?: CompactionInfo;\n}\n\nexport function initialConvState(): ConvReducerState {\n  return {\n    cwd: \"\",\n    sessionId: \"\",\n    nameSlots: {},\n    latestTimestamp: \"\",\n    lastPrompt: \"\",\n    pendingToolUses: {},\n    teamInfo: {},\n  };\n}\n\n// Fold one JSONL entry. Returns the produced ConversationMessage (its message\n// index is the count of messages emitted so far), or null if the line produced\n// no message. Mutates `state` (header fields + cross-line maps).\nexport function reduceConvLine(\n  state: ConvReducerState,\n  entry: Record<string, unknown>,\n): ConversationMessage | null {\n  if (entry.cwd && !state.cwd) state.cwd = entry.cwd as string;\n  if (entry.sessionId && !state.sessionId) state.sessionId = entry.sessionId as string;\n  state.nameSlots = applyClaudeNameEntry(state.nameSlots, entry);\n  if (entry.timestamp) {\n    const ts = entry.timestamp as string;\n    if (!state.latestTimestamp || ts > state.latestTimestamp) state.latestTimestamp = ts;\n  }\n\n  const type = entry.type as string;\n\n  if (type === \"last-prompt\") {\n    if (entry.lastPrompt && !state.lastPrompt) state.lastPrompt = entry.lastPrompt as string;\n    return null;\n  }\n  if (type === \"system\" && entry.subtype === \"compact_boundary\") {\n    const meta = (entry.compactMetadata ?? {}) as Record<string, unknown>;\n    state.pendingCompaction = {\n      trigger: typeof meta.trigger === \"string\" ? meta.trigger : undefined,\n      preTokens: typeof meta.preTokens === \"number\" ? meta.preTokens : undefined,\n      postTokens: typeof meta.postTokens === \"number\" ? meta.postTokens : undefined,\n    };\n    return null;\n  }\n  // system (incl. turn_duration), file-history-snapshot, etc. produce no message.\n  if (type !== \"user\" && type !== \"assistant\") return null;\n  if (entry.isMeta) return null;\n\n  const isCompactSummary = type === \"user\" && entry.isCompactSummary === true;\n  const compaction = isCompactSummary ? state.pendingCompaction : undefined;\n  if (isCompactSummary) delete state.pendingCompaction;\n\n  const msg = entry.message as Record<string, unknown> | undefined;\n\n  const toolUseBlocks = extractToolUseBlocks(msg?.content);\n  for (const block of toolUseBlocks) state.pendingToolUses[block.id] = block;\n\n  const hasToolUseResult = type === \"user\" && entry.toolUseResult != null;\n  const isToolResultOnly = hasToolUseResult && isOnlyToolResultContent(msg?.content);\n  const content = extractTextContent(msg?.content);\n  const thinking = type === \"assistant\" ? extractThinking(msg?.content) : null;\n  const hasThinking = !!(thinking?.content || thinking?.signature);\n\n  if (!(content || isToolResultOnly || toolUseBlocks.length > 0 || hasThinking)) return null;\n\n  const metadata: MessageMetadata = {};\n  if (msg?.model) metadata.model = msg.model as string;\n  if (msg?.stop_reason !== undefined) metadata.stopReason = msg.stop_reason as string | null;\n  if (entry.gitBranch) metadata.gitBranch = entry.gitBranch as string;\n  if (entry.version) metadata.version = entry.version as string;\n\n  const usage = msg?.usage as Record<string, number> | undefined;\n  if (usage) {\n    if (usage.input_tokens) metadata.inputTokens = usage.input_tokens;\n    if (usage.output_tokens) metadata.outputTokens = usage.output_tokens;\n    if (usage.cache_read_input_tokens) metadata.cacheReadTokens = usage.cache_read_input_tokens;\n    if (usage.cache_creation_input_tokens)\n      metadata.cacheCreationTokens = usage.cache_creation_input_tokens;\n  }\n\n  const toolUseNames = extractToolUseNames(msg?.content);\n  if (toolUseNames.length > 0) metadata.toolUses = toolUseNames;\n  if (toolUseBlocks.length > 0) metadata.toolUseBlocks = toolUseBlocks;\n\n  if (isToolResultOnly) {\n    const pending = new Map(Object.entries(state.pendingToolUses));\n    const toolResultBlocks = extractToolResultBlocks(msg?.content, pending);\n    if (toolResultBlocks.length > 0) {\n      metadata.toolResults = toolResultBlocks;\n      // A result consumes its tool_use: prune it so the cross-line map stays\n      // bounded by IN-FLIGHT tool calls instead of growing with the file.\n      // Unpruned, every checkpoint snapshot/serialization of this state costs\n      // O(file) — the structuredClone stall on large live conversations.\n      for (const block of toolResultBlocks) delete state.pendingToolUses[block.toolUseId];\n    }\n  }\n\n  if (entry.teamName) {\n    metadata.teamName = entry.teamName as string;\n    if (!state.teamInfo[metadata.teamName] && content) {\n      const info = parseTeammateMessageTag(content);\n      if (info) state.teamInfo[metadata.teamName] = info;\n    }\n  }\n\n  const thinkingContent = thinking?.content || undefined;\n  const thinkingSignature = thinking?.signature || undefined;\n  const hasMetadata = Object.keys(metadata).length > 0;\n\n  return {\n    role: type as MessageSender,\n    text: content || \"\",\n    timestamp: (entry.timestamp as string) || \"\",\n    uuid: (entry.uuid as string) || undefined,\n    metadata: hasMetadata ? metadata : undefined,\n    isToolResult: isToolResultOnly || undefined,\n    isThinking: thinkingContent || thinkingSignature ? true : undefined,\n    thinkingContent,\n    thinkingSignature,\n    parentUuid: entry.parentUuid !== undefined ? (entry.parentUuid as string | null) : undefined,\n    requestId: type === \"assistant\" ? (entry.requestId as string | undefined) : undefined,\n    promptId: type === \"user\" ? (entry.promptId as string | undefined) : undefined,\n    isSidechain: typeof entry.isSidechain === \"boolean\" ? entry.isSidechain : undefined,\n    permissionMode: type === \"user\" ? (entry.permissionMode as string | undefined) : undefined,\n    hasImages: hasImageBlocks(msg?.content) || undefined,\n    attachment:\n      entry.attachment !== undefined ? (entry.attachment as AttachmentSidecar) : undefined,\n    isCompactSummary: isCompactSummary || undefined,\n    compaction,\n  };\n}\n\n// Parse/classify one raw JSONL line — the exact line→message mapping the\n// scanner uses internally, exported for downstream indexers (e.g. a watcher\n// tailing appended lines) so their semantics cannot drift from the scanner's.\n// Returns the produced ConversationMessage, or null when the line yields none\n// (system/summary/sidecar records, blank or malformed lines). Whether a line\n// produces a message — and its core fields (role, uuid, timestamp) — is\n// state-independent, so calls without `state` classify correctly on their own;\n// pass one shared `state` across successive lines of a file to also resolve\n// cross-line references (tool_result → tool_use types, team info) identically\n// to a full parse.\nexport function parseJsonlLine(\n  line: string,\n  state: ConvReducerState = initialConvState(),\n): ConversationMessage | null {\n  const text = line.trimEnd();\n  if (text.trim().length === 0) return null;\n  let entry: Record<string, unknown>;\n  try {\n    entry = JSON.parse(text);\n  } catch {\n    return null;\n  }\n  return reduceConvLine(state, entry);\n}\n\n// Back-apply collected team info to a set of messages (the post-pass\n// parseConversation does). Safe to call on a window: teamInfo for a given name\n// is stable once first seen, so a window restored from a checkpoint that\n// carries earlier teamInfo gets the same result.\nexport function applyTeamInfo(messages: ConversationMessage[], state: ConvReducerState): void {\n  if (Object.keys(state.teamInfo).length === 0) return;\n  for (const m of messages) {\n    const name = m.metadata?.teamName;\n    if (name && state.teamInfo[name] && m.metadata) m.metadata.teamInfo = state.teamInfo[name];\n  }\n}\n","import { readFileSync, writeFileSync } from \"fs\";\nimport { getLogger } from \"../logger\";\nimport type { ConversationMeta } from \"../types\";\n\nexport const SIDECAR_VERSION = 1;\n\n// Portable, human-readable index summary written next to a JSONL file as\n// `<file>.idx.json`. Not the canonical index (SQLite is) — useful for\n// debugging, portability, and rebuilding the DB if it's lost. Off by default.\nexport interface Sidecar {\n  version: number;\n  sourcePath: string;\n  sizeBytes: number;\n  mtimeMs: number;\n  lastIndexedOffset: number;\n  lastIndexedLine: number;\n  messageCount: number;\n  projectPath: string;\n  projectName: string;\n  branch: string | null;\n  firstSentAt: string | null;\n  firstSentText: string | null;\n  lastSentAt: string | null;\n  lastSentText: string | null;\n  updatedAt: string;\n}\n\nexport function sidecarPath(jsonlPath: string): string {\n  return `${jsonlPath}.idx.json`;\n}\n\nexport function buildSidecar(\n  meta: ConversationMeta,\n  cursor: { sizeBytes: number; mtimeMs: number; offset: number; line: number },\n  updatedAt: string,\n): Sidecar {\n  return {\n    version: SIDECAR_VERSION,\n    sourcePath: meta.filePath,\n    sizeBytes: cursor.sizeBytes,\n    mtimeMs: cursor.mtimeMs,\n    lastIndexedOffset: cursor.offset,\n    lastIndexedLine: cursor.line,\n    messageCount: meta.messageCount,\n    projectPath: meta.projectPath,\n    projectName: meta.projectName,\n    branch: meta.gitBranch,\n    firstSentAt: meta.firstMessage?.timestamp ?? null,\n    firstSentText: meta.firstMessage?.text ?? null,\n    lastSentAt: meta.lastMessage?.timestamp ?? null,\n    lastSentText: meta.lastMessage?.text ?? null,\n    updatedAt,\n  };\n}\n\n// Write the sidecar; never throw into the indexing path — a sidecar is a\n// best-effort artifact, so a write failure (read-only dir, etc.) is logged and\n// swallowed.\nexport function writeSidecar(jsonlPath: string, sidecar: Sidecar): void {\n  try {\n    writeFileSync(sidecarPath(jsonlPath), JSON.stringify(sidecar, null, 2));\n  } catch (err) {\n    getLogger().warn({ jsonlPath, err }, \"sidecar: write failed\");\n  }\n}\n\nexport function readSidecar(jsonlPath: string): Sidecar | null {\n  try {\n    return JSON.parse(readFileSync(sidecarPath(jsonlPath), \"utf-8\")) as Sidecar;\n  } catch {\n    return null;\n  }\n}\n","import { mkdir, readFile, writeFile } from \"fs/promises\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { getLogger } from \"./logger\";\nimport type { Profile } from \"./types\";\n\nconst PROFILES_FILE = \"profiles.json\";\n\nexport function resolveConfigDir(configDir: string): string {\n  return configDir.replace(/^~/, homedir());\n}\n\nexport function getProjectsDir(profile: Profile): string {\n  return join(resolveConfigDir(profile.configDir), \"projects\");\n}\n\nexport async function detectDefaultProfile(): Promise<Profile> {\n  return {\n    id: \"default\",\n    label: \"Default\",\n    configDir: join(homedir(), \".claude\"),\n    enabled: true,\n    emoji: \"🤖\",\n  };\n}\n\nexport async function loadProfiles(configPath: string): Promise<Profile[]> {\n  const log = getLogger();\n  try {\n    const resolved = resolveConfigDir(configPath);\n    const data = await readFile(join(resolved, PROFILES_FILE), \"utf-8\");\n    const profiles = JSON.parse(data) as Profile[];\n    log.debug({ configPath, count: profiles.length }, \"profiles: loaded\");\n    return profiles;\n  } catch (err) {\n    log.debug({ configPath, err }, \"profiles: load failed, using default\");\n    const defaultProfile = await detectDefaultProfile();\n    return [defaultProfile];\n  }\n}\n\nexport async function saveProfiles(profiles: Profile[], configPath: string): Promise<void> {\n  const resolved = resolveConfigDir(configPath);\n  await mkdir(resolved, { recursive: true });\n  await writeFile(join(resolved, PROFILES_FILE), JSON.stringify(profiles, null, 2));\n  getLogger().debug({ configPath, count: profiles.length }, \"profiles: saved\");\n}\n","import fg from \"fast-glob\";\nimport { createReadStream } from \"fs\";\nimport { stat } from \"fs/promises\";\nimport { basename } from \"path\";\nimport { createInterface } from \"readline\";\nimport { canonicalPath } from \"../canonical-path\";\nimport { getLogger } from \"../logger\";\nimport { codexNameSlotsFor } from \"../name-stores\";\nimport {\n  applyNameCandidate,\n  isCodexInjectedContext,\n  type NameSlots,\n  resolveSessionName,\n} from \"../session-name\";\nimport { cleanSystemTags } from \"../tags\";\nimport type {\n  ContentTier,\n  Conversation,\n  ConversationMessage,\n  ConversationMeta,\n  MessageSender,\n  MessageSnapshot,\n  ToolUseBlock,\n} from \"../types\";\nimport {\n  CODEX_CLI_PROVIDER,\n  type DiscoveredConversationFile,\n  type ScannerProvider,\n} from \"./provider\";\n\n// Serializable fold state for a Codex CLI rollout session. Plain JSON so a\n// future persistent engine can resume it from a byte offset (same contract as\n// the Threadbase ReducerState).\nexport interface CodexAccumulator {\n  sessionId: string;\n  cwd: string;\n  gitBranch: string | null;\n  model: string | null;\n  latestTimestamp: string;\n  messageCount: number;\n  lastMessageSender: MessageSender;\n  isSubagent: boolean;\n  parentSessionUuid: string | null;\n  firstUser: MessageSnapshot | null;\n  nameSlots: NameSlots;\n  lastUser: MessageSnapshot | null;\n  lastAssistant: MessageSnapshot | null;\n  toolNames: string[];\n  previewParts: string[];\n  previewLength: number;\n  snippetParts: string[];\n  snippetLength: number;\n}\n\n// Codex CLI rollout sessions live as rollout-*.jsonl under a date-partitioned\n// tree. The Codex provider is opt-in: the scanner only discovers under roots the\n// caller passes explicitly (no default home scan).\nexport class CodexCliProvider implements ScannerProvider<CodexAccumulator> {\n  readonly name = CODEX_CLI_PROVIDER;\n\n  async discover(roots: string[]): Promise<DiscoveredConversationFile[]> {\n    const log = getLogger();\n    const results: DiscoveredConversationFile[] = [];\n    for (const root of roots) {\n      let paths: string[];\n      try {\n        paths = await fg([\"**/rollout-*.jsonl\", \"**/*.jsonl\"], {\n          cwd: root,\n          absolute: true,\n          dot: false,\n          unique: true,\n        });\n      } catch (err) {\n        log.warn({ root, err }, \"codex discovery: glob failed\");\n        continue;\n      }\n      for (const filePath of paths) {\n        try {\n          const s = await stat(filePath);\n          // Canonical form — fast-glob emits forward slashes even on Windows,\n          // but every by-path lookup keys off the canonical spelling.\n          if (s.size > 0) results.push({ filePath: canonicalPath(filePath), account: \"codex\" });\n        } catch (err) {\n          log.warn({ filePath, err }, \"codex discovery: stat failed\");\n        }\n      }\n    }\n    return results;\n  }\n\n  // Codex rollout lines carry distinctive top-level types.\n  canParse(_filePath: string, sample: string): boolean {\n    for (const line of sample.split(\"\\n\")) {\n      if (!line.trim()) continue;\n      try {\n        const e = JSON.parse(line) as Record<string, unknown>;\n        if (e.type === \"session_meta\" || e.type === \"response_item\" || e.type === \"event_msg\") {\n          return true;\n        }\n        if (e.type === \"user\" || e.type === \"assistant\") return false;\n      } catch {}\n    }\n    return false;\n  }\n\n  createEmptyAccumulator(): CodexAccumulator {\n    return {\n      sessionId: \"\",\n      cwd: \"\",\n      gitBranch: null,\n      model: null,\n      latestTimestamp: \"\",\n      messageCount: 0,\n      lastMessageSender: \"user\",\n      isSubagent: false,\n      parentSessionUuid: null,\n      firstUser: null,\n      nameSlots: {},\n      lastUser: null,\n      lastAssistant: null,\n      toolNames: [],\n      previewParts: [],\n      previewLength: 0,\n      snippetParts: [],\n      snippetLength: 0,\n    };\n  }\n\n  reduceEntry(acc: CodexAccumulator, entry: Record<string, unknown>, tier: ContentTier): void {\n    reduceCodexEntry(acc, entry, tier);\n  }\n\n  finalize(\n    acc: CodexAccumulator,\n    filePath: string,\n    account: string,\n    tier: ContentTier,\n  ): ConversationMeta | null {\n    return finalizeCodexMeta(acc, filePath, account, tier);\n  }\n}\n\n// ─── Reducer (exported for tests / future incremental engine) ───────────\n\nconst asString = (v: unknown): string => (typeof v === \"string\" ? v : \"\");\n\n// Pull display text out of a Codex content array of {type,text} blocks.\nfunction extractCodexText(content: unknown): string {\n  if (typeof content === \"string\") return cleanSystemTags(content);\n  if (!Array.isArray(content)) return \"\";\n  return content\n    .map((item) => {\n      if (typeof item === \"string\") return item;\n      const t = item?.type;\n      if ((t === \"input_text\" || t === \"output_text\" || t === \"text\") && item?.text) {\n        return item.text as string;\n      }\n      return \"\";\n    })\n    .filter(Boolean)\n    .map(cleanSystemTags)\n    .join(\" \");\n}\n\n// Map one raw Codex rollout JSONL line to the message it renders as, or null\n// when it renders as nothing (session_meta, event_msg, encrypted reasoning,\n// developer/system roles, an empty-after-cleanSystemTags body, malformed JSON).\n// Stateless — a Codex line carries everything the decision needs — and tolerant\n// by construction: it never throws. parseCodexConversation() below delegates to\n// it so the \"which lines render as messages\" rule has exactly one definition.\nexport function parseCodexJsonlLine(line: string): ConversationMessage | null {\n  if (!line.trim()) return null;\n  let entry: Record<string, unknown>;\n  try {\n    entry = JSON.parse(line);\n  } catch {\n    return null;\n  }\n  return codexEntryToMessage(entry);\n}\n\n// Same decision, on an already-parsed entry — lets parseCodexConversation reuse\n// its single JSON.parse (it also needs session_meta and the timestamp).\n//\n// Tool calls, their outputs and reasoning summaries render too, in the shape the\n// Claude parser gives them (toolUseBlocks / toolResults / thinkingContent with\n// empty text), so a host renders every provider with one mapper. `uuid` is the\n// rollout item id, which is also what a host's live stream keys the line on.\nfunction codexEntryToMessage(entry: Record<string, unknown>): ConversationMessage | null {\n  const payload = entry.payload as Record<string, unknown> | undefined;\n  if (!payload || typeof payload !== \"object\") return null;\n  if (entry.type !== \"response_item\") return null;\n  const timestamp = asString(entry.timestamp);\n  const uuid = asString(payload.id) || undefined;\n  const base = { timestamp, ...(uuid ? { uuid } : {}) };\n\n  switch (payload.type) {\n    case \"message\": {\n      const role = payload.role;\n      if (role !== \"user\" && role !== \"assistant\") return null;\n      const text = extractCodexText(payload.content);\n      if (!text) return null;\n      return { role: role as MessageSender, text, ...base };\n    }\n    case \"function_call\":\n    case \"custom_tool_call\":\n    case \"web_search_call\": {\n      const block = codexToolUseBlock(payload);\n      if (!block) return null;\n      return {\n        role: \"assistant\",\n        text: \"\",\n        ...base,\n        metadata: { toolUses: [block.name], toolUseBlocks: [block] },\n      };\n    }\n    case \"function_call_output\":\n    case \"custom_tool_call_output\": {\n      const toolUseId = asString(payload.call_id);\n      if (!toolUseId) return null;\n      return {\n        role: \"user\",\n        text: \"\",\n        ...base,\n        isToolResult: true,\n        metadata: {\n          toolResults: [\n            {\n              toolUseId,\n              type: \"generic\",\n              content: { output: codexToolOutputText(payload.output) },\n            },\n          ],\n        },\n      };\n    }\n    case \"reasoning\": {\n      // Almost always encrypted_content with an empty summary; only a readable\n      // summary renders.\n      const summary = Array.isArray(payload.summary) ? payload.summary : [];\n      const thinkingContent = summary\n        .map((s) => asString((s as Record<string, unknown>)?.text))\n        .filter((t) => t.trim())\n        .join(\"\\n\\n\");\n      if (!thinkingContent) return null;\n      return { role: \"assistant\", text: \"\", ...base, isThinking: true, thinkingContent };\n    }\n    default:\n      return null;\n  }\n}\n\nfunction codexToolUseBlock(payload: Record<string, unknown>): ToolUseBlock | null {\n  if (payload.type === \"web_search_call\") {\n    const action = payload.action;\n    return {\n      id: asString(payload.id),\n      name: \"web_search\",\n      input: action && typeof action === \"object\" ? (action as Record<string, unknown>) : {},\n    };\n  }\n  const name = asString(payload.name);\n  if (!name) return null;\n  const id = asString(payload.call_id);\n  if (payload.type === \"custom_tool_call\") {\n    return { id, name, input: { input: asString(payload.input) } };\n  }\n  const args = asString(payload.arguments);\n  let input: Record<string, unknown> = { arguments: args };\n  try {\n    const parsed = JSON.parse(args);\n    if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) input = parsed;\n  } catch {\n    // Not JSON — keep the raw string.\n  }\n  return { id, name, input };\n}\n\n// Tool output is a plain string or an array of {type: input_text, text} parts.\n// Not run through cleanSystemTags: it is program output, shown verbatim.\nfunction codexToolOutputText(output: unknown): string {\n  if (typeof output === \"string\") return output;\n  if (!Array.isArray(output)) return \"\";\n  return output\n    .map((item) => (typeof item === \"string\" ? item : asString(item?.text)))\n    .filter(Boolean)\n    .join(\"\\n\");\n}\n\n// Fold one Codex rollout line into the accumulator. Tolerant by construction:\n// any line it doesn't recognise is ignored, never thrown on.\nexport function reduceCodexEntry(\n  acc: CodexAccumulator,\n  entry: Record<string, unknown>,\n  tier: ContentTier,\n): void {\n  const ts = asString(entry.timestamp);\n  if (ts && (!acc.latestTimestamp || ts > acc.latestTimestamp)) acc.latestTimestamp = ts;\n\n  const payload = entry.payload as Record<string, unknown> | undefined;\n  if (!payload || typeof payload !== \"object\") return;\n\n  const type = entry.type as string;\n\n  if (type === \"session_meta\") {\n    if (!acc.sessionId) acc.sessionId = asString(payload.id);\n    if (!acc.cwd) acc.cwd = asString(payload.cwd);\n    const git = payload.git as Record<string, unknown> | undefined;\n    if (acc.gitBranch === null && git?.branch) acc.gitBranch = asString(git.branch) || null;\n    if (!acc.isSubagent && isSubagentSource(payload.source)) {\n      acc.isSubagent = true;\n      acc.parentSessionUuid = parentThreadId(payload.source);\n    }\n    return;\n  }\n\n  // Model lives on turn_context records (and occasionally elsewhere).\n  if (acc.model === null && payload.model) acc.model = asString(payload.model) || null;\n\n  if (type !== \"response_item\") return;\n\n  const ptype = payload.type as string;\n\n  // Tool calls — collect the tool name.\n  if (ptype === \"function_call\" || ptype === \"custom_tool_call\") {\n    const name = asString(payload.name);\n    if (name && !acc.toolNames.includes(name)) acc.toolNames.push(name);\n    return;\n  }\n\n  if (ptype !== \"message\") return;\n\n  const role = payload.role;\n  // developer/system/tool roles carry sandbox boilerplate — not user-visible turns.\n  if (role !== \"user\" && role !== \"assistant\") return;\n\n  const text = extractCodexText(payload.content);\n  if (!text) return;\n\n  // Codex prepends its AGENTS.md / sandbox preamble as the first `user` turn, so\n  // the role guard above cannot catch it: same category, wrong label. Counting it\n  // makes every conversation's preview and firstMessage that blob.\n  //\n  // Bounded to LEADING turns on purpose. A human pasting instruction text\n  // mid-conversation would otherwise go silently uncounted, and messageCount is a\n  // number people compare against what they can see. Codex only injects at the\n  // head, so the bound costs nothing real.\n  if (acc.messageCount === 0 && role === \"user\" && isCodexInjectedContext(text)) return;\n\n  const sender = role as MessageSender;\n  acc.messageCount++;\n  acc.lastMessageSender = sender;\n\n  const snapshot: MessageSnapshot = { text: text.slice(0, 200), timestamp: ts };\n  if (sender === \"user\") {\n    if (!acc.firstUser) acc.firstUser = snapshot;\n    acc.nameSlots = applyNameCandidate(acc.nameSlots, \"firstUserMessage\", text, CODEX_CLI_PROVIDER);\n    acc.lastUser = snapshot;\n  } else {\n    acc.lastAssistant = snapshot;\n  }\n\n  if (acc.previewLength < tier.previewMax) {\n    acc.previewParts.push(text);\n    acc.previewLength += text.length;\n  }\n  if (acc.snippetLength < tier.snippetMax) {\n    const remaining = tier.snippetMax - acc.snippetLength;\n    const chunk = text.length > remaining ? text.slice(0, remaining) : text;\n    acc.snippetParts.push(chunk);\n    acc.snippetLength += chunk.length;\n  }\n}\n\nexport function finalizeCodexMeta(\n  acc: CodexAccumulator,\n  filePath: string,\n  account: string,\n  tier: ContentTier,\n): ConversationMeta | null {\n  if (acc.messageCount === 0) return null;\n\n  const sessionId = acc.sessionId || basename(filePath, \".jsonl\");\n  const nameSlots = { ...acc.nameSlots, ...codexNameSlotsFor(filePath, acc.sessionId) };\n  const projectPath = acc.cwd;\n  // Treat a session with tool calls but no plain back-and-forth as task-shaped.\n  const kind: \"conversation\" | \"task\" =\n    acc.lastAssistant === null && acc.toolNames.length > 0 ? \"task\" : \"conversation\";\n\n  return {\n    id: filePath,\n    filePath,\n    provider: CODEX_CLI_PROVIDER,\n    kind,\n    externalSessionId: acc.sessionId || undefined,\n    sessionId,\n    sessionName: resolveSessionName(nameSlots).name ?? \"\",\n    nameSlots,\n    projectPath,\n    projectName: getShortProjectName(projectPath),\n    account,\n    timestamp: acc.latestTimestamp || new Date().toISOString(),\n    messageCount: acc.messageCount,\n    lastMessageSender: acc.lastMessageSender,\n    preview: acc.previewParts.join(\" \").slice(0, tier.previewMax),\n    contentSnippet: acc.snippetParts.join(\" \"),\n    gitBranch: acc.gitBranch,\n    model: acc.model,\n    isSubagent: acc.isSubagent,\n    // Codex exposes no path-shaped parent, and no per-child id of its own.\n    parentSessionId: null,\n    parentSessionUuid: acc.parentSessionUuid ?? undefined,\n    isTeammate: false,\n    teamName: null,\n    toolNames: acc.toolNames,\n    firstMessage: acc.firstUser,\n    lastMessage: acc.lastAssistant ?? acc.lastUser,\n    lastPrompt: acc.lastUser?.text || undefined,\n  };\n}\n\nexport { isCodexInjectedContext };\n\nfunction getShortProjectName(fullPath: string): string {\n  return fullPath.split(\"/\").filter(Boolean).slice(-3).join(\"/\");\n}\n\n// `session_meta.payload.source` is either a plain string for a top-level session\n// (\"cli\", \"vscode\", \"exec\", …) or an object naming how the child was spawned.\n// The `subagent` VALUE is not always a dict — {\"subagent\":\"review\"} is a real\n// shape on disk alongside {\"subagent\":{\"thread_spawn\":{…}}} and\n// {\"subagent\":{\"other\":\"guardian\"}} — so presence of the key is the test, not\n// its shape. Unknown string sources are treated as ordinary sessions rather\n// than failing, so a future Codex adds a source without breaking the scan.\nfunction isSubagentSource(source: unknown): boolean {\n  return typeof source === \"object\" && source !== null && \"subagent\" in source;\n}\n\n// Only thread_spawn names a parent. A subagent without a discoverable parent is\n// still a subagent — it just has no parent id to report.\nfunction parentThreadId(source: unknown): string | null {\n  if (!isSubagentSource(source)) return null;\n  const subagent = (source as { subagent: unknown }).subagent;\n  if (typeof subagent !== \"object\" || subagent === null) return null;\n  const spawn = (subagent as Record<string, unknown>).thread_spawn;\n  if (typeof spawn !== \"object\" || spawn === null) return null;\n  return asString((spawn as Record<string, unknown>).parent_thread_id) || null;\n}\n\n// Full conversation parse for a Codex rollout session — the Codex analogue of\n// parseConversation(). Streams the file, emitting one ConversationMessage per\n// user/assistant message item. Used by getConversation()/getConversationPage()\n// when the resolved meta is a codex-cli conversation.\nexport async function parseCodexConversation(\n  filePath: string,\n  account: string,\n): Promise<Conversation | null> {\n  const log = getLogger();\n  const messages: ConversationMessage[] = [];\n  const textParts: string[] = [];\n  let sessionId = \"\";\n  let cwd = \"\";\n  let latestTimestamp = \"\";\n  let lastUserText = \"\";\n  let nameSlots: NameSlots = {};\n\n  const rl = createInterface({ input: createReadStream(filePath), crlfDelay: Infinity });\n  try {\n    for await (const line of rl) {\n      if (!line.trim()) continue;\n      let entry: Record<string, unknown>;\n      try {\n        entry = JSON.parse(line);\n      } catch {\n        continue;\n      }\n      const ts = asString(entry.timestamp);\n      if (ts && (!latestTimestamp || ts > latestTimestamp)) latestTimestamp = ts;\n      const payload = entry.payload as Record<string, unknown> | undefined;\n      if (!payload || typeof payload !== \"object\") continue;\n\n      if (entry.type === \"session_meta\") {\n        if (!sessionId) sessionId = asString(payload.id);\n        if (!cwd) cwd = asString(payload.cwd);\n        continue;\n      }\n      const message = codexEntryToMessage(entry);\n      if (!message) continue;\n      messages.push(message);\n      // Tool and thinking messages carry no text; they must not blank lastPrompt.\n      if (!message.text) continue;\n      textParts.push(message.text);\n      if (message.role === \"user\") {\n        lastUserText = message.text;\n        nameSlots = applyNameCandidate(\n          nameSlots,\n          \"firstUserMessage\",\n          message.text,\n          CODEX_CLI_PROVIDER,\n        );\n      }\n    }\n  } catch (err) {\n    log.warn({ filePath, err }, \"parseCodexConversation: read failed\");\n    return null;\n  }\n\n  if (messages.length === 0) return null;\n\n  return {\n    id: filePath,\n    filePath,\n    projectPath: cwd,\n    projectName: getShortProjectName(cwd),\n    sessionId: sessionId || basename(filePath, \".jsonl\"),\n    sessionName:\n      resolveSessionName({ ...nameSlots, ...codexNameSlotsFor(filePath, sessionId) }).name ?? \"\",\n    messages,\n    fullText: textParts.join(\" \"),\n    timestamp: latestTimestamp || new Date().toISOString(),\n    messageCount: messages.length,\n    account,\n    lastPrompt: lastUserText || undefined,\n  };\n}\n","import { sep } from \"path\";\n\n// Canonical absolute-path form used as the identity key for a conversation file\n// across the whole index (SQLite rows, the in-memory metadata cache, the\n// dir-mtime watermarks, and every by-path lookup).\n//\n// The problem this solves: fast-glob (discovery) emits forward-slash paths even\n// on Windows, while the file watcher (chokidar), path.join(), and every caller\n// that builds a path with the platform API emit native (back)slashes. The same\n// real file could therefore be stored/looked-up under two different string keys\n// on Windows, so by-path APIs miss rows depending on the caller's separator\n// style. Normalizing collapses both forms onto one key.\n//\n// The native separator is the canonical form, not fast-glob's forward slashes:\n// a path handed back to a caller (ConversationMeta.filePath/id, a change event,\n// a metadata-cache key) is compared against platform-built paths by every\n// consumer, so the native spelling is the one the public surface has to speak.\n// The cost is a one-time reindex for an index.db written before this change\n// (its forward-slash rows miss, get reinserted in native form, and the same\n// pass's deletion-reconcile retires the old ones) — bounded and self-healing,\n// unlike the recurrent duplicate-row churn this fixes.\n//\n// On POSIX this is a no-op: the separator is already \"/\", and a backslash is a\n// legal filename character there, so it must be left untouched.\nexport function canonicalPath(p: string): string {\n  return sep === \"\\\\\" ? p.replace(/\\//g, \"\\\\\") : p;\n}\n","import fg from \"fast-glob\";\nimport { createReadStream, statSync } from \"fs\";\nimport { stat } from \"fs/promises\";\nimport { basename, dirname } from \"path\";\nimport { createInterface } from \"readline\";\nimport { canonicalPath } from \"../canonical-path\";\nimport { getLogger } from \"../logger\";\nimport { copilotNameSlotsFor } from \"../name-stores\";\nimport { applyNameCandidate, type NameSlots, resolveSessionName } from \"../session-name\";\nimport { cleanSystemTags } from \"../tags\";\nimport type {\n  ContentTier,\n  Conversation,\n  ConversationMessage,\n  ConversationMeta,\n  MessageSender,\n  MessageSnapshot,\n  ToolUseBlock,\n} from \"../types\";\nimport {\n  COPILOT_PROVIDER,\n  type DiscoveredConversationFile,\n  type ScannerProvider,\n} from \"./provider\";\n\n// GitHub Copilot CLI keeps one directory per session,\n// `<COPILOT_HOME or ~/.copilot>/session-state/<sessionId>/events.jsonl`.\n// Every line is `{ type, data, id, timestamp, parentId }` (verified on Copilot\n// CLI 1.0.81 through 1.0.92).\n//\n// Only three event types are read:\n//   session.start      data.sessionId, data.context.cwd, data.context.branch\n//   user.message       data.content — the prompt as typed. data.transformedContent\n//                      is the same prompt wrapped in <current_datetime> /\n//                      <system_reminder> blocks for the model, so it is not used.\n//   assistant.message  data.content, data.toolRequests[], data.model\n//\n// Everything else is ignored, and for `model.*` that is load-bearing:\n// model.message / model.response / model.messages_snapshot carry `role` and\n// `content` too, but they record side-channel calls to small utility models\n// (routing, classification), never the reply the user saw. Indexing them would\n// add text to the conversation that was never in it.\nexport interface CopilotAccumulator {\n  sessionId: string;\n  cwd: string;\n  gitBranch: string | null;\n  model: string | null;\n  latestTimestamp: string;\n  messageCount: number;\n  lastMessageSender: MessageSender;\n  firstUser: MessageSnapshot | null;\n  nameSlots: NameSlots;\n  lastUser: MessageSnapshot | null;\n  lastAssistant: MessageSnapshot | null;\n  toolNames: string[];\n  previewParts: string[];\n  previewLength: number;\n  snippetParts: string[];\n  snippetLength: number;\n}\n\nconst SESSION_START = \"session.start\";\nconst USER_MESSAGE = \"user.message\";\nconst ASSISTANT_MESSAGE = \"assistant.message\";\n\nexport class CopilotProvider implements ScannerProvider<CopilotAccumulator> {\n  readonly name = COPILOT_PROVIDER;\n\n  async discover(roots: string[]): Promise<DiscoveredConversationFile[]> {\n    const log = getLogger();\n    const results: DiscoveredConversationFile[] = [];\n    for (const root of roots) {\n      let paths: string[];\n      try {\n        // Accept either the session-state directory or the Copilot home above\n        // it. session-store.db (a listing subset of the same sessions) is not\n        // read, so a session is never counted twice.\n        paths = await fg([\"*/events.jsonl\", \"session-state/*/events.jsonl\"], {\n          cwd: root,\n          absolute: true,\n          dot: false,\n          unique: true,\n        });\n      } catch (err) {\n        log.warn({ root, err }, \"copilot discovery: glob failed\");\n        continue;\n      }\n      for (const filePath of paths) {\n        try {\n          const s = await stat(filePath);\n          if (s.size > 0) results.push({ filePath: canonicalPath(filePath), account: \"copilot\" });\n        } catch (err) {\n          log.warn({ filePath, err }, \"copilot discovery: stat failed\");\n        }\n      }\n    }\n    return results;\n  }\n\n  canParse(_filePath: string, sample: string): boolean {\n    for (const line of sample.split(\"\\n\")) {\n      if (!line.trim()) continue;\n      try {\n        if (looksLikeCopilotEvent(JSON.parse(line) as Record<string, unknown>)) return true;\n      } catch {}\n    }\n    return false;\n  }\n\n  createEmptyAccumulator(): CopilotAccumulator {\n    return {\n      sessionId: \"\",\n      cwd: \"\",\n      gitBranch: null,\n      model: null,\n      latestTimestamp: \"\",\n      messageCount: 0,\n      lastMessageSender: \"user\",\n      firstUser: null,\n      nameSlots: {},\n      lastUser: null,\n      lastAssistant: null,\n      toolNames: [],\n      previewParts: [],\n      previewLength: 0,\n      snippetParts: [],\n      snippetLength: 0,\n    };\n  }\n\n  reduceEntry(acc: CopilotAccumulator, entry: Record<string, unknown>, tier: ContentTier): void {\n    reduceCopilotEntry(acc, entry, tier);\n  }\n\n  finalize(\n    acc: CopilotAccumulator,\n    filePath: string,\n    account: string,\n    tier: ContentTier,\n  ): ConversationMeta | null {\n    return finalizeCopilotMeta(acc, filePath, account, tier);\n  }\n}\n\n// The envelope, not a type list: Claude lines have no `data`/`parentId` (they\n// use `parentUuid`), Codex lines carry `payload`, Cursor lines have no `type`.\nexport function looksLikeCopilotEvent(entry: Record<string, unknown>): boolean {\n  return (\n    typeof entry.type === \"string\" &&\n    typeof entry.id === \"string\" &&\n    \"parentId\" in entry &&\n    asRecord(entry.data) !== null\n  );\n}\n\nconst asString = (v: unknown): string => (typeof v === \"string\" ? v : \"\");\n\nfunction asRecord(v: unknown): Record<string, unknown> | null {\n  return v && typeof v === \"object\" && !Array.isArray(v) ? (v as Record<string, unknown>) : null;\n}\n\n// `arguments` is an object for most tool requests and a raw string for a few.\nfunction copilotToolUseBlocks(data: Record<string, unknown>): ToolUseBlock[] {\n  if (!Array.isArray(data.toolRequests)) return [];\n  const blocks: ToolUseBlock[] = [];\n  for (const item of data.toolRequests) {\n    const request = asRecord(item);\n    const name = asString(request?.name);\n    if (!request || !name) continue;\n    const input = asRecord(request.arguments) ?? { arguments: asString(request.arguments) };\n    blocks.push({ id: asString(request.toolCallId), name, input });\n  }\n  return blocks;\n}\n\nfunction copilotEntryToMessage(entry: Record<string, unknown>): ConversationMessage | null {\n  if (!looksLikeCopilotEvent(entry)) return null;\n  const data = entry.data as Record<string, unknown>;\n  const timestamp = asString(entry.timestamp);\n  const uuid = asString(data.messageId) || asString(entry.id);\n\n  if (entry.type === USER_MESSAGE) {\n    const text = cleanSystemTags(asString(data.content));\n    return text ? { role: \"user\", text, timestamp, uuid } : null;\n  }\n  if (entry.type === ASSISTANT_MESSAGE) {\n    const text = asString(data.content).trim();\n    const blocks = copilotToolUseBlocks(data);\n    // A tool-calling step has empty content; it still renders as its tool calls.\n    if (!text && blocks.length === 0) return null;\n    const model = asString(data.model);\n    const metadata = {\n      ...(model ? { model } : {}),\n      ...(blocks.length > 0 ? { toolUses: blocks.map((b) => b.name), toolUseBlocks: blocks } : {}),\n    };\n    return {\n      role: \"assistant\",\n      text,\n      timestamp,\n      uuid,\n      ...(Object.keys(metadata).length > 0 ? { metadata } : {}),\n    };\n  }\n  return null;\n}\n\n// Stateless line → message mapping, for a host tailing appended lines. Returns\n// null for every event that is not a user prompt or an assistant message.\nexport function parseCopilotJsonlLine(line: string): ConversationMessage | null {\n  const trimmed = line.trim();\n  if (!trimmed) return null;\n  try {\n    return copilotEntryToMessage(JSON.parse(trimmed) as Record<string, unknown>);\n  } catch {\n    return null;\n  }\n}\n\n// session.start is the only event that names the session and its directory.\nfunction readSessionStart(\n  entry: Record<string, unknown>,\n): { sessionId: string; cwd: string; branch: string } | null {\n  if (entry.type !== SESSION_START) return null;\n  const data = asRecord(entry.data);\n  if (!data) return null;\n  const context = asRecord(data.context);\n  return {\n    sessionId: asString(data.sessionId),\n    cwd: asString(context?.cwd),\n    branch: asString(context?.branch),\n  };\n}\n\nexport function reduceCopilotEntry(\n  acc: CopilotAccumulator,\n  entry: Record<string, unknown>,\n  tier: ContentTier,\n): void {\n  const start = readSessionStart(entry);\n  if (start) {\n    if (!acc.sessionId) acc.sessionId = start.sessionId;\n    if (!acc.cwd) acc.cwd = start.cwd;\n    if (!acc.gitBranch && start.branch) acc.gitBranch = start.branch;\n    return;\n  }\n\n  const message = copilotEntryToMessage(entry);\n  if (!message) return;\n\n  for (const name of message.metadata?.toolUses ?? []) {\n    if (!acc.toolNames.includes(name)) acc.toolNames.push(name);\n  }\n  if (message.metadata?.model) acc.model = message.metadata.model;\n  // Tool-only steps are not counted as messages, matching the other providers.\n  const { role, text, timestamp } = message;\n  if (!text) return;\n\n  if (timestamp && (!acc.latestTimestamp || timestamp > acc.latestTimestamp)) {\n    acc.latestTimestamp = timestamp;\n  }\n  acc.messageCount++;\n  acc.lastMessageSender = role;\n  const snapshot: MessageSnapshot = { text: text.slice(0, 200), timestamp };\n  if (role === \"user\") {\n    if (!acc.firstUser) acc.firstUser = snapshot;\n    acc.nameSlots = applyNameCandidate(acc.nameSlots, \"firstUserMessage\", text, COPILOT_PROVIDER);\n    acc.lastUser = snapshot;\n  } else {\n    acc.lastAssistant = snapshot;\n  }\n  if (acc.previewLength < tier.previewMax) {\n    acc.previewParts.push(text);\n    acc.previewLength += text.length;\n  }\n  if (acc.snippetLength < tier.snippetMax) {\n    const remaining = tier.snippetMax - acc.snippetLength;\n    const chunk = text.length > remaining ? text.slice(0, remaining) : text;\n    acc.snippetParts.push(chunk);\n    acc.snippetLength += chunk.length;\n  }\n}\n\nexport function finalizeCopilotMeta(\n  acc: CopilotAccumulator,\n  filePath: string,\n  account: string,\n  tier: ContentTier,\n): ConversationMeta | null {\n  if (acc.messageCount === 0) return null;\n\n  const sessionId = acc.sessionId || sessionIdFromPath(filePath);\n  const nameSlots = { ...acc.nameSlots, ...copilotNameSlotsFor(filePath) };\n  const kind: \"conversation\" | \"task\" =\n    acc.lastAssistant === null && acc.toolNames.length > 0 ? \"task\" : \"conversation\";\n\n  return {\n    id: filePath,\n    filePath,\n    provider: COPILOT_PROVIDER,\n    kind,\n    externalSessionId: sessionId,\n    sessionId,\n    sessionName: resolveSessionName(nameSlots).name ?? \"\",\n    nameSlots,\n    projectPath: acc.cwd,\n    projectName: getShortProjectName(acc.cwd),\n    account,\n    timestamp: acc.latestTimestamp || fileMtimeIso(filePath),\n    messageCount: acc.messageCount,\n    lastMessageSender: acc.lastMessageSender,\n    preview: acc.previewParts.join(\" \").slice(0, tier.previewMax),\n    contentSnippet: acc.snippetParts.join(\" \"),\n    gitBranch: acc.gitBranch,\n    model: acc.model,\n    isSubagent: false,\n    parentSessionId: null,\n    isTeammate: false,\n    teamName: null,\n    toolNames: acc.toolNames,\n    firstMessage: acc.firstUser,\n    lastMessage: acc.lastAssistant ?? acc.lastUser,\n    lastPrompt: acc.lastUser?.text || undefined,\n  };\n}\n\n// The session directory is named for the session id, which covers a file whose\n// session.start line is missing or unreadable.\nfunction sessionIdFromPath(filePath: string): string {\n  return basename(dirname(filePath));\n}\n\nfunction getShortProjectName(fullPath: string): string {\n  return fullPath.split(/[/\\\\]/).filter(Boolean).slice(-3).join(\"/\");\n}\n\nfunction fileMtimeIso(filePath: string): string {\n  try {\n    return new Date(statSync(filePath).mtimeMs).toISOString();\n  } catch {\n    return new Date().toISOString();\n  }\n}\n\nexport async function parseCopilotConversation(\n  filePath: string,\n  account: string,\n): Promise<Conversation | null> {\n  const log = getLogger();\n  const messages: ConversationMessage[] = [];\n  const textParts: string[] = [];\n  let sessionId = \"\";\n  let cwd = \"\";\n  let latestTimestamp = \"\";\n  let lastUserText = \"\";\n  let nameSlots: NameSlots = {};\n\n  const rl = createInterface({ input: createReadStream(filePath), crlfDelay: Infinity });\n  try {\n    for await (const line of rl) {\n      if (!line.trim()) continue;\n      let entry: Record<string, unknown>;\n      try {\n        entry = JSON.parse(line);\n      } catch {\n        continue;\n      }\n      const start = readSessionStart(entry);\n      if (start) {\n        if (!sessionId) sessionId = start.sessionId;\n        if (!cwd) cwd = start.cwd;\n        continue;\n      }\n      const message = copilotEntryToMessage(entry);\n      if (!message) continue;\n      if (message.timestamp && (!latestTimestamp || message.timestamp > latestTimestamp)) {\n        latestTimestamp = message.timestamp;\n      }\n      messages.push(message);\n      // Tool-only messages carry no text; they must not blank lastPrompt.\n      if (!message.text) continue;\n      textParts.push(message.text);\n      if (message.role === \"user\") {\n        lastUserText = message.text;\n        nameSlots = applyNameCandidate(\n          nameSlots,\n          \"firstUserMessage\",\n          message.text,\n          COPILOT_PROVIDER,\n        );\n      }\n    }\n  } catch (err) {\n    log.warn({ filePath, err }, \"parseCopilotConversation: read failed\");\n    return null;\n  }\n\n  if (messages.length === 0) return null;\n\n  return {\n    id: filePath,\n    filePath,\n    projectPath: cwd,\n    projectName: getShortProjectName(cwd),\n    sessionId: sessionId || sessionIdFromPath(filePath),\n    sessionName: resolveSessionName({ ...nameSlots, ...copilotNameSlotsFor(filePath) }).name ?? \"\",\n    messages,\n    fullText: textParts.join(\" \"),\n    timestamp: latestTimestamp || fileMtimeIso(filePath),\n    messageCount: messages.length,\n    account,\n    lastPrompt: lastUserText || undefined,\n  };\n}\n","import { createHash } from \"crypto\";\nimport fg from \"fast-glob\";\nimport { createReadStream, existsSync, statSync } from \"fs\";\nimport { stat } from \"fs/promises\";\nimport { basename, dirname, join, sep } from \"path\";\nimport { createInterface } from \"readline\";\nimport { canonicalPath } from \"../canonical-path\";\nimport { getLogger } from \"../logger\";\nimport { cursorNameSlotsFor } from \"../name-stores\";\nimport { applyNameCandidate, type NameSlots, resolveSessionName } from \"../session-name\";\nimport { cleanSystemTags } from \"../tags\";\nimport type {\n  ContentTier,\n  Conversation,\n  ConversationMessage,\n  ConversationMeta,\n  MessageSender,\n  MessageSnapshot,\n  ToolUseBlock,\n} from \"../types\";\nimport { parseCodexJsonlLine } from \"./codex-cli\";\nimport { CURSOR_PROVIDER, type DiscoveredConversationFile, type ScannerProvider } from \"./provider\";\n\n// Cursor agent-transcripts JSONL: `{ role, message: { content: [{ type, text }] } }`.\n// No envelope `type` of user/assistant (that's Claude), no session_meta (that's\n// Codex), and no per-line timestamp — user turns wrap the clock in <timestamp>.\nexport interface CursorAccumulator {\n  sessionId: string;\n  latestTimestamp: string;\n  messageCount: number;\n  lastMessageSender: MessageSender;\n  isSubagent: boolean;\n  parentSessionUuid: string | null;\n  subagentId: string | null;\n  firstUser: MessageSnapshot | null;\n  nameSlots: NameSlots;\n  lastUser: MessageSnapshot | null;\n  lastAssistant: MessageSnapshot | null;\n  isImportedFromClaude: boolean;\n  isImportedFromCodex: boolean;\n  toolNames: string[];\n  previewParts: string[];\n  previewLength: number;\n  snippetParts: string[];\n  snippetLength: number;\n}\n\nconst TIMESTAMP_RE = /<timestamp>\\s*([\\s\\S]*?)\\s*<\\/timestamp>/;\nconst USER_QUERY_RE = /<user_query>\\s*([\\s\\S]*?)\\s*<\\/user_query>/;\nconst SUBAGENT_PATH_RE = /[\\\\/]subagents[\\\\/]/;\n\nexport class CursorProvider implements ScannerProvider<CursorAccumulator> {\n  readonly name = CURSOR_PROVIDER;\n\n  async discover(roots: string[]): Promise<DiscoveredConversationFile[]> {\n    const log = getLogger();\n    const results: DiscoveredConversationFile[] = [];\n    for (const root of roots) {\n      let paths: string[];\n      try {\n        // Composer chat lives in state.vscdb — not these files. Only the JSONL\n        // agent-transcripts tree is in scope (feasibility Phase 1, not Phase 3).\n        paths = await fg([\"**/agent-transcripts/**/*.jsonl\"], {\n          cwd: root,\n          absolute: true,\n          dot: false,\n          unique: true,\n        });\n      } catch (err) {\n        log.warn({ root, err }, \"cursor discovery: glob failed\");\n        continue;\n      }\n      for (const filePath of paths) {\n        try {\n          const s = await stat(filePath);\n          if (s.size > 0) results.push({ filePath: canonicalPath(filePath), account: \"cursor\" });\n        } catch (err) {\n          log.warn({ filePath, err }, \"cursor discovery: stat failed\");\n        }\n      }\n    }\n    return results;\n  }\n\n  canParse(filePath: string, sample: string): boolean {\n    const underTranscripts = /[\\\\/]agent-transcripts[\\\\/]/.test(filePath);\n    for (const line of sample.split(\"\\n\")) {\n      if (!line.trim()) continue;\n      try {\n        const e = JSON.parse(line) as Record<string, unknown>;\n        // Native Claude/Codex files stay with those providers. Copies that land\n        // under agent-transcripts are Cursor imports — claim them here so they\n        // are not skipped, and fold the envelopes in reduceEntry.\n        if (looksLikeImportedCodexEntry(e) || looksLikeImportedClaudeEntry(e)) {\n          return underTranscripts;\n        }\n        if (looksLikeCursorChatEntry(e)) return true;\n      } catch {}\n    }\n    return false;\n  }\n\n  createEmptyAccumulator(): CursorAccumulator {\n    return {\n      sessionId: \"\",\n      latestTimestamp: \"\",\n      messageCount: 0,\n      lastMessageSender: \"user\",\n      isSubagent: false,\n      parentSessionUuid: null,\n      subagentId: null,\n      firstUser: null,\n      nameSlots: {},\n      lastUser: null,\n      lastAssistant: null,\n      isImportedFromClaude: false,\n      isImportedFromCodex: false,\n      toolNames: [],\n      previewParts: [],\n      previewLength: 0,\n      snippetParts: [],\n      snippetLength: 0,\n    };\n  }\n\n  reduceEntry(acc: CursorAccumulator, entry: Record<string, unknown>, tier: ContentTier): void {\n    reduceCursorEntry(acc, entry, tier);\n  }\n\n  finalize(\n    acc: CursorAccumulator,\n    filePath: string,\n    account: string,\n    tier: ContentTier,\n  ): ConversationMeta | null {\n    return finalizeCursorMeta(acc, filePath, account, tier);\n  }\n}\n\nexport function looksLikeCursorChatEntry(entry: Record<string, unknown>): boolean {\n  const role = entry.role;\n  if (role !== \"user\" && role !== \"assistant\") return false;\n  // Claude's envelope uses top-level type user/assistant. Cursor does not.\n  if (entry.type === \"user\" || entry.type === \"assistant\") return false;\n  return true;\n}\n\nfunction looksLikeImportedClaudeEntry(entry: Record<string, unknown>): boolean {\n  return entry.type === \"user\" || entry.type === \"assistant\";\n}\n\nfunction looksLikeImportedCodexEntry(entry: Record<string, unknown>): boolean {\n  return (\n    entry.type === \"session_meta\" || entry.type === \"response_item\" || entry.type === \"event_msg\"\n  );\n}\n\nfunction asString(value: unknown): string {\n  return typeof value === \"string\" ? value : \"\";\n}\n\nfunction provenanceToken(value: unknown): string {\n  if (typeof value === \"string\") return value.trim().toLowerCase();\n  if (value && typeof value === \"object\") {\n    const rec = value as Record<string, unknown>;\n    return provenanceToken(\n      rec.importedFrom ?? rec.imported_from ?? rec.sourceProvider ?? rec.source,\n    );\n  }\n  return \"\";\n}\n\nfunction importedFromClaudeToken(token: string): boolean {\n  return (\n    token === \"claude\" ||\n    token === \"claude-code\" ||\n    token === \"claude_code\" ||\n    token === \"anthropic\"\n  );\n}\n\nfunction importedFromCodexToken(token: string): boolean {\n  return (\n    token === \"codex\" || token === \"codex-cli\" || token === \"codex_cli\" || token === \"openai-codex\"\n  );\n}\n\nfunction noteImportProvenance(\n  acc: CursorAccumulator,\n  entry: Record<string, unknown>,\n  text: string,\n): void {\n  const token = provenanceToken(\n    entry.importedFrom ??\n      entry.imported_from ??\n      entry.sourceProvider ??\n      entry.source ??\n      (entry.message && typeof entry.message === \"object\"\n        ? ((entry.message as Record<string, unknown>).importedFrom ??\n          (entry.message as Record<string, unknown>).imported_from ??\n          (entry.message as Record<string, unknown>).sourceProvider)\n        : undefined),\n  );\n  if (importedFromClaudeToken(token) || looksLikeImportedClaudeEntry(entry)) {\n    acc.isImportedFromClaude = true;\n  }\n  if (importedFromCodexToken(token) || looksLikeImportedCodexEntry(entry)) {\n    acc.isImportedFromCodex = true;\n  }\n  if (/imported from claude/i.test(text) || /<imported_from_claude>/i.test(text)) {\n    acc.isImportedFromClaude = true;\n  }\n  if (/imported from codex/i.test(text) || /<imported_from_codex>/i.test(text)) {\n    acc.isImportedFromCodex = true;\n  }\n}\n\n// Only <user_query> text can name a session: a user turn without one is\n// skipped, so Cursor's own wrapper text never becomes a title.\nfunction cursorUserQuery(content: unknown): string | undefined {\n  const texts = typeof content === \"string\" ? [content] : Array.isArray(content) ? content : [];\n  for (const item of texts) {\n    const text = typeof item === \"string\" ? item : (item as { text?: unknown })?.text;\n    const query = typeof text === \"string\" ? text.match(USER_QUERY_RE) : null;\n    if (query) return query[1];\n  }\n  return undefined;\n}\n\nexport function unwrapCursorUserText(text: string): string {\n  const query = text.match(USER_QUERY_RE);\n  const body = query ? query[1] : text.replace(TIMESTAMP_RE, \"\");\n  return cleanSystemTags(body);\n}\n\nexport function timestampFromCursorUserText(text: string): string {\n  const match = text.match(TIMESTAMP_RE);\n  if (!match) return \"\";\n  const parsed = Date.parse(match[1].trim());\n  return Number.isNaN(parsed) ? \"\" : new Date(parsed).toISOString();\n}\n\nfunction extractCursorText(content: unknown): string {\n  if (typeof content === \"string\") return unwrapCursorUserText(content);\n  if (!Array.isArray(content)) return \"\";\n  return content\n    .map((item) => {\n      if (typeof item === \"string\") return unwrapCursorUserText(item);\n      const block = item as { type?: string; text?: string };\n      if ((block?.type === \"text\" || block?.type === undefined) && typeof block.text === \"string\") {\n        return unwrapCursorUserText(block.text);\n      }\n      return \"\";\n    })\n    .filter(Boolean)\n    .join(\" \");\n}\n\nfunction collectCursorToolNames(content: unknown, into: string[]): void {\n  if (!Array.isArray(content)) return;\n  for (const item of content) {\n    const block = item as { type?: string; name?: string };\n    if (block?.type === \"tool_use\" && typeof block.name === \"string\" && block.name) {\n      if (!into.includes(block.name)) into.push(block.name);\n    }\n  }\n}\n\nfunction firstTimestampHint(content: unknown): string {\n  if (typeof content === \"string\") return timestampFromCursorUserText(content);\n  if (!Array.isArray(content)) return \"\";\n  for (const item of content) {\n    if (typeof item === \"string\") {\n      const ts = timestampFromCursorUserText(item);\n      if (ts) return ts;\n    } else if (typeof (item as { text?: string })?.text === \"string\") {\n      const ts = timestampFromCursorUserText((item as { text: string }).text);\n      if (ts) return ts;\n    }\n  }\n  return \"\";\n}\n\n// `index` is the line's message index in its file — the position a full parse\n// gives it, which is also a host's message_index. Pass it whenever it is known:\n// it goes into the uuid (see cursorMessageUuid).\nexport function parseCursorJsonlLine(line: string, index?: number): ConversationMessage | null {\n  const trimmed = line.trim();\n  if (!trimmed) return null;\n  let entry: Record<string, unknown>;\n  try {\n    entry = JSON.parse(trimmed);\n  } catch {\n    return null;\n  }\n  const message = cursorEntryToMessage(entry);\n  if (!message || message.uuid) return message;\n  return { ...message, uuid: cursorMessageUuid(trimmed, message.role, index) };\n}\n\n// Cursor lines carry no id, so derive one a host can match a live copy of the\n// message against its history copy with: the same line at the same position\n// always yields the same uuid. Content alone is not enough — ~1% of real\n// transcript lines repeat an earlier line byte-for-byte (a re-sent prompt, the\n// same Read twice), and a content-only id would merge them. The position makes\n// repeats distinct; the content hash keeps a mismatched position from matching\n// a different message.\nfunction cursorMessageUuid(line: string, role: MessageSender, index: number | undefined): string {\n  const hash = createHash(\"sha1\").update(line).digest(\"hex\").slice(0, 16);\n  return index === undefined ? `cursor-${role}-${hash}` : `cursor-${role}-${index}-${hash}`;\n}\n\nfunction cursorEntryToMessage(entry: Record<string, unknown>): ConversationMessage | null {\n  if (looksLikeCursorChatEntry(entry)) {\n    const role = entry.role as MessageSender;\n    const message = entry.message as { content?: unknown } | undefined;\n    const content = message?.content ?? entry.content;\n    return withToolUses(\n      { role, text: extractCursorText(content), timestamp: firstTimestampHint(content) },\n      content,\n    );\n  }\n  if (looksLikeImportedClaudeEntry(entry)) {\n    const message = entry.message as { content?: unknown; role?: string } | undefined;\n    const role = (\n      message?.role === \"assistant\" || entry.type === \"assistant\" ? \"assistant\" : \"user\"\n    ) as MessageSender;\n    const content = message?.content ?? entry.content;\n    return withToolUses(\n      { role, text: extractCursorText(content), timestamp: asString(entry.timestamp) },\n      content,\n    );\n  }\n  return parseCodexJsonlLine(JSON.stringify(entry));\n}\n\n// Attach the line's tool_use blocks, the same metadata the Claude parser fills.\n// A message renders when it has text or tool calls; neither → null.\nfunction withToolUses(message: ConversationMessage, content: unknown): ConversationMessage | null {\n  const blocks = extractCursorToolUseBlocks(content);\n  if (blocks.length === 0) return message.text ? message : null;\n  return {\n    ...message,\n    metadata: { toolUses: blocks.map((b) => b.name), toolUseBlocks: blocks },\n  };\n}\n\n// Cursor's tool_use blocks carry no id, and ToolUseBlock needs one, so derive a\n// stable one from the call itself: re-reading the same line yields the same id.\nfunction extractCursorToolUseBlocks(content: unknown): ToolUseBlock[] {\n  if (!Array.isArray(content)) return [];\n  const blocks: ToolUseBlock[] = [];\n  for (const item of content) {\n    const block = item as { type?: string; id?: string; name?: string; input?: unknown };\n    if (block?.type !== \"tool_use\" || typeof block.name !== \"string\" || !block.name) continue;\n    const input =\n      block.input && typeof block.input === \"object\" && !Array.isArray(block.input)\n        ? (block.input as Record<string, unknown>)\n        : {};\n    const id =\n      typeof block.id === \"string\" && block.id\n        ? block.id\n        : `cursor-tool-${createHash(\"sha1\")\n            .update(`${block.name}\\0${JSON.stringify(input)}`)\n            .digest(\"hex\")\n            .slice(0, 16)}`;\n    blocks.push({ id, name: block.name, input });\n  }\n  return blocks;\n}\n\nfunction foldCursorMessage(\n  acc: CursorAccumulator,\n  role: MessageSender,\n  text: string,\n  timestamp: string,\n  tier: ContentTier,\n): void {\n  if (timestamp && (!acc.latestTimestamp || timestamp > acc.latestTimestamp)) {\n    acc.latestTimestamp = timestamp;\n  }\n  acc.messageCount++;\n  acc.lastMessageSender = role;\n  const snapshot: MessageSnapshot = { text: text.slice(0, 200), timestamp };\n  if (role === \"user\") {\n    if (!acc.firstUser) acc.firstUser = snapshot;\n    acc.lastUser = snapshot;\n  } else {\n    acc.lastAssistant = snapshot;\n  }\n  if (acc.previewLength < tier.previewMax) {\n    acc.previewParts.push(text);\n    acc.previewLength += text.length;\n  }\n  if (acc.snippetLength < tier.snippetMax) {\n    const remaining = tier.snippetMax - acc.snippetLength;\n    const chunk = text.length > remaining ? text.slice(0, remaining) : text;\n    acc.snippetParts.push(chunk);\n    acc.snippetLength += chunk.length;\n  }\n}\n\nexport function reduceCursorEntry(\n  acc: CursorAccumulator,\n  entry: Record<string, unknown>,\n  tier: ContentTier,\n): void {\n  const message = entry.message as { content?: unknown } | undefined;\n  const content = message?.content ?? entry.content;\n  const textHint = extractCursorText(content);\n  noteImportProvenance(acc, entry, textHint);\n\n  if (looksLikeCursorChatEntry(entry)) {\n    collectCursorToolNames(content, acc.toolNames);\n    const text = textHint;\n    if (!text) return;\n    if (entry.role === \"user\") {\n      acc.nameSlots = applyNameCandidate(\n        acc.nameSlots,\n        \"firstUserMessage\",\n        cursorUserQuery(content),\n        CURSOR_PROVIDER,\n      );\n    }\n    foldCursorMessage(acc, entry.role as MessageSender, text, firstTimestampHint(content), tier);\n    return;\n  }\n\n  if (looksLikeImportedClaudeEntry(entry)) {\n    collectCursorToolNames(content, acc.toolNames);\n    const msg = cursorEntryToMessage(entry);\n    if (!msg?.text) return;\n    foldCursorMessage(acc, msg.role, msg.text, msg.timestamp, tier);\n    return;\n  }\n\n  if (looksLikeImportedCodexEntry(entry)) {\n    const msg = parseCodexJsonlLine(JSON.stringify(entry));\n    if (!msg?.text) return;\n    foldCursorMessage(acc, msg.role, msg.text, msg.timestamp, tier);\n  }\n}\n\nexport function finalizeCursorMeta(\n  acc: CursorAccumulator,\n  filePath: string,\n  account: string,\n  tier: ContentTier,\n): ConversationMeta | null {\n  if (acc.messageCount === 0) return null;\n\n  const sessionId = basename(filePath, \".jsonl\");\n  const nameSlots = { ...acc.nameSlots, ...cursorNameSlotsFor(filePath, sessionId) };\n  const isSubagent = SUBAGENT_PATH_RE.test(filePath);\n  const parent = isSubagent ? parentFromSubagentPath(filePath) : null;\n  const { projectPath, projectName } = projectFromCursorPath(filePath);\n  const timestamp = acc.latestTimestamp || fileMtimeIso(filePath);\n  const kind: \"conversation\" | \"task\" =\n    acc.lastAssistant === null && acc.toolNames.length > 0 ? \"task\" : \"conversation\";\n\n  return {\n    id: filePath,\n    filePath,\n    provider: CURSOR_PROVIDER,\n    kind,\n    externalSessionId: sessionId,\n    sessionId,\n    sessionName: resolveSessionName(nameSlots).name ?? \"\",\n    nameSlots,\n    projectPath,\n    projectName,\n    account,\n    timestamp,\n    messageCount: acc.messageCount,\n    lastMessageSender: acc.lastMessageSender,\n    preview: acc.previewParts.join(\" \").slice(0, tier.previewMax),\n    contentSnippet: acc.snippetParts.join(\" \"),\n    gitBranch: null,\n    model: null,\n    isSubagent,\n    parentSessionId: parent?.parentFilePath ?? null,\n    subagentId: isSubagent ? sessionId : undefined,\n    parentSessionUuid: parent?.parentSessionUuid ?? undefined,\n    isTeammate: false,\n    teamName: null,\n    toolNames: acc.toolNames,\n    firstMessage: acc.firstUser,\n    lastMessage: acc.lastAssistant ?? acc.lastUser,\n    lastPrompt: acc.lastUser?.text || undefined,\n    ...(acc.isImportedFromClaude ? { isImportedFromClaude: true } : {}),\n    ...(acc.isImportedFromCodex ? { isImportedFromCodex: true } : {}),\n  };\n}\n\nfunction parentFromSubagentPath(\n  filePath: string,\n): { parentFilePath: string; parentSessionUuid: string } | null {\n  const subagentsDir = dirname(filePath);\n  const parentDir = dirname(subagentsDir);\n  const parentSessionUuid = basename(parentDir);\n  if (!parentSessionUuid) return null;\n  return {\n    parentSessionUuid,\n    parentFilePath: canonicalPath(join(parentDir, `${parentSessionUuid}.jsonl`)),\n  };\n}\n\n// Cursor encodes the workspace as ~/.cursor/projects/<slug>/agent-transcripts/…\n// The slug replaces path separators with '-'. That is lossy when a directory\n// name already contains hyphens, so we only promote a decoded path when it\n// exists on disk; otherwise the Cursor project directory is the identity.\nfunction projectFromCursorPath(filePath: string): { projectPath: string; projectName: string } {\n  const parts = filePath.split(/[/\\\\]/);\n  const idx = parts.lastIndexOf(\"agent-transcripts\");\n  const cursorProjectDir = idx > 0 ? parts.slice(0, idx).join(sep) : dirname(dirname(filePath));\n  const slug = basename(cursorProjectDir);\n  const decoded = decodeCursorProjectSlug(slug);\n  const projectPath = decoded ?? cursorProjectDir;\n  return { projectPath, projectName: getShortProjectName(projectPath) || slug };\n}\n\nexport function decodeCursorProjectSlug(slug: string): string | null {\n  if (!slug) return null;\n  const segments = slug.split(\"-\");\n  if (segments.length === 0) return null;\n  const rooted = slug.startsWith(\"Users-\") || slug.startsWith(\"home-\") || /^[A-Za-z]-/.test(slug);\n  let acc = rooted ? sep : \"\";\n  let i = 0;\n  if (rooted && segments[0] === \"Users\") {\n    const users = `${sep}Users`;\n    if (!existsSync(users)) return null;\n    acc = users;\n    i = 1;\n  }\n  while (i < segments.length) {\n    let found: string | null = null;\n    let next = i + 1;\n    for (let j = segments.length; j > i; j--) {\n      const candidate = join(acc, segments.slice(i, j).join(\"-\"));\n      try {\n        if (existsSync(candidate) && statSync(candidate).isDirectory()) {\n          found = candidate;\n          next = j;\n          break;\n        }\n      } catch {\n        // ignore\n      }\n    }\n    if (!found) return null;\n    acc = found;\n    i = next;\n  }\n  return acc;\n}\n\nfunction getShortProjectName(fullPath: string): string {\n  return fullPath.split(/[/\\\\]/).filter(Boolean).slice(-3).join(\"/\");\n}\n\nfunction fileMtimeIso(filePath: string): string {\n  try {\n    return new Date(statSync(filePath).mtimeMs).toISOString();\n  } catch {\n    return new Date().toISOString();\n  }\n}\n\nfunction cursorLineContent(line: string): unknown {\n  try {\n    const entry = JSON.parse(line) as { message?: { content?: unknown }; content?: unknown };\n    return entry.message?.content ?? entry.content;\n  } catch {\n    return undefined;\n  }\n}\n\nexport async function parseCursorConversation(\n  filePath: string,\n  account: string,\n): Promise<Conversation | null> {\n  const log = getLogger();\n  const messages: ConversationMessage[] = [];\n  const textParts: string[] = [];\n  let latestTimestamp = \"\";\n  let lastUserText = \"\";\n  let nameSlots: NameSlots = {};\n\n  const rl = createInterface({ input: createReadStream(filePath), crlfDelay: Infinity });\n  try {\n    for await (const line of rl) {\n      const message = parseCursorJsonlLine(line, messages.length);\n      if (!message) continue;\n      if (message.timestamp && (!latestTimestamp || message.timestamp > latestTimestamp)) {\n        latestTimestamp = message.timestamp;\n      }\n      messages.push(message);\n      // Tool-only messages carry no text; they must not blank lastPrompt.\n      if (!message.text) continue;\n      textParts.push(message.text);\n      if (message.role === \"user\") {\n        lastUserText = message.text;\n        if (nameSlots.firstUserMessage === undefined) {\n          nameSlots = applyNameCandidate(\n            nameSlots,\n            \"firstUserMessage\",\n            cursorUserQuery(cursorLineContent(line)),\n            CURSOR_PROVIDER,\n          );\n        }\n      }\n    }\n  } catch (err) {\n    log.warn({ filePath, err }, \"parseCursorConversation: read failed\");\n    return null;\n  }\n\n  if (messages.length === 0) return null;\n\n  const { projectPath, projectName } = projectFromCursorPath(filePath);\n  const sessionId = basename(filePath, \".jsonl\");\n  return {\n    id: filePath,\n    filePath,\n    projectPath,\n    projectName,\n    sessionId,\n    sessionName:\n      resolveSessionName({ ...nameSlots, ...cursorNameSlotsFor(filePath, sessionId) }).name ?? \"\",\n    messages,\n    fullText: textParts.join(\" \"),\n    timestamp: latestTimestamp || fileMtimeIso(filePath),\n    messageCount: messages.length,\n    account,\n    lastPrompt: lastUserText || undefined,\n  };\n}\n","import fg from \"fast-glob\";\nimport { stat } from \"fs/promises\";\nimport { canonicalPath } from \"./canonical-path\";\nimport { getLogger } from \"./logger\";\n\nexport interface DiscoveredFile {\n  filePath: string;\n  account: string;\n}\n\nconst EXCLUDED_SEGMENTS = [\"/memory/\", \"/tool-results/\"];\n\n// stat() is cheap and IO-bound, so a large history scans much faster when the\n// calls run concurrently rather than one-at-a-time. Cap concurrency well under\n// typical fd limits.\nconst STAT_CONCURRENCY = 32;\n\nexport async function discoverJsonlFiles(\n  dirs: { projectsDir: string; account: string }[],\n  onProgress?: (found: number) => void,\n): Promise<DiscoveredFile[]> {\n  const log = getLogger();\n  const results: DiscoveredFile[] = [];\n\n  for (const { projectsDir, account } of dirs) {\n    let filePaths: string[];\n    try {\n      filePaths = await fg(\"**/*.jsonl\", {\n        cwd: projectsDir,\n        absolute: true,\n        dot: false,\n      });\n    } catch (err) {\n      log.warn({ projectsDir, account, err }, \"discovery: glob failed\");\n      continue;\n    }\n\n    // Filter out excluded directory segments\n    const filtered = filePaths.filter((fp) => !EXCLUDED_SEGMENTS.some((seg) => fp.includes(seg)));\n\n    // Filter out empty files. Stat concurrently in bounded chunks; chunk order\n    // is preserved and intra-chunk order follows the input, so discovery order\n    // is stable (downstream sorts anyway).\n    let kept = 0;\n    let skippedEmpty = 0;\n    let skippedInaccessible = 0;\n    for (let i = 0; i < filtered.length; i += STAT_CONCURRENCY) {\n      const chunk = filtered.slice(i, i + STAT_CONCURRENCY);\n      const statted = await Promise.all(\n        chunk.map(async (filePath) => {\n          try {\n            const s = await stat(filePath);\n            return { filePath, size: s.size };\n          } catch (err) {\n            log.warn({ filePath, err }, \"discovery: stat failed\");\n            return { filePath, size: -1 };\n          }\n        }),\n      );\n      for (const { filePath, size } of statted) {\n        if (size < 0) {\n          skippedInaccessible++;\n        } else if (size > 0) {\n          // fast-glob emits forward slashes even on Windows. Emit the canonical\n          // form instead, so a discovered path is the same string as the one a\n          // watcher/path.join caller produces for that file — discovery output\n          // keys the in-memory metadata cache and the has_nested comparison in\n          // dir-watermark, neither of which goes through a repository.\n          results.push({ filePath: canonicalPath(filePath), account });\n          kept++;\n        } else {\n          skippedEmpty++;\n        }\n      }\n    }\n\n    log.debug(\n      {\n        projectsDir,\n        account,\n        globMatches: filePaths.length,\n        afterExclusions: filtered.length,\n        kept,\n        skippedEmpty,\n        skippedInaccessible,\n      },\n      \"discovery: directory scanned\",\n    );\n\n    onProgress?.(results.length);\n  }\n\n  log.debug({ totalFiles: results.length, dirs: dirs.length }, \"discovery: complete\");\n  return results;\n}\n","import { discoverJsonlFiles } from \"../discovery\";\nimport {\n  finalizeMeta,\n  initialReducerState,\n  type ReducerState,\n  reduceLine,\n} from \"../persistent/metadata-reducer\";\nimport type { ContentTier, ConversationMeta } from \"../types\";\nimport {\n  CLAUDE_CODE_PROVIDER,\n  type DiscoveredConversationFile,\n  type ScannerProvider,\n} from \"./provider\";\n\n// The existing Claude/Threadbase format, expressed as a provider. All behavior\n// is the already-shared reducer (metadata-reducer.ts) — no logic is duplicated\n// here. Discovery is delegated to discoverJsonlFiles; the scanner passes the\n// profiles' projects dirs as roots paired with their account ids.\nexport class ThreadbaseProvider implements ScannerProvider<ReducerState> {\n  readonly name = CLAUDE_CODE_PROVIDER;\n\n  // Roots are passed as \"<projectsDir>\\0<account>\" so the scanner can carry the\n  // per-root account through the shared interface. The scanner builds these.\n  async discover(roots: string[]): Promise<DiscoveredConversationFile[]> {\n    const dirs = roots.map((r) => {\n      const [projectsDir, account = \"default\"] = r.split(\"\\0\");\n      return { projectsDir, account };\n    });\n    return discoverJsonlFiles(dirs);\n  }\n\n  // Threadbase JSONL has top-level type \"user\"/\"assistant\" with a cwd/sessionId.\n  canParse(_filePath: string, sample: string): boolean {\n    for (const line of sample.split(\"\\n\")) {\n      if (!line.trim()) continue;\n      try {\n        const e = JSON.parse(line) as Record<string, unknown>;\n        if (e.type === \"user\" || e.type === \"assistant\") return true;\n        // Codex's distinctive top-level types — definitively not us.\n        if (e.type === \"session_meta\" || e.type === \"response_item\") return false;\n      } catch {}\n    }\n    return false;\n  }\n\n  createEmptyAccumulator(): ReducerState {\n    return initialReducerState();\n  }\n\n  reduceEntry(acc: ReducerState, entry: Record<string, unknown>, tier: ContentTier): void {\n    reduceLine(acc, entry, tier);\n  }\n\n  finalize(\n    acc: ReducerState,\n    filePath: string,\n    account: string,\n    tier: ContentTier,\n  ): ConversationMeta | null {\n    // finalizeMeta already tags provider: \"claude-code\".\n    return finalizeMeta(acc, filePath, account, tier);\n  }\n}\n","import { EventEmitter } from \"events\";\nimport { closeSync, openSync, readSync, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { LRUCache } from \"./cache\";\nimport { canonicalPath } from \"./canonical-path\";\nimport { discoverJsonlFiles } from \"./discovery\";\nimport {\n  applyAccountFilter,\n  applyIncludeFilter,\n  applyPagination,\n  applyProjectFilter,\n  applySinceFilter,\n  applySort,\n  parseSinceCutoff,\n} from \"./filters\";\nimport { readGitBranch } from \"./git\";\nimport { SearchIndexer } from \"./indexer\";\nimport { getLogger } from \"./logger\";\nimport { setCursorStateDb } from \"./name-stores\";\nimport { parseConversation, parseMeta } from \"./parser\";\nimport {\n  type CachedConversation,\n  extendConversation,\n  parseConversationResumable,\n} from \"./persistent/conversation-stream\";\nimport { classify } from \"./persistent/cursor\";\nimport { PersistentEngine } from \"./persistent/index-engine\";\nimport { getProjectsDir, loadProfiles } from \"./profiles\";\nimport { CodexCliProvider, parseCodexConversation } from \"./providers/codex-cli\";\nimport { CopilotProvider, parseCopilotConversation } from \"./providers/copilot\";\nimport { CursorProvider, parseCursorConversation } from \"./providers/cursor\";\nimport { parseMetaWithProvider } from \"./providers/parse\";\nimport {\n  CLAUDE_CODE_PROVIDER,\n  CODEX_CLI_PROVIDER,\n  COPILOT_PROVIDER,\n  CURSOR_PROVIDER,\n  canonicalizeProviderList,\n  providerMatches,\n  type ScannerProvider,\n} from \"./providers/provider\";\nimport { ThreadbaseProvider } from \"./providers/threadbase\";\nimport {\n  appendSearchDelta,\n  combineSearchContent,\n  emptySearchDocument,\n  extractSearchDelta,\n  type SearchDocument,\n} from \"./search-document\";\nimport { CONTENT_FIELD, generateMatches } from \"./search-matches\";\nimport { resolveTier } from \"./tiers\";\nimport type {\n  ContentTier,\n  Conversation,\n  ConversationMeta,\n  ConversationPage,\n  GetConversationOptions,\n  GetConversationPageOptions,\n  GroupedConversations,\n  Profile,\n  ScanOptions,\n  ScanResult,\n  SearchOptions,\n  SearchResult,\n  SingleFilePage,\n  TreeConversation,\n} from \"./types\";\nimport { FileWatcher } from \"./watcher/file-watcher\";\nimport { type IndexJob, IndexQueue } from \"./watcher/index-queue\";\n\nconst BATCH_SIZE = 12;\nconst DEFAULT_CONFIG_PATH = \"~/.config/threadbase-scanner\";\n\n// A test process that reaches the default path writes its fixtures into the\n// developer's real index. Measured on one live machine: 579 of 1,144 rows were\n// test fixtures, 357 of them from tb-streamer's suite, which constructs\n// scanners with no dbPath and has no TB_SCANNER_DB of its own. Those rows are\n// indistinguishable from real conversations in every coverage query, so they\n// silently corrupt any measurement taken of the index — including the\n// completeness metrics a durable archive would report.\n//\n// The env var already existed for this; it just failed open. Failing loudly is\n// what makes the omission visible at the moment it is introduced rather than a\n// year later, and it belongs here rather than in each consumer's setup because\n// the scanner is the thing that knows which path is the real one.\nfunction isTestProcess(): boolean {\n  return (\n    Boolean(process.env.VITEST) ||\n    Boolean(process.env.JEST_WORKER_ID) ||\n    process.env.NODE_ENV === \"test\"\n  );\n}\n\n// Default persistent-index location. Overridable via TB_SCANNER_DB (required\n// under a test runner, and handy for pointing at an alternate DB in ops).\nfunction defaultDbPath(): string {\n  const override = process.env.TB_SCANNER_DB;\n  if (override) return override;\n  if (isTestProcess()) {\n    throw new Error(\n      \"TB_SCANNER_DB must be set under a test runner. Refusing to open the default index at \" +\n        \"~/.config/threadbase-scanner/index.db, because test fixtures written there are \" +\n        \"indistinguishable from real conversations and corrupt every measurement of the index. \" +\n        \"Point it at a temp directory in your test setup, or pass persistent.dbPath explicitly.\",\n    );\n  }\n  return join(homedir(), \".config\", \"threadbase-scanner\", \"index.db\");\n}\n\n// Flatten a search document for the in-memory index, tail-capped to the content\n// tier. Tail rather than head for the same reason the buckets themselves are\n// tail-biased: people search for what they did recently.\n//\n// This is where `persistent: false` deliberately diverges from SQLite. The\n// FlexSearch index is `tokenize: \"forward\"` (every prefix of every token, held\n// in RAM), so handing it the full ~128 KB budget per conversation would cost\n// hundreds of MB to GB. Extraction RULES are shared with FTS5; indexed VOLUME\n// is not. Raise this only against a measurement, never on speculation.\nfunction capForMemory(doc: SearchDocument, max: number): string {\n  const combined = combineSearchContent(doc);\n  return combined.length > max ? combined.slice(-max) : combined;\n}\n\nexport interface PersistentConfig {\n  dbPath?: string;\n  // Write a portable <file>.idx.json sidecar next to each indexed JSONL for\n  // debugging/portability/recovery. Off by default. SQLite stays canonical.\n  sidecar?: boolean;\n}\n\nexport interface ConversationScannerOptions {\n  metadataCacheSize?: number;\n  conversationCacheSize?: number;\n  // SQLite-backed persistent index. Enabled by default (at DEFAULT_DB_PATH).\n  // Pass `false` for the legacy in-memory path (no native dependency, no DB\n  // file). Pass `{ dbPath }` to override the database location.\n  persistent?: false | PersistentConfig;\n}\n\nexport interface WatchOptions {\n  profiles?: Profile[];\n  // Watcher debounce window (ms). Default 400.\n  debounceMs?: number;\n  // Periodic full rescan as a correctness fallback for missed FS events.\n  // Default 60_000ms; set 0 to disable.\n  periodicMs?: number;\n}\n\n// Emitted after the index changes for one file. `meta` is the fresh metadata,\n// or null when the file was removed/emptied (deleted from the index).\nexport interface ScannerChangeEvent {\n  filePath: string;\n  account: string;\n  meta: ConversationMeta | null;\n  reason: IndexJob[\"reason\"];\n}\n\nexport class ConversationScanner {\n  private metadataCache: Map<string, ConversationMeta> = new Map();\n  // Parsed conversations plus (persistent claude-code entries only) the resume\n  // point that lets refreshFile extend them in place when the file grows.\n  private conversationLRU: LRUCache<string, CachedConversation>;\n  // session_id is NOT unique, so this maps a sessionId to every active meta that\n  // carries it. Resolution picks deterministically (newest timestamp, then path\n  // ascending) so dropping one file never hides another with the same id.\n  private sessionIdIndex: Map<string, ConversationMeta[]> = new Map();\n  private projects: Set<string> = new Set();\n  private indexer: SearchIndexer = new SearchIndexer();\n  // Search body per conversation for the in-memory path, already tail-capped to\n  // the content tier. Survives scan() so a statCache hit — which skips the parse\n  // entirely — can still index a body rather than an empty string.\n  private searchContents = new Map<string, string>();\n  // Tier the most recent scan() ran with, so refreshFile() re-parses a single\n  // file at the same content depth. Defaults to the standard tier.\n  private lastTier: ContentTier = resolveTier(\"standard\");\n\n  // null when persistent mode is disabled (legacy in-memory path). Lazily\n  // opened on first use so merely constructing a scanner never touches disk.\n  private readonly dbPath: string | null;\n  private readonly sidecarEnabled: boolean;\n  private engineInstance: PersistentEngine | null = null;\n  // The most recent scan()'s promise while it is still running, or null when\n  // idle. close() awaits it before closing the SQLite handle so a fire-and-\n  // forget scan can't have its DB shut mid-indexAll (\"database connection is\n  // not open\"). Tracks the latest scan only — concurrent scans on one instance\n  // aren't a supported pattern (a single writer DB), and the latest resolving\n  // implies earlier ones already settled in practice.\n  private inFlightScan: Promise<unknown> | null = null;\n\n  private emitter = new EventEmitter();\n  private watcher: FileWatcher | null = null;\n  private queue: IndexQueue | null = null;\n  private periodicTimer: ReturnType<typeof setInterval> | null = null;\n  // The in-flight periodicReconcile() job, if one is mid-run. unwatch() awaits\n  // it before dropping the queue/engine so its post-await engine.files access\n  // can't hit a closed DB (the watch-mode half of Bug #4).\n  private inFlightReconcile: Promise<void> | null = null;\n\n  constructor(options?: ConversationScannerOptions) {\n    this.conversationLRU = new LRUCache<string, CachedConversation>(\n      options?.conversationCacheSize ?? 5,\n    );\n    if (options?.persistent === false) {\n      this.dbPath = null;\n      this.sidecarEnabled = false;\n    } else {\n      this.dbPath = options?.persistent?.dbPath ?? defaultDbPath();\n      this.sidecarEnabled = options?.persistent?.sidecar ?? false;\n    }\n  }\n\n  private get persistent(): boolean {\n    return this.dbPath !== null;\n  }\n\n  private engine(): PersistentEngine {\n    if (!this.engineInstance) {\n      this.engineInstance = new PersistentEngine(this.dbPath as string, {\n        sidecar: this.sidecarEnabled,\n      });\n    }\n    return this.engineInstance;\n  }\n\n  // Release the SQLite connection. No-op in legacy mode. Safe to call\n  // repeatedly. Awaits watcher teardown AND any in-flight scan before closing\n  // the DB handle, so a fire-and-forget scan() can never have its connection\n  // shut mid-indexAll() (\"database connection is not open\"). This is why close()\n  // is async — callers should await it during shutdown. (Scanner review Bug #4.)\n  async close(): Promise<void> {\n    if (this.watcher || this.queue || this.periodicTimer) await this.unwatch();\n    // Let any running scan finish touching the DB first. Swallow its result/\n    // rejection here — the caller that started the scan owns its outcome; close()\n    // only needs it to stop using the handle before we close it.\n    if (this.inFlightScan) {\n      try {\n        await this.inFlightScan;\n      } catch {\n        // scan failed on its own; close() proceeds to release the handle\n      }\n    }\n    this.engineInstance?.close();\n    this.engineInstance = null;\n  }\n\n  async scan(options: ScanOptions = {}): Promise<ScanResult> {\n    // Track this scan so close() can await it before shutting the DB handle.\n    // Cleared on settle — but only if it's still the current one (a newer scan\n    // may have replaced it). Errors propagate to the caller unchanged.\n    const promise = this.runScan(options);\n    this.inFlightScan = promise;\n    try {\n      return await promise;\n    } finally {\n      if (this.inFlightScan === promise) this.inFlightScan = null;\n    }\n  }\n\n  private async runScan(options: ScanOptions): Promise<ScanResult> {\n    if (options.cursorStateDb !== undefined) setCursorStateDb(options.cursorStateDb);\n    const profiles = await this.resolveProfiles(options.profiles);\n    const activeProfiles = profiles.filter((p) => p.enabled && p.scanHistory !== false);\n    this.lastTier = resolveTier(options.tier ?? \"standard\", options.tiers);\n\n    if (this.persistent) {\n      return this.scanPersistent(activeProfiles, options);\n    }\n    return this.scanInMemory(activeProfiles, options);\n  }\n\n  // SQLite-backed scan: (re)index changed files into the DB, then query all\n  // active metas and run the identical filter/sort/view/paginate pipeline as\n  // the in-memory path — guaranteeing an identical ScanResult shape.\n  private async scanPersistent(\n    activeProfiles: Profile[],\n    options: ScanOptions,\n  ): Promise<ScanResult> {\n    const log = getLogger();\n    const startedAt = Date.now();\n    const engine = this.engine();\n\n    const { scanned } = await engine.indexAll(activeProfiles, options);\n    const allMetas = engine.allActive();\n\n    const { conversations, total } = this.finalize(allMetas, options);\n    log.info(\n      { scanned, kept: allMetas.length, filteredTotal: total, elapsedMs: Date.now() - startedAt },\n      \"scan: complete (persistent)\",\n    );\n    return { conversations, total, scanned };\n  }\n\n  // Apply include/project/account/since filters, sort, view transform, and\n  // pagination. Shared by both backends so results never diverge.\n  private finalize(\n    allMetas: ConversationMeta[],\n    options: ScanOptions,\n  ): { conversations: ScanResult[\"conversations\"]; total: number } {\n    let filtered = allMetas;\n    if (options.include && options.include !== \"all\") {\n      filtered = applyIncludeFilter(filtered, options.include);\n    }\n    if (options.project) filtered = applyProjectFilter(filtered, options.project);\n    if (options.account) filtered = applyAccountFilter(filtered, options.account);\n    if (options.since) filtered = applySinceFilter(filtered, options.since);\n\n    filtered = applySort(filtered, options.sort ?? \"recent\");\n    const total = filtered.length;\n    const conversations = this.transformView(filtered, options);\n\n    if (Array.isArray(conversations)) {\n      const limit = options.limit ?? 50;\n      const offset = options.offset ?? 0;\n      return { conversations: applyPagination(conversations, limit, offset).items, total };\n    }\n    return { conversations, total };\n  }\n\n  private async scanInMemory(activeProfiles: Profile[], options: ScanOptions): Promise<ScanResult> {\n    const log = getLogger();\n    const startedAt = Date.now();\n    const tier = this.lastTier;\n\n    log.info(\n      {\n        activeProfiles: activeProfiles.length,\n        tier: tier.name,\n        sort: options.sort ?? \"recent\",\n        include: options.include ?? \"all\",\n        view: options.view ?? \"flat\",\n      },\n      \"scan: start\",\n    );\n\n    // Clear caches. searchContents is deliberately NOT cleared: it is keyed by\n    // file path and only ever replaced by a fresh parse, so keeping it lets a\n    // statCache hit (which skips parsing) still index a real body.\n    this.metadataCache.clear();\n    this.conversationLRU.clear();\n    this.sessionIdIndex.clear();\n    this.projects.clear();\n    this.indexer.clear();\n\n    const files = await this.discoverWithProviders(activeProfiles, options);\n    const totalFiles = files.length;\n    let scanned = 0;\n    let parseFailures = 0;\n\n    const allMetas: ConversationMeta[] = [];\n    // Memoize git-branch lookups per project path for the duration of this\n    // scan. readGitBranch walks the filesystem for .git/HEAD; without this it\n    // runs once per conversation (thousands of times) even though there are\n    // only a handful of distinct project roots. Scoped to one scan() call —\n    // branches can change between scans, so a longer-lived memo would go stale.\n    const gitBranchMemo = new Map<string, string | null>();\n    const resolveGitBranch = (projectPath: string): string | null => {\n      let branch = gitBranchMemo.get(projectPath);\n      if (branch === undefined) {\n        branch = readGitBranch(projectPath);\n        gitBranchMemo.set(projectPath, branch);\n      }\n      return branch;\n    };\n    const { statCache } = options;\n    for (let i = 0; i < files.length; i += BATCH_SIZE) {\n      const batch = files.slice(i, i + BATCH_SIZE);\n      const results = await Promise.all(\n        batch.map(async ({ filePath, account, provider }) => {\n          if (statCache) {\n            const cached = statCache.get(filePath);\n            if (cached) {\n              try {\n                const s = statSync(filePath);\n                if (s.mtimeMs === cached.stat.mtimeMs && s.size === cached.stat.size) {\n                  // Unchanged file: whatever body we extracted last time is\n                  // still accurate, so reuse it instead of indexing nothing.\n                  return {\n                    meta: cached.meta,\n                    searchContent: this.searchContents.get(cached.meta.id),\n                  };\n                }\n              } catch {\n                // file disappeared — fall through to parse which will return null\n              }\n            }\n          }\n          try {\n            // Fold the search document over the same lines as the metadata\n            // parse — one read of the file, not two.\n            let doc = emptySearchDocument();\n            const meta = await parseMetaWithProvider(provider, filePath, account, tier, (entry) => {\n              doc = appendSearchDelta(doc, extractSearchDelta(entry));\n            });\n            // The provider may already know its branch (Codex reads it from\n            // session_meta). Only walk the filesystem when it doesn't.\n            if (meta && meta.gitBranch === null && meta.projectPath) {\n              meta.gitBranch = resolveGitBranch(meta.projectPath);\n            }\n            return { meta, searchContent: capForMemory(doc, tier.snippetMax) };\n          } catch (err) {\n            parseFailures++;\n            log.warn({ filePath, account, provider: provider.name, err }, \"scan: parse threw\");\n            return { meta: null, searchContent: undefined };\n          }\n        }),\n      );\n\n      const batchMetas: ConversationMeta[] = [];\n      for (const { meta, searchContent } of results) {\n        if (meta && meta.messageCount > 0) {\n          this.metadataCache.set(meta.id, meta);\n          this.addToSessionIndex(meta);\n          this.projects.add(meta.projectPath);\n          allMetas.push(meta);\n          batchMetas.push(meta);\n          const content = searchContent ?? this.searchContents.get(meta.id) ?? \"\";\n          this.searchContents.set(meta.id, content);\n          this.indexer.addDocument(meta, content);\n        }\n      }\n\n      if (batchMetas.length > 0) {\n        options.onBatch?.(batchMetas);\n      }\n\n      scanned += batch.length;\n      log.debug({ scanned, totalFiles, batchKept: batchMetas.length }, \"scan: batch complete\");\n      options.onProgress?.(scanned, totalFiles);\n    }\n\n    const { conversations, total } = this.finalize(allMetas, options);\n\n    const elapsedMs = Date.now() - startedAt;\n    log.info(\n      {\n        totalFiles,\n        scanned,\n        kept: allMetas.length,\n        filteredTotal: total,\n        parseFailures,\n        elapsedMs,\n      },\n      \"scan: complete\",\n    );\n\n    return { conversations, total, scanned };\n  }\n\n  async search(query: string, options: SearchOptions = {}): Promise<SearchResult[]> {\n    const log = getLogger();\n    log.debug({ query, indexSize: this.indexer.getDocumentCount() }, \"search: start\");\n\n    let results: SearchResult[];\n    if (this.persistent) {\n      // SQLite FTS5 is the persistent search engine. Index once if the DB is\n      // empty (mirroring the in-memory \"scan on first search\" contract), then\n      // query FTS directly — no in-memory index to warm.\n      const engine = this.engine();\n      if (engine.conversations.count() === 0) {\n        log.debug(\"search: persistent index empty, triggering scan\");\n        const profiles = await this.resolveProfiles(options.profiles);\n        const activeProfiles = profiles.filter((p) => p.enabled && p.scanHistory !== false);\n        await engine.indexAll(activeProfiles, { ...options, limit: undefined, offset: undefined });\n      }\n      if (query.trim()) {\n        // Filters go down into SQL so they run BEFORE the LIMIT. Applying them\n        // to an already-truncated hit list would report \"no results\" whenever\n        // the surviving conversations sit outside the unfiltered top-N — much\n        // more likely now that the corpus is wide.\n        const want = (options.limit ?? 50) + (options.offset ?? 0);\n        results = engine\n          .searchHits(query, want, {\n            account: options.account,\n            provider: options.provider,\n            project: options.project,\n            since: options.since ? parseSinceCutoff(options.since) : undefined,\n            include: options.include,\n          })\n          .map(({ meta, body }) => ({\n            meta,\n            score: 1,\n            matches: body\n              ? [\n                  { field: CONTENT_FIELD, snippet: body.snippet, highlights: body.highlights },\n                  ...generateMatches(meta, query).filter((m) => m.field !== \"preview\"),\n                ]\n              : generateMatches(meta, query),\n          }));\n      } else {\n        results = engine.recentMetas((options.limit ?? 50) + (options.offset ?? 0)).map((meta) => ({\n          meta,\n          score: 1,\n          matches: [{ field: \"timestamp\", snippet: meta.preview }],\n        }));\n      }\n    } else {\n      if (this.indexer.getDocumentCount() === 0) {\n        log.debug(\"search: index empty, triggering scan\");\n        await this.scan({ ...options, limit: undefined, offset: undefined });\n      }\n      results = this.indexer.search(query, {\n        fields: options.fields,\n        limit: (options.limit ?? 50) * 2,\n      });\n    }\n\n    if (options.include && options.include !== \"all\") {\n      results = results.filter((r) => {\n        switch (options.include) {\n          case \"conversations\":\n            return !r.meta.isSubagent && !r.meta.isTeammate;\n          case \"subagents\":\n            return r.meta.isSubagent;\n          case \"teammates\":\n            return r.meta.isTeammate;\n          default:\n            return true;\n        }\n      });\n    }\n    if (options.project) {\n      const lower = options.project.toLowerCase();\n      results = results.filter(\n        (r) =>\n          r.meta.projectPath.toLowerCase().includes(lower) ||\n          r.meta.projectName.toLowerCase().includes(lower),\n      );\n    }\n    if (options.account) {\n      results = results.filter((r) => r.meta.account === options.account);\n    }\n    if (options.provider) {\n      results = results.filter((r) => providerMatches(r.meta.provider, options.provider as string));\n    }\n    if (options.since) {\n      const cutoff = parseSinceCutoff(options.since);\n      results = results.filter((r) => new Date(r.meta.timestamp).getTime() >= cutoff.getTime());\n    }\n\n    const limit = options.limit ?? 50;\n    const offset = options.offset ?? 0;\n    const sliced = results.slice(offset, offset + limit);\n    log.debug({ query, matched: results.length, returned: sliced.length }, \"search: complete\");\n    return sliced;\n  }\n\n  async getConversation(\n    id: string,\n    _options?: GetConversationOptions,\n  ): Promise<Conversation | null> {\n    const log = getLogger();\n    // Canonicalize the file-path form of the id so a native (join/watcher) and a\n    // forward-slash (fast-glob) spelling of the same file resolve to one entry.\n    // A sessionId (no separators) is unaffected.\n    const cid = canonicalPath(id);\n    const cached = this.conversationLRU.get(cid);\n    if (cached) {\n      log.debug({ id }, \"getConversation: cache hit\");\n      return cached.conversation;\n    }\n\n    const meta = this.persistent\n      ? this.engine().getByIdOrSession(cid)\n      : (this.metadataCache.get(cid) ?? this.resolveSessionId(cid));\n    if (!meta) {\n      log.debug({ id }, \"getConversation: not found in metadata\");\n      return null;\n    }\n\n    log.debug({ id, filePath: meta.filePath }, \"getConversation: cache miss, parsing\");\n    try {\n      // Persistent claude-code files parse through the resumable streaming fold\n      // so the cached entry carries its resume point — refreshFile can then\n      // extend it with only the appended bytes instead of evicting it. Codex\n      // and legacy-mode parses stay on their existing parsers (no resume state;\n      // a refresh evicts them as before).\n      if (\n        this.persistent &&\n        meta.provider !== CODEX_CLI_PROVIDER &&\n        meta.provider !== CURSOR_PROVIDER &&\n        meta.provider !== COPILOT_PROVIDER\n      ) {\n        const parsed = await parseConversationResumable(meta.filePath, meta.account);\n        if (parsed) this.conversationLRU.set(cid, parsed);\n        return parsed?.conversation ?? null;\n      }\n      const conversation =\n        meta.provider === CODEX_CLI_PROVIDER\n          ? await parseCodexConversation(meta.filePath, meta.account)\n          : meta.provider === CURSOR_PROVIDER\n            ? await parseCursorConversation(meta.filePath, meta.account)\n            : meta.provider === COPILOT_PROVIDER\n              ? await parseCopilotConversation(meta.filePath, meta.account)\n              : await parseConversation(meta.filePath, meta.account);\n      if (conversation) {\n        this.conversationLRU.set(cid, { conversation });\n      }\n      return conversation;\n    } catch (err) {\n      log.warn({ id, filePath: meta.filePath, err }, \"getConversation: parse failed\");\n      return null;\n    }\n  }\n\n  // Return one bounded window of a conversation's messages plus the total\n  // message count, so a caller can serve the last page and scroll back without\n  // holding the whole conversation itself.\n  //\n  // The window is `[max(0, beforeIndex - limit), beforeIndex)` in chronological\n  // order; `beforeIndex` defaults to `total` (the newest page). `fromIndex` is\n  // the window's start index, so the caller can derive `has_more_older`\n  // (fromIndex > 0). Returns null when the id can't be resolved/parsed — the\n  // same contract as getConversation.\n  //\n  // Persistent mode: a true bounded read — the engine seeks from the nearest\n  // checkpoint and parses only the requested window (checkpoints are built\n  // lazily for large conversations). Windowed message indices are proven\n  // identical to parseConversation().messages.slice(...) by the paged-reader\n  // equivalence test.\n  //\n  // Legacy mode: parse-once-then-slice via getConversation (cached in the LRU),\n  // which is identical by construction.\n  async getConversationPage(\n    id: string,\n    options: GetConversationPageOptions,\n  ): Promise<ConversationPage | null> {\n    if (this.persistent) {\n      return this.engine().getPage(canonicalPath(id), options);\n    }\n\n    const conversation = await this.getConversation(id);\n    if (!conversation) return null;\n\n    const { messages } = conversation;\n    const total = messages.length;\n    const { limit } = options;\n    const beforeIndex = options.beforeIndex ?? total;\n\n    const fromIndex = Math.max(0, beforeIndex - limit);\n    const window = messages.slice(fromIndex, beforeIndex);\n\n    return { messages: window, total, fromIndex };\n  }\n\n  // Parse one JSONL file directly and slice a page window — without any prior\n  // scan() or metadata index. This is the cold-start fast path: a single\n  // conversation can be served from one file parse (~ms) instead of waiting\n  // for a full filesystem scan. The window is the same\n  // `[max(0, beforeIndex - limit), beforeIndex)` slice as getConversationPage,\n  // and the parsed Conversation is returned alongside so the caller can build\n  // the response meta (projectPath, timestamp, messageCount, …) without a\n  // second parse. Returns null when the file can't be parsed. `account` only\n  // feeds the conversation's account field; \"default\" is the single-profile\n  // fallback, matching refreshFile.\n  async parseSingleFilePage(\n    filePath: string,\n    account: string | undefined,\n    options: GetConversationPageOptions,\n  ): Promise<SingleFilePage | null> {\n    // There is no index row on this path — it is the cold/fallback read — so\n    // sniff the file for its provider. Handing a Codex rollout to\n    // parseConversation() returns nothing, which reads to the caller as a\n    // conversation that does not exist.\n    const provider = await this.resolveProviderForFile(filePath, null);\n    const conversation =\n      provider?.name === CODEX_CLI_PROVIDER\n        ? await parseCodexConversation(filePath, account ?? \"default\")\n        : provider?.name === CURSOR_PROVIDER\n          ? await parseCursorConversation(filePath, account ?? \"default\")\n          : provider?.name === COPILOT_PROVIDER\n            ? await parseCopilotConversation(filePath, account ?? \"default\")\n            : await parseConversation(filePath, account ?? \"default\");\n    if (!conversation) return null;\n\n    const { messages } = conversation;\n    const total = messages.length;\n    const { limit } = options;\n    const beforeIndex = options.beforeIndex ?? total;\n\n    const fromIndex = Math.max(0, beforeIndex - limit);\n    const window = messages.slice(fromIndex, beforeIndex);\n\n    return { messages: window, total, fromIndex, conversation };\n  }\n\n  // Re-parse a single JSONL file and update every in-memory index in place —\n  // metadata cache, sessionId index, project set, search index — and evict the\n  // file's parsed conversation from the LRU so the next getConversation()\n  // re-reads it. This lets a long-lived scanner stay current with a file that\n  // grew after the initial scan() without paying for a full rescan.\n  //\n  // `account` defaults to the account already recorded for this file (the id\n  // is the file path), falling back to \"default\" for a file the scanner has\n  // not seen before. Returns the fresh ConversationMeta, or null when the file\n  // no longer parses (missing/empty) — in which case any prior entry for it is\n  // dropped from all indexes.\n  //\n  // Single-flighted per path: concurrent callers (stacked client retries, a\n  // watcher tick racing a caller) await the one in-flight refresh instead of\n  // each re-reading the file.\n  private readonly refreshesInFlight = new Map<string, Promise<ConversationMeta | null>>();\n\n  refreshFile(filePath: string, account?: string): Promise<ConversationMeta | null> {\n    // Key every refresh off the canonical path so a fast-glob (forward slash)\n    // and a watcher/join (native) spelling of the same file share one in-flight\n    // refresh and hit the same stored row.\n    const cpath = canonicalPath(filePath);\n    const inFlight = this.refreshesInFlight.get(cpath);\n    if (inFlight) return inFlight;\n    const refresh = this.doRefreshFile(cpath, account).finally(() => {\n      if (this.refreshesInFlight.get(cpath) === refresh) {\n        this.refreshesInFlight.delete(cpath);\n      }\n    });\n    this.refreshesInFlight.set(cpath, refresh);\n    return refresh;\n  }\n\n  private async doRefreshFile(\n    filePath: string,\n    account?: string,\n  ): Promise<ConversationMeta | null> {\n    const log = getLogger();\n\n    if (this.persistent) {\n      const engine = this.engine();\n      const previous = engine.getByIdOrSession(filePath);\n      const resolvedAccount = account ?? previous?.account ?? \"default\";\n      // Resolve the provider so a Codex file refreshes through the Codex reducer\n      // rather than the Threadbase tail-read. Prefer stored metadata; fall back\n      // to a structural sniff for a file the index hasn't seen yet.\n      const provider = await this.resolveProviderForFile(filePath, previous);\n      // force=false: let classify() decide. It already distinguishes\n      // unchanged/appended/reindex/vanished correctly (including the\n      // replace-with-larger-file case, cursor.ts), so the watcher's live-append\n      // path resumes from the byte cursor instead of reparsing the whole file\n      // on every debounced tick.\n      const { meta, change } = await engine.indexFile(\n        filePath,\n        resolvedAccount,\n        this.lastTier.name,\n        undefined,\n        readGitBranch,\n        false,\n        provider,\n      );\n\n      // Cached parses are keyed by whatever id getConversation() was asked for\n      // — the file-path id or the sessionId — so collect every candidate key.\n      const cacheKeys = new Set<string>();\n      for (const m of [previous, meta]) {\n        if (m) {\n          cacheKeys.add(m.id);\n          cacheKeys.add(m.sessionId);\n        }\n      }\n      if (!meta || change === \"reindex\" || change === \"vanished\") {\n        // Truncated/replaced/gone: the cached parse no longer matches the\n        // bytes on disk — drop it so the next read re-parses.\n        for (const key of cacheKeys) this.conversationLRU.delete(key);\n      } else if (change === \"appended\") {\n        // The file only grew: advance any cached parse by folding just the\n        // appended bytes, leaving the LRU warm instead of evicting it.\n        await this.extendCachedConversations(cacheKeys, filePath, meta.account);\n      }\n      // unchanged → the cached parse is still valid; leave it warm.\n\n      log.debug({ filePath, change, kept: !!meta }, \"refreshFile: updated persistent index\");\n      return meta;\n    }\n\n    const previous = this.metadataCache.get(filePath);\n    const resolvedAccount = account ?? previous?.account ?? \"default\";\n\n    let meta: ConversationMeta | null = null;\n    let searchDoc = emptySearchDocument();\n    const onEntry = (entry: Record<string, unknown>) => {\n      searchDoc = appendSearchDelta(searchDoc, extractSearchDelta(entry));\n    };\n    // Same provider resolution the persistent branch above does: without it a\n    // Codex file is folded by the Threadbase reducer, yields no meta, and is\n    // dropped from the index by the eviction below.\n    const provider = await this.resolveProviderForFile(filePath, previous ?? null);\n    try {\n      meta = provider\n        ? await parseMetaWithProvider(provider, filePath, resolvedAccount, this.lastTier, onEntry)\n        : await parseMeta(filePath, resolvedAccount, this.lastTier, onEntry);\n    } catch (err) {\n      log.warn({ filePath, err }, \"refreshFile: parseMeta threw\");\n      meta = null;\n    }\n\n    // The LRU is keyed by the id used at getConversation() time, which can be\n    // either the file-path id or the sessionId — evict both for the prior and\n    // refreshed metas so no stale parse survives.\n    const evict = (m: ConversationMeta | undefined | null) => {\n      if (!m) return;\n      this.conversationLRU.delete(m.id);\n      this.conversationLRU.delete(m.sessionId);\n    };\n    evict(previous);\n    evict(meta);\n\n    if (!meta || meta.messageCount === 0) {\n      if (previous) {\n        this.metadataCache.delete(previous.id);\n        this.removeFromSessionIndex(previous);\n        this.indexer.removeDocument(previous.id);\n        this.searchContents.delete(previous.id);\n      }\n      log.debug({ filePath }, \"refreshFile: dropped (no parseable messages)\");\n      return null;\n    }\n\n    meta.gitBranch = readGitBranch(meta.projectPath);\n\n    // A re-parse can change the sessionId mapping; clear the old entry first so\n    // we don't leave this file listed under a stale sessionId.\n    if (previous) this.removeFromSessionIndex(previous);\n    this.metadataCache.set(meta.id, meta);\n    this.addToSessionIndex(meta);\n    this.projects.add(meta.projectPath);\n    const searchContent = capForMemory(searchDoc, this.lastTier.snippetMax);\n    this.searchContents.set(meta.id, searchContent);\n    if (previous) {\n      this.indexer.updateDocument(meta, searchContent);\n    } else {\n      this.indexer.addDocument(meta, searchContent);\n    }\n\n    log.debug(\n      { filePath, messageCount: meta.messageCount },\n      \"refreshFile: updated in-memory indexes\",\n    );\n    return meta;\n  }\n\n  // Advance every cached parse of an appended file by folding only the new\n  // bytes through the conversation reducer — the in-memory analogue of the\n  // persisted metadata fold. Entries without resume state (Codex) and entries\n  // whose extension fails are evicted so the next read re-parses from scratch.\n  private async extendCachedConversations(\n    cacheKeys: Set<string>,\n    filePath: string,\n    account: string,\n  ): Promise<void> {\n    // Multiple keys can point at the same wrapper (path id + sessionId) —\n    // group by wrapper so each cached parse is extended exactly once.\n    const wrappers = new Map<CachedConversation, string[]>();\n    for (const key of cacheKeys) {\n      const wrapper = this.conversationLRU.get(key);\n      if (!wrapper) continue;\n      const keys = wrappers.get(wrapper) ?? [];\n      keys.push(key);\n      wrappers.set(wrapper, keys);\n    }\n    for (const [wrapper, keys] of wrappers) {\n      if (!wrapper.resume) {\n        for (const key of keys) this.conversationLRU.delete(key);\n        continue;\n      }\n      try {\n        const extended = await extendConversation(\n          wrapper.conversation,\n          wrapper.resume,\n          filePath,\n          account,\n        );\n        // Mutate the wrapper in place so every key holding it sees the update.\n        wrapper.conversation = extended.conversation;\n        wrapper.resume = extended.resume;\n      } catch (err) {\n        getLogger().warn({ filePath, err }, \"refreshFile: cache extension failed, evicting\");\n        for (const key of keys) this.conversationLRU.delete(key);\n      }\n    }\n  }\n\n  getMetadataCache(): Map<string, ConversationMeta> {\n    if (this.persistent) {\n      const map = new Map<string, ConversationMeta>();\n      for (const meta of this.engine().allActive()) map.set(meta.id, meta);\n      return map;\n    }\n    return this.metadataCache;\n  }\n\n  // Collision-safe sessionId lookup. session_id is NOT unique (the parser falls\n  // back to the file basename, and resumed/subagent sessions repeat ids), so\n  // getConversation(sessionId) is a convenience that resolves to one match;\n  // this returns every active conversation sharing the id, newest first.\n  getConversationsBySessionId(sessionId: string): ConversationMeta[] {\n    if (this.persistent) {\n      return this.engine().getAllBySessionId(sessionId);\n    }\n    return this.sortBySessionPriority(this.sessionIdIndex.get(sessionId) ?? []);\n  }\n\n  getProjects(): string[] {\n    const source = this.persistent ? this.engine().getProjects() : this.projects;\n    const normalized = new Set<string>();\n    for (const p of source) {\n      normalized.add(p.replace(/\\/+$/, \"\"));\n    }\n    return Array.from(normalized).sort();\n  }\n\n  // ── File watching (persistent mode only) ────────────────────────────────\n\n  // Subscribe to index changes. Events: \"change\" → ScannerChangeEvent,\n  // \"error\" → Error. Returns this for chaining.\n  on(event: \"change\", listener: (e: ScannerChangeEvent) => void): this;\n  on(event: \"error\", listener: (err: Error) => void): this;\n  on(event: string, listener: (...args: any[]) => void): this {\n    this.emitter.on(event, listener);\n    return this;\n  }\n\n  off(event: string, listener: (...args: any[]) => void): this {\n    this.emitter.off(event, listener);\n    return this;\n  }\n\n  // Start watching the active profiles' project dirs. A filesystem watcher\n  // feeds debounced, path-deduplicated index jobs through a single-writer\n  // queue; a periodic full rescan backstops any events the watcher misses\n  // (sleep/wake, network FS, restarts). Emits \"change\" per indexed file.\n  // Persistent mode only — throws in legacy mode (no durable index to update).\n  async watch(options: WatchOptions = {}): Promise<void> {\n    if (!this.persistent) {\n      throw new Error(\"watch() requires persistent mode; construct with persistent enabled\");\n    }\n    if (this.watcher) return; // already watching\n\n    const profiles = await this.resolveProfiles(options.profiles);\n    const activeProfiles = profiles.filter((p) => p.enabled && p.scanHistory !== false);\n\n    this.queue = new IndexQueue(async (job) => {\n      try {\n        // Create/change and delete both route through refreshFile: a removed\n        // file stat-fails or parses empty, so refreshFile drops its row and\n        // returns null. The index stays correct either way.\n        const meta = await this.refreshFile(job.filePath, job.account);\n        this.emitter.emit(\"change\", {\n          filePath: job.filePath,\n          account: job.account,\n          meta,\n          reason: job.reason,\n        } satisfies ScannerChangeEvent);\n      } catch (err) {\n        this.emitter.emit(\"error\", err instanceof Error ? err : new Error(String(err)));\n      }\n    });\n\n    this.watcher = new FileWatcher(\n      activeProfiles,\n      (e) => {\n        this.queue?.enqueue({\n          filePath: e.filePath,\n          account: e.account,\n          reason: \"watcher\",\n        });\n      },\n      { debounceMs: options.debounceMs },\n    );\n    await this.watcher.start();\n\n    const periodicMs = options.periodicMs ?? 60_000;\n    if (periodicMs > 0) {\n      this.periodicTimer = setInterval(() => {\n        // Track the job so unwatch() can await it — otherwise its post-await\n        // engine.files access can land after the DB is closed.\n        const job = this.periodicReconcile(activeProfiles).finally(() => {\n          if (this.inFlightReconcile === job) this.inFlightReconcile = null;\n        });\n        this.inFlightReconcile = job;\n      }, periodicMs);\n      // Don't keep the process alive solely for the rescan timer.\n      this.periodicTimer.unref?.();\n    }\n\n    getLogger().info({ profiles: activeProfiles.length, periodicMs }, \"watch: started\");\n  }\n\n  // Periodic correctness backstop: re-discover all files and enqueue them\n  // through the same queue the watcher uses, so any add/change the watcher\n  // missed still gets indexed and emits a \"change\" event. Vanished files\n  // (active in the DB but no longer on disk) are enqueued too — refreshFile\n  // drops them. Routing through the queue (not a direct scan) keeps event\n  // emission and single-writer serialization unified.\n  private async periodicReconcile(activeProfiles: Profile[]): Promise<void> {\n    if (!this.queue) return;\n    try {\n      const engine = this.engine();\n      const configDirs = activeProfiles.map((p) => ({\n        projectsDir: getProjectsDir(p),\n        account: p.id,\n      }));\n      const discovered = await discoverJsonlFiles(configDirs);\n      const seen = new Set<string>();\n      for (const { filePath, account } of discovered) {\n        seen.add(filePath);\n        // Only enqueue genuinely-changed files so an idle tick stays silent\n        // (no spurious \"change\" events for thousands of unchanged files).\n        const { change } = classify(filePath, engine.files.getByPath(filePath));\n        if (change !== \"unchanged\") {\n          this.queue.enqueue({ filePath, account, reason: \"periodic\" });\n        }\n      }\n      // Files in the index but gone from disk → enqueue so refreshFile drops them.\n      for (const path of engine.files.allActivePaths()) {\n        if (!seen.has(path)) {\n          this.queue.enqueue({ filePath: path, account: \"default\", reason: \"periodic\" });\n        }\n      }\n    } catch (err) {\n      this.emitter.emit(\"error\", err instanceof Error ? err : new Error(String(err)));\n    }\n  }\n\n  // Stop watching and drain any in-flight index jobs.\n  async unwatch(): Promise<void> {\n    if (this.periodicTimer) {\n      clearInterval(this.periodicTimer);\n      this.periodicTimer = null;\n    }\n    // Let a reconcile that already started finish touching the DB before we\n    // drop the queue/engine below. Its own catch swallows errors; we only wait.\n    if (this.inFlightReconcile) {\n      await this.inFlightReconcile;\n    }\n    if (this.watcher) {\n      await this.watcher.stop();\n      this.watcher = null;\n    }\n    if (this.queue) {\n      await this.queue.onIdle();\n      this.queue = null;\n    }\n  }\n\n  // ── Provider plumbing (in-memory path) ──────────────────────────────────\n\n  // Build the flat parse worklist for the enabled providers. Threadbase\n  // discovers under the active profiles' project dirs; Codex discovers under\n  // the explicit codexRoots (opt-in — no default home scan).\n  private async discoverWithProviders(\n    activeProfiles: Profile[],\n    options: ScanOptions,\n  ): Promise<{ filePath: string; account: string; provider: ScannerProvider }[]> {\n    const enabled = canonicalizeProviderList(options.providers);\n    const work: { filePath: string; account: string; provider: ScannerProvider }[] = [];\n\n    if (enabled.includes(CLAUDE_CODE_PROVIDER)) {\n      const provider = new ThreadbaseProvider();\n      const roots = activeProfiles.map((p) => `${getProjectsDir(p)}\\0${p.id}`);\n      for (const f of await provider.discover(roots)) {\n        work.push({ ...f, provider });\n      }\n    }\n    if (enabled.includes(CODEX_CLI_PROVIDER) && (options.codexRoots?.length ?? 0) > 0) {\n      const provider = new CodexCliProvider();\n      for (const f of await provider.discover(options.codexRoots as string[])) {\n        work.push({ ...f, provider });\n      }\n    }\n    if (enabled.includes(CURSOR_PROVIDER) && (options.cursorRoots?.length ?? 0) > 0) {\n      const provider = new CursorProvider();\n      for (const f of await provider.discover(options.cursorRoots as string[])) {\n        work.push({ ...f, provider });\n      }\n    }\n    if (enabled.includes(COPILOT_PROVIDER) && (options.copilotRoots?.length ?? 0) > 0) {\n      const provider = new CopilotProvider();\n      for (const f of await provider.discover(options.copilotRoots as string[])) {\n        work.push({ ...f, provider });\n      }\n    }\n    return work;\n  }\n\n  // Resolve which provider should (re)parse a file. Stored metadata wins; for a\n  // file the index has not seen, sniff the first lines with each non-Threadbase\n  // provider's canParse. Returns undefined for Threadbase (the engine's default\n  // tail-read path).\n  private async resolveProviderForFile(\n    filePath: string,\n    previous: ConversationMeta | null,\n  ): Promise<ScannerProvider | undefined> {\n    if (previous?.provider === CODEX_CLI_PROVIDER) return new CodexCliProvider();\n    if (providerMatches(previous?.provider, CURSOR_PROVIDER)) {\n      return new CursorProvider();\n    }\n    if (previous?.provider === COPILOT_PROVIDER) return new CopilotProvider();\n    if (previous?.provider) return undefined; // known Threadbase\n    let sample = \"\";\n    try {\n      const fd = openSync(filePath, \"r\");\n      try {\n        const buf = Buffer.alloc(8192);\n        const n = readSync(fd, buf, 0, buf.length, 0);\n        sample = buf.subarray(0, n).toString(\"utf8\");\n      } finally {\n        closeSync(fd);\n      }\n    } catch {\n      return undefined;\n    }\n    const cursor = new CursorProvider();\n    if (cursor.canParse(filePath, sample)) return cursor;\n    const codex = new CodexCliProvider();\n    if (codex.canParse(filePath, sample)) return codex;\n    const copilot = new CopilotProvider();\n    if (copilot.canParse(filePath, sample)) return copilot;\n    return undefined;\n  }\n\n  private addToSessionIndex(meta: ConversationMeta): void {\n    const list = this.sessionIdIndex.get(meta.sessionId);\n    if (list) {\n      // Replace any existing entry for the same file (re-index), else append.\n      const i = list.findIndex((m) => m.id === meta.id);\n      if (i >= 0) list[i] = meta;\n      else list.push(meta);\n    } else {\n      this.sessionIdIndex.set(meta.sessionId, [meta]);\n    }\n  }\n\n  private removeFromSessionIndex(meta: ConversationMeta): void {\n    const list = this.sessionIdIndex.get(meta.sessionId);\n    if (!list) return;\n    const next = list.filter((m) => m.id !== meta.id);\n    if (next.length > 0) this.sessionIdIndex.set(meta.sessionId, next);\n    else this.sessionIdIndex.delete(meta.sessionId);\n  }\n\n  // Deterministic single-result sessionId resolution: newest timestamp first,\n  // tie-broken by absolute path ascending.\n  private resolveSessionId(sessionId: string): ConversationMeta | undefined {\n    return this.sortBySessionPriority(this.sessionIdIndex.get(sessionId) ?? [])[0];\n  }\n\n  private sortBySessionPriority(metas: ConversationMeta[]): ConversationMeta[] {\n    return [...metas].sort((a, b) => {\n      if (a.timestamp !== b.timestamp) return a.timestamp < b.timestamp ? 1 : -1;\n      return a.filePath < b.filePath ? -1 : a.filePath > b.filePath ? 1 : 0;\n    });\n  }\n\n  private async resolveProfiles(profiles?: Profile[]): Promise<Profile[]> {\n    // An explicit array (including an empty one) means \"use exactly these\" —\n    // `[]` scans zero profiles. Only an absent argument falls back to defaults.\n    // Treating `[]` as \"load defaults\" silently scanned the real ~/.claude\n    // history, which looked like a hang for callers isolating from it.\n    if (profiles) return profiles;\n    return loadProfiles(DEFAULT_CONFIG_PATH);\n  }\n\n  private transformView(\n    metas: ConversationMeta[],\n    options: ScanOptions,\n  ): ConversationMeta[] | TreeConversation[] | GroupedConversations {\n    switch (options.view) {\n      case \"tree\":\n        return this.toTree(metas);\n      case \"grouped\":\n        return this.toGrouped(metas);\n      default:\n        return metas;\n    }\n  }\n\n  private toTree(metas: ConversationMeta[]): TreeConversation[] {\n    const parents: TreeConversation[] = [];\n    const subagents: ConversationMeta[] = [];\n\n    for (const meta of metas) {\n      if (meta.isSubagent) {\n        subagents.push(meta);\n      } else {\n        parents.push({ ...meta, subagents: [] });\n      }\n    }\n\n    const parentById = new Map(parents.map((p) => [p.id, p]));\n    for (const sub of subagents) {\n      const parent = sub.parentSessionId ? parentById.get(sub.parentSessionId) : undefined;\n      if (parent) {\n        parent.subagents.push(sub);\n      } else {\n        parents.push({ ...sub, subagents: [] });\n      }\n    }\n\n    return parents;\n  }\n\n  private toGrouped(metas: ConversationMeta[]): GroupedConversations {\n    const groups: GroupedConversations = {};\n    for (const meta of metas) {\n      const key = meta.teamName || \"_default\";\n      if (!groups[key]) groups[key] = [];\n      groups[key].push(meta);\n    }\n    return groups;\n  }\n}\n","export class LRUCache<K, V> {\n  private map = new Map<K, V>();\n  private readonly capacity: number;\n\n  constructor(capacity: number) {\n    this.capacity = capacity;\n  }\n\n  get(key: K): V | undefined {\n    const value = this.map.get(key);\n    if (value === undefined) return undefined;\n    // Move to end (most recently used)\n    this.map.delete(key);\n    this.map.set(key, value);\n    return value;\n  }\n\n  set(key: K, value: V): void {\n    this.map.delete(key);\n    this.map.set(key, value);\n    if (this.map.size > this.capacity) {\n      const oldest = this.map.keys().next();\n      if (!oldest.done) this.map.delete(oldest.value);\n    }\n  }\n\n  has(key: K): boolean {\n    return this.map.has(key);\n  }\n\n  delete(key: K): boolean {\n    return this.map.delete(key);\n  }\n\n  clear(): void {\n    this.map.clear();\n  }\n\n  get size(): number {\n    return this.map.size;\n  }\n}\n","import { basename } from \"path\";\nimport { getShortProjectName } from \"../parser\";\nimport { resolveSessionName } from \"../session-name\";\nimport type { Conversation, ConversationMessage, TurnDuration } from \"../types\";\nimport { applyTeamInfo, type ConvReducerState, initialConvState } from \"./conversation-reducer\";\nimport { streamMessages } from \"./paged-reader\";\n\n// A parsed conversation held in the scanner's LRU together with the resume\n// point needed to extend it in place when its file grows: the conversation\n// reducer state at EOF plus the byte/line offset just past the last complete\n// line. `resume` is absent for providers without a resumable reducer (Codex)\n// — a refresh evicts those entries instead of extending them.\nexport interface ConversationResume {\n  state: ConvReducerState;\n  offset: number;\n  line: number;\n}\n\nexport interface CachedConversation {\n  conversation: Conversation;\n  resume?: ConversationResume;\n}\n\n// Fold [resume.offset, EOF) through the conversation reducer, collecting the\n// produced messages plus turn_duration entries. turn_duration lines are\n// consumed BEFORE the reducer — exactly as parseConversation does — so their\n// header fields (timestamps) never leak into the conversation state. Mutates\n// resume.state in place; on failure the caller must discard the cached entry.\nasync function foldTail(filePath: string, resume: ConversationResume) {\n  const messages: ConversationMessage[] = [];\n  const textParts: string[] = [];\n  const turnDurations: TurnDuration[] = [];\n  const end = await streamMessages(\n    filePath,\n    resume.offset,\n    resume.line,\n    resume.state,\n    (message) => {\n      messages.push(message);\n      if (message.text && !message.isCompactSummary) textParts.push(message.text);\n      return false; // never stop early — fold to EOF\n    },\n    (entry) => {\n      if (\n        entry.type === \"system\" &&\n        entry.subtype === \"turn_duration\" &&\n        typeof entry.durationMs === \"number\"\n      ) {\n        turnDurations.push({\n          durationMs: entry.durationMs as number,\n          messageCount: (entry.messageCount as number) || 0,\n          uuid: entry.uuid as string | undefined,\n        });\n        return true;\n      }\n      return false;\n    },\n  );\n  return { messages, textParts, turnDurations, end };\n}\n\n// Assemble the Conversation shape exactly as parseConversation() does.\nfunction assemble(\n  filePath: string,\n  account: string,\n  messages: ConversationMessage[],\n  fullText: string,\n  turnDurations: TurnDuration[],\n  state: ConvReducerState,\n): Conversation {\n  return {\n    id: filePath,\n    filePath,\n    projectPath: state.cwd,\n    projectName: getShortProjectName(state.cwd),\n    sessionId: state.sessionId || basename(filePath, \".jsonl\"),\n    sessionName: resolveSessionName(state.nameSlots).name ?? \"\",\n    messages,\n    fullText,\n    timestamp: state.latestTimestamp || new Date().toISOString(),\n    messageCount: messages.length,\n    account,\n    turnDurations: turnDurations.length > 0 ? turnDurations : undefined,\n    lastPrompt: state.lastPrompt || undefined,\n  };\n}\n\n// Full parse that also captures the resume point, so a later append can extend\n// the result in place instead of re-parsing the file. Identical to\n// parseConversation() by construction (same reducer, same assembly); the one\n// deliberate difference is torn-write discipline: a final line with no\n// trailing newline is treated as in-flight (not parsed yet) rather than\n// parsed, matching the persistent index's byte-cursor semantics.\nexport async function parseConversationResumable(\n  filePath: string,\n  account: string,\n): Promise<CachedConversation | null> {\n  const resume: ConversationResume = { state: initialConvState(), offset: 0, line: 0 };\n  const { messages, textParts, turnDurations, end } = await foldTail(filePath, resume);\n  if (messages.length === 0) return null;\n  applyTeamInfo(messages, resume.state);\n  const conversation = assemble(\n    filePath,\n    account,\n    messages,\n    textParts.join(\" \"),\n    turnDurations,\n    resume.state,\n  );\n  return { conversation, resume: { state: resume.state, offset: end.offset, line: end.line } };\n}\n\n// Extend a cached conversation with only the bytes appended since its resume\n// point. Returns a NEW Conversation with a fresh messages array — the old\n// ARRAY is never mutated, but team info discovered in the delta enriches the\n// shared message objects in place (the same enrichment a full re-parse would\n// emit), so element-level immutability is not guaranteed.\nexport async function extendConversation(\n  previous: Conversation,\n  resume: ConversationResume,\n  filePath: string,\n  account: string,\n): Promise<CachedConversation> {\n  const { messages: fresh, textParts, turnDurations, end } = await foldTail(filePath, resume);\n  const messages = fresh.length > 0 ? previous.messages.concat(fresh) : previous.messages;\n  // Team info discovered in the delta back-applies to earlier messages, the\n  // same way a full parse applies it once at the end.\n  applyTeamInfo(messages, resume.state);\n  const fullText =\n    textParts.length === 0\n      ? previous.fullText\n      : previous.fullText\n        ? `${previous.fullText} ${textParts.join(\" \")}`\n        : textParts.join(\" \");\n  const allTurnDurations = (previous.turnDurations ?? []).concat(turnDurations);\n  const conversation = assemble(\n    filePath,\n    account,\n    messages,\n    fullText,\n    allTurnDurations,\n    resume.state,\n  );\n  return { conversation, resume: { state: resume.state, offset: end.offset, line: end.line } };\n}\n","import { createReadStream } from \"fs\";\nimport { setImmediate as yieldToEventLoop } from \"timers/promises\";\nimport type { ConversationMessage, ConversationPage } from \"../types\";\nimport {\n  applyTeamInfo,\n  type ConvReducerState,\n  initialConvState,\n  reduceConvLine,\n} from \"./conversation-reducer\";\nimport { YIELD_EVERY_LINES } from \"./jsonl-tail-reader\";\nimport type { Checkpoint } from \"./repositories/checkpoints.repo\";\n\n// Snapshot the parser state every CHECKPOINT_INTERVAL messages.\nexport const CHECKPOINT_INTERVAL = 500;\n\n// Stream a JSONL file from `startOffset`, folding each complete line through the\n// conversation reducer. `onMessage` receives every produced message plus the\n// byte offset of the line that FOLLOWS it (a safe resume point) and the current\n// state. `onEntry` (optional) sees every parsed entry first — return true to\n// consume it before it reaches the reducer (parseConversation's turn_duration\n// handling). Returns the offset/line just past the last COMPLETE line consumed\n// — a trailing torn line is never included, so the result is a safe resume\n// point for a later incremental fold.\nexport async function streamMessages(\n  filePath: string,\n  startOffset: number,\n  startLine: number,\n  state: ConvReducerState,\n  // Return true to stop streaming (window filled).\n  onMessage: (message: ConversationMessage, nextByteOffset: number, nextLine: number) => boolean,\n  onEntry?: (entry: Record<string, unknown>) => boolean,\n): Promise<{ offset: number; line: number }> {\n  const stream = createReadStream(filePath, { start: startOffset, encoding: \"utf8\" });\n  let buffer = \"\";\n  let offset = startOffset;\n  let line = startLine;\n  let sinceYield = 0;\n\n  for await (const chunk of stream) {\n    buffer += chunk;\n    let nl: number;\n    // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic line scan\n    while ((nl = buffer.indexOf(\"\\n\")) >= 0) {\n      const lineWithNewline = buffer.slice(0, nl + 1);\n      const text = lineWithNewline.trimEnd();\n      buffer = buffer.slice(nl + 1);\n      offset += Buffer.byteLength(lineWithNewline, \"utf8\");\n      line += 1;\n\n      // Same cooperative yield as the tail reader: without it, buffered chunks\n      // drain through microtasks and a full-file walk blocks the event loop.\n      if (++sinceYield >= YIELD_EVERY_LINES) {\n        sinceYield = 0;\n        await yieldToEventLoop();\n      }\n\n      if (text.length === 0) continue;\n      let entry: Record<string, unknown>;\n      try {\n        entry = JSON.parse(text);\n      } catch {\n        continue;\n      }\n      if (onEntry?.(entry)) continue;\n      const message = reduceConvLine(state, entry);\n      if (message && onMessage(message, offset, line)) {\n        stream.destroy();\n        return { offset, line };\n      }\n    }\n  }\n  return { offset, line };\n}\n\n// Build checkpoints by streaming the file once. Each checkpoint captures the\n// byte offset + reducer state immediately AFTER message (k*interval - 1), i.e.\n// a clean resume point for message index k*interval. Pass `from` (the last\n// persisted checkpoint) to EXTEND an existing chain: the stream resumes from\n// its offset/state and only checkpoints past it are returned — an append costs\n// O(new bytes), never a re-walk of the immutable prefix.\nexport async function buildCheckpoints(\n  filePath: string,\n  interval = CHECKPOINT_INTERVAL,\n  from: Checkpoint | null = null,\n): Promise<Checkpoint[]> {\n  const checkpoints: Checkpoint[] = [];\n  const state = from ? from.state : initialConvState();\n  let index = from ? from.messageIndex : 0;\n\n  await streamMessages(\n    filePath,\n    from?.byteOffset ?? 0,\n    from?.lineNumber ?? 0,\n    state,\n    (_msg, nextOffset, nextLine) => {\n      index += 1;\n      // After consuming `index` messages, if the next message index is a multiple\n      // of the interval, snapshot a resume point for it.\n      if (index % interval === 0) {\n        checkpoints.push({\n          messageIndex: index,\n          byteOffset: nextOffset,\n          lineNumber: nextLine,\n          state: structuredClone(state),\n        });\n      }\n      return false; // never stop early — we want the whole file\n    },\n  );\n\n  return checkpoints;\n}\n\n// Read a bounded message window [fromIndex, beforeIndex) plus the total message\n// count, seeking from the nearest checkpoint <= fromIndex. `total` requires\n// knowing the message count; callers pass it (from the indexed summary) so we\n// never scan the whole file just to count.\nexport async function readPage(\n  filePath: string,\n  total: number,\n  options: { beforeIndex?: number; limit: number },\n  floor: Checkpoint | null,\n): Promise<ConversationPage> {\n  const beforeIndex = options.beforeIndex ?? total;\n  const fromIndex = Math.max(0, beforeIndex - options.limit);\n\n  const state = floor ? structuredClone(floor.state) : initialConvState();\n  const startOffset = floor ? floor.byteOffset : 0;\n  const startLine = floor ? floor.lineNumber : 0;\n  let index = floor ? floor.messageIndex : 0;\n\n  const window: ConversationMessage[] = [];\n  await streamMessages(filePath, startOffset, startLine, state, (message) => {\n    const current = index;\n    index += 1;\n    if (current >= fromIndex && current < beforeIndex) window.push(message);\n    return index >= beforeIndex; // stop once the window is filled\n  });\n\n  applyTeamInfo(window, state);\n  return { messages: window, total, fromIndex };\n}\n","import { createReadStream } from \"fs\";\nimport { setImmediate as yieldToEventLoop } from \"timers/promises\";\nimport type { ContentTier } from \"../types\";\nimport { type ReducerState, reduceLine } from \"./metadata-reducer\";\n\n// Cooperatively yield every N folded lines so a full parse of a large file\n// (cold index, truncation fallback) never blocks the event loop for its whole\n// duration — buffered stream chunks otherwise drain through microtasks without\n// ever reaching the macrotask queue.\nexport const YIELD_EVERY_LINES = 500;\n\nexport interface TailReadResult {\n  // Byte offset of the first un-consumed byte (start of the trailing partial\n  // line, or EOF). Safe to persist as the next cursor.\n  newOffset: number;\n  newLine: number;\n  parsedLines: number;\n  badJsonLines: number;\n}\n\n// Stream a JSONL file from `startOffset` to EOF, folding each COMPLETE line into\n// `state`. A trailing line with no newline (a writer mid-append) is left\n// un-consumed: the offset is not advanced past it, so the next pass re-reads it\n// once it's complete. Offsets are byte offsets (Buffer.byteLength), never string\n// .length, so multibyte content stays aligned. (Spec §7.2–7.4.)\n// `onEntry` rides the same line loop as the metadata fold, so search-document\n// extraction costs no extra file read — important when a cold index or a v5\n// migration re-reads the whole corpus.\nexport async function tailReduce(\n  filePath: string,\n  startOffset: number,\n  startLine: number,\n  state: ReducerState,\n  tier: ContentTier,\n  onEntry?: (entry: Record<string, unknown>) => void,\n): Promise<TailReadResult> {\n  const stream = createReadStream(filePath, { start: startOffset, encoding: \"utf8\" });\n\n  let buffer = \"\";\n  let offset = startOffset;\n  let line = startLine;\n  let parsedLines = 0;\n  let sinceYield = 0;\n\n  for await (const chunk of stream) {\n    buffer += chunk;\n    let nl: number;\n    // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic line scan\n    while ((nl = buffer.indexOf(\"\\n\")) >= 0) {\n      const lineWithNewline = buffer.slice(0, nl + 1);\n      const text = lineWithNewline.trimEnd();\n      buffer = buffer.slice(nl + 1);\n\n      if (text.length > 0) {\n        try {\n          const entry = JSON.parse(text) as Record<string, unknown>;\n          reduceLine(state, entry, tier);\n          onEntry?.(entry);\n        } catch {\n          state.badJsonLines++;\n        }\n        parsedLines++;\n      }\n\n      offset += Buffer.byteLength(lineWithNewline, \"utf8\");\n      line++;\n      if (++sinceYield >= YIELD_EVERY_LINES) {\n        sinceYield = 0;\n        await yieldToEventLoop();\n      }\n    }\n  }\n\n  return { newOffset: offset, newLine: line, parsedLines, badJsonLines: state.badJsonLines };\n}\n","import { createHash } from \"crypto\";\nimport { closeSync, openSync, readSync, statSync } from \"fs\";\nimport type { FileRow } from \"./repositories/conversation-files.repo\";\n\nexport type FileChange = \"unchanged\" | \"appended\" | \"reindex\" | \"vanished\";\n\nexport interface FileStat {\n  size: number;\n  mtimeMs: number;\n}\n\n// Cheap content fingerprint: hash of the first + last 4KB plus the size. Lets\n// us detect a same-size in-place rewrite (e.g. a file replaced atomically)\n// without hashing an entire 200MB file. (Spec §9.4.)\nconst FP_BYTES = 4096;\n\nexport function fingerprint(filePath: string, size: number): string {\n  const hash = createHash(\"sha1\");\n  hash.update(String(size));\n  const fd = openSync(filePath, \"r\");\n  try {\n    const head = Buffer.alloc(Math.min(FP_BYTES, size));\n    if (head.length > 0) {\n      readSync(fd, head, 0, head.length, 0);\n      hash.update(head);\n    }\n    if (size > FP_BYTES) {\n      const tailLen = Math.min(FP_BYTES, size);\n      const tail = Buffer.alloc(tailLen);\n      readSync(fd, tail, 0, tailLen, size - tailLen);\n      hash.update(tail);\n    }\n  } finally {\n    closeSync(fd);\n  }\n  return hash.digest(\"hex\");\n}\n\n// Classify how a file changed relative to its persisted cursor row. Drives\n// whether the indexer skips, tail-reads from the cursor, or reindexes from 0.\n// (Spec §9.)\nexport function classify(\n  filePath: string,\n  existing: FileRow | undefined,\n): { change: FileChange; stat?: FileStat } {\n  let stat: FileStat;\n  try {\n    const s = statSync(filePath);\n    stat = { size: s.size, mtimeMs: s.mtimeMs };\n  } catch {\n    return { change: \"vanished\" };\n  }\n\n  if (!existing || existing.status !== \"active\" || existing.last_indexed_offset === 0) {\n    return { change: \"reindex\", stat };\n  }\n\n  // Smaller than where we left off → truncated/replaced. Reindex.\n  if (stat.size < existing.last_indexed_offset) {\n    return { change: \"reindex\", stat };\n  }\n\n  // Same size + same mtime → nothing to do.\n  if (stat.size === existing.size_bytes && stat.mtimeMs === existing.mtime_ms) {\n    return { change: \"unchanged\", stat };\n  }\n\n  // Same size but mtime moved → possible in-place rewrite. Fingerprint to be\n  // sure; a changed fingerprint means the bytes we already indexed are stale.\n  if (stat.size === existing.last_indexed_offset) {\n    const fp = fingerprint(filePath, stat.size);\n    if (existing.content_fingerprint && fp !== existing.content_fingerprint) {\n      return { change: \"reindex\", stat };\n    }\n    return { change: \"unchanged\", stat };\n  }\n\n  // Grew past the cursor. Usually a genuine append — but an atomic replace with\n  // a DIFFERENT, LONGER file also lands here (size > offset, mtime moved). If we\n  // trusted \"appended\" blindly we'd resume the OLD reducer state and fold only\n  // the new tail, blending two conversations. So re-check the region we already\n  // folded before taking the fast path.\n  //\n  // The stored content_fingerprint was computed as fingerprint(_, size_bytes)\n  // over the old file. It describes the same byte range as fingerprint(current,\n  // last_indexed_offset) ONLY when size_bytes === last_indexed_offset (no\n  // trailing partial line was pending at index time — the normal case). When\n  // that holds and the fingerprints match, we treat it as a real append and\n  // resume the cursor; a mismatch means the indexed region changed → reindex.\n  // When it doesn't hold (a partial line was pending), we can't reconstruct the\n  // stored window, so we reindex to be safe.\n  //\n  // ponytail: fingerprint() only covers the first 4KB + the 4KB ending at the\n  // offset — NOT the whole [0, offset) prefix. So this catches every real atomic\n  // replace (any rewrite changes line 1 → different head 4KB) but NOT a rewrite\n  // that keeps both 8KB windows byte-identical and differs only in the middle.\n  // That's the same bounded ceiling the existing same-size edge-fingerprint has,\n  // accepted by design. Upgrade to a full-prefix hash (or an offset-keyed head\n  // fingerprint column) only if a mid-file-preserving-envelope rewrite is ever\n  // observed in practice — and that same column would also let the pending-\n  // partial-then-grew case resume instead of reindexing.\n  if (\n    existing.content_fingerprint &&\n    existing.size_bytes === existing.last_indexed_offset &&\n    fingerprint(filePath, existing.last_indexed_offset) === existing.content_fingerprint\n  ) {\n    return { change: \"appended\", stat };\n  }\n  return { change: \"reindex\", stat };\n}\n","import { statSync } from \"fs\";\nimport { canonicalPath } from \"../canonical-path\";\nimport { readGitBranch } from \"../git\";\nimport { getLogger } from \"../logger\";\nimport { nameStoreSlotsFor } from \"../name-stores\";\nimport { getProjectsDir } from \"../profiles\";\nimport { CodexCliProvider, parseCodexConversation } from \"../providers/codex-cli\";\nimport { CopilotProvider, parseCopilotConversation } from \"../providers/copilot\";\nimport { CursorProvider, parseCursorConversation } from \"../providers/cursor\";\nimport { parseMetaWithProvider } from \"../providers/parse\";\nimport {\n  CLAUDE_CODE_PROVIDER,\n  CODEX_CLI_PROVIDER,\n  COPILOT_PROVIDER,\n  CURSOR_PROVIDER,\n  canonicalizeProviderList,\n  type ScannerProvider,\n} from \"../providers/provider\";\nimport { appendSearchDelta, emptySearchDocument, extractSearchDelta } from \"../search-document\";\nimport { resolveTier } from \"../tiers\";\nimport type {\n  ConversationMeta,\n  ConversationPage,\n  GetConversationPageOptions,\n  Profile,\n  ScanOptions,\n  SearchHighlight,\n} from \"../types\";\nimport { classify, type FileChange, fingerprint } from \"./cursor\";\nimport { type DB, openDatabase } from \"./db\";\nimport { discoverJsonlFilesGated, FULL_RECONCILE_EVERY_N_SCANS } from \"./dir-watermark\";\nimport { type TailReadResult, tailReduce } from \"./jsonl-tail-reader\";\nimport { finalizeMeta, initialReducerState, type ReducerState } from \"./metadata-reducer\";\nimport { buildCheckpoints, CHECKPOINT_INTERVAL, readPage } from \"./paged-reader\";\nimport { CheckpointsRepo } from \"./repositories/checkpoints.repo\";\nimport { ConversationFilesRepo } from \"./repositories/conversation-files.repo\";\nimport { ConversationsRepo } from \"./repositories/conversations.repo\";\nimport { FtsRepo, type FtsSearchFilters } from \"./repositories/fts.repo\";\nimport { ScannedDirsRepo } from \"./repositories/scanned-dirs.repo\";\nimport { buildSidecar, writeSidecar } from \"./sidecar\";\n\nconst BATCH_SIZE = 12;\n\nexport interface EngineSearchHit {\n  meta: ConversationMeta;\n  // Null when only metadata matched — the caller falls back to generateMatches.\n  body: { snippet: string; highlights: SearchHighlight[] } | null;\n}\n\n// Persistent indexing engine. Owns the SQLite connection and the discover ->\n// classify -> tail-read -> upsert pipeline. Query helpers return ConversationMeta\n// straight from the DB so the scanner facade can run the existing\n// filter/sort/view pipeline over them, guaranteeing parity with the in-memory\n// path.\n//\n// Indexing is incremental: an appended file resumes the persisted reducer fold\n// from its byte cursor and reads only the new bytes (cursor.ts + the tail\n// reader); a truncated/replaced file reindexes from offset 0.\nexport class PersistentEngine {\n  readonly db: DB;\n  readonly files: ConversationFilesRepo;\n  readonly conversations: ConversationsRepo;\n  readonly fts: FtsRepo;\n  readonly checkpoints: CheckpointsRepo;\n  readonly scannedDirs: ScannedDirsRepo;\n  // When true, write a portable <file>.idx.json sidecar next to each indexed\n  // JSONL. Off by default.\n  private readonly sidecar: boolean;\n  // Counts indexAll() passes so the dir-mtime gate's full-reconcile backstop\n  // (FULL_RECONCILE_EVERY_N_SCANS) can fire periodically. In-memory only: a\n  // restart just means the first few post-restart scans don't force an early\n  // backstop pass, which is harmless (watermarks themselves persist in the DB).\n  private scanCount = 0;\n  // In-flight checkpoint build/extension per file, so concurrent getPage\n  // callers share one stream instead of each walking the file.\n  private readonly checkpointBuilds = new Map<string, Promise<void>>();\n\n  constructor(dbPath: string, options: { sidecar?: boolean } = {}) {\n    this.db = openDatabase(dbPath);\n    this.files = new ConversationFilesRepo(this.db);\n    this.conversations = new ConversationsRepo(this.db);\n    this.fts = new FtsRepo(this.db);\n    this.checkpoints = new CheckpointsRepo(this.db);\n    this.scannedDirs = new ScannedDirsRepo(this.db);\n    this.sidecar = options.sidecar ?? false;\n  }\n\n  close(): void {\n    this.db.close();\n  }\n\n  // Discover all JSONL files under the active profiles, (re)parse files that\n  // changed since the last index, and upsert their metadata. Returns the number\n  // of files seen on disk this pass (the scan \"scanned\" count).\n  async indexAll(activeProfiles: Profile[], options: ScanOptions): Promise<{ scanned: number }> {\n    const log = getLogger();\n    const tier = resolveTier(options.tier ?? \"standard\", options.tiers);\n\n    const enabled = canonicalizeProviderList(options.providers);\n\n    // Each discovered file carries the provider that should parse it. claude-code\n    // files fold through the byte-offset-resumable tail reader; Codex files\n    // (opt-in, only under explicit codexRoots) reparse from offset 0.\n    const discovered: { filePath: string; account: string; provider?: ScannerProvider }[] = [];\n\n    if (enabled.includes(CLAUDE_CODE_PROVIDER)) {\n      const configDirs = activeProfiles.map((p) => ({\n        projectsDir: getProjectsDir(p),\n        account: p.id,\n      }));\n      // Escape hatch: an explicit fullRescan bypasses the dir-mtime gate\n      // entirely — an explicit refresh is precisely the \"don't trust the\n      // gate, check for real\" signal. Otherwise every FULL_RECONCILE_EVERY_N_SCANS\n      // passes also bypasses it, self-healing any add/remove a filesystem that\n      // doesn't honor directory mtimes might have hidden from the gate.\n      this.scanCount++;\n      const forceFullGlob =\n        options.fullRescan === true || this.scanCount % FULL_RECONCILE_EVERY_N_SCANS === 0;\n      const gated = await discoverJsonlFilesGated(configDirs, this.files, this.scannedDirs, {\n        fullRescan: forceFullGlob,\n      });\n      for (const f of gated) discovered.push(f);\n    }\n\n    const codex = new CodexCliProvider();\n    if (enabled.includes(CODEX_CLI_PROVIDER) && (options.codexRoots?.length ?? 0) > 0) {\n      for (const f of await codex.discover(options.codexRoots as string[])) {\n        discovered.push({ ...f, provider: codex });\n      }\n    }\n    const cursor = new CursorProvider();\n    if (enabled.includes(CURSOR_PROVIDER) && (options.cursorRoots?.length ?? 0) > 0) {\n      for (const f of await cursor.discover(options.cursorRoots as string[])) {\n        discovered.push({ ...f, provider: cursor });\n      }\n    }\n    const copilot = new CopilotProvider();\n    if (enabled.includes(COPILOT_PROVIDER) && (options.copilotRoots?.length ?? 0) > 0) {\n      for (const f of await copilot.discover(options.copilotRoots as string[])) {\n        discovered.push({ ...f, provider: copilot });\n      }\n    }\n    let scanned = 0;\n\n    const gitBranchMemo = new Map<string, string | null>();\n    const resolveGitBranch = (projectPath: string): string | null => {\n      if (gitBranchMemo.has(projectPath)) {\n        return gitBranchMemo.get(projectPath) ?? null;\n      }\n      const branch = readGitBranch(projectPath);\n      gitBranchMemo.set(projectPath, branch);\n      return branch;\n    };\n\n    for (let i = 0; i < discovered.length; i += BATCH_SIZE) {\n      const batch = discovered.slice(i, i + BATCH_SIZE);\n      const results = await Promise.all(\n        batch.map(async ({ filePath, account, provider }) => {\n          const { meta } = await this.indexFile(\n            filePath,\n            account,\n            tier.name,\n            options.tiers,\n            resolveGitBranch,\n            false,\n            provider,\n          );\n          return meta;\n        }),\n      );\n      const kept = results.filter((m): m is ConversationMeta => m != null);\n      if (kept.length > 0) options.onBatch?.(kept);\n      scanned += batch.length;\n      options.onProgress?.(scanned, discovered.length);\n    }\n\n    // Reconcile deletions: any previously-active file that wasn't discovered\n    // this pass is gone from disk — mark it deleted. This is the correctness\n    // backstop for unlink events the watcher may have missed (spec §9.5).\n    //\n    // Scope the reconcile to the accounts THIS scan actually covered. The index\n    // is shared across profiles/accounts (one index.db), so a scan of account A\n    // must not mark account B's files deleted just because B's files weren't in\n    // A's discovered set — B is simply out of this scan's scope, not gone from\n    // disk. Covered = the claude-code profile ids scanned (when that provider is\n    // enabled) plus \"codex\" (when codex roots were scanned).\n    const coveredAccounts = new Set<string>();\n    if (enabled.includes(CLAUDE_CODE_PROVIDER)) {\n      for (const p of activeProfiles) coveredAccounts.add(p.id);\n    }\n    if (enabled.includes(CODEX_CLI_PROVIDER) && (options.codexRoots?.length ?? 0) > 0) {\n      coveredAccounts.add(\"codex\");\n    }\n    if (enabled.includes(CURSOR_PROVIDER) && (options.cursorRoots?.length ?? 0) > 0) {\n      coveredAccounts.add(\"cursor\");\n    }\n    if (enabled.includes(COPILOT_PROVIDER) && (options.copilotRoots?.length ?? 0) > 0) {\n      coveredAccounts.add(\"copilot\");\n    }\n    // Canonical form on both sides: the stored paths are canonical, and a\n    // discovery source that emits native separators would otherwise leave every\n    // one of its files out of the seen-set and get them marked deleted.\n    const seen = new Set(discovered.map((d) => canonicalPath(d.filePath)));\n    for (const path of this.files.activePathsByAccounts([...coveredAccounts])) {\n      if (!seen.has(path)) this.markDeleted(path);\n    }\n\n    log.info({ scanned, indexed: this.conversations.count() }, \"persistent: indexAll complete\");\n    return { scanned };\n  }\n\n  // (Re)index a single file. Classifies the change vs. the persisted cursor:\n  // unchanged → return the stored summary; appended → resume the fold and read\n  // only new bytes; reindex/force → fold from offset 0. Writes the summary +\n  // cursor + reducer state in one transaction so a crash never leaves a\n  // half-written row or an over-advanced cursor. Returns the classification\n  // alongside the meta so callers (refreshFile) can keep, extend, or evict\n  // their own per-file caches without re-stat'ing the file (racy) themselves.\n  async indexFile(\n    rawFilePath: string,\n    account: string,\n    tierName: string,\n    customTiers: ScanOptions[\"tiers\"],\n    resolveGitBranch: (projectPath: string) => string | null,\n    force = false,\n    provider?: ScannerProvider,\n  ): Promise<{ meta: ConversationMeta | null; change: FileChange }> {\n    const log = getLogger();\n    const tier = resolveTier(tierName, customTiers);\n\n    // Canonicalize once, here: everything downstream (the cursor row, the\n    // conversation's source_path/id, the checkpoint + FTS keys, the sidecar, and\n    // the meta handed back to the caller) then keys off the one canonical\n    // spelling, whichever separator style the caller passed in.\n    const filePath = canonicalPath(rawFilePath);\n    const existing = this.files.getByPath(filePath);\n    const classified = classify(filePath, existing);\n    const stat = classified.stat;\n    let change = classified.change;\n\n    if (change === \"vanished\" || !stat) {\n      // File gone between discovery and indexing — treat as deleted.\n      this.markDeleted(filePath);\n      return { meta: null, change: \"vanished\" };\n    }\n    // Unchanged: serve the persisted summary without re-reading (unless a\n    // caller forces a re-parse, e.g. refreshFile after a same-size edit).\n    if (change === \"unchanged\" && !force) {\n      const meta = this.conversations.getBySourcePath(filePath);\n      // Codex/Cursor/Copilot names also come from stores beside the transcript,\n      // which can change while the transcript does not. Re-reading them is cheap\n      // (cached, or one small file) next to a reparse, which only runs on a diff.\n      // ponytail: one read per provider file per pass; stat-gate it if a root\n      // ever holds thousands of Cursor chats.\n      const sideStoresMoved =\n        provider &&\n        provider.name !== CLAUDE_CODE_PROVIDER &&\n        meta &&\n        JSON.stringify(nameStoreSlotsFor(provider.name, filePath, meta.sessionId)) !==\n          existing?.reducer_state;\n      if (!sideStoresMoved) return { meta, change };\n      change = \"reindex\";\n    }\n\n    // ponytail: Codex reparses from offset 0 on every change rather than\n    // resuming a byte cursor like claude-code. Codex rollout sessions are small,\n    // so a full reparse is cheap; the resumable-fold path stays claude-code-only.\n    // Upgrade path if Codex files ever get large: give CodexAccumulator the same\n    // serialized-reducer-state treatment and route it through tailReduce.\n    if (provider && provider.name !== CLAUDE_CODE_PROVIDER) {\n      const meta = await this.indexFileWithProvider(\n        provider,\n        filePath,\n        account,\n        tier,\n        stat,\n        resolveGitBranch,\n      );\n      return { meta, change };\n    }\n\n    // appended → resume the persisted fold from the cursor; reindex/force →\n    // start fresh from offset 0. This is the incremental win: an append only\n    // reads the newly-written bytes.\n    const resume = change === \"appended\" && !force && existing?.reducer_state;\n    const state: ReducerState = resume\n      ? (JSON.parse(existing.reducer_state as string) as ReducerState)\n      : initialReducerState();\n    const startOffset = resume ? existing.last_indexed_offset : 0;\n    const startLine = resume ? existing.last_indexed_line : 0;\n\n    // Search extraction is a sibling fold over the same lines as the metadata\n    // reducer — no second read of the file, and an append only ever extracts\n    // from the newly-read region.\n    let searchDelta = emptySearchDocument();\n    const collectSearch = (entry: Record<string, unknown>) => {\n      searchDelta = appendSearchDelta(searchDelta, extractSearchDelta(entry));\n    };\n\n    let result: TailReadResult;\n    try {\n      result = await tailReduce(filePath, startOffset, startLine, state, tier, collectSearch);\n    } catch (err) {\n      log.warn({ filePath, err }, \"persistent: tail read failed\");\n      return { meta: null, change };\n    }\n\n    const meta = finalizeMeta(state, filePath, account, tier);\n    if (!meta) {\n      this.markDeleted(filePath);\n      return { meta: null, change };\n    }\n    meta.gitBranch = resolveGitBranch(meta.projectPath);\n\n    const fp = stat.size > 0 ? fingerprint(filePath, stat.size) : null;\n    const fileId = this.files.ensure(filePath, account);\n\n    // A resumed fold extends the buckets already in FTS; a from-0 fold replaces\n    // them. Tail-append means a full bucket still admits the new material and\n    // drops its oldest instead of freezing.\n    const base = resume\n      ? (this.fts.readDocument(fileId) ?? emptySearchDocument())\n      : emptySearchDocument();\n    const searchDoc = appendSearchDelta(base, searchDelta);\n\n    const upsert = this.db.transaction(() => {\n      this.conversations.upsert(fileId, meta, state.pageMessageCount);\n      // Re-tokenizing ~128 KB is the expensive part of an append; skip it when\n      // the row (body + metadata) would come out identical.\n      if (!this.fts.isCurrent(fileId, meta, searchDoc)) {\n        this.fts.upsert(fileId, meta, searchDoc);\n      }\n      // Checkpoints cover the file's immutable prefix, so an append (a resumed\n      // fold) leaves them valid — drop them only when the fold restarted from\n      // offset 0 (truncate/replace/new). They're rebuilt lazily on page access.\n      if (!resume) this.checkpoints.remove(filePath);\n      this.files.updateCursor(fileId, {\n        sizeBytes: stat.size,\n        mtimeMs: stat.mtimeMs,\n        // Advance only to the last fully-parsed line; a trailing partial line\n        // is left for the next pass.\n        offset: result.newOffset,\n        line: result.newLine,\n        reducerState: JSON.stringify(state),\n        fingerprint: fp,\n        status: \"active\",\n      });\n    });\n    upsert();\n\n    if (this.sidecar) {\n      writeSidecar(\n        filePath,\n        buildSidecar(\n          meta,\n          {\n            sizeBytes: stat.size,\n            mtimeMs: stat.mtimeMs,\n            offset: result.newOffset,\n            line: result.newLine,\n          },\n          new Date().toISOString(),\n        ),\n      );\n    }\n\n    log.debug(\n      { filePath, change, bytesRead: result.newOffset - startOffset, msgs: meta.messageCount },\n      \"persistent: indexed file\",\n    );\n    return { meta, change };\n  }\n\n  // Index a non-Threadbase provider file: full reparse from offset 0 through the\n  // provider's reducer/finalize, then the same upsert + FTS write + cursor bump\n  // the Threadbase path uses. The cursor records size/mtime (and offset = size)\n  // so the next pass classifies an unchanged file as \"unchanged\" and skips it;\n  // any change reparses from 0 again. reducer_state holds the side-store name slots, not a fold.\n  private async indexFileWithProvider(\n    provider: ScannerProvider,\n    filePath: string,\n    account: string,\n    tier: ReturnType<typeof resolveTier>,\n    stat: { size: number; mtimeMs: number },\n    resolveGitBranch: (projectPath: string) => string | null,\n  ): Promise<ConversationMeta | null> {\n    const log = getLogger();\n\n    // Codex always reparses from byte 0, so the search document is rebuilt whole\n    // from the same pass rather than tail-extended.\n    let searchDoc = emptySearchDocument();\n    const meta = await parseMetaWithProvider(provider, filePath, account, tier, (entry) => {\n      searchDoc = appendSearchDelta(searchDoc, extractSearchDelta(entry));\n    });\n    if (!meta) {\n      this.markDeleted(filePath);\n      return null;\n    }\n    // The provider may already know its branch (Codex reads it from\n    // session_meta). Only walk the filesystem when it doesn't.\n    if (meta.gitBranch === null && meta.projectPath) {\n      meta.gitBranch = resolveGitBranch(meta.projectPath);\n    }\n\n    const sideSlots = nameStoreSlotsFor(provider.name, filePath, meta.sessionId);\n    const fp = stat.size > 0 ? fingerprint(filePath, stat.size) : null;\n    const fileId = this.files.ensure(filePath, account);\n    const upsert = this.db.transaction(() => {\n      this.conversations.upsert(fileId, meta, meta.messageCount);\n      if (!this.fts.isCurrent(fileId, meta, searchDoc)) {\n        this.fts.upsert(fileId, meta, searchDoc);\n      }\n      this.checkpoints.remove(filePath);\n      this.files.updateCursor(fileId, {\n        sizeBytes: stat.size,\n        mtimeMs: stat.mtimeMs,\n        // offset = size marks the file fully consumed (non-zero so the next pass\n        // can classify it \"unchanged\"); no resumable reducer state is kept.\n        offset: stat.size,\n        line: 0,\n        // Not resumable state: the side-store name slots this parse used, so the\n        // next pass can tell when they moved (see indexFile).\n        reducerState: JSON.stringify(sideSlots),\n        fingerprint: fp,\n        status: \"active\",\n      });\n    });\n    upsert();\n\n    log.debug(\n      { filePath, provider: provider.name, msgs: meta.messageCount },\n      \"persistent: indexed provider file\",\n    );\n    return meta;\n  }\n\n  private markDeleted(filePath: string): void {\n    const existing = this.files.getByPath(filePath);\n    if (!existing) return;\n    const tx = this.db.transaction(() => {\n      this.conversations.deleteByFileId(existing.id);\n      this.fts.remove(existing.id);\n      this.checkpoints.remove(filePath);\n      this.files.setStatus(existing.id, \"deleted\");\n    });\n    tx();\n  }\n\n  // ── Query helpers (read straight from SQLite) ───────────────────────────\n\n  allActive(): ConversationMeta[] {\n    return this.conversations.allActive();\n  }\n\n  getByIdOrSession(id: string): ConversationMeta | null {\n    return this.conversations.getByIdOrSession(id);\n  }\n\n  getAllBySessionId(sessionId: string): ConversationMeta[] {\n    return this.conversations.getAllBySessionId(sessionId);\n  }\n\n  // Ranked hits matching the FTS query, best first, each already resolved to its\n  // active conversation row and carrying the body excerpt when the match was in\n  // the conversation body.\n  //\n  // Filters are passed down into SQL rather than applied to the result: with a\n  // wide corpus, filtering an already-LIMITed list drops conversations that\n  // would have matched.\n  searchHits(query: string, limit: number, filters: FtsSearchFilters = {}): EngineSearchHit[] {\n    const hits: EngineSearchHit[] = [];\n    for (const hit of this.fts.search(query, limit, filters)) {\n      const meta = this.conversations.getBySourcePath(hit.sourcePath);\n      if (meta) hits.push({ meta, body: hit.body });\n    }\n    return hits;\n  }\n\n  // Empty-query listing, mirroring the in-memory indexer's behavior.\n  recentMetas(limit: number): ConversationMeta[] {\n    return this.conversations.recent(limit);\n  }\n\n  getProjects(): string[] {\n    return this.conversations.distinctProjects();\n  }\n\n  // Bounded conversation page: read only the requested window from the file,\n  // seeking from the nearest checkpoint. Returns null if the id can't be\n  // resolved to an indexed conversation. For conversations large enough to span\n  // a checkpoint interval, checkpoints are built lazily on first access and\n  // reused thereafter. Smaller conversations read from the start (cheap).\n  async getPage(id: string, options: GetConversationPageOptions): Promise<ConversationPage | null> {\n    const meta = this.conversations.getByIdOrSession(id);\n    if (!meta) return null;\n    const filePath = meta.filePath;\n\n    // Codex files don't fold through the conversation-reducer the bounded reader\n    // (readPage) uses — that reducer only understands the claude-code line shape,\n    // so readPage would return an empty window for a Codex file. Codex rollout\n    // sessions are small (already reparsed from offset 0 on every change), so\n    // parse the whole conversation and slice the window — identical math to the\n    // claude-code path and to the legacy getConversationPage slice.\n    if (\n      meta.provider === CODEX_CLI_PROVIDER ||\n      meta.provider === CURSOR_PROVIDER ||\n      meta.provider === COPILOT_PROVIDER\n    ) {\n      const conversation =\n        meta.provider === CODEX_CLI_PROVIDER\n          ? await parseCodexConversation(filePath, meta.account)\n          : meta.provider === CURSOR_PROVIDER\n            ? await parseCursorConversation(filePath, meta.account)\n            : await parseCopilotConversation(filePath, meta.account);\n      if (!conversation) return null;\n      const { messages } = conversation;\n      const total = messages.length;\n      const beforeIndex = options.beforeIndex ?? total;\n      const fromIndex = Math.max(0, beforeIndex - options.limit);\n      return { messages: messages.slice(fromIndex, beforeIndex), total, fromIndex };\n    }\n\n    // Bounded paging uses the parseConversation message total, which differs\n    // from meta.messageCount (the metadata count excludes tool_use-only and\n    // thinking-only lines).\n    const total = this.conversations.pageMessageCount(filePath);\n\n    await this.ensureCheckpoints(filePath, total);\n\n    const beforeIndex = options.beforeIndex ?? total;\n    const fromIndex = Math.max(0, beforeIndex - options.limit);\n    let floor = this.checkpoints.floor(filePath, fromIndex);\n    // A checkpoint past the current EOF is stale — the file was truncated or\n    // replaced on disk and no refresh has landed yet. Seeking there would read\n    // nothing (or garbage), so fall back to byte 0: correct, just slower, and\n    // the next refresh drops the stale chain. A failed stat is left to\n    // readPage, which surfaces the read error exactly as before.\n    if (floor) {\n      try {\n        if (floor.byteOffset > statSync(filePath).size) floor = null;\n      } catch {\n        // fall through to readPage's own error handling\n      }\n    }\n    return readPage(filePath, total, options, floor);\n  }\n\n  // Build or extend the checkpoint chain so it covers `total` messages. Cold\n  // file → full build; a file that grew → extend from the last persisted\n  // checkpoint (reads only past its offset, never the prefix). Single-flighted\n  // per path: concurrent getPage callers await the same build instead of\n  // streaming the file in parallel.\n  private ensureCheckpoints(filePath: string, total: number): Promise<void> {\n    if (total <= CHECKPOINT_INTERVAL) return Promise.resolve();\n    const inFlight = this.checkpointBuilds.get(filePath);\n    if (inFlight) return inFlight;\n\n    const build = (async () => {\n      const last = this.checkpoints.last(filePath);\n      // Chain already covers every checkpointable index — nothing to do.\n      if (last && total < last.messageIndex + CHECKPOINT_INTERVAL) return;\n      const fresh = await buildCheckpoints(filePath, CHECKPOINT_INTERVAL, last);\n      if (fresh.length === 0) return;\n      if (last) this.checkpoints.append(filePath, fresh);\n      else this.checkpoints.replaceAll(filePath, fresh);\n    })().finally(() => {\n      if (this.checkpointBuilds.get(filePath) === build) this.checkpointBuilds.delete(filePath);\n    });\n    this.checkpointBuilds.set(filePath, build);\n    return build;\n  }\n}\n","import { createReadStream } from \"fs\";\nimport { createInterface } from \"readline\";\nimport { getLogger } from \"../logger\";\nimport type { ContentTier, ConversationMeta } from \"../types\";\nimport type { ScannerProvider } from \"./provider\";\n\n// Stream a file through a provider's reducer triplet. This is the same fold a\n// future offset-resumed incremental parse would run (createEmptyAccumulator →\n// reduceEntry per line → finalize), just starting from offset 0. Bad JSON lines\n// and unknown entry shapes are skipped, never fatal.\n// `onEntry` lets a caller fold a second, independent accumulator (the search\n// document) over the same lines without re-reading the file. It never affects\n// the provider's own reduce.\nexport async function parseMetaWithProvider(\n  provider: ScannerProvider,\n  filePath: string,\n  account: string,\n  tier: ContentTier,\n  onEntry?: (entry: Record<string, unknown>) => void,\n): Promise<ConversationMeta | null> {\n  const log = getLogger();\n  const acc = provider.createEmptyAccumulator();\n  const rl = createInterface({\n    input: createReadStream(filePath),\n    crlfDelay: Infinity,\n  });\n\n  try {\n    for await (const line of rl) {\n      if (!line.trim()) continue;\n      let entry: Record<string, unknown>;\n      try {\n        entry = JSON.parse(line);\n      } catch {\n        continue;\n      }\n      try {\n        provider.reduceEntry(acc, entry, tier);\n        onEntry?.(entry);\n      } catch (err) {\n        // A provider reducer should never throw, but one bad line must not kill\n        // the whole file.\n        log.warn({ filePath, provider: provider.name, err }, \"provider reduce threw; line skipped\");\n      }\n    }\n  } catch (err) {\n    log.warn({ filePath, provider: provider.name, err }, \"provider parse: read failed\");\n    return null;\n  }\n\n  return provider.finalize(acc, filePath, account, tier);\n}\n","import { extractThinking } from \"./parser\";\nimport { looksLikeCopilotEvent, parseCopilotJsonlLine } from \"./providers/copilot\";\nimport { looksLikeCursorChatEntry, unwrapCursorUserText } from \"./providers/cursor\";\nimport { cleanSystemTags } from \"./tags\";\n\n// The derived search corpus: one document per conversation, split into three\n// independently budgeted buckets so a later user message always lands in `text`\n// rather than after previously-indexed tool output. Assembled into FTS body\n// columns (persistent) or one combined string (FlexSearch).\n//\n// Budgets are JavaScript string lengths (UTF-16 code units), NOT bytes. ASCII\n// makes the two coincide; Hebrew/CJK/emoji cost 2-3 UTF-8 bytes per unit, so a\n// 64K `textMax` can hold ~128-192 KB on disk. Size index-growth estimates from\n// that, and don't describe these as byte caps.\nexport const SEARCH_BUDGET = {\n  textMax: 64 * 1024,\n  thinkingMax: 32 * 1024,\n  toolsMax: 32 * 1024,\n\n  toolPayloadMax: 4 * 1024,\n} as const;\n\nexport interface SearchDocument {\n  text: string;\n  thinking: string;\n  tools: string;\n}\n\n// Same shape as SearchDocument, but holding only the material extracted from\n// newly-read lines — what gets tail-appended onto the durable buckets.\nexport type SearchDocumentDelta = SearchDocument;\n\nconst SEP = \"\\n\\n\";\n\nexport function emptySearchDocument(): SearchDocument {\n  return { text: \"\", thinking: \"\", tools: \"\" };\n}\n\nexport function isEmptyDelta(delta: SearchDocumentDelta): boolean {\n  return !delta.text && !delta.thinking && !delta.tools;\n}\n\nexport function searchDocumentsEqual(a: SearchDocument, b: SearchDocument): boolean {\n  return a.text === b.text && a.thinking === b.thinking && a.tools === b.tools;\n}\n\n// Tail-biased append: each bucket is a sliding window over the NEWEST material.\n// Head-biased fill (stop once full) would freeze a conversation the moment it\n// crossed the budget — the same bug as the old 5 KB contentSnippet, just larger,\n// and it would miss exactly the actively-growing conversations search exists for.\nexport function appendSearchDelta(doc: SearchDocument, delta: SearchDocumentDelta): SearchDocument {\n  return {\n    text: tailAppend(doc.text, delta.text, SEARCH_BUDGET.textMax),\n    thinking: tailAppend(doc.thinking, delta.thinking, SEARCH_BUDGET.thinkingMax),\n    tools: tailAppend(doc.tools, delta.tools, SEARCH_BUDGET.toolsMax),\n  };\n}\n\n// ponytail: slice(-max) can cut mid-token; the truncated head token simply\n// doesn't match. Grapheme/token-aware windowing isn't worth it for v1.\nfunction tailAppend(current: string, incoming: string, max: number): string {\n  if (!incoming) return current;\n  const joined = current ? current + SEP + incoming : incoming;\n  return joined.length <= max ? joined : joined.slice(-max);\n}\n\n// Flatten to one string for the in-memory FlexSearch index, which has a single\n// body field. The persistent path keeps the buckets as separate FTS columns\n// instead (concatenating there would double-weight body hits).\nexport function combineSearchContent(doc: SearchDocument): string {\n  return [doc.text, doc.thinking, doc.tools].filter(Boolean).join(SEP);\n}\n\n// Truncate one tool payload before it joins the tools bucket, so a single huge\n// Read/Bash/compiler dump can't consume the whole window.\nexport function capToolPayload(value: unknown): string {\n  const raw = stringifyPayload(value);\n  if (!raw) return \"\";\n  return raw.length > SEARCH_BUDGET.toolPayloadMax\n    ? raw.slice(0, SEARCH_BUDGET.toolPayloadMax)\n    : raw;\n}\n\nfunction stringifyPayload(value: unknown): string {\n  if (value === null || value === undefined) return \"\";\n  if (typeof value === \"string\") return value;\n  try {\n    return JSON.stringify(value) ?? \"\";\n  } catch {\n    return \"\";\n  }\n}\n\n// Extract the search-document delta from one already-parsed JSONL entry.\n// Handles both Claude/Threadbase and Codex CLI line shapes so every indexing\n// path — persistent, in-memory, both providers — shares one corpus definition.\nexport function extractSearchDelta(entry: Record<string, unknown>): SearchDocumentDelta {\n  if (entry.type === \"response_item\" || entry.type === \"session_meta\") {\n    return extractCodexDelta(entry);\n  }\n  if (looksLikeCursorChatEntry(entry)) {\n    return extractCursorDelta(entry);\n  }\n  if (looksLikeCopilotEvent(entry)) {\n    return extractCopilotDelta(entry);\n  }\n  return extractClaudeDelta(entry);\n}\n\n// Copilot's message text and tool-call arguments. Reuses the provider's own\n// line mapping, so search indexes exactly what the conversation shows.\nfunction extractCopilotDelta(entry: Record<string, unknown>): SearchDocumentDelta {\n  const message = parseCopilotJsonlLine(JSON.stringify(entry));\n  if (!message) return emptySearchDocument();\n  const tools = (message.metadata?.toolUseBlocks ?? [])\n    .map((block) => capToolPayload(block.input))\n    .filter(Boolean)\n    .join(SEP);\n  return { text: message.text, thinking: \"\", tools };\n}\n\nfunction extractCursorDelta(entry: Record<string, unknown>): SearchDocumentDelta {\n  const msg = entry.message as Record<string, unknown> | undefined;\n  const content = msg?.content ?? entry.content;\n  return {\n    text: extractCursorSearchText(content),\n    thinking: \"\",\n    tools: extractClaudeToolContent(content),\n  };\n}\n\nfunction extractCursorSearchText(content: unknown): string {\n  if (typeof content === \"string\") return unwrapCursorUserText(content);\n  if (!Array.isArray(content)) return \"\";\n  const parts: string[] = [];\n  for (const item of content) {\n    if (typeof item === \"string\") {\n      const cleaned = unwrapCursorUserText(item);\n      if (cleaned) parts.push(cleaned);\n    } else if (\n      (item?.type === \"text\" || item?.type === undefined) &&\n      typeof item?.text === \"string\"\n    ) {\n      const cleaned = unwrapCursorUserText(item.text);\n      if (cleaned) parts.push(cleaned);\n    }\n  }\n  return parts.join(SEP);\n}\n\nfunction extractClaudeDelta(entry: Record<string, unknown>): SearchDocumentDelta {\n  const type = entry.type;\n  if (type !== \"user\" && type !== \"assistant\") return emptySearchDocument();\n  if (entry.isMeta || entry.isCompactSummary === true) return emptySearchDocument();\n\n  const msg = entry.message as Record<string, unknown> | undefined;\n  const content = msg?.content;\n\n  const tools = [\n    extractClaudeToolContent(content),\n    // Claude stores the rich/structured tool result at the JSONL entry's top\n    // level, not inside message.content — indexing only message.content would\n    // miss most real tool output (file reads, command stdout).\n    capToolPayload(entry.toolUseResult),\n  ]\n    .filter(Boolean)\n    .join(SEP);\n\n  return {\n    text: extractClaudeText(content),\n    thinking: type === \"assistant\" ? extractThinking(content).content : \"\",\n    tools,\n  };\n}\n\n// Human/assistant prose only. Deliberately NOT parser.extractTextContent():\n// that helper folds `tool_result` strings into the same string as user text,\n// which would let a large tool result eat the high-priority text window.\n//\n// It DOES keep extractTextContent's cleanSystemTags step, and that is\n// mandatory: without it every user turn's <system-reminder> (CLAUDE.md dump,\n// skill listing, memory block) lands in `text`, exhausting the window in a\n// couple of turns and making every conversation on the machine a hit for tokens\n// that only ever appear in those wrappers.\nfunction extractClaudeText(content: unknown): string {\n  if (typeof content === \"string\") return cleanSystemTags(content);\n  if (!Array.isArray(content)) return \"\";\n  const parts: string[] = [];\n  for (const item of content) {\n    if (typeof item === \"string\") {\n      const cleaned = cleanSystemTags(item);\n      if (cleaned) parts.push(cleaned);\n    } else if (item?.type === \"text\" && typeof item.text === \"string\") {\n      const cleaned = cleanSystemTags(item.text);\n      if (cleaned) parts.push(cleaned);\n    }\n  }\n  return parts.join(SEP);\n}\n\n// Tool I/O is the payload, so it is NOT tag-cleaned — stripping it as if it\n// were a prompt wrapper would delete the content being indexed. The per-payload\n// cap is the safety valve instead.\nfunction extractClaudeToolContent(content: unknown): string {\n  if (!Array.isArray(content)) return \"\";\n  const parts: string[] = [];\n  for (const item of content) {\n    if (item?.type === \"tool_use\") {\n      const capped = capToolPayload(item.input);\n      if (capped) parts.push(capped);\n    } else if (item?.type === \"tool_result\") {\n      const capped = capToolPayload(item.content);\n      if (capped) parts.push(capped);\n    }\n  }\n  return parts.join(SEP);\n}\n\nfunction extractCodexDelta(entry: Record<string, unknown>): SearchDocumentDelta {\n  const payload = entry.payload as Record<string, unknown> | undefined;\n  if (!payload || typeof payload !== \"object\") return emptySearchDocument();\n\n  const ptype = payload.type as string;\n\n  if (ptype === \"function_call\" || ptype === \"custom_tool_call\") {\n    return { text: \"\", thinking: \"\", tools: capToolPayload(payload.arguments) };\n  }\n  if (ptype === \"function_call_output\" || ptype === \"custom_tool_call_output\") {\n    return { text: \"\", thinking: \"\", tools: capToolPayload(payload.output) };\n  }\n  if (ptype === \"reasoning\") {\n    return { text: \"\", thinking: extractCodexReasoning(payload), tools: \"\" };\n  }\n  if (ptype === \"message\") {\n    const role = payload.role;\n    if (role !== \"user\" && role !== \"assistant\") return emptySearchDocument();\n    return { text: extractCodexText(payload.content), thinking: \"\", tools: \"\" };\n  }\n  return emptySearchDocument();\n}\n\n// Codex reasoning carries its text under `summary` (and sometimes `content`),\n// each a list of {type, text} blocks. Never a signature — there isn't one here,\n// and on the Claude side the thinking signature is a base64 crypto blob that is\n// pure noise in a search index.\nfunction extractCodexReasoning(payload: Record<string, unknown>): string {\n  const parts: string[] = [];\n  for (const key of [\"summary\", \"content\"] as const) {\n    const blocks = payload[key];\n    if (!Array.isArray(blocks)) continue;\n    for (const block of blocks) {\n      if (typeof block === \"string\") parts.push(block);\n      else if (typeof block?.text === \"string\") parts.push(block.text);\n    }\n  }\n  return parts.filter(Boolean).join(SEP);\n}\n\nfunction extractCodexText(content: unknown): string {\n  if (typeof content === \"string\") return cleanSystemTags(content);\n  if (!Array.isArray(content)) return \"\";\n  const parts: string[] = [];\n  for (const item of content) {\n    if (typeof item === \"string\") {\n      const cleaned = cleanSystemTags(item);\n      if (cleaned) parts.push(cleaned);\n      continue;\n    }\n    const t = item?.type;\n    if (\n      (t === \"input_text\" || t === \"output_text\" || t === \"text\") &&\n      typeof item.text === \"string\"\n    ) {\n      const cleaned = cleanSystemTags(item.text);\n      if (cleaned) parts.push(cleaned);\n    }\n  }\n  return parts.join(SEP);\n}\n","import type { ContentTier } from \"./types\";\n\nexport const DEFAULT_TIERS: Record<string, ContentTier> = {\n  standard: { name: \"standard\", previewMax: 200, snippetMax: 5_000 },\n  full: { name: \"full\", previewMax: 1_200, snippetMax: 50_000 },\n};\n\nexport function resolveTier(\n  tierName: string,\n  customTiers?: Record<string, ContentTier>,\n): ContentTier {\n  const tier = customTiers?.[tierName] ?? DEFAULT_TIERS[tierName];\n  if (!tier) {\n    throw new Error(\n      `Unknown tier \"${tierName}\". Available: ${Object.keys({ ...DEFAULT_TIERS, ...customTiers }).join(\", \")}`,\n    );\n  }\n  return tier;\n}\n","import Database from \"better-sqlite3\";\nimport { mkdirSync } from \"fs\";\nimport { dirname } from \"path\";\nimport { getLogger } from \"../logger\";\nimport { runMigrations } from \"./migrations\";\n\nexport type DB = Database.Database;\n\n// Open (creating if needed) the SQLite index at dbPath, apply WAL pragmas, and\n// run migrations. \":memory:\" is supported for tests.\nexport function openDatabase(dbPath: string): DB {\n  if (dbPath !== \":memory:\") {\n    mkdirSync(dirname(dbPath), { recursive: true });\n  }\n\n  let db: DB;\n  try {\n    db = new Database(dbPath);\n  } catch (err) {\n    if (isNativeBindingFailure(err)) throw nativeBindingError(err, dbPath);\n    throw err;\n  }\n\n  db.pragma(\"journal_mode = WAL\");\n  db.pragma(\"synchronous = NORMAL\");\n  db.pragma(\"temp_store = MEMORY\");\n  db.pragma(\"foreign_keys = ON\");\n  // Let a reader briefly wait out a concurrent indexing write instead of failing\n  // immediately with SQLITE_BUSY. Operational resilience only — search recall is\n  // unaffected.\n  db.pragma(\"busy_timeout = 5000\");\n\n  runMigrations(db);\n\n  getLogger().debug({ dbPath }, \"db: opened\");\n  return db;\n}\n\n// better-sqlite3 is a native addon, and `require()` of it succeeds even when its\n// binary is absent — the JS entry point loads fine and the addon is only touched\n// when a Database is constructed. So a broken install stays invisible until this\n// exact line, then fails with an opaque list of candidate .node paths that reads\n// like an ABI mismatch and sends people chasing Node versions.\n//\n// It usually isn't an ABI mismatch: the path in the message is normally correct\n// for the running Node, and the file simply was never produced.\nfunction isNativeBindingFailure(err: unknown): boolean {\n  const message = err instanceof Error ? err.message : String(err);\n  return /could not locate the bindings file|NODE_MODULE_VERSION|was compiled against|\\.node['\"\\s]/i.test(\n    message,\n  );\n}\n\nfunction nativeBindingError(err: unknown, dbPath: string): Error {\n  const detail = err instanceof Error ? err.message : String(err);\n  return new Error(\n    [\n      `Could not load better-sqlite3's native binary, so the persistent index at ${dbPath} cannot be opened.`,\n      \"This usually means the binary was never built or downloaded — not that your Node version is wrong.\",\n      \"\",\n      \"Try, in order:\",\n      \"  1. npm rebuild better-sqlite3\",\n      \"  2. rm -rf node_modules && npm install\",\n      \"     (npm 12 blocks package install scripts by default; approve better-sqlite3 if asked)\",\n      \"  3. Run without SQLite entirely: new ConversationScanner({ persistent: false })\",\n      \"     or pass --no-persist on the CLI. Search falls back to the in-memory index.\",\n      \"\",\n      `Original error: ${detail}`,\n    ].join(\"\\n\"),\n  );\n}\n","// Persistent index schema for @threadbase-sh/scanner.\n//\n// Embedded as a string (not a sibling .sql file) so it survives tsup bundling\n// into dist/ without runtime file I/O.\n//\n// One JSONL file == one conversation, so conversation_files.id maps 1:1 to\n// conversations.file_id.\n//\n// NOTE: session_id is intentionally NOT unique. parseMeta() falls back to the\n// file's basename when a line carries no sessionId, and resumed/subagent\n// sessions can repeat a sessionId across files. The stable unique key is the\n// file's absolute_path (and conversations.file_id).\n\n// Bumped whenever DDL below changes; drives migrations.ts via PRAGMA user_version.\n// v4: added the scanned_dirs table (dir-mtime discovery gate). runMigrations()\n// early-returns when the stored user_version >= SCHEMA_VERSION, so a new table\n// in SCHEMA_SQL is only reached on an existing DB if this number is bumped —\n// otherwise every DB already at v3 skips the migration and never gets the table.\n// v5: FTS body split from one `content` column into text/thinking/tools, fed by\n// the tail-biased search document instead of meta.contentSnippet.\n// v6: subagent_id + parent_session_uuid — a subagent transcript's own identity,\n// which sessionId cannot carry (it holds the PARENT's id on a sidechain).\n// v7: reindex only — Codex previews/titles/counts no longer include the\n// AGENTS.md turn Codex prepends as the first `user` message.\n// v8: import-provenance flags on conversations (is_imported_from_*).\n// v9: rename stored provider `cursor-cli` → `cursor`.\n// v10: reindex only — cleanSystemTags() now unwraps <pasted_content> instead of\n// leaving the tag as the title/preview.\n// v11: name_slots (JSON) on conversations + a reindex, so every session name is\n// resolved from the same five slots (see session-name.ts).\nexport const SCHEMA_VERSION = 11;\n\nexport const SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS conversation_files (\n  id INTEGER PRIMARY KEY AUTOINCREMENT,\n\n  absolute_path TEXT NOT NULL UNIQUE,\n  parent_dir TEXT NOT NULL,\n  file_name TEXT NOT NULL,\n\n  account TEXT NOT NULL DEFAULT 'default',\n\n  size_bytes INTEGER NOT NULL DEFAULT 0,\n  mtime_ms INTEGER NOT NULL DEFAULT 0,\n\n  last_indexed_offset INTEGER NOT NULL DEFAULT 0,\n  last_indexed_line INTEGER NOT NULL DEFAULT 0,\n\n  reducer_state TEXT,\n\n  content_fingerprint TEXT,\n\n  status TEXT NOT NULL DEFAULT 'active',\n\n  last_indexed_at TEXT,\n  created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  deleted_at TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_conversation_files_status ON conversation_files(status);\nCREATE INDEX IF NOT EXISTS idx_conversation_files_parent_dir ON conversation_files(parent_dir);\nCREATE INDEX IF NOT EXISTS idx_conversation_files_account ON conversation_files(account);\n\nCREATE TABLE IF NOT EXISTS conversations (\n  id INTEGER PRIMARY KEY AUTOINCREMENT,\n\n  file_id INTEGER NOT NULL UNIQUE,\n\n  source_path TEXT NOT NULL UNIQUE,\n  -- Which provider produced this row. Canonical identity is (provider,\n  -- source_path); session_id stays non-unique across providers too.\n  provider TEXT NOT NULL DEFAULT 'claude-code',\n  kind TEXT,\n  external_session_id TEXT,\n  session_id TEXT NOT NULL,\n  session_name TEXT,\n  -- JSON NameSlots the name was resolved from; lets a side-store change\n  -- re-resolve the name without reparsing the transcript.\n  name_slots TEXT,\n\n  project_path TEXT,\n  project_name TEXT,\n  account TEXT NOT NULL DEFAULT 'default',\n  branch TEXT,\n\n  preview TEXT,\n  content_snippet TEXT,\n\n  message_count INTEGER NOT NULL DEFAULT 0,\n  -- Count of messages parseConversation() produces (broader than message_count;\n  -- includes tool_use-only and thinking-only lines). The total for bounded paging.\n  page_message_count INTEGER NOT NULL DEFAULT 0,\n  last_message_sender TEXT NOT NULL DEFAULT 'user',\n  timestamp TEXT,\n\n  -- Monotonic write counter; the highest value is the most recently indexed\n  -- row. Drives last-writer-wins resolution for shared session_ids (matching\n  -- the in-memory sessionId map), at the sub-second precision updated_at lacks.\n  index_seq INTEGER NOT NULL DEFAULT 0,\n\n  first_sent_at TEXT,\n  first_sent_text TEXT,\n\n  last_sent_at TEXT,\n  last_sent_text TEXT,\n\n  model TEXT,\n  is_subagent INTEGER NOT NULL DEFAULT 0,\n  -- The parent's JSONL PATH (matched against source_path), not an id.\n  parent_session_id TEXT,\n  -- The subagent's own id (Claude agentId), and the parent's session UUID.\n  subagent_id TEXT,\n  parent_session_uuid TEXT,\n  is_teammate INTEGER NOT NULL DEFAULT 0,\n  team_name TEXT,\n  tool_names_json TEXT,\n  last_prompt TEXT,\n\n  is_imported_from_claude INTEGER NOT NULL DEFAULT 0,\n  is_imported_from_codex INTEGER NOT NULL DEFAULT 0,\n  is_imported_from_cursor INTEGER NOT NULL DEFAULT 0,\n\n  status TEXT NOT NULL DEFAULT 'active',\n\n  created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\n  FOREIGN KEY (file_id) REFERENCES conversation_files(id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_conversations_session_id ON conversations(session_id);\nCREATE INDEX IF NOT EXISTS idx_conversations_provider_session ON conversations(provider, session_id);\nCREATE INDEX IF NOT EXISTS idx_conversations_provider_recent ON conversations(provider, timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_conversations_provider_project_branch ON conversations(provider, project_path, branch);\nCREATE INDEX IF NOT EXISTS idx_conversations_recent ON conversations(timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_conversations_project_recent ON conversations(project_path, timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_conversations_project_branch_recent ON conversations(project_path, branch, timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_conversations_account_recent ON conversations(account, timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_conversations_subagent_recent ON conversations(is_subagent, timestamp DESC);\nCREATE INDEX IF NOT EXISTS idx_conversations_team_recent ON conversations(team_name, timestamp DESC);\n\n-- Full-text search index over conversation body + metadata. Kept separate from\n-- the metadata tables so list-screen queries stay small and fast. One FTS row\n-- per conversation, replaced on each upsert.\n--\n-- The row's rowid IS conversation_files.id. That is load-bearing, not cosmetic:\n-- FTS5's query planner only handles MATCH, rowid and rank, so any other\n-- constraint (including a source_path equality on an UNINDEXED column) has no\n-- index and linear-scans the whole table. At ~128 KB of body per row that would\n-- mean scanning the entire corpus on every append. Look rows up by rowid.\n-- source_path stays stored-but-UNINDEXED so a search hit can resolve back to a\n-- conversations row.\n--\n-- Body is three columns, not one: text > thinking > tools priority has to\n-- survive an append (a new user message belongs in the text column, not after\n-- the tool output already written). They are deliberately NOT concatenated into\n-- a fourth column - that would double-weight body hits and inflate bm25 length.\nCREATE VIRTUAL TABLE IF NOT EXISTS conversation_messages_fts USING fts5(\n  source_path UNINDEXED,\n  text,\n  thinking,\n  tools,\n  project_name,\n  session_id,\n  session_name,\n  account,\n  model,\n  branch,\n  tool_names,\n  tokenize = 'unicode61'\n);\n\n-- Seek index for large conversations: every N messages, record the byte offset\n-- where the next message's line begins plus the parser's cross-line state\n-- (pending tool_use blocks, team info) needed to resume an equivalent parse\n-- from that point. Lets getConversationPage() read a window near the end of a\n-- huge file without parsing from byte 0.\nCREATE TABLE IF NOT EXISTS message_checkpoints (\n  id INTEGER PRIMARY KEY AUTOINCREMENT,\n  source_path TEXT NOT NULL,\n  message_index INTEGER NOT NULL,\n  byte_offset INTEGER NOT NULL,\n  line_number INTEGER NOT NULL,\n  parser_state TEXT NOT NULL,\n  created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE INDEX IF NOT EXISTS idx_message_checkpoints_lookup\n  ON message_checkpoints(source_path, message_index);\n\n-- Per-directory mtime watermark for the discovery dir-mtime gate (skips the\n-- glob for a directory whose file/subdir SET hasn't changed; never skips\n-- per-file classify()). One row for a profile's projectsDir itself (parent_root\n-- IS NULL) and one row per immediate project subdirectory discovered under it\n-- (parent_root = the projectsDir path). has_nested marks a project dir known to\n-- contain files below its own top level (e.g. subagents/) — those always\n-- re-glob regardless of mtime, since a project-dir-level watermark can't see a\n-- change two levels down.\nCREATE TABLE IF NOT EXISTS scanned_dirs (\n  path TEXT PRIMARY KEY,\n  parent_root TEXT,\n  mtime_ms INTEGER NOT NULL,\n  has_nested INTEGER NOT NULL DEFAULT 0,\n  updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE INDEX IF NOT EXISTS idx_scanned_dirs_parent_root ON scanned_dirs(parent_root);\n`;\n","import type { Database } from \"better-sqlite3\";\nimport { getLogger } from \"../logger\";\nimport { SCHEMA_SQL, SCHEMA_VERSION } from \"./schema\";\n\n// Minimal migration runner gated by PRAGMA user_version. The whole schema uses\n// CREATE TABLE/INDEX IF NOT EXISTS, so a fresh DB and an up-to-date DB both\n// no-op safely. When SCHEMA_VERSION bumps in the future, add ALTER statements\n// keyed on the stored version before stamping the new version.\nexport function runMigrations(db: Database): void {\n  const current = db.pragma(\"user_version\", { simple: true }) as number;\n  if (current >= SCHEMA_VERSION) return;\n\n  const log = getLogger();\n  log.info({ from: current, to: SCHEMA_VERSION }, \"migrations: applying\");\n\n  // v1 → v2: add provider columns to a pre-existing conversations table BEFORE\n  // running SCHEMA_SQL, because SCHEMA_SQL now creates indexes on those columns.\n  // CREATE TABLE IF NOT EXISTS can't add columns, so ALTER the existing one.\n  // Non-destructive: existing rows keep their data and default to 'claude-code'.\n  // Guarded on table existence so a fresh DB (table created by SCHEMA_SQL below)\n  // skips this entirely.\n  if (current >= 1 && current < 2 && tableExists(db, \"conversations\")) {\n    for (const [col, ddl] of [\n      [\n        \"provider\",\n        // Keep the historical DEFAULT 'threadbase' — v2→v3 below updates these rows.\n        \"ALTER TABLE conversations ADD COLUMN provider TEXT NOT NULL DEFAULT 'threadbase'\",\n      ],\n      [\"kind\", \"ALTER TABLE conversations ADD COLUMN kind TEXT\"],\n      [\"external_session_id\", \"ALTER TABLE conversations ADD COLUMN external_session_id TEXT\"],\n    ] as const) {\n      if (!hasColumn(db, \"conversations\", col)) db.exec(ddl);\n    }\n  }\n\n  // v2 → v3: rename provider 'threadbase' → 'claude-code'.\n  if (current >= 1 && current < 3 && tableExists(db, \"conversations\")) {\n    db.exec(\"UPDATE conversations SET provider = 'claude-code' WHERE provider = 'threadbase'\");\n  }\n\n  // v4 → v5: the FTS body split from a single `content` column (fed by the 5 KB\n  // head-biased meta.contentSnippet) into text/thinking/tools columns fed by the\n  // tail-biased search document. CREATE VIRTUAL TABLE IF NOT EXISTS cannot\n  // reshape an existing table, so drop it and let SCHEMA_SQL recreate it.\n  //\n  // Then force every known file to reindex from byte 0. classify() already\n  // returns \"reindex\" whenever last_indexed_offset === 0, so resetting the\n  // cursor is the entire trigger. Without it an unchanged conversation would\n  // keep its old FTS row forever — nothing would ever append to it, so nothing\n  // would rebuild it.\n  //\n  // Deliberately cheap: no JSONL is read here. Opening the database must stay in\n  // milliseconds; the actual re-parse happens in the next indexAll (newest files\n  // first), not inside a pragma migration.\n  if (current >= 1 && current < 5) {\n    db.exec(\"DROP TABLE IF EXISTS conversation_messages_fts\");\n    // A database old enough to still be at v1 can predate some cursor columns,\n    // and CREATE TABLE IF NOT EXISTS below won't add them — so reset only what\n    // this database actually has.\n    if (tableExists(db, \"conversation_files\")) {\n      const assignments: string[] = [];\n      if (hasColumn(db, \"conversation_files\", \"last_indexed_offset\")) {\n        assignments.push(\"last_indexed_offset = 0\");\n      }\n      if (hasColumn(db, \"conversation_files\", \"last_indexed_line\")) {\n        assignments.push(\"last_indexed_line = 0\");\n      }\n      if (hasColumn(db, \"conversation_files\", \"reducer_state\")) {\n        assignments.push(\"reducer_state = NULL\");\n      }\n      if (assignments.length > 0) {\n        db.exec(`UPDATE conversation_files SET ${assignments.join(\", \")}`);\n      }\n    }\n  }\n\n  // v5 → v6: subagent identity columns. CREATE TABLE IF NOT EXISTS cannot add\n  // columns to the existing table, so ALTER first (before SCHEMA_SQL, matching\n  // the v1 → v2 block). Non-destructive: existing rows default to NULL.\n  //\n  // Then force a reindex, because these two fields are folded out of the JSONL\n  // — an already-indexed subagent would otherwise keep NULL forever, since\n  // nothing appends to a finished transcript and nothing would rebuild it.\n  // classify() returns \"reindex\" whenever last_indexed_offset === 0, so\n  // resetting the cursor is the whole trigger. No JSONL is read here: the\n  // re-parse happens in the next indexAll, not inside a pragma migration.\n  if (current >= 1 && current < 6 && tableExists(db, \"conversations\")) {\n    for (const [col, ddl] of [\n      [\"subagent_id\", \"ALTER TABLE conversations ADD COLUMN subagent_id TEXT\"],\n      [\"parent_session_uuid\", \"ALTER TABLE conversations ADD COLUMN parent_session_uuid TEXT\"],\n    ] as const) {\n      if (!hasColumn(db, \"conversations\", col)) db.exec(ddl);\n    }\n    if (tableExists(db, \"conversation_files\")) {\n      const assignments: string[] = [];\n      if (hasColumn(db, \"conversation_files\", \"last_indexed_offset\")) {\n        assignments.push(\"last_indexed_offset = 0\");\n      }\n      if (hasColumn(db, \"conversation_files\", \"last_indexed_line\")) {\n        assignments.push(\"last_indexed_line = 0\");\n      }\n      if (hasColumn(db, \"conversation_files\", \"reducer_state\")) {\n        assignments.push(\"reducer_state = NULL\");\n      }\n      if (assignments.length > 0) {\n        db.exec(`UPDATE conversation_files SET ${assignments.join(\", \")}`);\n      }\n    }\n  }\n\n  // v6 → v7: no schema change — a reindex only. Codex previews, titles and\n  // message counts previously included the AGENTS.md / sandbox preamble Codex\n  // prepends as the first `user` turn. All three are folded out of the JSONL\n  // into reducer_state, so an already-indexed rollout would keep its old\n  // \"# AGENTS.md instructions for …\" preview forever: nothing appends to a\n  // finished rollout, so nothing would rebuild the row. Resetting the cursor is\n  // the whole trigger (classify() returns \"reindex\" at offset 0); no JSONL is\n  // read here, the re-parse happens in the next indexAll.\n  if (current >= 1 && current < 7 && tableExists(db, \"conversation_files\")) {\n    const assignments: string[] = [];\n    if (hasColumn(db, \"conversation_files\", \"last_indexed_offset\")) {\n      assignments.push(\"last_indexed_offset = 0\");\n    }\n    if (hasColumn(db, \"conversation_files\", \"last_indexed_line\")) {\n      assignments.push(\"last_indexed_line = 0\");\n    }\n    if (hasColumn(db, \"conversation_files\", \"reducer_state\")) {\n      assignments.push(\"reducer_state = NULL\");\n    }\n    if (assignments.length > 0) {\n      db.exec(`UPDATE conversation_files SET ${assignments.join(\", \")}`);\n    }\n  }\n\n  // v7 → v8: import provenance columns. Cursor agent-transcripts can be copies\n  // of Claude or Codex sessions; the flags are folded out of the JSONL, so an\n  // already-indexed Cursor file would keep 0 forever without a reindex.\n  if (current >= 1 && current < 8 && tableExists(db, \"conversations\")) {\n    for (const [col, ddl] of [\n      [\n        \"is_imported_from_claude\",\n        \"ALTER TABLE conversations ADD COLUMN is_imported_from_claude INTEGER NOT NULL DEFAULT 0\",\n      ],\n      [\n        \"is_imported_from_codex\",\n        \"ALTER TABLE conversations ADD COLUMN is_imported_from_codex INTEGER NOT NULL DEFAULT 0\",\n      ],\n      [\n        \"is_imported_from_cursor\",\n        \"ALTER TABLE conversations ADD COLUMN is_imported_from_cursor INTEGER NOT NULL DEFAULT 0\",\n      ],\n    ] as const) {\n      if (!hasColumn(db, \"conversations\", col)) db.exec(ddl);\n    }\n    if (tableExists(db, \"conversation_files\")) {\n      const assignments: string[] = [];\n      if (hasColumn(db, \"conversation_files\", \"last_indexed_offset\")) {\n        assignments.push(\"last_indexed_offset = 0\");\n      }\n      if (hasColumn(db, \"conversation_files\", \"last_indexed_line\")) {\n        assignments.push(\"last_indexed_line = 0\");\n      }\n      if (hasColumn(db, \"conversation_files\", \"reducer_state\")) {\n        assignments.push(\"reducer_state = NULL\");\n      }\n      if (assignments.length > 0) {\n        db.exec(\n          `UPDATE conversation_files SET ${assignments.join(\", \")}\n           WHERE id IN (SELECT file_id FROM conversations WHERE provider IN ('cursor', 'cursor-cli'))`,\n        );\n      }\n    }\n  }\n\n  // v8 → v9: wire name `cursor-cli` → `cursor`. Live PTY shipped the old name.\n  if (current >= 1 && current < 9 && tableExists(db, \"conversations\")) {\n    db.exec(`UPDATE conversations SET provider = 'cursor' WHERE provider = 'cursor-cli'`);\n  }\n\n  // v9 → v10: no schema change — a reindex only. cleanSystemTags() now unwraps\n  // <pasted_content id=\"XXXX\"> instead of leaving the opening tag as the\n  // title/preview, so an already-indexed conversation with a pasted first\n  // message would keep showing the tag forever without a reindex.\n  if (current >= 1 && current < 10 && tableExists(db, \"conversation_files\")) {\n    const assignments: string[] = [];\n    if (hasColumn(db, \"conversation_files\", \"last_indexed_offset\")) {\n      assignments.push(\"last_indexed_offset = 0\");\n    }\n    if (hasColumn(db, \"conversation_files\", \"last_indexed_line\")) {\n      assignments.push(\"last_indexed_line = 0\");\n    }\n    if (hasColumn(db, \"conversation_files\", \"reducer_state\")) {\n      assignments.push(\"reducer_state = NULL\");\n    }\n    if (assignments.length > 0) {\n      db.exec(`UPDATE conversation_files SET ${assignments.join(\", \")}`);\n    }\n  }\n\n  // v10 → v11: name_slots column + a reindex. Rows indexed before it carry no\n  // slots, and a finished transcript is never appended to, so only a reparse\n  // from offset 0 (classify() returns \"reindex\" for it) can fill them. No JSONL\n  // is read here: the re-parse happens in the next indexAll.\n  //\n  // Checkpoints go too: they snapshot the conversation reducer state, and one\n  // written before v11 has no nameSlots, so a paged read restoring it before the\n  // reindex reaches that file would fail. They are rebuilt lazily on page access.\n  if (current >= 1 && current < 11 && tableExists(db, \"conversations\")) {\n    if (tableExists(db, \"message_checkpoints\")) db.exec(\"DELETE FROM message_checkpoints\");\n    if (!hasColumn(db, \"conversations\", \"name_slots\")) {\n      db.exec(\"ALTER TABLE conversations ADD COLUMN name_slots TEXT\");\n    }\n    if (tableExists(db, \"conversation_files\")) {\n      const assignments: string[] = [];\n      if (hasColumn(db, \"conversation_files\", \"last_indexed_offset\")) {\n        assignments.push(\"last_indexed_offset = 0\");\n      }\n      if (hasColumn(db, \"conversation_files\", \"last_indexed_line\")) {\n        assignments.push(\"last_indexed_line = 0\");\n      }\n      if (hasColumn(db, \"conversation_files\", \"reducer_state\")) {\n        assignments.push(\"reducer_state = NULL\");\n      }\n      if (assignments.length > 0) {\n        db.exec(`UPDATE conversation_files SET ${assignments.join(\", \")}`);\n      }\n    }\n  }\n\n  // Fresh DB and re-runs both no-op safely (CREATE ... IF NOT EXISTS). Creates\n  // any missing tables/indexes, including the new provider indexes.\n  db.exec(SCHEMA_SQL);\n\n  db.pragma(`user_version = ${SCHEMA_VERSION}`);\n}\n\nfunction tableExists(db: Database, table: string): boolean {\n  return (\n    db.prepare(\"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?\").get(table) !==\n    undefined\n  );\n}\n\nfunction hasColumn(db: Database, table: string, column: string): boolean {\n  const cols = db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[];\n  return cols.some((c) => c.name === column);\n}\n","import { readdir, stat } from \"fs/promises\";\nimport { dirname, join } from \"path\";\nimport { canonicalPath } from \"../canonical-path\";\nimport { type DiscoveredFile, discoverJsonlFiles } from \"../discovery\";\nimport { getLogger } from \"../logger\";\nimport type { ConversationFilesRepo } from \"./repositories/conversation-files.repo\";\nimport type { ScannedDirsRepo } from \"./repositories/scanned-dirs.repo\";\n\n// ponytail: full-reconcile backstop runs every Nth gated pass, ignoring all\n// watermarks, so a filesystem that doesn't honor directory mtimes (some\n// NFS/SMB mounts, a few atomic-replace editors) can't permanently hide a\n// missed add/remove. Bump this if that self-heal interval feels too slow for\n// a given deployment; there's no config surface for it yet.\nexport const FULL_RECONCILE_EVERY_N_SCANS = 20;\n\n// Discover JSONL files under each profile's projectsDir, skipping the glob for\n// a directory whose file/subdirectory SET hasn't changed since the last pass.\n//\n// The core invariant (see SCAN-OPTIMIZATION-PROMPT.md Stage 4): the gate only\n// ever skips the GLOB (which exists to catch added/removed files — an event\n// that bumps the containing directory's mtime). It never skips classify() for\n// an already-known file (which exists to catch content changes, e.g. an\n// append — an event that does NOT bump the directory's mtime). Every active\n// file previously indexed under a project dir is always included in the\n// returned list, glob or no glob, so index-engine.ts's deletion-reconcile\n// never mistakes a skipped-but-still-known file for a vanished one.\n//\n// Two-level watermark, matching the real ~/.claude/projects/ shape (one\n// project subdirectory per conversation history, sometimes with nested\n// subagent/workflow trees below that):\n//   - projectsDir itself: catches added/removed PROJECT DIRECTORIES.\n//   - each immediate project subdirectory: catches added/removed FILES\n//     directly in it. A project dir known to contain nested files (subagents/\n//     etc) always re-globs regardless of mtime — a project-dir-level\n//     watermark can't see a change two levels down, so it isn't trusted to\n//     skip those dirs. This still skips the glob for the common case: most\n//     project dirs are flat (session-uuid.jsonl directly inside).\nexport async function discoverJsonlFilesGated(\n  dirs: { projectsDir: string; account: string }[],\n  files: ConversationFilesRepo,\n  scannedDirs: ScannedDirsRepo,\n  options: { fullRescan?: boolean } = {},\n): Promise<DiscoveredFile[]> {\n  const log = getLogger();\n  if (options.fullRescan) {\n    return discoverJsonlFiles(dirs);\n  }\n\n  const results: DiscoveredFile[] = [];\n\n  for (const rawDir of dirs) {\n    const { account } = rawDir;\n    // Canonicalize: the reuse branch below compares these dir keys against\n    // parent_dir/scanned_dirs.path, which the repositories store canonically.\n    // Without this the has_nested/parent_dir lookups silently miss on Windows\n    // when a caller's projectsDir came in with the other separator style, and\n    // the caller's seen-set then loses those files to the deletion-reconcile.\n    const projectsDir = canonicalPath(rawDir.projectsDir);\n\n    const resolved = await resolveProjectDirs(projectsDir, scannedDirs);\n    if (resolved === null) continue; // projectsDir unreadable/vanished\n\n    for (const projectDir of resolved.entries) {\n      let dirStat: { mtimeMs: number };\n      try {\n        dirStat = await stat(projectDir);\n      } catch {\n        // Project dir vanished between listing and stat — drop its watermark;\n        // its previously-known files simply won't appear in this pass's\n        // result, so index-engine.ts's deletion-reconcile marks them deleted.\n        scannedDirs.remove(projectDir);\n        continue;\n      }\n\n      const watermark = scannedDirs.get(projectDir);\n      const canReuse =\n        watermark !== undefined &&\n        watermark.mtime_ms === dirStat.mtimeMs &&\n        watermark.has_nested === 0;\n\n      if (canReuse) {\n        const known = files.activePathsByParentDir(projectDir);\n        // A watermarked, unchanged dir must have had ≥1 indexed file when it was\n        // globbed. Zero known files means the watermark is out of sync with\n        // conversation_files — most often a race: another connection sharing this\n        // index.db committed the scanned_dirs watermark (during its own discovery)\n        // before it finished writing that dir's file rows, and we read in that\n        // window. Trusting the empty reuse would drop the dir's conversations from\n        // `discovered`, so the caller's deletion-reconcile (or a stale allActive)\n        // never surfaces them → 404. Fall through to the glob, which reads the\n        // real on-disk truth and re-indexes what's actually there.\n        if (known.length > 0) {\n          for (const row of known) {\n            results.push({ filePath: row.absolute_path, account: row.account });\n          }\n          continue;\n        }\n      }\n\n      const found = await discoverJsonlFiles([{ projectsDir: projectDir, account }]);\n      const hasNested = found.some((f) => dirnameOf(f.filePath) !== projectDir);\n      scannedDirs.upsert(projectDir, projectsDir, dirStat.mtimeMs, hasNested);\n      results.push(...found);\n    }\n\n    // Commit the root watermark only after every project dir under it has been\n    // processed without throwing. Committing it earlier (e.g. right after the\n    // readdir) would let a crash mid-loop leave un-globbed project dirs with no\n    // scanned_dirs row — the next pass's root-reuse would then return only the\n    // dirs that DID get a row, silently dropping the crashed-out dirs' files\n    // from `seen` and mass-deleting their conversations.\n    if (resolved.commitRoot) {\n      scannedDirs.upsert(projectsDir, null, resolved.commitRoot.mtimeMs, false);\n      const seen = new Set(resolved.entries);\n      for (const known of scannedDirs.childrenOf(projectsDir)) {\n        if (!seen.has(known.path)) scannedDirs.remove(known.path);\n      }\n    }\n  }\n\n  log.debug(\n    { totalFiles: results.length, dirs: dirs.length },\n    \"dir-watermark: gated discovery complete\",\n  );\n  return results;\n}\n\ninterface ResolvedProjectDirs {\n  entries: string[];\n  // Present only when projectsDir was actually re-listed this pass (root\n  // watermark was stale/missing) — the caller commits it after the per-dir\n  // loop succeeds. Absent when the root watermark was reused as-is (nothing\n  // new to commit).\n  commitRoot?: { mtimeMs: number };\n}\n\n// The list of immediate project subdirectories under projectsDir, reusing the\n// stored list when projectsDir's own mtime hasn't moved (no dirs added or\n// removed), otherwise re-listing (but NOT yet persisting — see commitRoot).\n// Returns null if projectsDir itself can't be read (e.g. a profile pointing at\n// a not-yet-created config dir).\nasync function resolveProjectDirs(\n  projectsDir: string,\n  scannedDirs: ScannedDirsRepo,\n): Promise<ResolvedProjectDirs | null> {\n  let rootStat: { mtimeMs: number };\n  try {\n    rootStat = await stat(projectsDir);\n  } catch {\n    return null;\n  }\n\n  const rootWatermark = scannedDirs.get(projectsDir);\n  if (rootWatermark !== undefined && rootWatermark.mtime_ms === rootStat.mtimeMs) {\n    return { entries: scannedDirs.childrenOf(projectsDir).map((row) => row.path) };\n  }\n\n  let entries: string[];\n  try {\n    entries = (await readdir(projectsDir, { withFileTypes: true }))\n      .filter((e) => e.isDirectory())\n      .map((e) => joinPath(projectsDir, e.name));\n  } catch {\n    return null;\n  }\n\n  return { entries, commitRoot: { mtimeMs: rootStat.mtimeMs } };\n}\n\n// Platform-aware: the paths threaded through here are canonical (native\n// separator on Windows), so a hardcoded \"/\" split would read the whole path as\n// the dirname and mis-derive has_nested.\nfunction dirnameOf(filePath: string): string {\n  return dirname(filePath);\n}\n\nfunction joinPath(dir: string, name: string): string {\n  return join(dir, name);\n}\n","import type { Database } from \"better-sqlite3\";\nimport { canonicalPath } from \"../../canonical-path\";\nimport type { ConvReducerState } from \"../conversation-reducer\";\n\nexport interface Checkpoint {\n  messageIndex: number;\n  byteOffset: number;\n  lineNumber: number;\n  state: ConvReducerState;\n}\n\ninterface CheckpointRow {\n  message_index: number;\n  byte_offset: number;\n  line_number: number;\n  parser_state: string;\n}\n\n// Stores periodic seek points for a conversation so a page near the end of a\n// large file can be read without parsing from byte 0.\nexport class CheckpointsRepo {\n  constructor(private db: Database) {}\n\n  replaceAll(sourcePath: string, checkpoints: Checkpoint[]): void {\n    const path = canonicalPath(sourcePath);\n    const tx = this.db.transaction(() => {\n      this.db.prepare(\"DELETE FROM message_checkpoints WHERE source_path = ?\").run(path);\n      const insert = this.db.prepare(\n        `INSERT INTO message_checkpoints\n           (source_path, message_index, byte_offset, line_number, parser_state)\n         VALUES (?, ?, ?, ?, ?)`,\n      );\n      for (const c of checkpoints) {\n        insert.run(path, c.messageIndex, c.byteOffset, c.lineNumber, JSON.stringify(c.state));\n      }\n    });\n    tx();\n  }\n\n  // Insert checkpoints without touching existing rows. Appends never invalidate\n  // the chain covering the immutable prefix (Kafka sparse-index style); rows are\n  // only ever removed on truncation/replace or deletion.\n  append(sourcePath: string, checkpoints: Checkpoint[]): void {\n    const path = canonicalPath(sourcePath);\n    const tx = this.db.transaction(() => {\n      const insert = this.db.prepare(\n        `INSERT INTO message_checkpoints\n           (source_path, message_index, byte_offset, line_number, parser_state)\n         VALUES (?, ?, ?, ?, ?)`,\n      );\n      for (const c of checkpoints) {\n        insert.run(path, c.messageIndex, c.byteOffset, c.lineNumber, JSON.stringify(c.state));\n      }\n    });\n    tx();\n  }\n\n  // The latest checkpoint at or before `messageIndex`, or null if none (read\n  // from the file start). Lets a page seek to the nearest prior anchor.\n  floor(sourcePath: string, messageIndex: number): Checkpoint | null {\n    const row = this.db\n      .prepare(\n        `SELECT message_index, byte_offset, line_number, parser_state\n         FROM message_checkpoints\n         WHERE source_path = ? AND message_index <= ?\n         ORDER BY message_index DESC LIMIT 1`,\n      )\n      .get(canonicalPath(sourcePath), messageIndex) as CheckpointRow | undefined;\n    return row ? toCheckpoint(row) : null;\n  }\n\n  // The highest-index checkpoint for a file, or null if none. The resume point\n  // for extending the chain after an append.\n  last(sourcePath: string): Checkpoint | null {\n    const row = this.db\n      .prepare(\n        `SELECT message_index, byte_offset, line_number, parser_state\n         FROM message_checkpoints\n         WHERE source_path = ?\n         ORDER BY message_index DESC LIMIT 1`,\n      )\n      .get(canonicalPath(sourcePath)) as CheckpointRow | undefined;\n    return row ? toCheckpoint(row) : null;\n  }\n\n  count(sourcePath: string): number {\n    return (\n      this.db\n        .prepare(\"SELECT COUNT(*) AS n FROM message_checkpoints WHERE source_path = ?\")\n        .get(canonicalPath(sourcePath)) as { n: number }\n    ).n;\n  }\n\n  remove(sourcePath: string): void {\n    this.db\n      .prepare(\"DELETE FROM message_checkpoints WHERE source_path = ?\")\n      .run(canonicalPath(sourcePath));\n  }\n}\n\nfunction toCheckpoint(row: CheckpointRow): Checkpoint {\n  return {\n    messageIndex: row.message_index,\n    byteOffset: row.byte_offset,\n    lineNumber: row.line_number,\n    state: JSON.parse(row.parser_state) as ConvReducerState,\n  };\n}\n","import type { Database } from \"better-sqlite3\";\nimport { basename, dirname } from \"path\";\nimport { canonicalPath } from \"../../canonical-path\";\n\n// One tracked JSONL source file + its incremental indexing cursor.\nexport interface FileRow {\n  id: number;\n  absolute_path: string;\n  parent_dir: string;\n  file_name: string;\n  account: string;\n  size_bytes: number;\n  mtime_ms: number;\n  last_indexed_offset: number;\n  last_indexed_line: number;\n  reducer_state: string | null;\n  content_fingerprint: string | null;\n  status: string;\n  last_indexed_at: string | null;\n}\n\nexport class ConversationFilesRepo {\n  constructor(private db: Database) {}\n\n  getByPath(absolutePath: string): FileRow | undefined {\n    return this.db\n      .prepare(\"SELECT * FROM conversation_files WHERE absolute_path = ?\")\n      .get(canonicalPath(absolutePath)) as FileRow | undefined;\n  }\n\n  // Insert a freshly-discovered file at offset 0; returns its row id. Existing\n  // path is left untouched (returns the existing id) so a re-discovery is safe.\n  //\n  // absolute_path/parent_dir are stored canonicalized so a caller that built the\n  // path with the native separator (watcher, path.join) and one that got it from\n  // fast-glob (forward slashes, even on Windows) land on the same row instead of\n  // inserting a duplicate.\n  ensure(absolutePath: string, account: string): number {\n    const canonical = canonicalPath(absolutePath);\n    const existing = this.getByPath(canonical);\n    if (existing) return existing.id;\n\n    const info = this.db\n      .prepare(\n        `INSERT INTO conversation_files (absolute_path, parent_dir, file_name, account)\n         VALUES (?, ?, ?, ?)`,\n      )\n      .run(canonical, dirname(canonical), basename(canonical), account);\n    return Number(info.lastInsertRowid);\n  }\n\n  // Advance the cursor + persisted reducer state after a successful index pass.\n  updateCursor(\n    id: number,\n    fields: {\n      sizeBytes: number;\n      mtimeMs: number;\n      offset: number;\n      line: number;\n      reducerState: string | null;\n      fingerprint: string | null;\n      status?: string;\n    },\n  ): void {\n    this.db\n      .prepare(\n        `UPDATE conversation_files\n         SET size_bytes = ?, mtime_ms = ?, last_indexed_offset = ?, last_indexed_line = ?,\n             reducer_state = ?, content_fingerprint = ?, status = ?,\n             last_indexed_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP\n         WHERE id = ?`,\n      )\n      .run(\n        fields.sizeBytes,\n        fields.mtimeMs,\n        fields.offset,\n        fields.line,\n        fields.reducerState,\n        fields.fingerprint,\n        fields.status ?? \"active\",\n        id,\n      );\n  }\n\n  // Reset the cursor to 0 for a truncated/replaced file before a full reindex.\n  resetCursor(id: number): void {\n    this.db\n      .prepare(\n        `UPDATE conversation_files\n         SET last_indexed_offset = 0, last_indexed_line = 0, reducer_state = NULL,\n             status = 'needs_reindex', updated_at = CURRENT_TIMESTAMP\n         WHERE id = ?`,\n      )\n      .run(id);\n  }\n\n  setStatus(id: number, status: string): void {\n    const deletedAt = status === \"deleted\" ? \"CURRENT_TIMESTAMP\" : \"deleted_at\";\n    this.db\n      .prepare(\n        `UPDATE conversation_files\n         SET status = ?, deleted_at = ${deletedAt}, updated_at = CURRENT_TIMESTAMP\n         WHERE id = ?`,\n      )\n      .run(status, id);\n  }\n\n  allActivePaths(): string[] {\n    const rows = this.db\n      .prepare(\"SELECT absolute_path FROM conversation_files WHERE status != 'deleted'\")\n      .all() as { absolute_path: string }[];\n    return rows.map((r) => r.absolute_path);\n  }\n\n  // Active file paths belonging to any of the given accounts. Backs the\n  // deletion-reconcile so a scan that covered only some accounts can't mark\n  // another account's files deleted (they live in the same shared index.db but\n  // a different profile owns them). Returns [] for an empty account list.\n  activePathsByAccounts(accounts: string[]): string[] {\n    if (accounts.length === 0) return [];\n    const placeholders = accounts.map(() => \"?\").join(\", \");\n    const rows = this.db\n      .prepare(\n        `SELECT absolute_path FROM conversation_files\n         WHERE status != 'deleted' AND account IN (${placeholders})`,\n      )\n      .all(...accounts) as { absolute_path: string }[];\n    return rows.map((r) => r.absolute_path);\n  }\n\n  // Active files whose immediate parent is exactly parentDir (no nested\n  // subdirectories). Backs the dir-mtime gate's reuse path: a project dir with\n  // an unchanged mtime and no nested files can skip the glob entirely.\n  activePathsByParentDir(parentDir: string): { absolute_path: string; account: string }[] {\n    return this.db\n      .prepare(\n        \"SELECT absolute_path, account FROM conversation_files WHERE parent_dir = ? AND status != 'deleted'\",\n      )\n      .all(canonicalPath(parentDir)) as { absolute_path: string; account: string }[];\n  }\n}\n","import type { Database } from \"better-sqlite3\";\nimport { canonicalPath } from \"../../canonical-path\";\nimport { CLAUDE_CODE_PROVIDER } from \"../../providers/provider\";\nimport type { NameSlots } from \"../../session-name\";\nimport type { ConversationMeta, MessageSender, ProviderName } from \"../../types\";\n\nexport interface ConversationRow {\n  id: number;\n  file_id: number;\n  source_path: string;\n  provider: string;\n  kind: string | null;\n  external_session_id: string | null;\n  session_id: string;\n  session_name: string | null;\n  project_path: string | null;\n  project_name: string | null;\n  account: string;\n  branch: string | null;\n  preview: string | null;\n  content_snippet: string | null;\n  message_count: number;\n  page_message_count: number;\n  last_message_sender: string;\n  timestamp: string | null;\n  first_sent_at: string | null;\n  first_sent_text: string | null;\n  last_sent_at: string | null;\n  last_sent_text: string | null;\n  model: string | null;\n  is_subagent: number;\n  parent_session_id: string | null;\n  subagent_id: string | null;\n  parent_session_uuid: string | null;\n  is_teammate: number;\n  team_name: string | null;\n  tool_names_json: string | null;\n  last_prompt: string | null;\n  is_imported_from_claude: number;\n  is_imported_from_codex: number;\n  is_imported_from_cursor: number;\n  name_slots: string | null;\n  status: string;\n}\n\n// Row -> ConversationMeta. Must reproduce the exact in-memory shape so the\n// persistent and legacy paths return identical ScanResult/SearchResult objects.\nexport function rowToMeta(row: ConversationRow): ConversationMeta {\n  return {\n    id: row.source_path,\n    filePath: row.source_path,\n    provider: (row.provider as ProviderName) ?? CLAUDE_CODE_PROVIDER,\n    kind: (row.kind as ConversationMeta[\"kind\"]) ?? undefined,\n    externalSessionId: row.external_session_id ?? undefined,\n    sessionId: row.session_id,\n    sessionName: row.session_name ?? \"\",\n    nameSlots: row.name_slots ? (JSON.parse(row.name_slots) as NameSlots) : undefined,\n    projectPath: row.project_path ?? \"\",\n    projectName: row.project_name ?? \"\",\n    account: row.account,\n    timestamp: row.timestamp ?? \"\",\n    messageCount: row.message_count,\n    lastMessageSender: row.last_message_sender as MessageSender,\n    preview: row.preview ?? \"\",\n    contentSnippet: row.content_snippet ?? \"\",\n    gitBranch: row.branch,\n    model: row.model,\n    isSubagent: row.is_subagent === 1,\n    parentSessionId: row.parent_session_id,\n    subagentId: row.subagent_id ?? undefined,\n    parentSessionUuid: row.parent_session_uuid ?? undefined,\n    isTeammate: row.is_teammate === 1,\n    teamName: row.team_name,\n    toolNames: row.tool_names_json ? (JSON.parse(row.tool_names_json) as string[]) : [],\n    firstMessage: row.first_sent_text\n      ? { text: row.first_sent_text, timestamp: row.first_sent_at ?? \"\" }\n      : null,\n    lastMessage: row.last_sent_text\n      ? { text: row.last_sent_text, timestamp: row.last_sent_at ?? \"\" }\n      : null,\n    lastPrompt: row.last_prompt ?? undefined,\n    ...(row.is_imported_from_claude === 1 ? { isImportedFromClaude: true } : {}),\n    ...(row.is_imported_from_codex === 1 ? { isImportedFromCodex: true } : {}),\n    ...(row.is_imported_from_cursor === 1 ? { isImportedFromCursor: true } : {}),\n  };\n}\n\nexport class ConversationsRepo {\n  constructor(private db: Database) {}\n\n  // Upsert by file_id (1 file = 1 conversation). Keyed on the unique file_id so\n  // a re-index overwrites the prior summary in place.\n  upsert(fileId: number, meta: ConversationMeta, pageMessageCount = meta.messageCount): void {\n    this.db\n      .prepare(\n        `INSERT INTO conversations (\n           file_id, source_path, provider, kind, external_session_id,\n           session_id, session_name, project_path, project_name,\n           account, branch, preview, content_snippet, message_count, page_message_count,\n           last_message_sender,\n           timestamp, index_seq, first_sent_at, first_sent_text, last_sent_at, last_sent_text,\n           model, is_subagent, parent_session_id, subagent_id, parent_session_uuid,\n           is_teammate, team_name, tool_names_json,\n           last_prompt, is_imported_from_claude, is_imported_from_codex,\n           is_imported_from_cursor, name_slots, status, updated_at\n         ) VALUES (\n           @file_id, @source_path, @provider, @kind, @external_session_id,\n           @session_id, @session_name, @project_path, @project_name,\n           @account, @branch, @preview, @content_snippet, @message_count, @page_message_count,\n           @last_message_sender,\n           @timestamp,\n           (SELECT COALESCE(MAX(index_seq), 0) + 1 FROM conversations),\n           @first_sent_at, @first_sent_text, @last_sent_at, @last_sent_text,\n           @model, @is_subagent, @parent_session_id, @subagent_id, @parent_session_uuid,\n           @is_teammate, @team_name, @tool_names_json,\n           @last_prompt, @is_imported_from_claude, @is_imported_from_codex,\n           @is_imported_from_cursor, @name_slots, 'active', CURRENT_TIMESTAMP\n         )\n         ON CONFLICT(file_id) DO UPDATE SET\n           source_path = excluded.source_path,\n           provider = excluded.provider,\n           kind = excluded.kind,\n           external_session_id = excluded.external_session_id,\n           session_id = excluded.session_id,\n           session_name = excluded.session_name,\n           project_path = excluded.project_path,\n           project_name = excluded.project_name,\n           account = excluded.account,\n           branch = excluded.branch,\n           preview = excluded.preview,\n           content_snippet = excluded.content_snippet,\n           message_count = excluded.message_count,\n           page_message_count = excluded.page_message_count,\n           last_message_sender = excluded.last_message_sender,\n           timestamp = excluded.timestamp,\n           first_sent_at = excluded.first_sent_at,\n           first_sent_text = excluded.first_sent_text,\n           last_sent_at = excluded.last_sent_at,\n           last_sent_text = excluded.last_sent_text,\n           model = excluded.model,\n           is_subagent = excluded.is_subagent,\n           parent_session_id = excluded.parent_session_id,\n           subagent_id = excluded.subagent_id,\n           parent_session_uuid = excluded.parent_session_uuid,\n           is_teammate = excluded.is_teammate,\n           team_name = excluded.team_name,\n           tool_names_json = excluded.tool_names_json,\n           last_prompt = excluded.last_prompt,\n           is_imported_from_claude = excluded.is_imported_from_claude,\n           is_imported_from_codex = excluded.is_imported_from_codex,\n           is_imported_from_cursor = excluded.is_imported_from_cursor,\n           name_slots = excluded.name_slots,\n           status = 'active',\n           index_seq = (SELECT COALESCE(MAX(index_seq), 0) + 1 FROM conversations),\n           updated_at = CURRENT_TIMESTAMP`,\n      )\n      .run({\n        file_id: fileId,\n        // Canonical form, matching conversation_files.absolute_path — otherwise\n        // a native-separator caller writes a second spelling of the same file\n        // that every by-path lookup (and the deletion-reconcile) then misses.\n        source_path: canonicalPath(meta.id),\n        provider: meta.provider ?? CLAUDE_CODE_PROVIDER,\n        kind: meta.kind ?? null,\n        external_session_id: meta.externalSessionId ?? null,\n        session_id: meta.sessionId,\n        session_name: meta.sessionName || null,\n        project_path: meta.projectPath || null,\n        project_name: meta.projectName || null,\n        account: meta.account,\n        branch: meta.gitBranch,\n        preview: meta.preview || null,\n        content_snippet: meta.contentSnippet || null,\n        message_count: meta.messageCount,\n        page_message_count: pageMessageCount,\n        last_message_sender: meta.lastMessageSender,\n        timestamp: meta.timestamp || null,\n        first_sent_at: meta.firstMessage?.timestamp ?? null,\n        first_sent_text: meta.firstMessage?.text ?? null,\n        last_sent_at: meta.lastMessage?.timestamp ?? null,\n        last_sent_text: meta.lastMessage?.text ?? null,\n        model: meta.model,\n        is_subagent: meta.isSubagent ? 1 : 0,\n        parent_session_id: meta.parentSessionId,\n        subagent_id: meta.subagentId ?? null,\n        parent_session_uuid: meta.parentSessionUuid ?? null,\n        is_teammate: meta.isTeammate ? 1 : 0,\n        team_name: meta.teamName,\n        tool_names_json: JSON.stringify(meta.toolNames),\n        last_prompt: meta.lastPrompt ?? null,\n        is_imported_from_claude: meta.isImportedFromClaude ? 1 : 0,\n        is_imported_from_codex: meta.isImportedFromCodex ? 1 : 0,\n        is_imported_from_cursor: meta.isImportedFromCursor ? 1 : 0,\n        name_slots: meta.nameSlots ? JSON.stringify(meta.nameSlots) : null,\n      });\n  }\n\n  getBySourcePath(sourcePath: string): ConversationMeta | null {\n    const row = this.db\n      .prepare(\"SELECT * FROM conversations WHERE source_path = ? AND status = 'active'\")\n      .get(canonicalPath(sourcePath)) as ConversationRow | undefined;\n    return row ? rowToMeta(row) : null;\n  }\n\n  // Dual lookup matching scanner.getConversation: resolve by source_path (the\n  // canonical id) first, then by session_id.\n  //\n  // session_id is NOT unique (see schema header) — the sessionId form is a\n  // compatibility convenience. Resolution is deterministic and matches the\n  // in-memory scanner: among active rows sharing the session_id, newest\n  // timestamp wins, index_seq breaks sub-second ties (precision updated_at\n  // lacks), then source_path ascending. Collision-safe callers should use\n  // getAllBySessionId() instead.\n  getByIdOrSession(id: string): ConversationMeta | null {\n    const direct = this.getBySourcePath(id);\n    if (direct) return direct;\n    const row = this.db\n      .prepare(\n        `SELECT * FROM conversations WHERE session_id = ? AND status = 'active'\n         ORDER BY COALESCE(timestamp, '') DESC, index_seq DESC, source_path ASC LIMIT 1`,\n      )\n      .get(id) as ConversationRow | undefined;\n    return row ? rowToMeta(row) : null;\n  }\n\n  // All active conversations sharing a session_id, newest first (same ordering\n  // as getByIdOrSession). Collision-safe counterpart to the convenience\n  // getByIdOrSession() sessionId lookup.\n  getAllBySessionId(sessionId: string): ConversationMeta[] {\n    const rows = this.db\n      .prepare(\n        `SELECT * FROM conversations WHERE session_id = ? AND status = 'active'\n         ORDER BY COALESCE(timestamp, '') DESC, index_seq DESC, source_path ASC`,\n      )\n      .all(sessionId) as ConversationRow[];\n    return rows.map(rowToMeta);\n  }\n\n  // All active metas (unsorted/unfiltered) — callers apply the existing\n  // filters/view transforms. Used by scan() before SQL filtering is wired in.\n  allActive(): ConversationMeta[] {\n    const rows = this.db\n      .prepare(\"SELECT * FROM conversations WHERE status = 'active'\")\n      .all() as ConversationRow[];\n    return rows.map(rowToMeta);\n  }\n\n  // Most recent active conversations, newest first. Backs the empty-query\n  // search path (mirrors the in-memory indexer's getRecent).\n  recent(limit: number): ConversationMeta[] {\n    const rows = this.db\n      .prepare(\n        `SELECT * FROM conversations WHERE status = 'active'\n         ORDER BY COALESCE(timestamp, '') DESC, source_path ASC LIMIT ?`,\n      )\n      .all(limit) as ConversationRow[];\n    return rows.map(rowToMeta);\n  }\n\n  distinctProjects(): string[] {\n    const rows = this.db\n      .prepare(\n        `SELECT DISTINCT project_path FROM conversations\n         WHERE status = 'active' AND project_path IS NOT NULL AND project_path != ''\n         ORDER BY project_path ASC`,\n      )\n      .all() as { project_path: string }[];\n    return rows.map((r) => r.project_path);\n  }\n\n  deleteByFileId(fileId: number): void {\n    this.db\n      .prepare(\n        \"UPDATE conversations SET status = 'deleted', updated_at = CURRENT_TIMESTAMP WHERE file_id = ?\",\n      )\n      .run(fileId);\n  }\n\n  // The parseConversation message total for a file (for bounded paging), or 0\n  // if not indexed.\n  pageMessageCount(sourcePath: string): number {\n    const row = this.db\n      .prepare(\n        \"SELECT page_message_count AS n FROM conversations WHERE source_path = ? AND status = 'active'\",\n      )\n      .get(canonicalPath(sourcePath)) as { n: number } | undefined;\n    return row?.n ?? 0;\n  }\n\n  count(): number {\n    return (\n      this.db.prepare(\"SELECT COUNT(*) AS n FROM conversations WHERE status = 'active'\").get() as {\n        n: number;\n      }\n    ).n;\n  }\n}\n","import type { Database } from \"better-sqlite3\";\nimport { canonicalPath } from \"../../canonical-path\";\nimport {\n  CURSOR_PROVIDER,\n  canonicalizeProviderName,\n  LEGACY_CURSOR_PROVIDER,\n} from \"../../providers/provider\";\nimport type { SearchDocument } from \"../../search-document\";\nimport {\n  FTS_ELLIPSIS,\n  FTS_HIT_CLOSE,\n  FTS_HIT_OPEN,\n  FTS_SNIPPET_TOKENS,\n  parseFtsSnippet,\n} from \"../../search-matches\";\nimport type { ConversationMeta, SearchHighlight } from \"../../types\";\n\n// Body column positions in conversation_messages_fts, in snippet() priority\n// order. source_path is column 0.\nconst BODY_COLUMNS = [\n  { name: \"text\", index: 1 },\n  { name: \"thinking\", index: 2 },\n  { name: \"tools\", index: 3 },\n] as const;\n\nexport interface FtsSearchFilters {\n  account?: string;\n  provider?: string;\n  project?: string;\n  since?: Date;\n  include?: \"all\" | \"conversations\" | \"subagents\" | \"teammates\";\n}\n\nexport interface FtsHit {\n  sourcePath: string;\n  // Present when the match was in a body column. Null for a metadata-only hit,\n  // which the caller resolves through generateMatches() instead.\n  body: { snippet: string; highlights: SearchHighlight[] } | null;\n}\n\n// FTS5-backed search index over conversation body + metadata. One row per\n// conversation, addressed by rowid == conversation_files.id.\n//\n// Never look a row up by source_path: FTS5's planner handles only MATCH, rowid\n// and rank, so any other constraint linear-scans the table. That was tolerable\n// when a row held <=5 KB; with ~128 KB of body per row it would scan the whole\n// corpus on every append.\nexport class FtsRepo {\n  constructor(private db: Database) {}\n\n  upsert(rowId: number, meta: ConversationMeta, doc: SearchDocument): void {\n    // Canonical form, matching conversations.source_path — a search hit is\n    // resolved back through getBySourcePath(), so the two must agree.\n    const sourcePath = canonicalPath(meta.id);\n    const tx = this.db.transaction(() => {\n      this.db.prepare(\"DELETE FROM conversation_messages_fts WHERE rowid = ?\").run(rowId);\n      this.db\n        .prepare(\n          `INSERT INTO conversation_messages_fts\n             (rowid, source_path, text, thinking, tools,\n              project_name, session_id, session_name, account, model, branch, tool_names)\n           VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n        )\n        .run(\n          rowId,\n          sourcePath,\n          doc.text,\n          doc.thinking,\n          doc.tools,\n          meta.projectName ?? \"\",\n          meta.sessionId ?? \"\",\n          meta.sessionName ?? \"\",\n          meta.account ?? \"\",\n          meta.model ?? \"\",\n          meta.gitBranch ?? \"\",\n          meta.toolNames.join(\" \"),\n        );\n    });\n    tx();\n  }\n\n  // Current durable buckets for a conversation, so an append can tail-extend\n  // them without reparsing the file. Returns null when no row exists yet.\n  readDocument(rowId: number): SearchDocument | null {\n    const row = this.db\n      .prepare(\"SELECT text, thinking, tools FROM conversation_messages_fts WHERE rowid = ?\")\n      .get(rowId) as { text: string; thinking: string; tools: string } | undefined;\n    if (!row) return null;\n    return { text: row.text ?? \"\", thinking: row.thinking ?? \"\", tools: row.tools ?? \"\" };\n  }\n\n  // True when the stored row already equals what we would write, so an append\n  // can skip re-tokenizing ~128 KB of body for nothing.\n  //\n  // The check covers the metadata columns too, not just the buckets: a\n  // newly-seen tool name or a late-resolved session_name changes metadata while\n  // the body is byte-identical, and nothing else ever rewrites this row — so\n  // skipping on \"body unchanged\" alone would strand that stale value forever.\n  isCurrent(rowId: number, meta: ConversationMeta, doc: SearchDocument): boolean {\n    const row = this.db\n      .prepare(\n        `SELECT text, thinking, tools,\n                project_name, session_id, session_name, account, model, branch, tool_names\n         FROM conversation_messages_fts WHERE rowid = ?`,\n      )\n      .get(rowId) as Record<string, string> | undefined;\n    if (!row) return false;\n\n    return (\n      row.text === doc.text &&\n      row.thinking === doc.thinking &&\n      row.tools === doc.tools &&\n      row.project_name === (meta.projectName ?? \"\") &&\n      row.session_id === (meta.sessionId ?? \"\") &&\n      row.session_name === (meta.sessionName ?? \"\") &&\n      row.account === (meta.account ?? \"\") &&\n      row.model === (meta.model ?? \"\") &&\n      row.branch === (meta.gitBranch ?? \"\") &&\n      row.tool_names === meta.toolNames.join(\" \")\n    );\n  }\n\n  remove(rowId: number): void {\n    this.db.prepare(\"DELETE FROM conversation_messages_fts WHERE rowid = ?\").run(rowId);\n  }\n\n  // Ranked hits matching the query, best first. Filters run in SQL BEFORE LIMIT:\n  // with a wide corpus a popular term matches far more conversations than one\n  // page, so post-filtering an already-truncated list would report \"no results\"\n  // for queries that do have them.\n  search(query: string, limit: number, filters: FtsSearchFilters = {}): FtsHit[] {\n    const match = toMatchQuery(query);\n    if (!match) return [];\n\n    const snippetSelects = BODY_COLUMNS.map(\n      (c) => `snippet(conversation_messages_fts, ${c.index}, ?, ?, ?, ?) AS snippet_${c.name}`,\n    ).join(\", \");\n\n    const params: unknown[] = [];\n    for (const _col of BODY_COLUMNS) {\n      params.push(FTS_HIT_OPEN, FTS_HIT_CLOSE, FTS_ELLIPSIS, FTS_SNIPPET_TOKENS);\n    }\n    params.push(match);\n\n    const where: string[] = [\"conversation_messages_fts MATCH ?\", \"c.status = 'active'\"];\n\n    if (filters.account) {\n      where.push(\"c.account = ?\");\n      params.push(filters.account);\n    }\n    if (filters.provider) {\n      const provider = canonicalizeProviderName(filters.provider) ?? filters.provider;\n      if (provider === CURSOR_PROVIDER) {\n        where.push(\"(c.provider = ? OR c.provider = ?)\");\n        params.push(CURSOR_PROVIDER, LEGACY_CURSOR_PROVIDER);\n      } else {\n        where.push(\"c.provider = ?\");\n        params.push(provider);\n      }\n    }\n    if (filters.project) {\n      where.push(\"(lower(c.project_path) LIKE ? OR lower(c.project_name) LIKE ?)\");\n      const like = `%${filters.project.toLowerCase()}%`;\n      params.push(like, like);\n    }\n    if (filters.since) {\n      where.push(\"c.timestamp >= ?\");\n      params.push(filters.since.toISOString());\n    }\n    if (filters.include === \"conversations\") {\n      where.push(\"c.is_subagent = 0 AND c.is_teammate = 0\");\n    } else if (filters.include === \"subagents\") {\n      where.push(\"c.is_subagent = 1\");\n    } else if (filters.include === \"teammates\") {\n      where.push(\"c.is_teammate = 1\");\n    }\n\n    params.push(limit);\n\n    const rows = this.db\n      .prepare(\n        `SELECT conversation_messages_fts.source_path AS source_path, ${snippetSelects}\n         FROM conversation_messages_fts\n         JOIN conversations c ON c.source_path = conversation_messages_fts.source_path\n         WHERE ${where.join(\" AND \")}\n         ORDER BY rank\n         LIMIT ?`,\n      )\n      .all(...params) as Record<string, string>[];\n\n    return rows.map((row) => ({ sourcePath: row.source_path, body: pickBodySnippet(row) }));\n  }\n\n  count(): number {\n    return (\n      this.db.prepare(\"SELECT COUNT(*) AS n FROM conversation_messages_fts\").get() as { n: number }\n    ).n;\n  }\n}\n\n// snippet() returns a column's head even when the match was elsewhere, so the\n// hit markers are the only signal that THIS column matched. Take the first\n// marked column in priority order: text, then thinking, then tools.\nfunction pickBodySnippet(row: Record<string, string>) {\n  for (const col of BODY_COLUMNS) {\n    const parsed = parseFtsSnippet(row[`snippet_${col.name}`]);\n    if (parsed) return parsed;\n  }\n  return null;\n}\n\n// Turn free-text into a safe FTS5 prefix query. Each whitespace-separated term\n// is wrapped in double quotes (so FTS5 special chars are treated literally) and\n// suffixed with * for prefix matching, mirroring FlexSearch's forward tokenizer.\n// Terms are ANDed together. Returns \"\" when nothing usable remains.\nfunction toMatchQuery(query: string): string {\n  const terms = query\n    .trim()\n    .split(/\\s+/)\n    .map((t) => t.replace(/\"/g, \"\").trim())\n    .filter(Boolean);\n  if (terms.length === 0) return \"\";\n  return terms.map((t) => `\"${t}\"*`).join(\" AND \");\n}\n","import type { Database } from \"better-sqlite3\";\nimport { canonicalPath } from \"../../canonical-path\";\n\nexport interface ScannedDirRow {\n  path: string;\n  parent_root: string | null;\n  mtime_ms: number;\n  has_nested: number;\n}\n\n// Per-directory mtime watermarks backing the discovery dir-mtime gate. One row\n// for a profile's projectsDir (parent_root NULL), one row per immediate\n// project subdirectory (parent_root = the projectsDir path).\nexport class ScannedDirsRepo {\n  constructor(private db: Database) {}\n\n  get(path: string): ScannedDirRow | undefined {\n    return this.db.prepare(\"SELECT * FROM scanned_dirs WHERE path = ?\").get(canonicalPath(path)) as\n      | ScannedDirRow\n      | undefined;\n  }\n\n  // Known project subdirectories under a root, path ascending (stable order).\n  childrenOf(parentRoot: string): ScannedDirRow[] {\n    return this.db\n      .prepare(\"SELECT * FROM scanned_dirs WHERE parent_root = ? ORDER BY path ASC\")\n      .all(canonicalPath(parentRoot)) as ScannedDirRow[];\n  }\n\n  upsert(path: string, parentRoot: string | null, mtimeMs: number, hasNested: boolean): void {\n    this.db\n      .prepare(\n        `INSERT INTO scanned_dirs (path, parent_root, mtime_ms, has_nested, updated_at)\n         VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)\n         ON CONFLICT(path) DO UPDATE SET\n           parent_root = excluded.parent_root,\n           mtime_ms = excluded.mtime_ms,\n           has_nested = excluded.has_nested,\n           updated_at = CURRENT_TIMESTAMP`,\n      )\n      .run(\n        canonicalPath(path),\n        parentRoot === null ? null : canonicalPath(parentRoot),\n        mtimeMs,\n        hasNested ? 1 : 0,\n      );\n  }\n\n  // Drop a project subdirectory's watermark row (it vanished from disk).\n  remove(path: string): void {\n    this.db.prepare(\"DELETE FROM scanned_dirs WHERE path = ?\").run(canonicalPath(path));\n  }\n}\n","import type { FSWatcher } from \"chokidar\";\nimport chokidar from \"chokidar\";\nimport { getLogger } from \"../logger\";\nimport { getProjectsDir } from \"../profiles\";\nimport type { Profile } from \"../types\";\n\nconst EXCLUDED_SEGMENTS = [\"/memory/\", \"/tool-results/\"];\n\nexport interface WatchEvent {\n  filePath: string;\n  account: string;\n  type: \"add\" | \"change\" | \"unlink\";\n}\n\nexport interface FileWatcherOptions {\n  // Debounce window (ms) coalescing rapid events for one file. Spec §8.\n  debounceMs?: number;\n}\n\n// Watches each active profile's projects dir for *.jsonl changes and forwards\n// debounced add/change/unlink events. Thin wrapper over chokidar; the scanner\n// owns what to do with the events (enqueue index jobs, emit to host apps).\nexport class FileWatcher {\n  private watchers: FSWatcher[] = [];\n  private timers = new Map<string, ReturnType<typeof setTimeout>>();\n  private readonly debounceMs: number;\n\n  constructor(\n    private profiles: Profile[],\n    private onEvent: (event: WatchEvent) => void,\n    options: FileWatcherOptions = {},\n  ) {\n    this.debounceMs = options.debounceMs ?? 400;\n  }\n\n  // Resolves once every underlying chokidar watcher has finished its initial\n  // scan, so the caller knows subsequent FS changes will be observed.\n  async start(): Promise<void> {\n    const log = getLogger();\n    const ready: Promise<void>[] = [];\n    for (const profile of this.profiles) {\n      const dir = getProjectsDir(profile);\n      const watcher = chokidar.watch(dir, {\n        ignoreInitial: true,\n        ignored: (p: string) => EXCLUDED_SEGMENTS.some((seg) => p.includes(seg)),\n        awaitWriteFinish: { stabilityThreshold: this.debounceMs, pollInterval: 100 },\n      });\n      watcher\n        .on(\"add\", (p) => this.dispatch(p, profile.id, \"add\"))\n        .on(\"change\", (p) => this.dispatch(p, profile.id, \"change\"))\n        .on(\"unlink\", (p) => this.dispatch(p, profile.id, \"unlink\"));\n      ready.push(new Promise<void>((resolve) => watcher.once(\"ready\", () => resolve())));\n      this.watchers.push(watcher);\n      log.debug({ dir, account: profile.id }, \"watcher: watching\");\n    }\n    await Promise.all(ready);\n  }\n\n  async stop(): Promise<void> {\n    for (const t of this.timers.values()) clearTimeout(t);\n    this.timers.clear();\n    await Promise.all(this.watchers.map((w) => w.close()));\n    this.watchers = [];\n  }\n\n  private dispatch(filePath: string, account: string, type: WatchEvent[\"type\"]): void {\n    if (!filePath.endsWith(\".jsonl\")) return;\n    // Debounce per path: collapse a flurry of events into the latest one.\n    const existing = this.timers.get(filePath);\n    if (existing) clearTimeout(existing);\n    this.timers.set(\n      filePath,\n      setTimeout(() => {\n        this.timers.delete(filePath);\n        this.onEvent({ filePath, account, type });\n      }, this.debounceMs),\n    );\n  }\n}\n","import { getLogger } from \"../logger\";\n\nexport type JobReason = \"watcher\" | \"manual\" | \"periodic\";\n\nexport interface IndexJob {\n  filePath: string;\n  account: string;\n  reason: JobReason;\n}\n\n// In-process job queue for incremental indexing. Jobs are deduplicated by file\n// path (the latest job for a path wins) and processed one at a time, because\n// SQLite has a single writer. A burst of filesystem events for the same file\n// collapses to one index pass.\nexport class IndexQueue {\n  private pending = new Map<string, IndexJob>();\n  private running = false;\n  private idleResolvers: Array<() => void> = [];\n\n  constructor(private process: (job: IndexJob) => Promise<void>) {}\n\n  enqueue(job: IndexJob): void {\n    this.pending.set(job.filePath, job);\n    void this.drain();\n  }\n\n  // Resolves when the queue has fully drained — useful for tests and for a\n  // clean shutdown.\n  onIdle(): Promise<void> {\n    if (!this.running && this.pending.size === 0) return Promise.resolve();\n    return new Promise((resolve) => this.idleResolvers.push(resolve));\n  }\n\n  get size(): number {\n    return this.pending.size;\n  }\n\n  private async drain(): Promise<void> {\n    if (this.running) return;\n    this.running = true;\n    const log = getLogger();\n\n    while (this.pending.size > 0) {\n      const [path, job] = this.pending.entries().next().value as [string, IndexJob];\n      this.pending.delete(path);\n      try {\n        await this.process(job);\n      } catch (err) {\n        log.warn({ filePath: job.filePath, err }, \"index-queue: job failed\");\n      }\n    }\n\n    this.running = false;\n    const resolvers = this.idleResolvers;\n    this.idleResolvers = [];\n    for (const r of resolvers) r();\n  }\n}\n","import type { NameSlots } from \"./session-name\";\n\n// ─── Primitives ─────────────────────────────────────────────────────\n\nexport type MessageSender = \"user\" | \"assistant\";\nexport type Include = \"all\" | \"conversations\" | \"subagents\" | \"teammates\";\nexport type View = \"flat\" | \"tree\" | \"grouped\";\nexport type SortOrder = \"recent\" | \"oldest\" | \"messages-desc\" | \"messages-asc\" | \"alpha\";\n\nexport const VALID_SORT_ORDERS: SortOrder[] = [\n  \"recent\",\n  \"oldest\",\n  \"messages-desc\",\n  \"messages-asc\",\n  \"alpha\",\n];\n\n// ─── Profile ────────────────────────────────────────────────────────\n\nexport interface Profile {\n  id: string;\n  label: string;\n  configDir: string;\n  enabled: boolean;\n  emoji?: string;\n  scanHistory?: boolean;\n}\n\n// ─── Content Tiers ──────────────────────────────────────────────────\n\nexport interface ContentTier {\n  name: string;\n  previewMax: number;\n  snippetMax: number;\n}\n\n// ─── ConversationMeta (full superset) ───────────────────────────────\n\nexport interface MessageSnapshot {\n  text: string;\n  timestamp: string;\n}\n\nexport type ProviderName = \"claude-code\" | \"codex-cli\" | \"cursor\" | \"copilot\";\n\nexport interface ConversationMeta {\n  id: string;\n  filePath: string;\n  sessionId: string;\n  // Which provider produced this meta. Optional/additive: existing claude-code\n  // metas default to \"claude-code\" when unset.\n  provider?: ProviderName;\n  // Distinguishes plain chat conversations from task-oriented logs (Codex).\n  kind?: \"conversation\" | \"task\";\n  // Provider-native session id when it differs from the (non-unique) sessionId.\n  externalSessionId?: string;\n  sessionName: string;\n  // The slots sessionName was resolved from (resolveSessionName(nameSlots).name),\n  // so a host can merge further candidates through applyNameCandidate.\n  nameSlots?: NameSlots;\n  projectPath: string;\n  projectName: string;\n  account: string;\n  timestamp: string;\n  messageCount: number;\n  lastMessageSender: MessageSender;\n  preview: string;\n  contentSnippet: string;\n  gitBranch: string | null;\n  model: string | null;\n  isSubagent: boolean;\n  // NOTE: despite the name, this holds the parent's JSONL FILE PATH, not an id\n  // — it is matched against `id` (also a file path) by the \"tree\" view. For the\n  // parent's actual session UUID use `parentSessionUuid` below.\n  parentSessionId: string | null;\n  // The subagent's OWN identity, distinct from `sessionId`: a Claude sidechain\n  // transcript carries its PARENT's id in the `sessionId` field, so `sessionId`\n  // alone collapses every sibling subagent (and the parent) onto one identity.\n  // Claude's `agentId`; undefined for Codex, which exposes no per-child id.\n  subagentId?: string;\n  // The parent conversation's session UUID (Claude: the sidechain line's\n  // `sessionId`; Codex: `thread_spawn.parent_thread_id`). Undefined on ordinary\n  // conversations and on subagents whose parent is not discoverable.\n  parentSessionUuid?: string;\n  isTeammate: boolean;\n  teamName: string | null;\n  toolNames: string[];\n  firstMessage: MessageSnapshot | null;\n  lastMessage: MessageSnapshot | null;\n  lastPrompt?: string;\n  // Set when a Cursor agent-transcripts file is a copy of another provider's\n  // session. isImportedFromCursor is reserved for Claude/Codex providers.\n  isImportedFromClaude?: boolean;\n  isImportedFromCodex?: boolean;\n  isImportedFromCursor?: boolean;\n}\n\n// ─── View Variants ──────────────────────────────────────────────────\n\nexport interface TreeConversation extends ConversationMeta {\n  subagents: ConversationMeta[];\n}\n\nexport interface GroupedConversations {\n  [groupKey: string]: ConversationMeta[];\n}\n\n// ─── Options ────────────────────────────────────────────────────────\n\nexport interface FileStatEntry {\n  mtimeMs: number;\n  size: number;\n}\n\nexport interface ScanOptions {\n  profiles?: Profile[];\n  // Providers to scan. Defaults to [\"claude-code\"]. Including \"codex-cli\"\n  // requires codexRoots; including \"cursor\" requires cursorRoots; including\n  // \"copilot\" requires copilotRoots. None glob $HOME by default. The live-PTY alias \"cursor-cli\" is accepted and\n  // stored as \"cursor\".\n  providers?: Array<ProviderName | \"cursor-cli\">;\n  // Absolute roots to discover Codex CLI history under (e.g. ~/.codex/sessions).\n  codexRoots?: string[];\n  // Absolute roots to discover Cursor agent-transcripts under\n  // (e.g. ~/.cursor/projects). Composer state.vscdb is not indexed, only\n  // read for names (see cursorStateDb).\n  cursorRoots?: string[];\n  // Absolute path of Cursor's IDE `state.vscdb` (its `composerData:<id>` rows name\n  // IDE chats). Opened read-only, one key lookup per chat; never read unless set.\n  // Process-wide once set, like the chat transcripts it names.\n  cursorStateDb?: string;\n  // Absolute roots to discover GitHub Copilot CLI sessions under\n  // (e.g. ~/.copilot/session-state). session-store.db is not read.\n  copilotRoots?: string[];\n  tier?: string;\n  tiers?: Record<string, ContentTier>;\n  include?: Include;\n  view?: View;\n  sort?: SortOrder;\n  since?: string;\n  project?: string;\n  account?: string;\n  limit?: number;\n  offset?: number;\n  onProgress?: (scanned: number, total: number) => void;\n  onBatch?: (metas: ConversationMeta[]) => void;\n  /** Known file stats from a previous scan. Files whose (mtimeMs, size) match\n   *  are skipped — the cached ConversationMeta is reused instead. */\n  statCache?: Map<string, { stat: FileStatEntry; meta: ConversationMeta }>;\n  /** Persistent mode only: bypass the dir-mtime discovery gate and force a\n   *  full glob of every profile's projectsDir, ignoring stored watermarks.\n   *  Use for an explicit user-triggered refresh that shouldn't trust the\n   *  gate's optimization. */\n  fullRescan?: boolean;\n}\n\nexport interface ScanResult {\n  conversations: ConversationMeta[] | TreeConversation[] | GroupedConversations;\n  total: number;\n  scanned: number;\n}\n\nexport interface SearchOptions extends ScanOptions {\n  fields?: string[];\n  // Restrict results to one provider. Accepts the live-PTY alias \"cursor-cli\".\n  provider?: string;\n}\n\n// Half-open [start, end) character range into a SearchMatch.snippet marking one\n// matched term, so a client can emphasise it without parsing markup. Ranges\n// follow FTS token boundaries, not the query string: a prefix search for\n// \"timeout\" highlights the whole token \"timeoutMs\".\nexport interface SearchHighlight {\n  start: number;\n  end: number;\n}\n\nexport interface SearchMatch {\n  field: string;\n  snippet: string;\n  // Present on body hits (field === \"content\"), where the excerpt comes from\n  // FTS5 snippet(). Absent on metadata hits from generateMatches().\n  highlights?: SearchHighlight[];\n}\n\nexport interface SearchResult {\n  meta: ConversationMeta;\n  score: number;\n  matches: SearchMatch[];\n}\n\nexport interface GetConversationOptions {\n  profiles?: Profile[];\n}\n\nexport interface GetConversationPageOptions {\n  beforeIndex?: number;\n  limit: number;\n}\n\nexport interface ConversationPage {\n  messages: ConversationMessage[];\n  total: number;\n  fromIndex: number;\n}\n\n// A page sliced directly from a single parsed JSONL file, carrying the parsed\n// Conversation alongside the window so a caller can build a full response\n// (meta + messages) from one parse, without a prior scan().\nexport interface SingleFilePage extends ConversationPage {\n  conversation: Conversation;\n}\n\n// ─── Full Conversation ──────────────────────────────────────────────\n\nexport interface TurnDuration {\n  durationMs: number;\n  messageCount: number;\n  uuid?: string;\n}\n\nexport interface DeferredToolsDeltaAttachment {\n  type: \"deferred_tools_delta\";\n  addedNames: string[];\n  addedLines: unknown[];\n  removedNames: string[];\n}\n\nexport type AttachmentSidecar =\n  | DeferredToolsDeltaAttachment\n  | { type: string; [key: string]: unknown };\n\nexport interface ToolUseBlock {\n  id: string;\n  name: string;\n  input: Record<string, unknown>;\n}\n\nexport interface ToolResultBlock {\n  toolUseId: string;\n  type:\n    | \"edit\"\n    | \"write\"\n    | \"read\"\n    | \"bash\"\n    | \"grep\"\n    | \"glob\"\n    | \"taskAgent\"\n    | \"taskCreate\"\n    | \"taskUpdate\"\n    | \"generic\";\n  content: Record<string, unknown>;\n  isError?: boolean;\n}\n\nexport interface TeamInfo {\n  teammateId: string;\n  summary?: string;\n  color?: string;\n}\n\nexport interface MessageMetadata {\n  model?: string;\n  stopReason?: string | null;\n  inputTokens?: number;\n  outputTokens?: number;\n  cacheReadTokens?: number;\n  cacheCreationTokens?: number;\n  gitBranch?: string;\n  version?: string;\n  toolUses?: string[];\n  toolUseBlocks?: ToolUseBlock[];\n  toolResults?: ToolResultBlock[];\n  teamName?: string;\n  teamInfo?: TeamInfo;\n}\n\nexport interface ConversationMessage {\n  role: MessageSender;\n  text: string;\n  timestamp: string;\n  uuid?: string;\n  metadata?: MessageMetadata;\n  isToolResult?: boolean;\n  isThinking?: boolean;\n  thinkingContent?: string;\n  thinkingSignature?: string;\n  parentUuid?: string | null;\n  requestId?: string;\n  promptId?: string;\n  isSidechain?: boolean;\n  permissionMode?: string;\n  hasImages?: boolean;\n  attachment?: AttachmentSidecar;\n  // Claude Code's generated compaction summary (\"This session is being\n  // continued…\"), written as a user row the user never typed. Kept in the\n  // message list so message indexes don't move; consumers render it apart.\n  isCompactSummary?: boolean;\n  // The compact_boundary row that precedes the summary.\n  compaction?: CompactionInfo;\n}\n\nexport interface CompactionInfo {\n  trigger?: string;\n  preTokens?: number;\n  postTokens?: number;\n}\n\nexport interface Conversation {\n  id: string;\n  filePath: string;\n  projectPath: string;\n  projectName: string;\n  sessionId: string;\n  sessionName: string;\n  messages: ConversationMessage[];\n  fullText: string;\n  timestamp: string;\n  messageCount: number;\n  account: string;\n  turnDurations?: TurnDuration[];\n  lastPrompt?: string;\n}\n"],"mappings":"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","names":["pino","FlexSearchModule","import_fs","import_path","Database","import_fs","import_path","import_path","getShortProjectName","import_fs","import_path","import_fs","import_promises","import_path","import_readline","import_path","fg","getShortProjectName","getShortProjectName","import_fast_glob","import_fs","import_promises","import_path","import_readline","fg","asString","getShortProjectName","import_fast_glob","import_fs","import_promises","import_path","import_readline","SUBAGENT_PATH_RE","fg","asString","fileMtimeIso","getShortProjectName","import_fast_glob","import_promises","fg","import_fs","import_os","import_path","import_path","import_fs","import_promises","import_fs","import_promises","yieldToEventLoop","yieldToEventLoop","getShortProjectName","import_crypto","import_fs","stat","import_fs","import_fs","import_readline","extractCodexText","import_better_sqlite3","import_fs","import_path","Database","import_promises","import_path","import_path","stat","meta","total","beforeIndex","fromIndex","EXCLUDED_SEGMENTS","chokidar","process","BATCH_SIZE","previous","resolvedAccount","provider","meta"]}