{"version":3,"file":"helpers-crDEr6S2.mjs","names":[],"sources":["../src/utils/hash.ts","../src/memory/chunker.ts","../src/memory/indexer/helpers.ts"],"sourcesContent":["/**\n * Shared hashing utilities. Centralises all SHA-256 usage so every module\n * obtains digests through the same function rather than inlining createHash.\n */\n\nimport { createHash } from \"node:crypto\";\n\n/**\n * Compute a SHA-256 hex digest of the given string.\n * Aliased as sha256File for compatibility with existing call-sites that use\n * that name to hash file contents.\n */\nexport function sha256(content: string): string {\n  return createHash(\"sha256\").update(content).digest(\"hex\");\n}\n\n/** Alias kept for backwards compatibility with memory/indexer call-sites. */\nexport const sha256File = sha256;\n","/**\n * Markdown text chunker for the PAI memory engine.\n *\n * Splits markdown files into overlapping text segments suitable for BM25\n * full-text indexing.  Respects heading boundaries where possible, falling\n * back to paragraph and sentence splitting when sections are large.\n */\n\nimport { sha256 } from \"../utils/hash.js\";\n\nexport interface Chunk {\n  text: string;\n  startLine: number;  // 1-indexed\n  endLine: number;    // 1-indexed, inclusive\n  hash: string;       // SHA-256 of text\n}\n\nexport interface ChunkOptions {\n  /** Approximate maximum tokens per chunk. Default 400. */\n  maxTokens?: number;\n  /** Overlap in tokens from the previous chunk. Default 80. */\n  overlap?: number;\n}\n\nconst DEFAULT_MAX_TOKENS = 400;\nconst DEFAULT_OVERLAP = 80;\n\n/**\n * Approximate token count using a words * 1.3 heuristic.\n * Matches the OpenClaw estimate approach.\n */\nexport function estimateTokens(text: string): number {\n  const wordCount = text.split(/\\s+/).filter(Boolean).length;\n  return Math.ceil(wordCount * 1.3);\n}\n\n// sha256 imported from utils/hash.ts\n\n// ---------------------------------------------------------------------------\n// Internal section / paragraph / sentence splitters\n// ---------------------------------------------------------------------------\n\n/**\n * A contiguous block of lines associated with an approximate token count.\n */\ninterface LineBlock {\n  lines: Array<{ text: string; lineNo: number }>;\n  tokens: number;\n}\n\n/**\n * Split content into sections delimited by ## or ### headings.\n * Each section starts at its heading line (or at line 1 for a preamble).\n */\nfunction splitBySections(\n  lines: Array<{ text: string; lineNo: number }>,\n): LineBlock[] {\n  const sections: LineBlock[] = [];\n  let current: Array<{ text: string; lineNo: number }> = [];\n\n  for (const line of lines) {\n    const isHeading = /^#{1,3}\\s/.test(line.text);\n    if (isHeading && current.length > 0) {\n      const text = current.map((l) => l.text).join(\"\\n\");\n      sections.push({ lines: current, tokens: estimateTokens(text) });\n      current = [];\n    }\n    current.push(line);\n  }\n\n  if (current.length > 0) {\n    const text = current.map((l) => l.text).join(\"\\n\");\n    sections.push({ lines: current, tokens: estimateTokens(text) });\n  }\n\n  return sections;\n}\n\n/**\n * Split a LineBlock by double-newline paragraph boundaries.\n */\nfunction splitByParagraphs(block: LineBlock): LineBlock[] {\n  const paragraphs: LineBlock[] = [];\n  let current: Array<{ text: string; lineNo: number }> = [];\n\n  for (const line of block.lines) {\n    if (line.text.trim() === \"\" && current.length > 0) {\n      // Empty line — potential paragraph boundary\n      const text = current.map((l) => l.text).join(\"\\n\");\n      paragraphs.push({ lines: [...current], tokens: estimateTokens(text) });\n      current = [];\n    } else {\n      current.push(line);\n    }\n  }\n\n  if (current.length > 0) {\n    const text = current.map((l) => l.text).join(\"\\n\");\n    paragraphs.push({ lines: current, tokens: estimateTokens(text) });\n  }\n\n  return paragraphs.length > 0 ? paragraphs : [block];\n}\n\n/**\n * Split a LineBlock by sentence boundaries (. ! ?) when even paragraphs are\n * too large.  Works character-by-character within joined lines.\n */\nfunction splitBySentences(block: LineBlock, maxTokens: number): LineBlock[] {\n  const fullText = block.lines.map((l) => l.text).join(\" \");\n  // Very rough sentence split — split on '. ', '! ', '? ' followed by uppercase\n  const sentenceRe = /(?<=[.!?])\\s+(?=[A-Z\"'])/g;\n  const sentences = fullText.split(sentenceRe);\n\n  const result: LineBlock[] = [];\n  let accText = \"\";\n  // We can't recover exact line numbers inside a single oversized paragraph,\n  // so we approximate using the block's start/end lines distributed evenly.\n  const startLine = block.lines[0]?.lineNo ?? 1;\n  const endLine = block.lines[block.lines.length - 1]?.lineNo ?? startLine;\n  const totalLines = endLine - startLine + 1;\n  const linesPerSentence = Math.max(1, Math.floor(totalLines / Math.max(1, sentences.length)));\n\n  let sentenceIdx = 0;\n  let approxLine = startLine;\n\n  const flush = () => {\n    if (!accText.trim()) return;\n    const endApprox = Math.min(approxLine + linesPerSentence - 1, endLine);\n    result.push({\n      lines: [{ text: accText.trim(), lineNo: approxLine }],\n      tokens: estimateTokens(accText),\n    });\n    approxLine = endApprox + 1;\n    accText = \"\";\n  };\n\n  for (const sentence of sentences) {\n    sentenceIdx++;\n    const candidateText = accText ? accText + \" \" + sentence : sentence;\n    if (estimateTokens(candidateText) > maxTokens && accText) {\n      flush();\n      accText = sentence;\n    } else {\n      accText = candidateText;\n    }\n  }\n  void sentenceIdx; // used only for iteration count\n  flush();\n\n  return result.length > 0 ? result : [block];\n}\n\n// ---------------------------------------------------------------------------\n// Overlap helper\n// ---------------------------------------------------------------------------\n\n/**\n * Extract the last `overlapTokens` worth of text from a list of previously\n * emitted chunks to prepend to the next chunk.\n */\nfunction buildOverlapPrefix(\n  chunks: Chunk[],\n  overlapTokens: number,\n): Array<{ text: string; lineNo: number }> {\n  if (overlapTokens <= 0 || chunks.length === 0) return [];\n\n  const lastChunk = chunks[chunks.length - 1];\n  if (!lastChunk) return [];\n\n  const lines = lastChunk.text.split(\"\\n\");\n  const kept: string[] = [];\n  let acc = 0;\n\n  for (let i = lines.length - 1; i >= 0; i--) {\n    const lineTokens = estimateTokens(lines[i] ?? \"\");\n    acc += lineTokens;\n    kept.unshift(lines[i] ?? \"\");\n    if (acc >= overlapTokens) break;\n  }\n\n  // Distribute overlap lines across the lastChunk's line range\n  const startLine = lastChunk.endLine - kept.length + 1;\n  return kept.map((text, idx) => ({ text, lineNo: Math.max(lastChunk.startLine, startLine + idx) }));\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Chunk a markdown file into overlapping segments for BM25 indexing.\n *\n * Strategy:\n *  1. Split by headings (##, ###) as natural boundaries.\n *  2. If a section exceeds maxTokens, split by paragraphs.\n *  3. If a paragraph still exceeds maxTokens, split by sentences.\n *  4. Apply overlap: each chunk includes the last `overlap` tokens from the\n *     previous chunk.\n */\n/**\n * Strip `<private>...</private>` blocks from content before indexing.\n * Content within these tags is excluded from memory — never stored or searched.\n */\nexport function stripPrivateTags(content: string): string {\n  return content.replace(/<private>[\\s\\S]*?<\\/private>/gi, \"\");\n}\n\nexport function chunkMarkdown(content: string, opts?: ChunkOptions): Chunk[] {\n  const maxTokens = opts?.maxTokens ?? DEFAULT_MAX_TOKENS;\n  const overlapTokens = opts?.overlap ?? DEFAULT_OVERLAP;\n\n  // Strip private content before indexing\n  content = stripPrivateTags(content);\n\n  if (!content.trim()) return [];\n\n  const rawLines = content.split(\"\\n\");\n  const lines: Array<{ text: string; lineNo: number }> = rawLines.map((text, idx) => ({\n    text,\n    lineNo: idx + 1, // 1-indexed\n  }));\n\n  // Step 1: section split\n  const sections = splitBySections(lines);\n\n  // Step 2 & 3: further split oversized sections\n  const finalBlocks: LineBlock[] = [];\n  for (const section of sections) {\n    if (section.tokens <= maxTokens) {\n      finalBlocks.push(section);\n      continue;\n    }\n    // Too big — split by paragraphs\n    const paras = splitByParagraphs(section);\n    for (const para of paras) {\n      if (para.tokens <= maxTokens) {\n        finalBlocks.push(para);\n        continue;\n      }\n      // Still too big — split by sentences\n      const sentences = splitBySentences(para, maxTokens);\n      finalBlocks.push(...sentences);\n    }\n  }\n\n  // Step 4: build final chunks with overlap\n  const chunks: Chunk[] = [];\n\n  for (const block of finalBlocks) {\n    if (block.lines.length === 0) continue;\n\n    // Build overlap prefix from previous chunks\n    const overlapLines = buildOverlapPrefix(chunks, overlapTokens);\n\n    // Combine overlap + block lines\n    const allLines = [...overlapLines, ...block.lines];\n    const text = allLines.map((l) => l.text).join(\"\\n\").trim();\n\n    if (!text) continue;\n\n    const startLine = block.lines[0]?.lineNo ?? 1;\n    const endLine = block.lines[block.lines.length - 1]?.lineNo ?? startLine;\n\n    chunks.push({\n      text,\n      startLine,\n      endLine,\n      hash: sha256(text),\n    });\n  }\n\n  return chunks;\n}\n","/**\n * Shared helpers for the PAI memory indexers.\n *\n * Contains utilities used by both the sync (SQLite) and async (StorageBackend)\n * indexer paths: hashing, chunk ID generation, directory walking, and path guards.\n */\n\nimport { readdirSync, existsSync } from \"node:fs\";\nimport { createHash } from \"node:crypto\";\nimport { sha256File } from \"../../utils/hash.js\";\nimport { join, normalize } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { basename } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Tier detection\n// ---------------------------------------------------------------------------\n\n/**\n * Classify a relative file path into one of the four memory tiers.\n *\n * Rules (in priority order):\n *  - MEMORY.md anywhere in memory/  → 'evergreen'\n *  - YYYY-MM-DD.md in memory/       → 'daily'\n *  - anything else in memory/       → 'topic'\n *  - anything in Notes/             → 'session'\n */\nexport function detectTier(\n  relativePath: string,\n): \"evergreen\" | \"daily\" | \"topic\" | \"session\" {\n  // Normalise to forward slashes and strip leading ./\n  const p = relativePath.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\");\n\n  // Notes directory → session tier\n  if (p.startsWith(\"Notes/\") || p === \"Notes\") {\n    return \"session\";\n  }\n\n  const fileName = basename(p);\n\n  // MEMORY.md (case-sensitive match) → evergreen\n  if (fileName === \"MEMORY.md\") {\n    return \"evergreen\";\n  }\n\n  // YYYY-MM-DD.md → daily\n  if (/^\\d{4}-\\d{2}-\\d{2}\\.md$/.test(fileName)) {\n    return \"daily\";\n  }\n\n  // Default for memory/ files\n  return \"topic\";\n}\n\n// ---------------------------------------------------------------------------\n// Hashing and chunk ID generation\n// ---------------------------------------------------------------------------\n\n// sha256File imported from ../../utils/hash.js\nexport { sha256File } from \"../../utils/hash.js\";\n\n/**\n * Generate a deterministic chunk ID from its coordinates.\n * Format: sha256(\"projectId:path:chunkIndex:startLine:endLine\")\n *\n * The chunkIndex (0-based position within the file) is included so that\n * chunks with approximated line numbers (e.g. from splitBySentences) never\n * produce colliding IDs even when multiple chunks share the same startLine/endLine.\n */\nexport function chunkId(\n  projectId: number,\n  path: string,\n  chunkIndex: number,\n  startLine: number,\n  endLine: number,\n): string {\n  return createHash(\"sha256\")\n    .update(`${projectId}:${path}:${chunkIndex}:${startLine}:${endLine}`)\n    .digest(\"hex\");\n}\n\n// ---------------------------------------------------------------------------\n// Event loop yield\n// ---------------------------------------------------------------------------\n\n/**\n * Yield to the Node.js event loop so that IPC server can process requests\n * during long index runs.\n *\n * Uses setTimeout(10ms) rather than setImmediate — the 10ms pause gives the\n * event loop enough time to accept and process incoming IPC connections\n * (socket data, new connections, etc.). Without this, synchronous ONNX\n * inference blocks IPC for the full duration of each embedding (~50-100ms\n * per chunk).\n */\nexport function yieldToEventLoop(): Promise<void> {\n  return new Promise((resolve) => setTimeout(resolve, 10));\n}\n\n// ---------------------------------------------------------------------------\n// Directory skip sets\n// ---------------------------------------------------------------------------\n\n/**\n * Directories to ALWAYS skip, at any depth, during any directory walk.\n * These are build artifacts, dependency trees, and VCS internals that\n * should never be indexed regardless of where they appear in the tree.\n */\nexport const ALWAYS_SKIP_DIRS = new Set([\n  // Version control\n  \".git\",\n  // Dependency directories (any language)\n  \"node_modules\",\n  \"vendor\",\n  \"Pods\",              // CocoaPods (iOS/macOS)\n  // Build / compile output\n  \"dist\",\n  \"build\",\n  \"out\",\n  \"DerivedData\",       // Xcode\n  \".next\",             // Next.js\n  // Python virtual environments and caches\n  \".venv\",\n  \"venv\",\n  \"__pycache__\",\n  // General caches\n  \".cache\",\n  \".bun\",\n  // Backup snapshots (Carbon Copy Cloner, Time Machine, etc.)\n  \"snaps\",\n  \".Trashes\",\n]);\n\n/**\n * Directories to skip when doing a root-level content scan.\n * These are either already handled by dedicated scans or should never be indexed.\n */\nexport const ROOT_SCAN_SKIP_DIRS = new Set([\n  \"memory\",\n  \"Notes\",\n  \".claude\",\n  \".DS_Store\",\n  // Everything in ALWAYS_SKIP_DIRS is also excluded at root level\n  ...ALWAYS_SKIP_DIRS,\n]);\n\n/**\n * Additional directories to skip at the content-scan level (first level below root).\n * These are common macOS/Linux home-directory or repo noise directories that are\n * never meaningful as project content.\n */\nexport const CONTENT_SCAN_SKIP_DIRS = new Set([\n  // macOS home directory standard folders\n  \"Library\",\n  \"Applications\",\n  \"Music\",\n  \"Movies\",\n  \"Pictures\",\n  \"Desktop\",\n  \"Downloads\",\n  \"Public\",\n  // Common dev noise\n  \"coverage\",\n  // Everything in ALWAYS_SKIP_DIRS is also excluded at this level\n  ...ALWAYS_SKIP_DIRS,\n]);\n\n// ---------------------------------------------------------------------------\n// Directory walkers\n// ---------------------------------------------------------------------------\n\n/**\n * Safety cap: maximum number of .md files collected per project scan.\n * Prevents runaway scans on huge root paths (e.g. home directory).\n * Projects with more files than this are scanned up to the cap only.\n */\nconst MAX_FILES_PER_PROJECT = 5_000;\n\n/**\n * Maximum recursion depth for directory walks.\n * Prevents deep traversal of large directory trees (e.g. development repos).\n * Depth 0 = the given directory itself (no recursion).\n * Value 6 allows: root → subdirs → sub-subdirs → ... up to 6 levels.\n * Sufficient for memory/, Notes/, and typical docs structures.\n */\nconst MAX_WALK_DEPTH = 6;\n\n/**\n * Recursively collect all .md files under a directory.\n * Returns absolute paths. Stops early if the accumulated count hits the cap\n * or if the recursion depth exceeds MAX_WALK_DEPTH.\n *\n * @param dir    Directory to scan.\n * @param acc    Shared accumulator array (mutated in place for early exit).\n * @param cap    Maximum number of files to collect (across all recursive calls).\n * @param depth  Current recursion depth (0 = the initial call).\n */\nexport function walkMdFiles(\n  dir: string,\n  acc?: string[],\n  cap = MAX_FILES_PER_PROJECT,\n  depth = 0,\n): string[] {\n  const results = acc ?? [];\n  if (!existsSync(dir)) return results;\n  if (results.length >= cap) return results;\n  if (depth > MAX_WALK_DEPTH) return results;\n\n  try {\n    for (const entry of readdirSync(dir, { withFileTypes: true })) {\n      if (results.length >= cap) break;\n      if (entry.isSymbolicLink()) continue;\n      // Skip known junk directories at every recursion depth\n      if (ALWAYS_SKIP_DIRS.has(entry.name)) continue;\n      const full = join(dir, entry.name);\n      if (entry.isDirectory()) {\n        walkMdFiles(full, results, cap, depth + 1);\n      } else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n        results.push(full);\n      }\n    }\n  } catch {\n    // Unreadable directory — skip\n  }\n  return results;\n}\n\n/**\n * Recursively collect all .md files under rootPath, excluding directories\n * that are already covered by dedicated scans (memory/, Notes/) and\n * common noise directories (.git, node_modules, etc.).\n *\n * Returns absolute paths for files NOT already handled by the specific scanners.\n * Stops collecting once MAX_FILES_PER_PROJECT is reached.\n */\nexport function walkContentFiles(rootPath: string): string[] {\n  if (!existsSync(rootPath)) return [];\n\n  const results: string[] = [];\n  try {\n    for (const entry of readdirSync(rootPath, { withFileTypes: true })) {\n      if (results.length >= MAX_FILES_PER_PROJECT) break;\n      if (entry.isSymbolicLink()) continue;\n      if (ROOT_SCAN_SKIP_DIRS.has(entry.name)) continue;\n      if (CONTENT_SCAN_SKIP_DIRS.has(entry.name)) continue;\n\n      const full = join(rootPath, entry.name);\n      if (entry.isDirectory()) {\n        walkMdFiles(full, results, MAX_FILES_PER_PROJECT);\n      } else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n        // Skip root-level MEMORY.md — handled by the dedicated evergreen scan\n        if (entry.name !== \"MEMORY.md\") {\n          results.push(full);\n        }\n      }\n    }\n  } catch {\n    // Unreadable directory — skip\n  }\n  return results;\n}\n\n// ---------------------------------------------------------------------------\n// Path safety guard\n// ---------------------------------------------------------------------------\n\n/** Paths that must never be indexed — system/temp dirs that can contain backup snapshots. */\nconst BLOCKED_ROOTS = new Set([\"/tmp\", \"/private/tmp\", \"/var\", \"/private/var\"]);\n\n/**\n * Returns true if rootPath should skip the recursive content scan.\n *\n * Skips content scanning for:\n *  - The home directory itself or any ancestor (too broad — millions of files)\n *  - Git repositories (code repos — index memory/ and Notes/ only, not all .md files)\n *\n * The content scan is still useful for Obsidian vaults, Notes folders, and\n * other doc-centric project trees where ALL markdown files are meaningful.\n *\n * The memory/, Notes/, and claude_notes_dir scans always run regardless.\n */\nexport function isPathTooBroadForContentScan(rootPath: string): boolean {\n  const normalized = normalize(rootPath);\n\n  // Block system/temp directories outright (CCC snapshots live here)\n  if (BLOCKED_ROOTS.has(normalized)) return true;\n  for (const blocked of BLOCKED_ROOTS) {\n    if (normalized.startsWith(blocked + \"/\")) return true;\n  }\n\n  const home = homedir();\n\n  // Skip the home directory itself or any ancestor of home\n  if (home.startsWith(normalized) || normalized === \"/\") {\n    return true;\n  }\n\n  // Skip home directory itself (depth 0)\n  if (normalized.startsWith(home)) {\n    const rel = normalized.slice(home.length).replace(/^\\//, \"\");\n    const depth = rel ? rel.split(\"/\").length : 0;\n    if (depth === 0) return true;\n  }\n\n  // Skip git repositories — content scan is only for doc-centric projects\n  // (Obsidian vaults, knowledge bases). Code repos use memory/ and Notes/ only.\n  if (existsSync(join(normalized, \".git\"))) {\n    return true;\n  }\n\n  return false;\n}\n\n// ---------------------------------------------------------------------------\n// Session title parser\n// ---------------------------------------------------------------------------\n\nconst SESSION_TITLE_RE = /^(\\d{4})\\s*-\\s*(\\d{4}-\\d{2}-\\d{2})\\s*-\\s*(.+)\\.md$/;\n\n/**\n * Parse a session title from a Notes filename.\n * Format: \"NNNN - YYYY-MM-DD - Descriptive Title.md\"\n * Returns a synthetic chunk text like \"Session #0086 2026-02-23: Pai Daemon Background Service\"\n * or null if the filename doesn't match the expected pattern.\n */\nexport function parseSessionTitleChunk(fileName: string): string | null {\n  const m = SESSION_TITLE_RE.exec(fileName);\n  if (!m) return null;\n  const [, num, date, title] = m;\n  return `Session #${num} ${date}: ${title}`;\n}\n\n/** Number of files to process before yielding to the event loop inside indexProject. */\nexport const INDEX_YIELD_EVERY = 10;\n"],"mappings":"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"}