@remnic/coding-graph 9.3.759
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -0
- package/dist/chunk-5I2DBHOQ.js +1042 -0
- package/dist/chunk-5I2DBHOQ.js.map +1 -0
- package/dist/chunk-CPYJACC5.js +1838 -0
- package/dist/chunk-CPYJACC5.js.map +1 -0
- package/dist/chunk-ZVCMIM4T.js +216 -0
- package/dist/chunk-ZVCMIM4T.js.map +1 -0
- package/dist/cypher/query-parser.d.ts +253 -0
- package/dist/cypher/query-parser.js +17 -0
- package/dist/cypher/query-parser.js.map +1 -0
- package/dist/graph-schema.d.ts +84 -0
- package/dist/graph-schema.js +17 -0
- package/dist/graph-schema.js.map +1 -0
- package/dist/graph-store.d.ts +938 -0
- package/dist/graph-store.js +16 -0
- package/dist/graph-store.js.map +1 -0
- package/dist/index.d.ts +1953 -0
- package/dist/index.js +3509 -0
- package/dist/index.js.map +1 -0
- package/grammars/tree-sitter-bash.wasm +0 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-ruby.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +79 -0
- package/src/co-change.test.ts +175 -0
- package/src/co-change.ts +167 -0
- package/src/cypher/query-parser.test.ts +1107 -0
- package/src/cypher/query-parser.ts +1692 -0
- package/src/detect-changes.test.ts +533 -0
- package/src/detect-changes.ts +367 -0
- package/src/engine/emit.ts +556 -0
- package/src/engine/engine.test.ts +1417 -0
- package/src/engine/engine.ts +182 -0
- package/src/engine/extractors.ts +486 -0
- package/src/engine/fixtures.ts +364 -0
- package/src/engine/language-sniff.ts +56 -0
- package/src/engine/parser-backend.ts +206 -0
- package/src/engine/utf16-offsets.ts +68 -0
- package/src/git-invoker.test.ts +116 -0
- package/src/git-invoker.ts +426 -0
- package/src/graph-schema.test.ts +541 -0
- package/src/graph-schema.ts +383 -0
- package/src/graph-store-pr2.test.ts +1879 -0
- package/src/graph-store.test.ts +1420 -0
- package/src/graph-store.ts +3489 -0
- package/src/index-status.test.ts +303 -0
- package/src/index-status.ts +135 -0
- package/src/index.ts +384 -0
- package/src/lsp/byte-position.ts +173 -0
- package/src/lsp/characterization.test.ts +174 -0
- package/src/lsp/client.test.ts +275 -0
- package/src/lsp/client.ts +484 -0
- package/src/lsp/config.ts +219 -0
- package/src/lsp/degradation.ts +86 -0
- package/src/lsp/fixtures/fake-server.mjs +198 -0
- package/src/lsp/framing.test.ts +180 -0
- package/src/lsp/framing.ts +177 -0
- package/src/lsp/resolution.test.ts +497 -0
- package/src/lsp/resolution.ts +483 -0
- package/src/lsp/status.ts +140 -0
- package/src/lsp/types.ts +167 -0
- package/src/reindex.test.ts +1038 -0
- package/src/reindex.ts +908 -0
- package/src/row-types.ts +45 -0
- package/src/semantic/canonical-text.test.ts +150 -0
- package/src/semantic/canonical-text.ts +219 -0
- package/src/semantic/config.ts +235 -0
- package/src/semantic/index.ts +78 -0
- package/src/semantic/minhash.test.ts +197 -0
- package/src/semantic/minhash.ts +261 -0
- package/src/semantic/semantic-query.ts +173 -0
- package/src/semantic/semantic.test.ts +1315 -0
- package/src/semantic/similarity.ts +268 -0
- package/src/semantic/types.ts +145 -0
- package/src/semantic/vectors.ts +235 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/engine/engine.ts","../src/engine/parser-backend.ts","../src/engine/emit.ts","../src/engine/extractors.ts","../src/engine/utf16-offsets.ts","../src/engine/language-sniff.ts","../src/reindex.ts","../src/detect-changes.ts","../src/co-change.ts","../src/index-status.ts","../src/git-invoker.ts","../src/lsp/client.ts","../src/lsp/framing.ts","../src/lsp/degradation.ts","../src/lsp/config.ts","../src/lsp/resolution.ts","../src/lsp/byte-position.ts","../src/lsp/status.ts","../src/semantic/config.ts","../src/semantic/canonical-text.ts","../src/semantic/minhash.ts","../src/semantic/vectors.ts","../src/semantic/similarity.ts","../src/semantic/semantic-query.ts"],"sourcesContent":["/**\n * @remnic/coding-graph — symbol-extraction engine + SQLite knowledge-graph\n * store for codebase memory.\n *\n * À-la-carte optional companion of @remnic/core (CLAUDE.md rule 57).\n *\n * This package unifies two PR1 surfaces:\n * - The web-tree-sitter engine scaffold (#1551 step 1): the package and\n * its build wiring exist; the engine public surface is declared and\n * the placeholder factory throws a tagged\n * `CodingGraphError(\"not_implemented\", …)`. The real backend lands in\n * #1551 PR2.\n * - The SQLite knowledge-graph store (#1552 PR1): versioned schema + the\n * write pipeline (upsert/drop file batches, node-id derivation,\n * dangling-edge accounting). Traversal, search, dead-code, and the\n * openCypher subset land in #1552 PR2/PR3.\n *\n * Type-source direction:\n * The contract types (CodingGraphEngine, FileIR, etc.) and the\n * TIER_1_LANGUAGES / CODING_GRAPH_ENGINE_VERSION constants live in\n * @remnic/core (packages/remnic-core/src/coding/coding-graph-types.ts,\n * re-exported from the main index). This package imports them and\n * implements against them; it does NOT redefine them. That keeps a\n * single source of truth so updating the engine version in one place\n * keeps every consumer in lockstep (Cursor Bugbot low-severity on\n * PR #1588 round 2: \"ENGINE_VERSION duplicated not imported\").\n *\n * @remnic/coding-graph declares @remnic/core as both `peerDependencies`\n * and `devDependencies: \"workspace:*\"` in its package.json, so the\n * pnpm workspace link exists and the `import from \"@remnic/core\"`\n * below resolves in development.\n *\n * The store modules (graph-schema, graph-store, row-types) are local to\n * this package; they import `openBetterSqlite3` from\n * `@remnic/core/runtime/better-sqlite` so the native-binding lifecycle\n * is paid for once there (rule 23/38: do not invent a new pattern).\n *\n * IR-type re-export policy:\n * graph-store.ts imports the core IR contract types (`FileIR`,\n * `SymbolIR`, etc.) from `@remnic/core/coding/coding-graph-types`\n * and re-exports them so existing `import { type FileIR } from\n * \"./graph-store.js\"` call-sites continue to resolve. The store\n * does NOT redefine these types — it derives from the core contract\n * so PR2 callers can pass `ParseResult.ir` directly\n * (chatgpt-codex-connector P2: 'Derive store FileIR from the core\n * parser contract'). At the package root, `FileIR`/`SymbolIR`\n * resolve to the @remnic/core contract types re-exported below;\n * the store-specific `StoreFileIR` (FileIR + edges extension) and\n * `EdgeIR` are re-exported from the root via graph-store.\n */\n\nimport {\n CODING_GRAPH_ENGINE_VERSION,\n TIER_1_LANGUAGES,\n type CodingGraphEngine,\n type CodingGraphErrorCode,\n type CodingGraphLanguage,\n type CreateCodingGraphEngineOptions,\n type FileIR,\n type ParseFileInput,\n type ParseResult,\n type SymbolIR,\n} from \"@remnic/core\";\n\n// ---------------------------------------------------------------------------\n// Engine version — single source of truth lives in @remnic/core as\n// `CODING_GRAPH_ENGINE_VERSION`. We re-export it under the conventional\n// `ENGINE_VERSION` name (the dynamic-import loader in @remnic/core\n// validates the shape using this field name) AND keep the core alias\n// available so any consumer that wants the local name or the core\n// name gets the same value. Updating the constant in core propagates\n// here automatically.\n// ---------------------------------------------------------------------------\n\n/** Public engine version. Imported from @remnic/core (single source of truth). */\nexport const ENGINE_VERSION = CODING_GRAPH_ENGINE_VERSION;\n\n/** Core-alias re-export so callers can use either name. */\nexport { CODING_GRAPH_ENGINE_VERSION };\n\n// ---------------------------------------------------------------------------\n// Tier-1 language list re-export. The list itself lives in @remnic/core\n// (single source of truth) so consumers that reach the optional package\n// via the loader get the same shape they would get from core directly.\n// ---------------------------------------------------------------------------\nexport { TIER_1_LANGUAGES };\nexport type { CodingGraphLanguage };\n\n// ---------------------------------------------------------------------------\n// Tagged error — `code` is the load-bearing signal for programmatic\n// detection (see PR2 contract).\n// ---------------------------------------------------------------------------\n\nexport type { CodingGraphErrorCode } from \"@remnic/core\";\n\n/**\n * Thrown by `createCodingGraphEngine` while the real implementation is\n * being landed. It is *not* a generic Error — the `code` field is the\n * load-bearing signal for programmatic detection (see PR2 contract).\n */\nexport class CodingGraphError extends Error {\n readonly code: CodingGraphErrorCode;\n readonly engineVersion: string;\n\n constructor(\n code: CodingGraphErrorCode,\n message: string,\n engineVersion: string = ENGINE_VERSION,\n ) {\n super(message);\n this.name = \"CodingGraphError\";\n this.code = code;\n this.engineVersion = engineVersion;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Engine implementation (PR2): WASM tree-sitter parser + tier-1 extractors.\n// Delegates to ./engine/engine.js which wires the ParserBackend, per-language\n// .scm queries, and the FileIR emitter.\n// ---------------------------------------------------------------------------\n\nexport { createCodingGraphEngine } from \"./engine/engine.js\";\n\n// Backwards-compat: re-export the engine module's internal types for any\n// consumer that reached into the implementation.\nexport type { ParserBackend } from \"./engine/parser-backend.js\";\nexport { WasmTreeSitterBackend } from \"./engine/parser-backend.js\";\n\n// ---------------------------------------------------------------------------\n// Re-export the contract types so the public surface is stable whether a\n// consumer reaches into @remnic/coding-graph or @remnic/core. `export type`\n// guarantees these are erased at runtime — no double-emit.\n// ---------------------------------------------------------------------------\n\nexport type {\n CodingGraphEngine,\n CreateCodingGraphEngineOptions,\n FileIR,\n ParseFileInput,\n ParseResult,\n SymbolIR,\n};\n\n// ---------------------------------------------------------------------------\n// SQLite knowledge-graph store (#1552 PR1 + PR2): versioned schema, write\n// pipeline, and PR2 read primitives (traverse / searchGraph / schemaStats /\n// deadCode / snippetFor). Re-exported from the package root so consumers\n// can import `import { GraphStore } from \"@remnic/coding-graph\"` (the\n// subpath exports `./graph-schema` and `./graph-store` remain available\n// for callers that want only one half). Only non-colliding store types\n// are re-exported here — see the file header's IR-type policy note.\n// ---------------------------------------------------------------------------\n\nexport {\n CODING_GRAPH_SCHEMA_VERSION,\n EDGE_PROVENANCE_VALUES,\n applyCodingGraphSchema,\n readSchemaVersion,\n isEdgeProvenance,\n type EdgeProvenance,\n} from \"./graph-schema.js\";\n\nexport {\n GraphStore,\n nodeIdFor,\n DEFAULT_TRAVERSE_PATHS_MAX,\n MAX_TRAVERSE_PATHS_HOPS,\n DEAD_CODE_EXCLUSION,\n type ByteSpan,\n type DeadCodeHit,\n type DeadCodeResult,\n type EdgeIR,\n type ExportIR,\n type GraphStoreFailure,\n type GraphStoreFailureCode,\n type GraphStoreOptions,\n type ImportIR,\n type NodeIdInput,\n type SchemaStats,\n type SchemaStatsResult,\n type SearchHit,\n type SearchQuery,\n type SearchResult,\n type SnippetFailureCode,\n type SnippetQuery,\n type SnippetResult,\n type SnippetSuccess,\n type StoreFileIR,\n type SymbolKind,\n type TraverseDirection,\n type TraverseHit,\n type TraverseQuery,\n type TraversePathHit,\n type TraversePathsQuery,\n type TraversePathsResult,\n type TraverseResult,\n type ReadCoChangeEdge,\n type ReadCoChangesResult,\n type ReadFileHashesResult,\n type ReadMetaResult,\n type UpsertBatchResult,\n type UpsertEdgesResult,\n type UpsertEdgesSuccess,\n type UpsertResult,\n type UpsertSuccess,\n} from \"./graph-store.js\";\n// ---------------------------------------------------------------------------\n// PR3 (issue #1553): incremental git-based reindex, detect_changes + blast\n// radius, co-change mining, and index-status reporting. Re-exported from\n// the package root so consumers can import them alongside the store.\n// ---------------------------------------------------------------------------\n\nexport {\n planReindex,\n executeReindex,\n readLastIndexedHead,\n readFileHashes,\n hashContent,\n META_KEY_LAST_HEAD,\n META_KEY_PENDING_PARSE_FAILURES,\n type ParseFileFn,\n type ReadFileFn,\n type ReindexGitFacts,\n type ReindexPlan,\n type ReindexResult,\n type ReindexState,\n} from \"./reindex.js\";\n\nexport {\n classifyRisk,\n computeBlastRadius,\n findDirectlyAffectedSymbols,\n byteSpanToLines,\n rangesOverlap,\n BLAST_RADIUS_EDGE_TYPES,\n FAN_IN_ESCALATION_THRESHOLD,\n DEFAULT_BLAST_RADIUS_DEPTH,\n type AffectedSymbol,\n type BlastRadiusResult,\n type DetectChangesResult,\n type RiskLevel,\n} from \"./detect-changes.js\";\n\nexport {\n mineCoChangeEdges,\n mineAndStoreCoChanges,\n DEFAULT_CO_CHANGE_CONFIG,\n type CoChangeConfig,\n type CoChangeEdge,\n type MineCoChangesResult,\n} from \"./co-change.js\";\n\nexport {\n getIndexStatus,\n type IndexStatus,\n type IndexStatusMode,\n} from \"./index-status.js\";\n\nexport {\n defaultCodingGitInvoker,\n parseNameStatus,\n parseHunks,\n parseLogFiles,\n type CodingGitInvoker,\n type DiffHunk,\n type GitFailure,\n type LogFilesEntry,\n type NameStatusEntry,\n} from \"./git-invoker.js\";\n\n// ---------------------------------------------------------------------------\n// PR3 (issue #1552): openCypher read-subset — parser + executor. Re-exported\n// from the package root so consumers can `import { executeCypher } from\n// \"@remnic/coding-graph\"` (the subpath export `./cypher` remains for callers\n// that want only this layer).\n// ---------------------------------------------------------------------------\n\nexport {\n executeCypher,\n executeAst,\n parseCypher,\n VALID_CYPHER_LABELS,\n type CypherAst,\n type CypherFailure,\n type CypherFailureCode,\n type CypherNodeValue,\n type CypherParseResult,\n type CypherResult,\n type CypherRow,\n type CypherScalar,\n type CypherSuccess,\n type CypherValue,\n} from \"./cypher/query-parser.js\";\n// ---------------------------------------------------------------------------\n// PR (issue #1555): Phase B type resolution — LSP client layer over\n// installed language servers. Minimal JSON-RPC-over-stdio client, server\n// registry, resolution pass, and status surfacing. All behind\n// `codingGraph.lsp.enabled` (default false — rule 30/48).\n// ---------------------------------------------------------------------------\n\nexport {\n LspClient,\n pathToUri,\n uriToPath,\n type LspClientOptions,\n} from \"./lsp/client.js\";\n\nexport {\n LspFrameDecoder,\n encodeLspFrame,\n type FrameDecodeError,\n type FrameDecodeErrorKind,\n type FrameDecodeResult,\n} from \"./lsp/framing.js\";\n\nexport {\n DEFAULT_LSP_CONFIG,\n DEFAULT_LSP_MAX_REQUESTS_PER_RUN,\n DEFAULT_LSP_TIMEOUT_MS,\n parseLspConfig,\n readLspEnabledEnv,\n type LspConfig,\n type LspConfigParseResult,\n type LspServerLaunchSpec,\n type LspServerOverrides,\n} from \"./lsp/config.js\";\n\nexport {\n lspDegradation,\n type LspBackend,\n type LspDegradation,\n type LspDegradationCode,\n type LspResult,\n} from \"./lsp/degradation.js\";\n\nexport {\n executeLspResolution,\n mapLocationToNode,\n planLspUpgrades,\n type EdgeUpgrade,\n type LspBudget,\n type NodeLocator,\n type PlanResult,\n type PlannedLspRequest,\n type ResolutionResult,\n type ResolveOptions,\n type UnresolvedCallSite,\n} from \"./lsp/resolution.js\";\n\nexport {\n formatLspStatusLine,\n getLspStatus,\n resolutionResultToStatusMaps,\n type LspStatusEntry,\n type LspStatusInput,\n} from \"./lsp/status.js\";\nexport {\n buildLineOffsetMap,\n byteOffsetToPosition,\n positionToByteOffset,\n type LineOffsetMap,\n} from \"./lsp/byte-position.js\";\n\nexport type {\n LspInitializeParams,\n LspInitializeResult,\n LspLocation,\n LspPosition,\n LspRange,\n LspServerCapabilities,\n LspTextDocumentItem,\n LspTextDocumentPositionParams,\n} from \"./lsp/types.js\";\n\n\n// ---------------------------------------------------------------------------\n// Semantic layer (issue #1556): symbol embeddings, SIMILAR_TO near-clone\n// edges, and semantic_query. OFF by default (SemanticConfig.enabled = false).\n// The layer builds on @remnic/core's host embedding provider + fallback —\n// no new embedding stack. See ./semantic/index.ts for the full surface.\n// ---------------------------------------------------------------------------\n\nexport * from \"./semantic/index.js\";\n","/**\n * Engine factory — constructs a `CodingGraphEngine` backed by the WASM\n * tree-sitter parser + tier-1 extractors.\n *\n * Rule 11 (no module-level state): the backend, query caches, and parser\n * instances all live on the returned engine object. Two `createCodingGraphEngine`\n * calls in the same process have fully isolated state.\n *\n * Rule 30/48: `codingGraph.enabled` defaults `false` in @remnic/core; the\n * loader is never invoked when disabled, so this factory is only reached when\n * the user has explicitly opted in.\n */\nimport {\n CODING_GRAPH_ENGINE_VERSION,\n TIER_1_LANGUAGES,\n type CodingGraphEngine,\n type CodingGraphLanguage,\n type CreateCodingGraphEngineOptions,\n type FileIR,\n type ParseFileInput,\n type ParseResult,\n} from \"@remnic/core\";\n\nimport { WasmTreeSitterBackend, type ParserBackend } from \"./parser-backend.js\";\nimport { emitFileIR } from \"./emit.js\";\nimport { sniffLanguage, isTier1Language } from \"./language-sniff.js\";\n\n/**\n * The internal engine object. Implements `CodingGraphEngine` from @remnic/core.\n */\nclass CodingGraphEngineImpl implements CodingGraphEngine {\n readonly engineVersion: string;\n readonly supportedLanguages: readonly CodingGraphLanguage[];\n private readonly backend: ParserBackend;\n private disposed = false;\n /**\n * Serialize parse calls. The backend's single Parser instance is shared\n * across all languages, so concurrent setLanguage/parse calls would race.\n * Each parseFile call awaits the previous before touching the parser.\n */\n private parseChain: Promise<void> = Promise.resolve();\n\n constructor(backend: ParserBackend) {\n this.engineVersion = CODING_GRAPH_ENGINE_VERSION;\n this.supportedLanguages = TIER_1_LANGUAGES;\n this.backend = backend;\n }\n async parseFile(input: ParseFileInput): Promise<ParseResult> {\n if (this.disposed) {\n return {\n ok: false,\n code: \"parse_failed\",\n path: input.path,\n message: \"engine has been disposed\",\n };\n }\n\n // Serialize: wait for any in-flight parse before touching the shared parser.\n const previous = this.parseChain;\n let release!: () => void;\n this.parseChain = new Promise<void>((resolve) => {\n release = resolve;\n });\n await previous;\n // Re-check disposed: another caller may have disposed the engine\n // while we were waiting for the previous parse to finish.\n if (this.disposed) {\n release();\n return {\n ok: false,\n code: \"parse_failed\",\n path: input.path,\n message: \"engine has been disposed\",\n };\n }\n try {\n return await this.doParseFile(input);\n } finally {\n release();\n }\n }\n\n private async doParseFile(input: ParseFileInput): Promise<ParseResult> {\n\n // Resolve the language — explicit override wins, then sniff from path.\n const lang: CodingGraphLanguage | null =\n input.language ?? sniffLanguage(input.path);\n if (!lang || !isTier1Language(lang)) {\n return {\n ok: false,\n code: \"parse_failed\",\n path: input.path,\n message: `unsupported language for path \"${input.path}\"; ` +\n `supported extensions map to: ${TIER_1_LANGUAGES.join(\", \")}`,\n };\n }\n\n // Ensure the grammar is loaded (rule 51 — error, not silent skip).\n try {\n await this.backend.ensureLanguage(lang);\n } catch (err) {\n return {\n ok: false,\n code: \"parse_failed\",\n path: input.path,\n message: `failed to load grammar for ${lang}: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n\n // web-tree-sitter parses UTF-16 internally, so node offsets are UTF-16\n // code-unit offsets. We convert to UTF-8 byte offsets after extraction\n // (issue #1659 item 3 — multibyte span accuracy).\n const contentStr = Buffer.from(input.content).toString(\"utf-8\");\n const tree = this.backend.parse(lang, contentStr);\n if (!tree) {\n return {\n ok: false,\n code: \"parse_failed\",\n path: input.path,\n message: `tree-sitter returned null for ${lang} (grammar may be corrupt)`,\n };\n }\n\n try {\n const root = tree.rootNode;\n const language = this.backend.getLanguage(lang);\n if (!language) {\n return {\n ok: false,\n code: \"parse_failed\",\n path: input.path,\n message: `language object unavailable for ${lang}`,\n };\n }\n\n const ir: FileIR = emitFileIR(\n input.path,\n lang,\n input.content,\n root,\n language,\n contentStr,\n );\n return { ok: true, ir };\n } catch (err) {\n // Rule 44: extraction/query errors surface as tagged parse_failed,\n // not thrown exceptions that abort batch reindex.\n return {\n ok: false,\n code: \"parse_failed\",\n path: input.path,\n message: `extraction failed for ${lang}: ${err instanceof Error ? err.message : String(err)}`,\n };\n } finally {\n tree.delete();\n }\n }\n\n async dispose(): Promise<void> {\n if (this.disposed) return;\n this.disposed = true;\n // Wait for any in-flight parse to finish before tearing down the\n // backend. Without this, a parseFile call that already passed the\n // disposed guard can touch freed parser/grammar state.\n await this.parseChain;\n await this.backend.dispose();\n }\n}\n\n/**\n * Construct a coding-graph engine. PR2 implementation: real WASM tree-sitter\n * parser with tier-1 extractors. No longer throws `not_implemented`.\n *\n * @throws never — load failures surface as `{ ok: false, code: \"parse_failed\" }`\n * per-file, not as constructor exceptions.\n */\nexport function createCodingGraphEngine(\n _options: CreateCodingGraphEngineOptions = {},\n): CodingGraphEngine {\n const backend = new WasmTreeSitterBackend();\n return new CodingGraphEngineImpl(backend);\n}\n","/**\n * ParserBackend — the abstraction layer between the tree-sitter parsing\n * engine and the per-language extractors.\n *\n * Design rationale (issue #1551): everything goes through this interface so\n * that a native `node-tree-sitter` backend (or the external C binary via the\n * subprocess provider) can slot in later without touching extractors. The WASM\n * backend (`WasmTreeSitterBackend`) is the default; it costs ~2–3× parse speed\n * vs native but has no per-platform build toolchain — the class of pain\n * documented in #1518 and #1538.\n *\n * Rule 11 (no module-level caches): every parser instance, grammar cache, and\n * initialization flag lives on the *engine instance*, never at module scope.\n * This means two engines in the same process have fully isolated state.\n */\nimport { Parser, Language, type Node as TSNode, type Tree as TSTree, type Language as TSLanguage } from \"web-tree-sitter\";\nimport { fileURLToPath } from \"node:url\";\nimport path from \"node:path\";\nimport { statSync } from \"node:fs\";\nimport type { CodingGraphLanguage } from \"@remnic/core\";\n\n/** Re-export tree-sitter types so extractors don't depend on web-tree-sitter directly. */\nexport type { TSNode, TSTree, TSLanguage };\n\n/**\n * The opaque interface a backend exposes to extractors. Each method is keyed\n * per-instance so the engine lifecycle (`dispose()`) can clean everything up.\n */\nexport interface ParserBackend {\n /** Initialize the backend runtime (e.g. load the WASM core). Idempotent per instance. */\n init(): Promise<void>;\n /** Ensure the grammar for `lang` is loaded and a parser is ready. Idempotent per instance. */\n ensureLanguage(lang: CodingGraphLanguage): Promise<void>;\n /**\n * Parse `content` (a UTF-8 string) using the grammar for `lang`. Returns the\n * tree-sitter Tree on success, or `null` if the grammar is not loaded / the\n * parser cannot produce a tree. Node offsets are UTF-16 code-unit offsets;\n * callers must convert to UTF-8 byte offsets for multibyte content (#1659).\n */\n parse(lang: CodingGraphLanguage, content: string): TSTree | null;\n /** Return the loaded Language for `lang`, or null if not loaded. */\n getLanguage(lang: CodingGraphLanguage): TSLanguage | null;\n /** Release all parsers, grammars, and the WASM runtime. Safe to call multiple times. */\n dispose(): Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// Grammar directory resolution.\n//\n// The .wasm grammar files ship inside the package at `grammars/`. Their\n// location relative to the running module varies by context:\n// - tsx source run (src/engine/): ../../grammars\n// - tsup dist (dist/): ../grammars\n// - published package (dist/): ../grammars\n// We probe candidates and pick the first that exists. This avoids hard-coding\n// a depth that breaks under different bundler layouts.\n// ---------------------------------------------------------------------------\n\nfunction resolveGrammarDir(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n const candidates = [\n path.join(here, \"..\", \"grammars\"), // dist/ → ../grammars\n path.join(here, \"..\", \"..\", \"grammars\"), // src/engine/ → ../../grammars\n path.join(here, \"grammars\"), // flat layout fallback\n ];\n for (const candidate of candidates) {\n try {\n if (statSync(candidate).isDirectory()) return candidate;\n } catch {\n // try next candidate\n }\n }\n throw new Error(\n `coding-graph: could not locate the grammars/ directory from ${here}. ` +\n `Expected one of: ${candidates.join(\", \")}`,\n );\n}\n\n/**\n * web-tree-sitter 0.25 WASM backend.\n *\n * Per rule 11, all mutable state (init flag, parser instance, grammar cache)\n * lives on the instance — never at module scope. The backend is constructed by\n * `createCodingGraphEngine` and disposed alongside the engine.\n */\nexport class WasmTreeSitterBackend implements ParserBackend {\n private initialized = false;\n private initializing: Promise<void> | null = null;\n private parser: Parser | null = null;\n private readonly languages = new Map<CodingGraphLanguage, Language>();\n private readonly loadingLanguages = new Map<CodingGraphLanguage, Promise<void>>();\n private grammarDir = \"\";\n private grammarDirResolved = false;\n private readonly grammarDirHint: string | null;\n\n constructor(grammarDir?: string) {\n // Defer resolveGrammarDir() to first use so that a missing grammars/\n // directory surfaces as a per-file parse_failed (rule 44) rather than\n // a constructor exception that bricks createCodingGraphEngine.\n this.grammarDirHint = grammarDir ?? null;\n }\n\n private getGrammarDir(): string {\n if (this.grammarDirResolved) return this.grammarDir;\n this.grammarDir = this.grammarDirHint ?? resolveGrammarDir();\n this.grammarDirResolved = true;\n return this.grammarDir;\n }\n\n async init(): Promise<void> {\n if (this.initialized) return;\n if (this.initializing) return this.initializing;\n this.initializing = (async () => {\n try {\n await Parser.init();\n this.parser = new Parser();\n this.initialized = true;\n } finally {\n // Clear the in-flight promise on BOTH success and failure. Without\n // this, a transient Parser.init() rejection leaves the rejected\n // promise cached in `initializing`, so every subsequent call\n // re-awaits the same failure instead of retrying.\n this.initializing = null;\n }\n })();\n return this.initializing;\n }\n\n async ensureLanguage(lang: CodingGraphLanguage): Promise<void> {\n await this.init();\n if (this.languages.has(lang)) return;\n if (this.loadingLanguages.has(lang)) return this.loadingLanguages.get(lang)!;\n const p = (async () => {\n try {\n const wasmPath = path.join(this.getGrammarDir(), grammarFileName(lang));\n const language = await Language.load(wasmPath);\n this.languages.set(lang, language);\n } finally {\n // Drop the in-flight promise on BOTH success and failure. Without\n // this, a transient Language.load() rejection leaves the rejected\n // promise cached in `loadingLanguages`, permanently bricking that\n // language until dispose() — the next call would re-await the same\n // failure instead of retrying the load.\n this.loadingLanguages.delete(lang);\n }\n })();\n this.loadingLanguages.set(lang, p);\n return p;\n }\n\n parse(lang: CodingGraphLanguage, content: string): TSTree | null {\n if (!this.parser) return null;\n const language = this.languages.get(lang);\n if (!language) return null;\n this.parser.setLanguage(language);\n return this.parser.parse(content);\n }\n\n /** Return the loaded Language object for `lang`, or null if not loaded. */\n getLanguage(lang: CodingGraphLanguage): Language | null {\n return this.languages.get(lang) ?? null;\n }\n\n async dispose(): Promise<void> {\n if (this.parser) {\n try {\n this.parser.delete();\n } catch {\n // already deleted — ignore\n }\n this.parser = null;\n }\n this.languages.clear();\n this.loadingLanguages.clear();\n this.initialized = false;\n this.initializing = null;\n this.grammarDirResolved = false;\n this.grammarDir = \"\";\n }\n}\n\n/**\n * Map a tier-1 language to its .wasm grammar filename. The names match the\n * files shipped by `tree-sitter-wasms` (0.1.13) and the grammars/ directory.\n * C# uses `c_sharp` because the upstream grammar names C# with an underscore.\n */\nexport function grammarFileName(lang: CodingGraphLanguage): string {\n const map: Record<CodingGraphLanguage, string> = {\n typescript: \"tree-sitter-typescript.wasm\",\n tsx: \"tree-sitter-tsx.wasm\",\n javascript: \"tree-sitter-javascript.wasm\",\n python: \"tree-sitter-python.wasm\",\n go: \"tree-sitter-go.wasm\",\n rust: \"tree-sitter-rust.wasm\",\n java: \"tree-sitter-java.wasm\",\n c: \"tree-sitter-c.wasm\",\n cpp: \"tree-sitter-cpp.wasm\",\n csharp: \"tree-sitter-c_sharp.wasm\",\n ruby: \"tree-sitter-ruby.wasm\",\n php: \"tree-sitter-php.wasm\",\n kotlin: \"tree-sitter-kotlin.wasm\",\n swift: \"tree-sitter-swift.wasm\",\n bash: \"tree-sitter-bash.wasm\",\n };\n return map[lang];\n}\n","/**\n * FileIR emitter — runs tree-sitter queries against a parsed tree and\n * assembles the neutral intermediate representation.\n *\n * Determinism (rule 38): every collection is sorted before it leaves this\n * module. Sort keys are chosen so output is byte-identical across runs:\n * symbols → (startByte, name)\n * imports → (startByte, module)\n * exports → (startByte, name)\n * callSites → (startByte, firstCandidate)\n * routes → (startByte, pathTemplate)\n */\nimport { createHash } from \"node:crypto\";\nimport { Query, type Language, type Node as TSNode } from \"web-tree-sitter\";\nimport type {\n CallSiteIR,\n ExportIR,\n FileIR,\n ImportIR,\n RouteIR,\n SymbolIR,\n} from \"@remnic/core/coding/coding-graph-types\";\nimport type { CodingGraphLanguage } from \"@remnic/core\";\n\nimport {\n EXTRACTORS,\n kindFromCapture,\n type DefKind,\n} from \"./extractors.js\";\nimport { buildUtf16ToByteOffsetMap, utf16ToByte } from \"./utf16-offsets.js\";\n\n// ---------------------------------------------------------------------------\n// Content hashing — SHA-256 of the raw bytes (rule 23).\n// ---------------------------------------------------------------------------\n\nexport function hashContent(content: Uint8Array): string {\n return createHash(\"sha256\").update(content).digest(\"hex\");\n}\n\n// ---------------------------------------------------------------------------\n// Module-specifier cleanup — strips quotes/brackets from the captured text.\n// ---------------------------------------------------------------------------\n\nfunction cleanModuleSpecifier(raw: string): string {\n let s = raw.trim();\n // Strip C/C++ system includes: <stdio.h>\n if (s.startsWith(\"<\") && s.endsWith(\">\")) return s.slice(1, -1);\n // Strip double/single/backtick quotes.\n if (s.length >= 2) {\n const f = s[0];\n const l = s[s.length - 1];\n if ((f === '\"' || f === \"'\" || f === \"`\") && f === l) return s.slice(1, -1);\n }\n return s;\n}\n\n// ---------------------------------------------------------------------------\n// Symbol extraction with qualified-name computation.\n//\n// Qualified names are computed via a nesting stack: definitions are sorted by\n// startByte; for each definition we pop the stack until the top contains the\n// current definition's start byte. The qualified name is the join of the\n// stack's names plus the current name. This correctly handles sibling methods\n// in a class, nested classes, etc.\n// ---------------------------------------------------------------------------\n\ninterface RawDef {\n readonly kind: DefKind;\n readonly name: string;\n readonly startByte: number;\n readonly endByte: number;\n /** Go receiver type (e.g. \"Server\" from `func (s *Server) Start()`). */\n readonly receiverType?: string;\n}\n\nfunction extractSymbols(root: TSNode, language: Language, lang: CodingGraphLanguage): SymbolIR[] {\n const extractor = EXTRACTORS[lang];\n const query = new Query(language, extractor.definitionsQuery);\n try {\n const matches = query.matches(root);\n\n const rawDefs: RawDef[] = [];\n for (const match of matches) {\n let kind: DefKind | null = null;\n let nameNode: TSNode | null = null;\n let defNode: TSNode | null = null;\n let receiverType = \"\";\n for (const cap of match.captures) {\n const k = kindFromCapture(cap.name);\n if (k) {\n kind = k;\n defNode = cap.node;\n } else if (cap.name === \"name\") {\n nameNode = cap.node;\n } else if (cap.name === \"__receiver.type\") {\n receiverType = cap.node.text;\n }\n }\n if (!kind || !defNode || !nameNode) continue;\n rawDefs.push({\n kind,\n name: nameNode.text,\n startByte: defNode.startIndex,\n endByte: defNode.endIndex,\n receiverType: receiverType || undefined,\n });\n }\n\n // Deduplicate: a function_item inside an impl block matches both the\n // general function_item pattern and the impl-scoped method pattern.\n // Keep the method version (which carries the receiver type for parent\n // qualification). Same startByte+endByte+name guarantees it's the same\n // AST node matched by two query patterns, not two distinct definitions.\n const seen = new Map<string, RawDef>();\n for (const def of rawDefs) {\n const key = `${def.startByte}:${def.endByte}:${def.name}`;\n const existing = seen.get(key);\n if (!existing || (def.receiverType && !existing.receiverType)) {\n seen.set(key, def);\n }\n }\n const deduped = [...seen.values()];\n deduped.sort((a, b) => a.startByte - b.startByte || a.name.localeCompare(b.name));\n\n // The stack stores each def's computed qualifiedName so that nested\n // definitions inside a receiver-qualified method (e.g. Config.new.helper)\n // get the full parent chain, not just the short method name.\n const stack: { endByte: number; qualifiedName: string }[] = [];\n const symbols: SymbolIR[] = [];\n for (const def of deduped) {\n while (stack.length > 0 && stack[stack.length - 1].endByte <= def.startByte) {\n stack.pop();\n }\n // Go/Rust methods sit outside their receiver struct, so byte-span\n // nesting cannot determine the parent. Use the captured receiver\n // type instead.\n // The last stack entry's qualifiedName already contains the full\n // ancestor chain (e.g. \"Server.start\"), so use it directly rather\n // than joining all entries (which would duplicate ancestors).\n const parentQualifiedName =\n def.receiverType ??\n (stack.length > 0 ? stack[stack.length - 1].qualifiedName : undefined);\n const qualifiedName = parentQualifiedName\n ? `${parentQualifiedName}.${def.name}`\n : def.name;\n const symbol: SymbolIR = parentQualifiedName\n ? {\n kind: def.kind,\n name: def.name,\n qualifiedName,\n span: { startByte: def.startByte, endByte: def.endByte },\n parentQualifiedName,\n }\n : {\n kind: def.kind,\n name: def.name,\n qualifiedName,\n span: { startByte: def.startByte, endByte: def.endByte },\n };\n symbols.push(symbol);\n stack.push({ endByte: def.endByte, qualifiedName });\n }\n return symbols;\n } finally {\n query.delete();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Import extraction — group captures by @__import.stmt node.\n// ---------------------------------------------------------------------------\n\nfunction extractImports(root: TSNode, language: Language, lang: CodingGraphLanguage): ImportIR[] {\n const extractor = EXTRACTORS[lang];\n if (!extractor.importsQuery) return [];\n const query = new Query(language, extractor.importsQuery);\n try {\n const matches = query.matches(root);\n\n // Group by import-statement node start index (unique per node in tree).\n // Group by (statement-start + module) so multi-module statements like\n // Python `import os, sys` produce separate import entries rather than\n // collapsing to a single module. Single-module statements like\n // `import { foo, bar } from \"module\"` still group correctly because\n // all captures share the same module.\n const groups = new Map<\n string,\n { module: string; names: Set<string>; startByte: number; endByte: number }\n >();\n\n for (const match of matches) {\n let moduleText = \"\";\n let stmtStart = -1;\n let stmtEnd = -1;\n const names: string[] = [];\n\n for (const cap of match.captures) {\n if (cap.name === \"import.module\") {\n moduleText = cleanModuleSpecifier(cap.node.text);\n } else if (cap.name === \"import.name\") {\n names.push(cap.node.text);\n } else if (cap.name === \"__import.stmt\") {\n stmtStart = cap.node.startIndex;\n stmtEnd = cap.node.endIndex;\n }\n }\n\n if (stmtStart < 0) {\n // Fallback: use the first capture's parent chain to find an import node.\n const firstCap = match.captures[0];\n if (firstCap) {\n stmtStart = firstCap.node.startIndex;\n stmtEnd = firstCap.node.endIndex;\n } else {\n continue;\n }\n }\n\n const key = `${stmtStart}:${moduleText}`;\n const existing = groups.get(key);\n if (existing) {\n for (const n of names) existing.names.add(n);\n } else {\n groups.set(key, {\n module: moduleText,\n names: new Set(names),\n startByte: stmtStart,\n endByte: stmtEnd,\n });\n }\n }\n\n return Array.from(groups.values())\n .map((g) => ({\n module: g.module,\n importedNames: Array.from(g.names).sort(),\n span: { startByte: g.startByte, endByte: g.endByte },\n }))\n .sort((a, b) => a.span.startByte - b.span.startByte || a.module.localeCompare(b.module));\n } finally {\n query.delete();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Export extraction.\n// ---------------------------------------------------------------------------\n\nfunction extractExports(root: TSNode, language: Language, lang: CodingGraphLanguage): ExportIR[] {\n const extractor = EXTRACTORS[lang];\n if (!extractor.exportsQuery) return [];\n const query = new Query(language, extractor.exportsQuery);\n try {\n const captures = query.captures(root);\n // Dedup a CommonJS pair overlap (#1659 review): a pair\n // `{ key: value }` whose value is an identifier is matched by BOTH\n // the value-identifier pattern (captures the real symbol) AND the\n // non-identifier fallback (captures the key). The fallback's\n // #not-match? regex is ASCII-only, so a Unicode identifier value\n // (e.g. Universität) defeats it and both patterns fire on the same\n // pair, duplicating the export. web-tree-sitter's query regex\n // engine does not support \\p{L}, so dedup here: if a pair already\n // exported its value identifier, drop the spurious key capture.\n const valueExportedPairs = new Set<number>();\n const pairOf = (node: TSNode): TSNode | null => {\n let cur: TSNode | null = node;\n for (let i = 0; i < 5 && cur; i++) {\n if (cur.type === \"pair\") return cur;\n cur = cur.parent;\n }\n return null;\n };\n for (const cap of captures) {\n if (cap.name !== \"export.name\") continue;\n const pair = pairOf(cap.node);\n if (pair && cap.node.type === \"identifier\") {\n valueExportedPairs.add(pair.id);\n }\n }\n const exports: ExportIR[] = [];\n for (const cap of captures) {\n if (cap.name !== \"export.name\") continue;\n const pair = pairOf(cap.node);\n if (\n pair &&\n cap.node.type === \"property_identifier\" &&\n valueExportedPairs.has(pair.id)\n ) {\n continue; // value identifier is the real export; drop the alias key\n }\n exports.push({\n name: cap.node.text,\n span: { startByte: cap.node.startIndex, endByte: cap.node.endIndex },\n });\n }\n return exports.sort(\n (a, b) => a.span.startByte - b.span.startByte || a.name.localeCompare(b.name),\n );\n } finally {\n query.delete();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Call-site extraction.\n// ---------------------------------------------------------------------------\n\nfunction extractCallSites(root: TSNode, language: Language, lang: CodingGraphLanguage): CallSiteIR[] {\n const extractor = EXTRACTORS[lang];\n if (!extractor.callSitesQuery) return [];\n const query = new Query(language, extractor.callSitesQuery);\n try {\n const captures = query.captures(root);\n const callSites: CallSiteIR[] = [];\n for (const cap of captures) {\n if (cap.name === \"call.callee\") {\n callSites.push({\n calleeNameCandidates: [cap.node.text],\n span: { startByte: cap.node.startIndex, endByte: cap.node.endIndex },\n });\n }\n }\n return callSites.sort(\n (a, b) => a.span.startByte - b.span.startByte ||\n (a.calleeNameCandidates[0] ?? \"\").localeCompare(b.calleeNameCandidates[0] ?? \"\"),\n );\n } finally {\n query.delete();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Route extraction (Express/Fastify/Flask/etc.).\n// ---------------------------------------------------------------------------\n\n\n// Common HTTP client variable names that should NOT produce routes.\n// These objects have methods named get/post/etc. that match the route\n// verb pattern but are client-side calls, not server route registrations.\n// Without this exclusion, httpClient.get(\"/api\", opts, cb) would produce\n// a spurious route with handler=cb, marking cb as is_route_handler and\n// hiding it from dead-code detection (chatgpt-codex-connector #1688 P2).\nconst HTTP_CLIENT_OBJECT_PATTERNS = /^(http|https|client|httpClient|axios|fetch|request|req|res|\\$|superagent|got)$/;\n\nfunction extractRoutes(root: TSNode, language: Language, lang: CodingGraphLanguage): RouteIR[] {\n const extractor = EXTRACTORS[lang];\n if (!extractor.routesQuery) return [];\n const query = new Query(language, extractor.routesQuery);\n try {\n const matches = query.matches(root);\n const routes: RouteIR[] = [];\n for (const match of matches) {\n let verb = \"\";\n let pathTemplate = \"\";\n let handler = \"\";\n let startByte = 0;\n let endByte = 0;\n let argsNode: TSNode | null = null;\n let routeObject = \"\";\n for (const cap of match.captures) {\n if (cap.name === \"route.verb\") {\n verb = cap.node.text.toUpperCase();\n startByte = cap.node.parent?.startIndex ?? cap.node.startIndex;\n // Extract the receiver object name for the HTTP-client exclusion.\n const memberExpr = cap.node.parent;\n const objectNode = memberExpr?.childForFieldName(\"object\");\n if (objectNode) {\n // Normalize nested receivers to their tail property so a call\n // like this.client.get(\"/api\", opts, cb) is caught by the HTTP-\n // client exclusion. objectNode.text for `this.client` is\n // \"this.client\", which misses the ^client$ pattern; descend to\n // the rightmost property (chatgpt-codex-connector #1688 P2:\n // 'Normalize receiver names before client-route filtering').\n let receiver = objectNode;\n for (\n let prop = receiver.childForFieldName(\"property\");\n prop;\n prop = receiver.childForFieldName(\"property\")\n ) {\n receiver = prop;\n }\n routeObject = receiver.text;\n }\n } else if (cap.name === \"route.path\") {\n pathTemplate = cleanModuleSpecifier(cap.node.text);\n } else if (cap.name === \"route.handler\") {\n // Python route handlers (function names) and legacy JS patterns.\n handler = cap.node.type === \"identifier\"\n ? cap.node.text\n : (findHandlerName(cap.node) ?? \"anonymous\");\n endByte = cap.node.endIndex;\n } else if (cap.name === \"route.args\") {\n argsNode = cap.node;\n endByte = cap.node.endIndex;\n }\n }\n // Extract handler from the last argument when we captured the args\n // node (JS routes). Handles middleware: handler is the LAST arg (#1659 #5).\n if (argsNode) {\n handler = extractHandlerFromArgs(argsNode);\n }\n // Guards: (1) path-prefix — routes start with \"/\" or \"*\";\n // (2) HTTP-client exclusion — objects named http/client/axios/etc.\n // are clients, not routers. Together these filter the most common\n // non-route call expressions that match the verb+string-arg pattern\n // (chatgpt-codex-connector #1688 P2: 'Reject client callbacks').\n const isRoutePath = pathTemplate.startsWith(\"/\") || pathTemplate.startsWith(\"*\");\n const isHttpClient = HTTP_CLIENT_OBJECT_PATTERNS.test(routeObject);\n if (verb && pathTemplate && handler && isRoutePath && !isHttpClient) {\n routes.push({\n verb,\n pathTemplate,\n handlerQualifiedName: handler,\n span: { startByte, endByte },\n });\n }\n }\n return routes.sort(\n (a, b) => a.span.startByte - b.span.startByte || a.pathTemplate.localeCompare(b.pathTemplate),\n );\n } finally {\n query.delete();\n }\n}\n\n/**\n * Extract the route handler name from the LAST argument of an arguments\n * node. Handles middleware: app.get(\"/path\", requireAuth, getUsers) →\n * handler=getUsers (the last arg), not requireAuth (issue #1659 #5).\n */\nfunction extractHandlerFromArgs(argsNode: TSNode): string {\n // Collect the real (non-comment) named args, skipping trailing inline/\n // block comments. tree-sitter treats comments as named children, so\n // `app.get(\"/users\", getUsers /* auth */)` would otherwise select the\n // comment as the last arg, miss the real handler, and leave it\n // un-protected by the route-handler exclusion (a false dead-code hit).\n // (chatgpt-codex-connector #1659 review: 'Skip comments when selecting\n // route handler'.)\n const realArgs: TSNode[] = [];\n for (let i = 0; i < argsNode.namedChildCount; i++) {\n const child = argsNode.namedChild(i);\n if (child && child.type !== \"comment\") realArgs.push(child);\n }\n if (realArgs.length < 2) return \"\";\n const lastArg = realArgs[realArgs.length - 1]!;\n if (lastArg.type === \"identifier\") {\n return lastArg.text;\n }\n if (lastArg.type === \"function_expression\") {\n return findHandlerName(lastArg) ?? \"anonymous\";\n }\n if (lastArg.type === \"arrow_function\") {\n return \"anonymous\";\n }\n // Non-handler last arg (object, number, call expression, etc.) —\n // not a route handler. Return empty so the caller skips the route\n // (cursor Bugbot: 'Spurious routes from client calls').\n return \"\";\n}\n\n/**\n * Try to find a handler name from a function/arrow expression node.\n * For named function expressions: `(function foo() {})` → \"foo\".\n * For arrow functions assigned to a variable, the variable name is not in this node;\n * the caller would need the parent. For now, return \"anonymous\" unless we find a name.\n */\nfunction findHandlerName(node: TSNode): string | null {\n // function_expression may have a name child (identifier)\n for (const child of node.namedChildren) {\n if (child && child.type === \"identifier\") return child.text;\n }\n // Python function_definition has a name field\n const nameChild = node.childForFieldName(\"name\");\n if (nameChild) return nameChild.text;\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Top-level emitter.\n// ---------------------------------------------------------------------------\n\n/**\n * Assemble a FileIR from a parsed tree. All collections are sorted for\n * deterministic output (rule 38).\n *\n * `contentStr` is the UTF-8 string that was passed to the parser. It is used\n * to build a UTF-16→byte offset map so all spans are converted from UTF-16\n * code-unit offsets (what web-tree-sitter returns) to UTF-8 byte offsets\n * (what on-disk files use). For ASCII-only content the two are identical;\n * multibyte content (comments, strings, identifiers) needs the conversion\n * (issue #1659 item 3).\n */\nexport function emitFileIR(\n filePath: string,\n lang: CodingGraphLanguage,\n content: Uint8Array,\n root: TSNode,\n language: Language,\n contentStr: string,\n): FileIR {\n const symbols = extractSymbols(root, language, lang);\n const imports = extractImports(root, language, lang);\n const exports = extractExports(root, language, lang);\n const callSites = extractCallSites(root, language, lang);\n const routes = extractRoutes(root, language, lang);\n\n // Convert UTF-16 code-unit offsets → UTF-8 byte offsets (issue #1659 #3).\n // Spans are readonly, so rebuild each object with converted offsets.\n const offsetMap = buildUtf16ToByteOffsetMap(contentStr);\n const convSymbols = symbols.map((s) => ({\n ...s,\n span: {\n startByte: utf16ToByte(offsetMap, s.span.startByte),\n endByte: utf16ToByte(offsetMap, s.span.endByte),\n },\n }));\n const convImports = imports.map((i) => ({\n ...i,\n span: {\n startByte: utf16ToByte(offsetMap, i.span.startByte),\n endByte: utf16ToByte(offsetMap, i.span.endByte),\n },\n }));\n const convExports = exports.map((e) => ({\n ...e,\n span: {\n startByte: utf16ToByte(offsetMap, e.span.startByte),\n endByte: utf16ToByte(offsetMap, e.span.endByte),\n },\n }));\n const convCallSites = callSites.map((c) => ({\n ...c,\n span: {\n startByte: utf16ToByte(offsetMap, c.span.startByte),\n endByte: utf16ToByte(offsetMap, c.span.endByte),\n },\n }));\n const convRoutes = routes.map((r) => ({\n ...r,\n span: {\n startByte: utf16ToByte(offsetMap, r.span.startByte),\n endByte: utf16ToByte(offsetMap, r.span.endByte),\n },\n }));\n\n return {\n path: filePath,\n language: lang,\n contentHash: hashContent(content),\n symbols: convSymbols,\n imports: convImports,\n exports: convExports,\n callSites: convCallSites,\n routes: convRoutes,\n };\n}\n","/**\n * Per-language symbol extractors for the tier-1 languages.\n *\n * Each tier-1 language has a `LanguageExtractor` providing tree-sitter query\n * strings. The `emit.ts` module compiles these against the loaded grammar,\n * runs them against the root node, and walks captures into FileIR.\n *\n * Query capture conventions (fixed across all languages):\n *\n * Definitions — every definition pattern captures:\n * @def.<kind> the definition node (span = [startIndex, endIndex))\n * @name the identifier node whose .text is the symbol name\n * <kind> ∈ function | method | class | interface | enum | type | module\n *\n * Imports — every import pattern captures:\n * @__import.stmt the import-statement node (grouping key)\n * @import.module the module specifier text (cleaned in emit.ts)\n * @import.name zero or more imported-name identifiers\n *\n * Exports — @export.name\n * Call sites — @call.callee\n * Routes — @route.verb + @route.path + @route.handler\n */\nimport type { CodingGraphLanguage } from \"@remnic/core\";\n\nexport type DefKind = \"function\" | \"method\" | \"class\" | \"interface\" | \"enum\" | \"type\" | \"module\";\n\nexport interface LanguageExtractor {\n readonly definitionsQuery: string;\n readonly importsQuery: string;\n readonly exportsQuery: string;\n readonly callSitesQuery: string;\n readonly routesQuery: string;\n}\n\n// ===========================================================================\n// JS family (JavaScript, TypeScript, TSX) — shared definition + call patterns.\n// ===========================================================================\n\nconst JS_FAMILY_DEFINITIONS = `\n(function_declaration name: (identifier) @name) @def.function\n(method_definition name: (property_identifier) @name) @def.method\n(class_declaration name: (type_identifier) @name) @def.class\n(interface_declaration name: (type_identifier) @name) @def.interface\n(enum_declaration name: (identifier) @name) @def.enum\n(type_alias_declaration name: (type_identifier) @name) @def.type\n\n; const handler = () => {} / const handler = function () {}\n; Indexes arrow-function and function-expression declarations so route\n; handlers and React components defined this way appear as symbols.\n(variable_declarator\n name: (identifier) @name\n value: [(arrow_function) (function_expression)]) @def.function\n`.trim();\n\nconst JS_IMPORTS = `\n(import_statement\n source: (string (string_fragment) @import.module)) @__import.stmt\n(import_statement\n (import_clause (identifier) @import.name)\n source: (string (string_fragment) @import.module)) @__import.stmt\n(import_statement\n (import_clause (namespace_import (identifier) @import.name))\n source: (string (string_fragment) @import.module)) @__import.stmt\n(import_statement\n (import_clause (named_imports (import_specifier name: (identifier) @import.name)))\n source: (string (string_fragment) @import.module)) @__import.stmt\n\n; CommonJS require(\"...\") — capture the module specifier so dependency\n; edges exist for Node/CommonJS codebases, not just ES-module imports.\n(call_expression\n function: (identifier) @__import.require\n arguments: (arguments (string (string_fragment) @import.module))\n (#eq? @__import.require \"require\")) @__import.stmt\n`.trim();\n\n// TS/TSX exports — class/interface names use type_identifier.\nconst TS_EXPORTS = `\n(export_statement declaration: (function_declaration name: (identifier) @export.name))\n(export_statement declaration: (class_declaration name: (type_identifier) @export.name))\n(export_statement (lexical_declaration (variable_declarator name: (identifier) @export.name)))\n(export_statement declaration: (enum_declaration name: (identifier) @export.name))\n(export_statement declaration: (interface_declaration name: (type_identifier) @export.name))\n(export_statement (export_clause (export_specifier name: (identifier) @export.name)))\n\n; export default App; (bare identifier re-export)\n(export_statement (identifier) @export.name)\n`.trim();\n\n// JavaScript exports — class names use identifier (no type_identifier in JS grammar).\n// Includes CommonJS export patterns (module.exports / exports.X) so\n// Node/CommonJS public APIs are not marked unexported.\nconst JS_EXPORTS = `\n(export_statement declaration: (function_declaration name: (identifier) @export.name))\n(export_statement declaration: (class_declaration name: (identifier) @export.name))\n(export_statement (lexical_declaration (variable_declarator name: (identifier) @export.name)))\n(export_statement (export_clause (export_specifier name: (identifier) @export.name)))\n\n; export default App; (bare identifier re-export)\n(export_statement (identifier) @export.name)\n\n; CommonJS: module.exports = { App, createRouter }\n(assignment_expression\n left: (member_expression object: (identifier) @__cjs.mod property: (property_identifier) @__cjs.exp)\n right: (object (shorthand_property_identifier) @export.name)\n (#eq? @__cjs.mod \"module\") (#eq? @__cjs.exp \"exports\"))\n; CommonJS: module.exports = { publicName: createRouter }\n; Capture the VALUE identifier (the actual symbol), not the key (public alias).\n(assignment_expression\n left: (member_expression object: (identifier) @__cjs.mod2 property: (property_identifier) @__cjs.exp2)\n right: (object (pair key: (property_identifier) @__cjs.key2 value: (identifier) @export.name))\n (#eq? @__cjs.mod2 \"module\") (#eq? @__cjs.exp2 \"exports\"))\n; Fallback: { foo: <non-identifier-value> } — capture the key as the public name\n; when the value is not a named symbol (arrow fn, literal, etc).\n(assignment_expression\n left: (member_expression object: (identifier) @__cjs.mod2b property: (property_identifier) @__cjs.exp2b)\n right: (object (pair key: (property_identifier) @export.name value: (_) @__cjs.nonId))\n (#eq? @__cjs.mod2b \"module\") (#eq? @__cjs.exp2b \"exports\")\n (#not-match? @__cjs.nonId \"^[A-Za-z_$][A-Za-z0-9_$]*$\"))\n; CommonJS: module.exports = App\n(assignment_expression\n left: (member_expression object: (identifier) @__cjs.mod3 property: (property_identifier) @__cjs.exp3)\n right: (identifier) @export.name\n (#eq? @__cjs.mod3 \"module\") (#eq? @__cjs.exp3 \"exports\"))\n; CommonJS: exports.handler = handler\n(assignment_expression\n left: (member_expression object: (identifier) @__cjs.exp4 property: (property_identifier) @export.name)\n (#eq? @__cjs.exp4 \"exports\"))\n`.trim();\n\nconst JS_CALLS = `\n(call_expression function: (identifier) @call.callee)\n(call_expression function: (member_expression property: (property_identifier) @call.callee))\n`.trim();\n\nconst JS_ROUTES = `\n; Unified route pattern: captures verb + path + the arguments node.\n; emit.ts extracts the handler from the LAST argument (identifier,\n; arrow_function, or function_expression), correctly handling middleware:\n; app.get(\"/users\", requireAuth, getUsers) → handler=getUsers\n(call_expression\n function: (member_expression\n object: (_) @__route.app\n property: (property_identifier) @route.verb)\n arguments: (arguments\n . (string (string_fragment) @route.path)\n ) @route.args\n (#match? @route.verb \"^(get|post|put|patch|delete|head|options|all|use)$\"))\n`.trim();\n\n// ===========================================================================\n// Per-language extractors.\n// ===========================================================================\n\nconst TYPESCRIPT_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: JS_FAMILY_DEFINITIONS,\n importsQuery: JS_IMPORTS,\n exportsQuery: TS_EXPORTS,\n callSitesQuery: JS_CALLS,\n routesQuery: JS_ROUTES,\n};\n\nconst TSX_EXTRACTOR: LanguageExtractor = TYPESCRIPT_EXTRACTOR;\n\nconst JAVASCRIPT_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: `\n(function_declaration name: (identifier) @name) @def.function\n(method_definition name: (property_identifier) @name) @def.method\n(class_declaration name: (identifier) @name) @def.class\n\n; const handler = () => {} / const handler = function () {}\n(variable_declarator\n name: (identifier) @name\n value: [(arrow_function) (function_expression)]) @def.function\n`.trim(),\n importsQuery: JS_IMPORTS,\n exportsQuery: JS_EXPORTS,\n callSitesQuery: JS_CALLS,\n routesQuery: JS_ROUTES,\n};\n\nconst PYTHON_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: `\n(function_definition name: (identifier) @name) @def.function\n(class_definition name: (identifier) @name) @def.class\n`.trim(),\n importsQuery: `\n(import_statement (dotted_name) @import.module) @__import.stmt\n(import_from_statement module_name: (dotted_name) @import.module) @__import.stmt\n; Python relative imports: from .models import User / from ..parent import X\n; tree-sitter-python wraps the module inside a relative_import node, so the\n; module_name field is NOT set. Capture the relative_import node itself so\n; the prefix dots are preserved (..parent, not just parent) — different\n; relative levels must not collapse to the same module name\n; (chatgpt-codex-connector #1688 P2: 'Preserve dots in Python relative imports').\n(import_from_statement (relative_import) @import.module) @__import.stmt\n`.trim(),\n exportsQuery: ``,\n callSitesQuery: `\n(call function: (identifier) @call.callee)\n(call function: (attribute attribute: (identifier) @call.callee))\n`.trim(),\n routesQuery: `\n(decorated_definition\n (decorator\n (call function: (attribute attribute: (identifier) @route.verb)\n arguments: (argument_list (string) @route.path)))\n definition: (function_definition name: (identifier) @route.handler)\n (#match? @route.verb \"^(get|post|put|patch|delete|route|api_route)$\"))\n`.trim(),\n};\n\nconst GO_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: `\n(function_declaration name: (identifier) @name) @def.function\n\n; Go methods sit outside their receiver struct, so byte-span nesting\n; cannot compute qualified names. Capture the receiver type_identifier\n; so extractSymbols can prefix the method name (Server.Start).\n(method_declaration\n receiver: (parameter_list\n (parameter_declaration type: (type_identifier) @__receiver.type))\n name: (field_identifier) @name) @def.method\n(method_declaration\n receiver: (parameter_list\n (parameter_declaration type: (pointer_type (type_identifier) @__receiver.type)))\n name: (field_identifier) @name) @def.method\n(type_spec name: (type_identifier) @name type: (struct_type)) @def.class\n(type_spec name: (type_identifier) @name type: (interface_type)) @def.interface\n(type_spec name: (type_identifier) @name type: (type_identifier)) @def.type\n`.trim(),\n importsQuery: `\n(import_spec path: (interpreted_string_literal) @import.module) @__import.stmt\n`.trim(),\n exportsQuery: ``,\n callSitesQuery: `\n(call_expression function: (identifier) @call.callee)\n(call_expression function: (selector_expression field: (field_identifier) @call.callee))\n`.trim(),\n routesQuery: ``,\n};\n\nconst RUST_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: `\n(function_item name: (identifier) @name) @def.function\n(function_signature_item name: (identifier) @name) @def.function\n(struct_item name: (type_identifier) @name) @def.class\n(enum_item name: (type_identifier) @name) @def.enum\n(trait_item name: (type_identifier) @name) @def.interface\n(type_item name: (type_identifier) @name) @def.type\n(mod_item name: (identifier) @name) @def.module\n\n; Rust impl methods — the impl block sits outside the struct's byte span,\n; so byte-span nesting cannot compute the parent struct. Capture the impl\n; type_identifier so extractSymbols can prefix qualified names (Config.new).\n; These also match functions caught by the general patterns above;\n; extractSymbols deduplicates by node identity (startByte+endByte+name).\n(impl_item\n type: (type_identifier) @__receiver.type\n body: (declaration_list\n (function_item name: (identifier) @name) @def.method))\n(impl_item\n type: (type_identifier) @__receiver.type\n body: (declaration_list\n (function_signature_item name: (identifier) @name) @def.method))\n`.trim(),\n importsQuery: `\n(use_declaration (scoped_identifier) @import.module) @__import.stmt\n(use_declaration (scoped_use_list) @import.module) @__import.stmt\n`.trim(),\n exportsQuery: ``,\n callSitesQuery: `\n(call_expression function: (identifier) @call.callee)\n(call_expression function: (field_expression field: (field_identifier) @call.callee))\n(call_expression function: (scoped_identifier) @call.callee)\n`.trim(),\n routesQuery: ``,\n};\n\nconst JAVA_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: `\n(class_declaration name: (identifier) @name) @def.class\n(interface_declaration name: (identifier) @name) @def.interface\n(enum_declaration name: (identifier) @name) @def.enum\n(record_declaration name: (identifier) @name) @def.class\n(method_declaration name: (identifier) @name) @def.method\n(constructor_declaration name: (identifier) @name) @def.method\n`.trim(),\n importsQuery: `\n(import_declaration (scoped_identifier) @import.module) @__import.stmt\n`.trim(),\n exportsQuery: ``,\n callSitesQuery: `\n(method_invocation name: (identifier) @call.callee)\n`.trim(),\n routesQuery: ``,\n};\n\nconst C_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: `\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @def.function\n(type_definition declarator: (type_identifier) @name) @def.type\n`.trim(),\n importsQuery: `\n(preproc_include path: (system_lib_string) @import.module) @__import.stmt\n(preproc_include path: (string_literal) @import.module) @__import.stmt\n`.trim(),\n exportsQuery: ``,\n callSitesQuery: `\n(call_expression function: (identifier) @call.callee)\n`.trim(),\n routesQuery: ``,\n};\n\nconst CPP_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: `\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @def.function\n(function_definition declarator: (function_declarator declarator: (field_identifier) @name)) @def.method\n; C++ out-of-class method definitions: void A::start() {}\n; The qualified_identifier (A::start) encodes the full qualified name.\n(function_definition declarator: (function_declarator declarator: (qualified_identifier) @name)) @def.method\n(class_specifier name: (type_identifier) @name) @def.class\n(struct_specifier name: (type_identifier) @name) @def.class\n(enum_specifier name: (type_identifier) @name) @def.enum\n(namespace_definition name: (namespace_identifier) @name) @def.module\n`.trim(),\n importsQuery: `\n(preproc_include path: (system_lib_string) @import.module) @__import.stmt\n(preproc_include path: (string_literal) @import.module) @__import.stmt\n`.trim(),\n exportsQuery: ``,\n callSitesQuery: `\n(call_expression function: (identifier) @call.callee)\n(call_expression function: (field_expression field: (field_identifier) @call.callee))\n`.trim(),\n routesQuery: ``,\n};\n\nconst CSHARP_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: `\n(class_declaration name: (identifier) @name) @def.class\n(interface_declaration name: (identifier) @name) @def.interface\n(enum_declaration name: (identifier) @name) @def.enum\n(struct_declaration name: (identifier) @name) @def.class\n(method_declaration name: (identifier) @name) @def.method\n`.trim(),\n importsQuery: `\n(using_directive (identifier) @import.module) @__import.stmt\n(using_directive (qualified_name) @import.module) @__import.stmt\n`.trim(),\n exportsQuery: ``,\n callSitesQuery: `\n(invocation_expression function: (identifier) @call.callee)\n(invocation_expression function: (member_access_expression name: (identifier) @call.callee))\n`.trim(),\n routesQuery: ``,\n};\n\nconst RUBY_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: `\n(class name: (constant) @name) @def.class\n(module name: (constant) @name) @def.module\n(method name: (identifier) @name) @def.method\n(singleton_method name: (identifier) @name) @def.method\n`.trim(),\n importsQuery: `\n(call\n method: (identifier) @__import.method\n arguments: (argument_list (string) @import.module)\n (#match? @__import.method \"^(require|require_relative|load)$\")) @__import.stmt\n`.trim(),\n exportsQuery: ``,\n callSitesQuery: `\n(call method: (identifier) @call.callee)\n`.trim(),\n routesQuery: ``,\n};\n\nconst PHP_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: `\n(class_declaration name: (name) @name) @def.class\n(interface_declaration name: (name) @name) @def.interface\n(trait_declaration name: (name) @name) @def.class\n(function_definition name: (name) @name) @def.function\n(method_declaration name: (name) @name) @def.method\n`.trim(),\n importsQuery: `\n(namespace_use_declaration (namespace_use_clause (qualified_name) @import.module)) @__import.stmt\n`.trim(),\n exportsQuery: ``,\n callSitesQuery: `\n(function_call_expression function: (name) @call.callee)\n(member_call_expression (name) @call.callee)\n`.trim(),\n routesQuery: ``,\n};\n\nconst KOTLIN_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: `\n(class_declaration (type_identifier) @name) @def.class\n(object_declaration (type_identifier) @name) @def.module\n(function_declaration (simple_identifier) @name) @def.function\n`.trim(),\n importsQuery: `\n; Capture the full identifier node — its .text is the complete import\n; path (e.g. \"kotlin.collections\"). Do NOT capture nested simple_identifier\n; children, which would emit bogus segment-level modules.\n(import_header (identifier) @import.module) @__import.stmt\n`.trim(),\n exportsQuery: ``,\n callSitesQuery: `\n(call_expression (simple_identifier) @call.callee)\n`.trim(),\n routesQuery: ``,\n};\n\nconst SWIFT_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: `\n(class_declaration name: (type_identifier) @name) @def.class\n(protocol_declaration name: (type_identifier) @name) @def.interface\n(function_declaration name: (simple_identifier) @name) @def.function\n`.trim(),\n importsQuery: `\n(import_declaration (identifier) @import.module) @__import.stmt\n`.trim(),\n exportsQuery: ``,\n callSitesQuery: `\n(call_expression (simple_identifier) @call.callee)\n`.trim(),\n routesQuery: ``,\n};\n\nconst BASH_EXTRACTOR: LanguageExtractor = {\n definitionsQuery: `\n(function_definition name: (word) @name) @def.function\n`.trim(),\n importsQuery: ``,\n exportsQuery: ``,\n callSitesQuery: `\n(command name: (command_name (word) @call.callee))\n`.trim(),\n routesQuery: ``,\n};\n\n// ===========================================================================\n// Registry — single source of truth.\n// ===========================================================================\n\nexport const EXTRACTORS: Record<CodingGraphLanguage, LanguageExtractor> = {\n typescript: TYPESCRIPT_EXTRACTOR,\n tsx: TSX_EXTRACTOR,\n javascript: JAVASCRIPT_EXTRACTOR,\n python: PYTHON_EXTRACTOR,\n go: GO_EXTRACTOR,\n rust: RUST_EXTRACTOR,\n java: JAVA_EXTRACTOR,\n c: C_EXTRACTOR,\n cpp: CPP_EXTRACTOR,\n csharp: CSHARP_EXTRACTOR,\n ruby: RUBY_EXTRACTOR,\n php: PHP_EXTRACTOR,\n kotlin: KOTLIN_EXTRACTOR,\n swift: SWIFT_EXTRACTOR,\n bash: BASH_EXTRACTOR,\n};\n\nconst VALID_KINDS = new Set<string>([\n \"function\",\n \"method\",\n \"class\",\n \"interface\",\n \"enum\",\n \"type\",\n \"module\",\n]);\n\n/**\n * Extract the SymbolIR kind from a `@def.<kind>` capture name.\n * Returns `null` for non-definition captures or invalid kinds.\n */\nexport function kindFromCapture(captureName: string): DefKind | null {\n if (!captureName.startsWith(\"def.\")) return null;\n const kind = captureName.slice(4);\n if (!VALID_KINDS.has(kind)) return null;\n return kind as DefKind;\n}\n","/**\n * UTF-16 code-unit offset → UTF-8 byte offset conversion (issue #1659 item 3).\n *\n * web-tree-sitter 0.25 parses strings internally by converting them to UTF-16\n * (`stringToUTF16` in the WASM glue), so every node's `startIndex`/`endIndex`\n * is a UTF-16 code-unit offset, NOT a UTF-8 byte offset. For pure-ASCII\n * content these are identical, but multibyte content (comments, strings, or\n * identifiers containing non-ASCII characters) produces incorrect spans:\n * dead-code analysis, navigation, and body extraction all rely on byte\n * offsets matching the on-disk file.\n *\n * This module builds a single lookup array per file (O(n) build, O(1)\n * lookup) so all offsets in a FileIR can be converted in one pass.\n */\n\n/**\n * Build a map from UTF-16 code-unit offset → UTF-8 byte offset for the given\n * string. `map[i]` is the byte offset of the i-th UTF-16 code unit.\n * `map[content.length]` is the total byte length.\n *\n * Surrogate pairs (U+10000–U+10FFFF) occupy 2 UTF-16 code units but encode to\n * 4 UTF-8 bytes. The high surrogate's entry points to the start of the pair;\n * the low surrogate's entry also points there (its byte delta is 0, added by\n * the high surrogate).\n */\nexport function buildUtf16ToByteOffsetMap(content: string): Uint32Array {\n const map = new Uint32Array(content.length + 1);\n let byteOffset = 0;\n for (let i = 0; i < content.length; i++) {\n map[i] = byteOffset;\n const code = content.charCodeAt(i);\n if (code < 0x80) {\n byteOffset += 1;\n } else if (code < 0x800) {\n byteOffset += 2;\n } else if (code >= 0xd800 && code <= 0xdbff) {\n // High surrogate of a surrogate pair. The full code point encodes to\n // 4 UTF-8 bytes; we charge all 4 here and skip the low surrogate.\n byteOffset += 4;\n // Both code units of the pair represent the SAME character, which\n // starts at map[i] (set at the top of the loop, before the increment).\n // The low surrogate must map to that SAME start byte — not to\n // byteOffset (which is now past the pair) — or a span whose start\n // falls on the low code unit gets an inflated startByte (cursor\n // #1659 review: 'Surrogate UTF-16 index maps wrong').\n map[i + 1] = map[i]!;\n i++;\n } else if (code >= 0xdc00 && code <= 0xdfff) {\n // Lone low surrogate (unpaired) — treat as 3-byte UTF-8 replacement.\n // This path is only hit for malformed input.\n byteOffset += 3;\n } else {\n byteOffset += 3;\n }\n }\n map[content.length] = byteOffset;\n return map;\n}\n\n/**\n * Convert a single UTF-16 code-unit offset to a UTF-8 byte offset using a\n * pre-built map. Returns 0 for negative offsets; clamps to the map length.\n */\nexport function utf16ToByte(map: Uint32Array, utf16Offset: number): number {\n if (utf16Offset <= 0) return 0;\n if (utf16Offset >= map.length) return map[map.length - 1];\n return map[utf16Offset];\n}\n","/**\n * Language sniffing — map a file path to a tier-1 CodingGraphLanguage by\n * its extension. Used when `ParseFileInput.language` is omitted.\n *\n * If the path doesn't match any tier-1 extension, returns `null` and the\n * engine emits a `parse_failed` result (rule 44 — no silent skip).\n */\nimport { TIER_1_LANGUAGES, type CodingGraphLanguage } from \"@remnic/core\";\n\n/** Extension (lowercase, dot-prefixed) → language. Static table. */\nconst EXTENSION_MAP: Record<string, CodingGraphLanguage> = {\n \".ts\": \"typescript\",\n \".tsx\": \"tsx\",\n \".mts\": \"typescript\",\n \".cts\": \"typescript\",\n \".js\": \"javascript\",\n \".jsx\": \"javascript\",\n \".mjs\": \"javascript\",\n \".cjs\": \"javascript\",\n \".py\": \"python\",\n \".pyi\": \"python\",\n \".go\": \"go\",\n \".rs\": \"rust\",\n \".java\": \"java\",\n \".c\": \"c\",\n \".h\": \"c\",\n \".cpp\": \"cpp\",\n \".cc\": \"cpp\",\n \".cxx\": \"cpp\",\n \".hpp\": \"cpp\",\n \".hxx\": \"cpp\",\n \".cs\": \"csharp\",\n \".rb\": \"ruby\",\n \".php\": \"php\",\n \".kt\": \"kotlin\",\n \".kts\": \"kotlin\",\n \".swift\": \"swift\",\n \".sh\": \"bash\",\n \".bash\": \"bash\",\n};\n\n/**\n * Sniff the tier-1 language from a file path's extension.\n * Returns `null` if the extension is not recognized.\n */\nexport function sniffLanguage(filePath: string): CodingGraphLanguage | null {\n const dot = filePath.lastIndexOf(\".\");\n if (dot < 0) return null;\n const ext = filePath.slice(dot).toLowerCase();\n return EXTENSION_MAP[ext] ?? null;\n}\n\n/** Return true if `lang` is a valid tier-1 language. */\nexport function isTier1Language(lang: string): lang is CodingGraphLanguage {\n return (TIER_1_LANGUAGES as readonly string[]).includes(lang);\n}\n","/**\n * Incremental git-based reindex for the coding-graph (issue #1553).\n *\n * Architecture: a PURE planner + a thin executor.\n *\n * planReindex(lastState, gitFacts) → ReindexPlan\n *\n * The planner is a pure function over plain data — unit-testable without\n * git or a real SQLite handle. It classifies the current situation into\n * one of four modes and returns the file set to (re)ingest:\n *\n * - \"full\" — no prior state (fresh DB); parse every candidate.\n * - \"noop\" — HEAD unchanged AND no working-tree dirt; zero writes.\n * - \"incremental\" — HEAD advanced; re-parse only the changed files.\n * - \"hash_scan\" — last_head is unreachable (rebase/force-push);\n * hash every candidate and re-parse only mismatches.\n *\n * The executor parses the plan's files (via an injected `ParseFile` fn),\n * runs the store's `upsertFileBatch`, and persists `last_indexed_head` ONLY\n * after the data transaction commits (rule 25 — a crash between must leave\n * the old head, tested with an injected mid-transaction failure).\n *\n * Post-write reindex invariant (AGENTS.md rule 31): direct-write paths that\n * bypass this pipeline — e.g. a caller using `store.upsertFileBatch`\n * directly — MUST trigger reindex afterward so the graph stays consistent\n * with the persisted content hashes. This module is the canonical way to\n * satisfy that invariant.\n *\n * Distinct from Track A's `session-delta.ts` last-seen-head: that tracks\n * the session's view of the repo for recall diffing; `last_indexed_head`\n * here tracks the GRAPH's persisted index state. They are separate concerns\n * that may legitimately disagree (e.g. a session attached before the first\n * reindex run).\n */\nimport { readFile as fsReadFile, realpath as fsRealpath } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { hashContent } from \"./engine/emit.js\";\n\nimport type { ParseFileInput, ParseResult } from \"@remnic/core\";\n\nimport type { CodingGitInvoker, GitFailure, NameStatusEntry } from \"./git-invoker.js\";\nimport type {\n GraphStore,\n StoreFileIR,\n ReadMetaResult,\n ReadFileHashesResult,\n} from \"./graph-store.js\";\nimport type { GraphStoreFailure } from \"./graph-store.js\";\n\n// ──────────────────────────────────────────────────────────────────────────\n// Public types — the planner's input and output\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * The persisted index state the planner reasons about.\n * `lastHead: null` means \"never indexed\" → full reindex.\n */\nexport interface ReindexState {\n /** The HEAD SHA at the time of the last successful reindex, or `null`. */\n readonly lastHead: string | null;\n /**\n * Per-file content hashes as of the last index. Used by hash_scan mode\n * to detect content drift without a reachable base commit.\n * Keyed by repo-relative forward-slash path.\n */\n readonly fileHashes: ReadonlyMap<string, string>;\n}\n\n/** Git facts the planner needs — gathered BEFORE the plan is computed. */\nexport interface ReindexGitFacts {\n /** Current `git rev-parse HEAD`. `null` when the repo has no commits. */\n readonly currentHead: string | null;\n /**\n * Whether `lastHead` (when non-null) is still reachable in the repo.\n * `false` after a rebase/force-push that rewrote history past the old\n * head. `true` when `lastHead` is null (no prior state to reach).\n */\n readonly lastHeadReachable: boolean;\n /**\n * Files changed between `lastHead` and `currentHead` (when both are\n * non-null and reachable). One entry per file from\n * `git diff --name-status`. Empty array for a noop or fresh repo.\n */\n readonly changedFiles: readonly NameStatusEntry[];\n}\n\n/** What the planner decided to do. */\nexport type ReindexPlan =\n | { readonly mode: \"full\"; readonly reason: string }\n | { readonly mode: \"noop\"; readonly reason: string }\n | { readonly mode: \"incremental\"; readonly changedPaths: readonly string[] }\n | {\n readonly mode: \"hash_scan\";\n readonly reason: string;\n /** Paths whose on-disk hash differs from the stored hash. */\n readonly mismatchedPaths: readonly string[];\n };\n\n/** Result of executing a reindex plan. */\nexport type ReindexResult =\n | {\n readonly ok: true;\n readonly mode: \"full\" | \"noop\" | \"incremental\" | \"hash_scan\";\n /** Number of files actually parsed + ingested. */\n readonly filesIngested: number;\n /** The new `last_indexed_head` persisted to meta (null on noop). */\n readonly head: string | null;\n }\n | ({ readonly ok: false } & GitFailure)\n | {\n readonly ok: false;\n readonly code: \"parse_failed\";\n readonly path: string;\n readonly message: string;\n }\n | {\n readonly ok: false;\n readonly code: \"store_error\";\n readonly message: string;\n };\n\n// ──────────────────────────────────────────────────────────────────────────\n// Meta-table keys — the store's `meta` table is a simple key/value store.\n// ──────────────────────────────────────────────────────────────────────────\n\nexport const META_KEY_LAST_HEAD = \"last_indexed_head\" as const;\n\n/**\n * Meta key holding a JSON array of repo-relative paths that failed to\n * parse on the last run (rule 44: files that fail to parse do not update\n * their stored content hash and MUST retry on the next run). Because a\n * HEAD-unchanged run would otherwise plan `noop` and skip them, the\n * executor consults this set at the top of every run and re-ingests any\n * pending paths even when the plan is noop (cursor Bugbot: 'Parse skips\n * block future reindex').\n */\nexport const META_KEY_PENDING_PARSE_FAILURES =\n \"pending_parse_failures\" as const;\n\n// ──────────────────────────────────────────────────────────────────────────\n// Pure planner — unit-testable without git or SQLite\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Decide what to do given the last state and current git facts.\n *\n * Decision tree:\n * 1. `currentHead === null` → noop (nothing to index; empty repo).\n * 2. `lastHead === null` → full (first index).\n * 3. `lastHead === currentHead` → noop (HEAD unchanged).\n * 4. `!lastHeadReachable` → hash_scan (rebase/force-push lost the base).\n * 5. Otherwise → incremental (re-parse changedFiles paths).\n *\n * Deleted files (status `D`) are included in `changedPaths` so the executor\n * can prune them from the store. Renames include both old and new paths.\n */\nexport function planReindex(\n lastState: ReindexState,\n facts: ReindexGitFacts,\n): ReindexPlan {\n // Case 1: no HEAD at all — nothing to do.\n if (facts.currentHead === null) {\n return { mode: \"noop\", reason: \"repo has no commits (HEAD is null)\" };\n }\n\n // Case 2: never indexed → full.\n if (lastState.lastHead === null) {\n return { mode: \"full\", reason: \"no prior last_indexed_head — first index\" };\n }\n\n // Case 3: HEAD unchanged → noop. No writes, no parse.\n if (lastState.lastHead === facts.currentHead) {\n return { mode: \"noop\", reason: \"HEAD unchanged since last index\" };\n }\n\n // Case 4: last_head is unreachable (rebase/force-push). Fall back to\n // hash_scan — the executor hashes every candidate and re-parses only\n // mismatches. Never crash, never silently full-reindex without saying so.\n if (!facts.lastHeadReachable) {\n return {\n mode: \"hash_scan\",\n reason: `last_indexed_head ${lastState.lastHead.slice(0, 12)} is unreachable (rebase/force-push)`,\n // mismatchedPaths is filled by the executor (needs to read files).\n mismatchedPaths: [],\n };\n }\n\n // Case 5: incremental. The diff is trustworthy; re-parse changed files.\n const changedPaths: string[] = [];\n for (const entry of facts.changedFiles) {\n // `D` = deleted → include so executor prunes the file.\n // `A` = added, `M` = modified → include to (re)parse.\n // `R*`/`C*` = renamed/copied → include both old and new paths.\n changedPaths.push(entry.path);\n if (entry.oldPath !== undefined && entry.oldPath !== entry.path) {\n changedPaths.push(entry.oldPath);\n }\n }\n return { mode: \"incremental\", changedPaths };\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Executor — parses files, drives the store, updates meta\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * A parse function — the engine seam. The executor calls this for each\n * file the plan says to (re)ingest. In production the orchestrator injects\n * the real `CodingGraphEngine.parseFile`; in tests a synthetic parser is\n * injected. This keeps the package decoupled from the engine implementation\n * (which is still a placeholder in #1551 PR1).\n */\nexport type ParseFileFn = (input: ParseFileInput) => Promise<ParseResult>;\n\n/** Injectable file reader — defaults to `node:fs/promises`.readFile. */\nexport type ReadFileFn = (absPath: string) => Promise<Uint8Array>;\n\n/**\n * Read the persisted `last_indexed_head` from the store's meta table.\n * Returns `null` when the key is absent (fresh DB).\n */\nexport function readLastIndexedHead(store: GraphStore): ReadMetaResult {\n return store.readMeta(META_KEY_LAST_HEAD);\n}\n\n/**\n * Read every file row's path → content_hash from the store. Used by\n * hash_scan to detect content drift without a reachable base commit.\n */\nexport function readFileHashes(store: GraphStore): ReadFileHashesResult {\n return store.readFileHashes();\n}\n\n/**\n * SHA-256 of raw bytes — single implementation lives in the engine layer\n * (`emit.ts`, where `FileIR.contentHash` is computed). Re-exported here\n * for backward compatibility with index.ts and consumers that import\n * from reindex.ts (rule 23: one hashing contract across engine + reindex).\n */\nexport { hashContent };\n\n/**\n * Reject non-canonical repo-relative paths before they reach the disk.\n * Git diff output is always canonical, but defense-in-depth prevents a\n * crafted `..` path from reading outside repoRoot (cursor Bugbot:\n * 'Reindex reads non-canonical paths'). Mirrors the store's\n * `assertCanonicalFilePath` boundary.\n */\nfunction isCanonicalRelativePath(p: string): boolean {\n if (typeof p !== \"string\" || p.length === 0) return false;\n if (p.includes(\"\\\\\")) return false; // backslash → Windows separator\n if (p.startsWith(\"/\") || /^[A-Za-z]:[\\\\/]/.test(p)) return false; // absolute\n if (p.split(\"/\").some((seg) => seg === \".\" || seg === \"..\")) return false;\n return true;\n}\n\n/**\n * Probe a repo-relative path: canonical check + existence/content read that\n * distinguishes a CONFIRMED deletion (ENOENT) from a transient I/O error\n * (EACCES / EBUSY / timeout). A transient error must NOT be treated as a\n * deletion — otherwise a momentary lock/permission error would drop the\n * file's nodes from the graph while the file still exists on disk\n * (cursor Bugbot: 'Read errors trigger graph deletes'). Also enforces the\n * canonical-path guard BEFORE any read so a crafted `../` path cannot\n * escape repoRoot (cursor Bugbot: 'Non-canonical paths read early').\n */\ntype ProbeRead =\n | { readonly kind: \"exists\"; readonly content: Uint8Array }\n | { readonly kind: \"missing\" }\n | { readonly kind: \"unknown\" }\n | { readonly kind: \"skip\" };\nasync function probeRead(\n repoRoot: string,\n relPath: string,\n readFile: ReadFileFn,\n): Promise<ProbeRead> {\n if (!isCanonicalRelativePath(relPath)) return { kind: \"skip\" };\n const probeAbs = resolveRepoPath(repoRoot, relPath);\n // Reject symlinks that escape repoRoot before reading (rule 3).\n if (await symlinkEscapesRoot(repoRoot, probeAbs)) return { kind: \"skip\" };\n try {\n const content = await readFile(probeAbs);\n return { kind: \"exists\", content };\n } catch (e) {\n const code =\n e && typeof e === \"object\"\n ? (e as { code?: unknown }).code\n : undefined;\n if (code === \"ENOENT\") return { kind: \"missing\" };\n // Transient error — the file may still exist. Do not prune.\n return { kind: \"unknown\" };\n }\n}\n\n/** Resolve a repo-relative forward-slash path to an absolute OS path. */\nfunction resolveRepoPath(repoRoot: string, relPath: string): string {\n // Split on forward slashes and re-join with the platform separator so\n // Windows backslash-in-relPath is never accidentally treated as escape.\n return path.resolve(repoRoot, ...relPath.split(\"/\"));\n}\n\n/**\n * Symlink-escape guard (AGENTS.md rule 3): a canonical repo-relative path can\n * still resolve — via a symlinked file OR any symlinked PARENT directory — to a\n * target outside repoRoot; following it would let a crafted/tracked symlink\n * read arbitrary files into the graph. `realpath` resolves every symlink in the\n * path (including parents), so we containment-check the fully-resolved target\n * against the resolved repoRoot. A non-existent path (realpath ENOENT) or any\n * realpath error returns false so the normal read path classifies it\n * (ENOENT→missing / transient→retry) and injected readers stay unaffected.\n */\nasync function symlinkEscapesRoot(repoRoot: string, absPath: string): Promise<boolean> {\n try {\n const [realRoot, realAbs] = await Promise.all([\n fsRealpath(repoRoot),\n fsRealpath(absPath),\n ]);\n const rel = path.relative(realRoot, realAbs);\n return rel === \"..\" || rel.startsWith(\"..\" + path.sep) || path.isAbsolute(rel);\n } catch {\n return false;\n }\n}\n\n/** Default file reader: reads from disk via node:fs/promises. */\nasync function defaultReadFile(absPath: string): Promise<Uint8Array> {\n const buf = await fsReadFile(absPath);\n // Return a Uint8Array view over the same buffer (Buffer IS a\n // Uint8Array subclass; this copy-free view satisfies the FileIR contract).\n return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);\n}\n\n/**\n * Per-store reindex serialization. Concurrent `executeReindex` calls\n * against the same store are serialized end-to-end so a slower run that\n * indexed an older HEAD cannot finish later and overwrite\n * `last_indexed_head` with a stale SHA (cursor Bugbot: 'Stale head\n * after concurrent reindex'). The store's write queue already serializes\n * individual upserts, but the head/meta writes around them are not\n * ordered across runs without this gate.\n */\nconst reindexLocks = new WeakMap<GraphStore, Promise<unknown>>();\n\n/**\n * Execute a reindex against a store + git repo.\n *\n * Steps:\n * 1. Gather git facts (currentHead, reachable, changedFiles).\n * 2. Plan via {@link planReindex}.\n * 3. For full/incremental/hash_scan: parse each file, build a batch,\n * call `store.upsertFileBatch`.\n * 4. Persist `last_indexed_head` ONLY after the batch commits (rule 25).\n *\n * The reindex is serialized per-store via the store's own write queue.\n * A session-start trigger racing a manual CLI trigger coalesces (rule 40).\n *\n * `candidatePaths` is needed for full and hash_scan modes (the set of\n * files to consider). Typically from `git ls-files` or a glob. When\n * omitted, full/hash_scan operate over the union of stored files and\n * git-tracked files.\n */\nexport async function executeReindex(options: {\n readonly store: GraphStore;\n readonly git: CodingGitInvoker;\n readonly repoRoot: string;\n readonly parseFile: ParseFileFn;\n readonly candidatePaths?: readonly string[];\n readonly readFile?: ReadFileFn;\n}): Promise<ReindexResult> {\n const { store, git, repoRoot, parseFile } = options;\n const readFile = options.readFile ?? defaultReadFile;\n\n // Serialize concurrent runs against the same store end-to-end.\n const prev = reindexLocks.get(store) ?? Promise.resolve();\n let release!: () => void;\n const next = new Promise<void>((resolve) => {\n release = resolve;\n });\n reindexLocks.set(store, prev.then(() => next));\n await prev;\n try {\n return await runReindex(store, git, repoRoot, parseFile, readFile, options);\n } finally {\n release();\n }\n}\n\n/** Inner reindex body — runs under the per-store serialization lock. */\nasync function runReindex(\n store: GraphStore,\n git: CodingGitInvoker,\n repoRoot: string,\n parseFile: ParseFileFn,\n readFile: ReadFileFn,\n options: {\n readonly candidatePaths?: readonly string[];\n },\n): Promise<ReindexResult> {\n\n // ── Gather git facts ──────────────────────────────────────────────────\n // readMeta now returns a tagged result (rule 22): a backend failure must\n // NOT be conflated with \"never indexed\" (null), which would send the\n // planner down a full-reindex path against a store it cannot read. Bail\n // before any mutation (cursor Bugbot: 'readMeta conflates absent key\n // with db failure').\n const lastHeadRead = readLastIndexedHead(store);\n if (!lastHeadRead.ok) {\n return {\n ok: false,\n code: \"store_error\",\n message: `read last_indexed_head: ${lastHeadRead.code}`,\n };\n }\n const lastHead = lastHeadRead.value;\n const headResult = git.revParseHead(repoRoot);\n if (!headResult.ok) return headResult;\n\n let reachable = true;\n if (lastHead !== null && headResult.head !== null) {\n const reachResult = git.isReachable(repoRoot, lastHead);\n if (!reachResult.ok) return reachResult;\n reachable = reachResult.reachable;\n }\n\n let changedFiles: NameStatusEntry[] = [];\n if (\n lastHead !== null &&\n headResult.head !== null &&\n reachable &&\n lastHead !== headResult.head\n ) {\n const diffResult = git.diffNameStatus(\n repoRoot,\n `${lastHead}..${headResult.head}`,\n );\n if (!diffResult.ok) {\n // Diff failed but heads were \"reachable\" — degrade to hash_scan.\n reachable = false;\n } else {\n changedFiles = [...diffResult.entries];\n }\n }\n\n const facts: ReindexGitFacts = {\n currentHead: headResult.head,\n lastHeadReachable: reachable,\n changedFiles,\n };\n // Read the file-hash snapshot ONCE. A backend failure here must NOT be\n // treated as an empty index — that would either skip pruning while head\n // advances, or prune against a falsely-empty set. Bail before any mutation\n // (rule 22; cursor Bugbot HIGH: 'readFileHashes conflates error with\n // empty'). The same snapshot is reused for every prune decision below:\n // nothing mutates the store between here and the ingest, so a fresh read\n // at each site would only re-introduce the error/empty conflation.\n const fileHashesRead = readFileHashes(store);\n if (!fileHashesRead.ok) {\n return {\n ok: false,\n code: \"store_error\",\n message: `readFileHashes: ${fileHashesRead.code}`,\n };\n }\n const fileHashes = fileHashesRead.hashes;\n\n const lastState: ReindexState = {\n lastHead,\n fileHashes,\n };\n\n const plan = planReindex(lastState, facts);\n\n // ── Pending parse-failure retry (rule 44) ─────────────────────────────\n // Paths that failed to parse on a prior run must be retried even when\n // HEAD is unchanged (a noop plan would otherwise skip them). Read once;\n // we rewrite this set after every run with the CURRENT run's failures.\n const pendingRetryRead = readPendingParseFailures(store);\n if (!pendingRetryRead.ok) {\n return {\n ok: false,\n code: \"store_error\",\n message: `read pending_parse_failures: ${pendingRetryRead.code}`,\n };\n }\n const pendingRetry = pendingRetryRead.paths;\n\n // ── Execute the plan ──────────────────────────────────────────────────\n switch (plan.mode) {\n case \"noop\": {\n if (pendingRetry.length === 0) {\n return { ok: true, mode: \"noop\", filesIngested: 0, head: lastHead };\n }\n // HEAD unchanged but some files still need a parse retry. Re-ingest\n // ONLY the pending set (no diff, no deletes). Clears paths that now\n // parse; keeps failures for the next run.\n const ingestResult = await ingestFiles(\n store,\n repoRoot,\n parseFile,\n readFile,\n pendingRetry,\n );\n if (!ingestResult.ok) return ingestResult;\n // Persist the updated pending set BEFORE any head write (rule 25:\n // meta updates after the data transaction commits).\n store.writeMeta(\n META_KEY_PENDING_PARSE_FAILURES,\n JSON.stringify(ingestResult.parseFailedPaths),\n );\n return {\n ok: true,\n mode: \"noop\",\n filesIngested: ingestResult.count,\n head: lastHead,\n };\n }\n\n case \"full\": {\n // An EMPTY explicit list is as insufficient as an omitted one: it is not\n // an authoritative \"the repo has zero files\" signal, so it must NOT drive\n // pruning or head advancement (cursor Bugbot HIGH: 'Empty candidate list\n // prunes graph'). Require a non-empty list to treat candidates as\n // authoritative. The genuine empty-repo case (no candidates, no retries)\n // is handled below as a noop.\n const candidatesProvided =\n options.candidatePaths !== undefined && options.candidatePaths.length > 0;\n const candidates = options.candidatePaths ?? [];\n if (candidates.length === 0 && pendingRetry.length === 0) {\n // Nothing to index and nothing to retry. Do NOT advance\n // last_indexed_head — without candidates we did not build an\n // index, so claiming freshness would be dishonest (an empty\n // repo reports mode:\"empty\" via index_status, which is correct).\n // (cursor Bugbot: 'Empty full run marks indexed'.)\n return { ok: true, mode: \"noop\", filesIngested: 0, head: lastHead };\n }\n // Dedup: a path can appear in both candidates and pendingRetry;\n // upsertFileBatch rejects duplicate paths in one batch (cursor\n // Bugbot: 'Full reindex duplicate ingest paths').\n const toIngest = [...new Set([...candidates, ...pendingRetry])];\n // Prune stored files that are absent from the candidate set so a\n // full reindex against a pre-existing store (v1 store, or a crash\n // before the first meta write) does not leave deleted-file symbols\n // behind while marking the index current (chatgpt-codex-connector:\n // 'Prune absent files during full reindex').\n // Only prune when the caller supplied an AUTHORITATIVE candidate list.\n // Without candidatePaths (e.g. a retry-only resume) we cannot know which\n // stored files are truly absent; pruning against a retry-only set would\n // destructively delete every successfully-indexed file. Likewise we must\n // not advance last_indexed_head in that case, since a partial (retry-only)\n // pass has not re-verified the whole tree (chatgpt-codex-connector: 'Do\n // not prune full indexes when candidates are absent').\n let fullDelete: string[] = [];\n if (candidatesProvided) {\n const fullCandidateSet = new Set(toIngest);\n fullDelete = [...fileHashes.keys()].filter(\n (p) => !fullCandidateSet.has(p),\n );\n }\n const ingestResult = await ingestFiles(\n store,\n repoRoot,\n parseFile,\n readFile,\n toIngest,\n fullDelete,\n );\n if (!ingestResult.ok) return ingestResult;\n store.writeMeta(\n META_KEY_PENDING_PARSE_FAILURES,\n JSON.stringify(\n computeNextPending({\n priorPending: pendingRetry,\n parseFailedPaths: ingestResult.parseFailedPaths,\n ingestedCandidates: toIngest,\n deleted: fullDelete,\n }),\n ),\n );\n // Rule 25: persist head ONLY after data + pending-set commit — and ONLY\n // when candidates were authoritative (see above).\n if (candidatesProvided) {\n store.writeMeta(META_KEY_LAST_HEAD, headResult.head ?? \"\");\n }\n return {\n ok: true,\n mode: \"full\",\n filesIngested: ingestResult.count,\n head: candidatesProvided ? headResult.head : lastHead,\n };\n }\n\n case \"incremental\": {\n // Deduplicate (rule 49 — a file can appear staged AND in the commit\n // diff). Use a Set for dynamic runtime membership tracking.\n const seen = new Set<string>();\n for (const p2 of plan.changedPaths) seen.add(p2);\n // Pending parse-failures from a prior run are also candidates.\n for (const p2 of pendingRetry) seen.add(p2);\n\n // Determine which paths still exist on disk vs are deleted. Use a\n // probe that distinguishes a confirmed deletion (ENOENT) from a\n // transient I/O error (which must NOT prune the file).\n const knownFiles = fileHashes;\n const toDelete: string[] = [];\n const toIngest: string[] = [];\n // New candidate paths that probe missing on disk (sparse checkout /\n // partial worktree / list-vs-disk race): retained in pending so a later\n // run indexes them instead of advancing head as if done.\n const missingNew: string[] = [];\n for (const p2 of seen) {\n const probe = await probeRead(repoRoot, p2, readFile);\n if (probe.kind === \"skip\") {\n // A previously-indexed path that is now non-canonical or an escaping\n // symlink (probe skip) is no longer a readable repo file — prune its\n // stale nodes rather than leaving them while head advances\n // (chatgpt-codex-connector: 'Retain skipped symlink paths before\n // advancing').\n if (knownFiles.has(p2)) toDelete.push(p2);\n continue;\n }\n if (probe.kind === \"exists\" || probe.kind === \"unknown\") {\n // exists → re-ingest; unknown (transient error) → route to\n // ingest so ingestFiles records it as a pending retry if the\n // read still fails, WITHOUT deleting the existing nodes.\n toIngest.push(p2);\n } else if (probe.kind === \"missing\") {\n // Confirmed deletion → prune if known; a NEW absent path → retain in\n // pending (cursor Bugbot: 'Missing new paths skip pending').\n if (knownFiles.has(p2)) toDelete.push(p2);\n else missingNew.push(p2);\n }\n }\n\n // Pass toDelete into upsertFileBatch so the prune + re-ingest land\n // in ONE transaction (rule 22/25 — a mid-batch failure rolls both\n // back; cursor Bugbot: 'Deletes commit before ingest fails').\n const ingestResult = await ingestFiles(\n store,\n repoRoot,\n parseFile,\n readFile,\n toIngest,\n toDelete,\n );\n if (!ingestResult.ok) return ingestResult;\n store.writeMeta(\n META_KEY_PENDING_PARSE_FAILURES,\n JSON.stringify(\n computeNextPending({\n priorPending: pendingRetry,\n parseFailedPaths: ingestResult.parseFailedPaths,\n extraRetry: missingNew,\n ingestedCandidates: toIngest,\n deleted: toDelete,\n }),\n ),\n );\n // Rule 25: persist head ONLY after data commits.\n store.writeMeta(META_KEY_LAST_HEAD, headResult.head ?? \"\");\n return {\n ok: true,\n mode: \"incremental\",\n filesIngested: ingestResult.count,\n head: headResult.head,\n };\n }\n\n case \"hash_scan\": {\n // Hash every candidate, re-parse only mismatches. This path is\n // taken when last_head is unreachable (rebase/force-push) or when\n // the incremental diff itself failed.\n // Union caller-supplied candidates WITH the previously-indexed\n // files AND pending retries. Omitting stored files would miss\n // newly-deleted files (their symbols would linger while head\n // advances); omitting candidates would miss newly-added files not\n // yet in the index (chatgpt-codex-connector: 'Require candidates\n // before completing hash-scan' / 'Include indexed files in\n // hash-scan candidates'). An EMPTY explicit list is as insufficient\n // as an omitted one, so it must not drive head advancement.\n const hashScanCandidatesProvided =\n options.candidatePaths !== undefined && options.candidatePaths.length > 0;\n const candidateSet = new Set<string>([\n ...(options.candidatePaths ?? []),\n ...lastState.fileHashes.keys(),\n ...pendingRetry,\n ]);\n // Hash every candidate via the canonical+ENOENT-aware probe. A\n // transient read error must NOT be treated as a deletion.\n const knownFiles = fileHashes;\n const toDelete: string[] = [];\n const toIngest: string[] = [];\n const hashScanRetry: string[] = [];\n for (const candidatePath of candidateSet) {\n const probe = await probeRead(repoRoot, candidatePath, readFile);\n if (probe.kind === \"skip\") {\n // Previously-indexed path now non-canonical / escaping symlink →\n // prune stale nodes rather than leave them while head advances.\n if (knownFiles.has(candidatePath)) toDelete.push(candidatePath);\n continue;\n }\n if (probe.kind === \"missing\") {\n // Confirmed deletion of a known file → prune. A NEW candidate absent\n // on disk (sparse checkout / list-vs-disk race) → retain in pending so\n // a later run indexes it rather than advancing head as if done\n // (cursor Bugbot: 'Missing new paths skip pending').\n if (knownFiles.has(candidatePath)) toDelete.push(candidatePath);\n else hashScanRetry.push(candidatePath);\n continue;\n }\n if (probe.kind === \"unknown\") {\n // Transient read error — keep the stored entry, do not prune,\n // and record the path for a pending retry so it is re-tried on\n // the next run even when HEAD is unchanged (chatgpt-codex-\n // connector: 'Retain hash-scan read failures for retry').\n hashScanRetry.push(candidatePath);\n continue;\n }\n // exists — compare content hash.\n const currentHash = hashContent(probe.content);\n const storedHash = lastState.fileHashes.get(candidatePath);\n if (storedHash !== currentHash) {\n toIngest.push(candidatePath);\n }\n }\n\n const ingestResult = await ingestFiles(\n store,\n repoRoot,\n parseFile,\n readFile,\n toIngest,\n toDelete,\n );\n if (!ingestResult.ok) return ingestResult;\n store.writeMeta(\n META_KEY_PENDING_PARSE_FAILURES,\n JSON.stringify(\n computeNextPending({\n priorPending: pendingRetry,\n parseFailedPaths: ingestResult.parseFailedPaths,\n extraRetry: hashScanRetry,\n ingestedCandidates: toIngest,\n deleted: toDelete,\n }),\n ),\n );\n // Rule 25: persist head ONLY after data commits — and ONLY when the\n // caller supplied candidates. Without candidatePaths the scan covers only\n // stored + pending paths, so a file newly added in the current HEAD would\n // be missed; advancing head would falsely report freshness (chatgpt-codex-\n // connector: 'Require current candidates before advancing hash-scan').\n if (hashScanCandidatesProvided) {\n store.writeMeta(META_KEY_LAST_HEAD, headResult.head ?? \"\");\n }\n return {\n ok: true,\n mode: \"hash_scan\",\n filesIngested: ingestResult.count,\n head: hashScanCandidatesProvided ? headResult.head : lastHead,\n };\n }\n }\n}\n\n/**\n * Read the persisted set of paths that failed to parse on the last run\n * (rule 44). Returns an empty array when the key is absent or malformed.\n */\nfunction readPendingParseFailures(\n store: GraphStore,\n): { ok: true; paths: string[] } | ({ ok: false } & GraphStoreFailure) {\n const rawRead = store.readMeta(META_KEY_PENDING_PARSE_FAILURES);\n if (!rawRead.ok) return rawRead;\n const raw = rawRead.value;\n if (raw === null) return { ok: true, paths: [] };\n try {\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) return { ok: true, paths: [] };\n return {\n ok: true,\n paths: parsed.filter((p): p is string => typeof p === \"string\"),\n };\n } catch {\n return { ok: true, paths: [] };\n }\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Helpers\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Compute the next `pending_parse_failures` set. A path must remain pending\n * until it is actually INGESTED (parsed + stored) or confirmed DELETED —\n * otherwise a pending path that was skipped (non-canonical), hash-matched in\n * hash_scan, or otherwise not re-ingested silently drops off the retry list\n * while `last_indexed_head` advances, so its symbols are never (re)built yet the\n * index reports fresh (cursor Bugbot HIGH: 'Pending retries cleared\n * incorrectly'). The next set is therefore the union of the prior pending set,\n * this run's parse failures, and any transient retries, MINUS the paths that\n * were successfully ingested or deleted this run.\n */\nfunction computeNextPending(args: {\n readonly priorPending: readonly string[];\n readonly parseFailedPaths: readonly string[];\n readonly extraRetry?: readonly string[];\n readonly ingestedCandidates: readonly string[];\n readonly deleted: readonly string[];\n}): string[] {\n const failed = new Set(args.parseFailedPaths);\n const successfullyIngested = new Set(\n args.ingestedCandidates.filter((p) => !failed.has(p)),\n );\n const deleted = new Set(args.deleted);\n const next = new Set<string>();\n for (const path of [\n ...args.priorPending,\n ...args.parseFailedPaths,\n ...(args.extraRetry ?? []),\n ]) {\n if (successfullyIngested.has(path) || deleted.has(path)) continue;\n next.add(path);\n }\n return [...next];\n}\n\n/**\n * Narrow result type for the ingest helper — avoids union overlap with\n * {@link ReindexResult}'s success branches so the caller can cleanly\n * discriminate on `ok` and read `.count` without ambiguity.\n */\ntype IngestResult =\n | {\n readonly ok: true;\n readonly count: number;\n /** Paths that failed to parse (rule 44 — must retry next run). */\n readonly parseFailedPaths: string[];\n }\n | { readonly ok: false; readonly code: \"store_error\"; readonly message: string };\n/**\n * Parse + ingest a list of files. Returns the count ingested or a tagged\n * failure. Files that fail to parse do NOT update their stored content\n * hash (rule 44 — they must retry next run). The caller records them in\n * the `pending_parse_failures` meta key so the NEXT run re-ingests them\n * even when HEAD is unchanged (a noop plan would otherwise skip them —\n * cursor Bugbot: 'Parse skips block future reindex').\n */\nasync function ingestFiles(\n store: GraphStore,\n repoRoot: string,\n parseFile: ParseFileFn,\n readFile: ReadFileFn,\n paths: readonly string[],\n deletePaths: readonly string[] = [],\n): Promise<IngestResult> {\n const batch: StoreFileIR[] = [];\n const parseFailedPaths: string[] = [];\n for (const relPath of paths) {\n if (!isCanonicalRelativePath(relPath)) {\n // Non-canonical path would read outside repoRoot / be rejected by\n // the store anyway. Record + skip (rule 44 — retry-safe).\n parseFailedPaths.push(relPath);\n continue;\n }\n const ingestAbs = resolveRepoPath(repoRoot, relPath);\n // Reject symlinks that escape repoRoot before reading (rule 3). A skipped\n // escape is not a parse failure — do not retain it as a pending retry.\n if (await symlinkEscapesRoot(repoRoot, ingestAbs)) continue;\n let content: Uint8Array;\n try {\n content = await readFile(ingestAbs);\n } catch {\n // File unreadable (deleted between plan and execution, or a\n // transient I/O error). Record it for retry so a transient read\n // failure does not silently drop a path that was never ingested\n // (cursor Bugbot: 'Read errors drop pending retries'). A truly\n // deleted file is pruned via deletePaths when the caller detected\n // the deletion; here we only keep it retrying.\n parseFailedPaths.push(relPath);\n continue;\n }\n const parseResult = await parseFile({ path: relPath, content });\n if (!parseResult.ok) {\n // Rule 44: record unparseable files so they retry next run; do\n // not fail the batch.\n parseFailedPaths.push(relPath);\n continue;\n }\n // FileIR is structurally assignable to StoreFileIR — the store only\n // reads the fields it needs and ignores extra FileIR fields (imports,\n // callSites). No cast needed.\n batch.push(parseResult.ir);\n }\n // Even when batch is empty we must run the upsert so deletePaths are\n // pruned atomically (the store's transaction wraps both). A zero-file,\n // zero-delete call is a cheap no-op.\n const upsertResult = await store.upsertFileBatch(batch, deletePaths);\n if (!upsertResult.ok) {\n return {\n ok: false,\n code: \"store_error\",\n message: `upsertFileBatch failed: ${upsertResult.code}`,\n };\n }\n return { ok: true, count: batch.length, parseFailedPaths };\n}\n\n","/**\n * detect_changes + blast radius for the coding-graph (issue #1553).\n *\n * Maps the current diff (staged + unstaged + committed-since-last-index)\n * hunks to symbols whose spans overlap the changed line ranges — against\n * a FRESH parse of the changed files, never against stale stored spans.\n *\n * Blast radius: reverse BFS from affected symbols over inbound\n * CALLS / IMPORTS / USES_TYPE edges with a depth cap. Risk classification\n * is a deterministic rubric (no LLM in the loop):\n *\n * ┌──────────────┬──────────────────────────────────────────────────┐\n * │ Risk │ Criterion │\n * ├──────────────┼──────────────────────────────────────────────────┤\n * │ \"direct\" │ The symbol itself is in a changed hunk (depth 0).│\n * │ \"near\" │ 1 hop away from a directly-changed symbol. │\n * │ \"transitive\" │ 2+ hops away. │\n * └──────────────┴──────────────────────────────────────────────────┘\n *\n * Fan-in escalation: when an affected symbol has ≥ FAN_IN_ESCALATION\n * inbound edges, its risk is escalated one level (near→direct,\n * transitive→near) because high-fan-in symbols concentrate blast radius.\n *\n * Half-open interval semantics (rule 35): a hunk range `[startLine, endLine)`\n * overlaps a symbol span `[symStartLine, symEndLine)` iff\n * `startLine < symEndLine && symStartLine < endLine`.\n * Boundary lines (exact hit on startLine or endLine-1) are tested.\n */\nimport { nodeIdFor, type GraphStore, type TraverseHit } from \"./graph-store.js\";\nimport type { DiffHunk } from \"./git-invoker.js\";\nimport type { SymbolIR, FileIR } from \"@remnic/core\";\n\n// ──────────────────────────────────────────────────────────────────────────\n// Public types\n// ──────────────────────────────────────────────────────────────────────────\n\n/** Risk classification rubric — deterministic, no LLM. */\nexport type RiskLevel = \"direct\" | \"near\" | \"transitive\";\n\n/** A symbol affected by the current diff, with its blast-radius classification. */\nexport interface AffectedSymbol {\n /** Qualified name of the symbol. */\n readonly qualifiedName: string;\n /** Simple name of the symbol. */\n readonly name: string;\n /** Symbol kind (function, class, method, ...). */\n readonly label: string;\n /** Repo-relative file path. */\n readonly filePath: string;\n /** Risk level from the deterministic rubric. */\n readonly risk: RiskLevel;\n /** BFS depth from the nearest directly-changed symbol (0 = direct). */\n readonly depth: number;\n /** Total inbound edge count (fan-in) at the time of classification. */\n readonly fanIn: number;\n}\n\n/** Result of detect_changes. */\nexport type DetectChangesResult =\n | { readonly ok: true; readonly affected: readonly AffectedSymbol[] }\n | { readonly ok: false; readonly code: \"git_error\" | \"store_error\" };\n\n/**\n * Result of computeBlastRadius. A backend/store failure during traversal is\n * surfaced as `{ ok: false; code: \"store_error\" }` rather than masked as an\n * empty result — the blast-radius computation is unreliable when the store\n * cannot be read (rule 22; cursor Bugbot: 'computeBlastRadius masks traverse\n * failures'). `{ ok: true; affected: [] }` is a genuinely empty blast radius.\n */\nexport type BlastRadiusResult =\n | { ok: true; affected: readonly AffectedSymbol[] }\n | { ok: false; code: \"store_error\" };\n\n// ──────────────────────────────────────────────────────────────────────────\n// Constants — the risk rubric\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Edge types traversed for blast-radius computation. These represent\n * \"depends on\" relationships — an inbound edge of any of these types\n * means the source symbol is affected when the destination changes.\n */\nexport const BLAST_RADIUS_EDGE_TYPES = [\n \"CALLS\",\n \"IMPORTS\",\n \"USES_TYPE\",\n] as const;\n\n/**\n * Fan-in threshold for risk escalation. When an affected symbol has this\n * many or more inbound edges, its risk is escalated one level because\n * high-fan-in symbols concentrate blast radius.\n */\nexport const FAN_IN_ESCALATION_THRESHOLD = 5;\n\n/** Maximum BFS depth for blast-radius traversal. */\nexport const DEFAULT_BLAST_RADIUS_DEPTH = 3;\n\n/**\n * Failure codes that indicate a backend/store problem (rule 22), as opposed\n * to a logical no-result (unknown_start / ambiguous_start / invalid_query).\n * Used by computeBlastRadius to decide whether to surface a traverse failure\n * or merely skip the symbol.\n */\nconst STORE_FAILURE_CODES: ReadonlySet<string> = new Set([\n \"db_locked\",\n \"db_corrupt\",\n \"db_error\",\n \"store_closed\",\n]);\nfunction isStoreFailureCode(code: string): boolean {\n return STORE_FAILURE_CODES.has(code);\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Line-range overlap (rule 35: half-open intervals on both sides)\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Byte offset → 1-based line number. Walks the content counting newlines\n * up to (but not including) the byte offset. Used to convert symbol spans\n * (byte offsets) to line ranges for hunk-overlap comparison.\n *\n * Returns `{ startLine, endLine }` where both are 1-based and `endLine`\n * is exclusive (half-open — rule 35). A symbol that starts at byte 0 on\n * line 1 and ends at byte 10 (still line 1) has `{ 1, 2 }`.\n */\nexport function byteSpanToLines(\n content: Uint8Array,\n startByte: number,\n endByte: number,\n): { startLine: number; endLine: number } {\n let line = 1;\n let startLine = 1;\n let endLine = 1;\n for (let i = 0; i < content.length; i += 1) {\n if (i === startByte) startLine = line;\n if (content[i] === 0x0a) line += 1;\n if (i === endByte - 1 && content[i] === 0x0a) {\n // endByte is exclusive; if the last byte is a newline, the symbol\n // ends at the start of the next line.\n endLine = line;\n } else if (i + 1 === endByte) {\n endLine = line + (content[i] === 0x0a ? 0 : 1);\n }\n }\n // Handle edge case: startByte or endByte at content boundary.\n if (startByte >= content.length) startLine = line;\n if (endByte > content.length) endLine = line + 1;\n return { startLine, endLine };\n}\n\n/**\n * Half-open overlap test (rule 35): two ranges `[aStart, aEnd)` and\n * `[bStart, bEnd)` overlap iff `aStart < bEnd && bStart < aEnd`.\n *\n * Boundary case: a hunk starting exactly at `symEndLine` does NOT overlap\n * (the symbol ends before the hunk starts). A hunk ending exactly at\n * `symStartLine` does NOT overlap (the hunk ends before the symbol starts).\n */\nexport function rangesOverlap(\n a: { startLine: number; endLine: number },\n b: { startLine: number; endLine: number },\n): boolean {\n return a.startLine < b.endLine && b.startLine < a.endLine;\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// detect_changes — map hunks → symbols → blast radius\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Classify risk from BFS depth + fan-in. Deterministic rubric:\n * - depth 0 → \"direct\"\n * - depth 1 → \"near\"\n * - depth 2+ → \"transitive\"\n * - fanIn ≥ threshold escalates one level (capped at \"direct\")\n */\nexport function classifyRisk(depth: number, fanIn: number): RiskLevel {\n let base: RiskLevel;\n if (depth === 0) base = \"direct\";\n else if (depth === 1) base = \"near\";\n else base = \"transitive\";\n // Fan-in escalation: high-fan-in symbols are more dangerous.\n if (fanIn >= FAN_IN_ESCALATION_THRESHOLD) {\n if (base === \"near\") base = \"direct\";\n else if (base === \"transitive\") base = \"near\";\n }\n return base;\n}\n\n/**\n * Find symbols whose line ranges overlap any hunk in the given file.\n * Uses a FRESH parse (never stale stored spans). Returns the set of\n * directly-affected qualified names.\n */\nexport function findDirectlyAffectedSymbols(\n hunksByPath: ReadonlyMap<string, readonly DiffHunk[]>,\n freshIRs: ReadonlyMap<string, FileIR>,\n contentsByPath: ReadonlyMap<string, Uint8Array>,\n): Set<string> {\n // Returns NODE IDS (not qualified names). Node identity is the full\n // (qualifiedName, filePath, label) triple, so two files that emit the\n // same qualifiedName produce distinct ids and downstream traversal\n // resolves unambiguously by id instead of returning 'ambiguous_start'\n // (chatgpt-codex-connector: 'Preserve node identity for direct\n // blast-radius hits').\n const affected = new Set<string>();\n for (const [filePath, hunks] of hunksByPath) {\n const ir = freshIRs.get(filePath);\n if (!ir) continue;\n const content = contentsByPath.get(filePath);\n if (!content) continue;\n for (const sym of ir.symbols) {\n const symLines = byteSpanToLines(\n content,\n sym.span.startByte,\n sym.span.endByte,\n );\n for (const hunk of hunks) {\n if (rangesOverlap(symLines, hunk.newRange)) {\n affected.add(nodeIdFor({ qualifiedName: sym.qualifiedName, filePath, label: sym.kind }));\n break;\n }\n }\n }\n }\n return affected;\n}\n\n/**\n * Compute blast radius from a set of directly-affected symbols using the\n * store's `traverse` primitive (direction: incoming). Reuses the existing\n * BFS — does NOT write a second traversal (rule 22 spirit).\n *\n * Returns affected symbols with their risk classification. Byte-stable:\n * the same diff + graph always produces the same output.\n */\nexport function computeBlastRadius(\n store: GraphStore,\n directlyAffected: ReadonlySet<string>,\n maxDepth: number = DEFAULT_BLAST_RADIUS_DEPTH,\n): BlastRadiusResult {\n if (directlyAffected.size === 0) return { ok: true, affected: [] };\n\n // Collect all reachable symbols via inbound traversal from each\n // directly-affected symbol. We traverse from the affected symbol\n // OUTWARD via incoming edges — meaning \"who depends on me\".\n const hitByDepth = new Map<string, number>();\n const hitMeta = new Map<\n string,\n { qualifiedName: string; name: string; label: string; filePath: string }\n >();\n\n for (const qname of directlyAffected) {\n const result = store.traverse({\n start: qname,\n direction: \"incoming\",\n edgeTypes: [...BLAST_RADIUS_EDGE_TYPES],\n maxDepth,\n });\n if (!result.ok) {\n // A backend failure (db_locked/corrupt/error/store_closed) means the\n // blast-radius computation is unreliable — surface it instead of\n // conflating it with \"no inbound edges\" (rule 22). A logical failure\n // (unknown_start/ambiguous_start/invalid_query) means this symbol\n // contributes no traversal, so skip it without aborting the set.\n if (isStoreFailureCode(result.code)) {\n return { ok: false, code: \"store_error\" };\n }\n continue;\n }\n for (const hit of result.hits) {\n const existing = hitByDepth.get(hit.nodeId);\n // Keep the minimum depth (closest to a directly-affected symbol).\n if (existing === undefined || hit.depth < existing) {\n hitByDepth.set(hit.nodeId, hit.depth);\n hitMeta.set(hit.nodeId, {\n qualifiedName: hit.qualifiedName,\n name: hit.name,\n label: hit.label,\n filePath: hit.filePath,\n });\n }\n }\n }\n\n // Build the result with risk classification.\n const out: AffectedSymbol[] = [];\n for (const [nodeId, depth] of hitByDepth) {\n const meta = hitMeta.get(nodeId);\n if (!meta) continue;\n // Compute fan-in: count inbound edges of the blast-radius types.\n // A separate traverse at depth 1 gives us the inbound neighbors.\n const inboundResult = store.traverse({\n start: nodeId,\n direction: \"incoming\",\n edgeTypes: [...BLAST_RADIUS_EDGE_TYPES],\n maxDepth: 1,\n });\n let fanIn: number;\n if (inboundResult.ok) {\n fanIn = inboundResult.hits.filter((h) => h.depth > 0).length;\n } else if (isStoreFailureCode(inboundResult.code)) {\n // Same discipline as the main loop: a store failure on the fan-in\n // read makes the classification unreliable — surface it (rule 22).\n return { ok: false, code: \"store_error\" };\n } else {\n // Logical failure (unknown_start/...): this node has no resolvable\n // inbound neighbors — fan-in is 0 for risk classification only.\n fanIn = 0;\n }\n const risk = classifyRisk(depth, fanIn);\n // filePath comes straight from the traverse hit (joined from\n // files.path in the store) — resolving via searchGraph by short\n // name would attach the wrong path when a simple name is declared\n // in more than one file (cursor Bugbot: 'Blast radius wrong file\n // path').\n out.push({\n qualifiedName: meta.qualifiedName,\n name: meta.name,\n label: meta.label,\n filePath: meta.filePath,\n risk,\n depth,\n fanIn,\n });\n }\n\n // Sort for byte-stability: by risk, then qualifiedName, then filePath,\n // then nodeId. A total order is required so two symbols that share a\n // qualifiedName across files (different filePaths / node ids) still\n // sort deterministically (cursor Bugbot: 'Blast radius sort not\n // byte-stable').\n const riskOrder: Record<RiskLevel, number> = {\n direct: 0,\n near: 1,\n transitive: 2,\n };\n // Build a (qualifiedName \u0000 filePath) -> nodeId lookup so the comparator\n // can resolve each AffectedSymbol back to its node id for the final\n // tiebreaker.\n const nodeIdByQFile = new Map<string, string>();\n for (const [nodeId, depth] of hitByDepth) {\n const m = hitMeta.get(nodeId);\n if (m) nodeIdByQFile.set(`${m.qualifiedName}\\0${m.filePath}\\0${m.label}`, nodeId);\n void depth;\n }\n out.sort((a, b) => {\n const riskDiff = riskOrder[a.risk] - riskOrder[b.risk];\n if (riskDiff !== 0) return riskDiff;\n const qCmp = a.qualifiedName.localeCompare(b.qualifiedName);\n if (qCmp !== 0) return qCmp;\n const fCmp = a.filePath.localeCompare(b.filePath);\n if (fCmp !== 0) return fCmp;\n // Node identity is (qualifiedName, filePath, label); include label so two\n // distinct nodes sharing qualifiedName+filePath do not collapse to one key\n // (chatgpt-codex-connector: 'Include label in blast-radius tiebreaker').\n const lCmp = a.label.localeCompare(b.label);\n if (lCmp !== 0) return lCmp;\n const aId = nodeIdByQFile.get(`${a.qualifiedName}\\0${a.filePath}\\0${a.label}`) ?? \"\";\n const bId = nodeIdByQFile.get(`${b.qualifiedName}\\0${b.filePath}\\0${b.label}`) ?? \"\";\n return aId.localeCompare(bId);\n });\n\n return { ok: true, affected: out };\n}\n","/**\n * FILE_CHANGES_WITH co-change edge mining (issue #1553).\n *\n * Mines co-change relationships from `git log --name-only` over a bounded\n * window (default 500 commits). An edge between two files is written when:\n * - support (co-change count) ≥ `minSupport`\n * - confidence (co-change / total changes of the less-changed file) ≥\n * `minConfidence`\n *\n * Deterministic given the same history — re-running on unchanged history\n * is idempotent (same edges, same confidence).\n *\n * Co-change edges are stored in a dedicated `co_changes` table (file-level,\n * not symbol-level — the existing `edges` table is between symbol nodes).\n * The table is additive to schema v1 (CREATE TABLE IF NOT EXISTS — same\n * pattern as `node_attributes` in PR2).\n */\nimport type { CodingGitInvoker, GitFailure, LogFilesEntry } from \"./git-invoker.js\";\nimport type { GraphStore } from \"./graph-store.js\";\n\n// ──────────────────────────────────────────────────────────────────────────\n// Public types\n// ──────────────────────────────────────────────────────────────────────────\n\n/** A mined co-change edge between two files. */\nexport interface CoChangeEdge {\n readonly fileA: string;\n readonly fileB: string;\n /** Number of commits where both files changed together. */\n readonly support: number;\n /** Confidence: support / min(totalChangesA, totalChangesB). */\n readonly confidence: number;\n}\n\n/** Result of co-change mining. */\nexport type MineCoChangesResult =\n | { readonly ok: true; readonly edges: readonly CoChangeEdge[] }\n | GitFailure\n | { readonly ok: false; readonly code: \"store_closed\" | \"db_error\" };\n\n/** Configuration for co-change mining. */\nexport interface CoChangeConfig {\n /** Maximum commits to scan (default 500). */\n readonly maxCommits: number;\n /** Minimum co-change count to write an edge (default 3). */\n readonly minSupport: number;\n /** Minimum confidence to write an edge (default 0.3). */\n readonly minConfidence: number;\n}\n\nexport const DEFAULT_CO_CHANGE_CONFIG: CoChangeConfig = {\n maxCommits: 500,\n minSupport: 3,\n minConfidence: 0.3,\n};\n\n// ──────────────────────────────────────────────────────────────────────────\n// Pure mining logic — unit-testable without git or SQLite\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Compute co-change edges from a list of commit→files entries.\n *\n * Algorithm:\n * 1. Count total changes per file (how many commits touched it).\n * 2. For each unordered file pair, count how many commits changed both.\n * 3. Confidence = coChangeCount / min(totalA, totalB).\n * 4. Keep edges where support ≥ minSupport AND confidence ≥ minConfidence.\n *\n * Deterministic: the same input always produces the same output, sorted\n * by (fileA, fileB) for byte-stability.\n *\n * Half-open time windows (rule 35): each commit is a discrete point; a\n * pair is \"co-changed\" in a commit iff both files are in that commit's\n * file list. No off-by-one — a file that appears once in a commit counts\n * once (deduped by the git-invoker's parser).\n */\nexport function mineCoChangeEdges(\n entries: readonly LogFilesEntry[],\n config: CoChangeConfig = DEFAULT_CO_CHANGE_CONFIG,\n): CoChangeEdge[] {\n // Step 1: total changes per file.\n const totalChanges = new Map<string, number>();\n for (const entry of entries) {\n for (const file of entry.files) {\n totalChanges.set(file, (totalChanges.get(file) ?? 0) + 1);\n }\n }\n\n // Step 2: co-change counts per unordered pair.\n // Key: \"fileA\\x00fileB\" where fileA < fileB lexicographically.\n const coChangeCounts = new Map<string, { a: string; b: string; count: number }>();\n for (const entry of entries) {\n const files = entry.files;\n // Only consider pairs within the same commit.\n for (let i = 0; i < files.length; i += 1) {\n for (let j = i + 1; j < files.length; j += 1) {\n const [a, b] = files[i]! < files[j]! ? [files[i]!, files[j]!] : [files[j]!, files[i]!];\n const key = `${a}\\u0000${b}`;\n const existing = coChangeCounts.get(key);\n if (existing) {\n existing.count += 1;\n } else {\n coChangeCounts.set(key, { a, b, count: 1 });\n }\n }\n }\n }\n\n // Step 3+4: filter by support + confidence thresholds.\n const edges: CoChangeEdge[] = [];\n for (const { a, b, count } of coChangeCounts.values()) {\n if (count < config.minSupport) continue;\n const totalA = totalChanges.get(a) ?? 0;\n const totalB = totalChanges.get(b) ?? 0;\n const minTotal = Math.min(totalA, totalB);\n if (minTotal === 0) continue;\n const confidence = count / minTotal;\n if (confidence < config.minConfidence) continue;\n edges.push({ fileA: a, fileB: b, support: count, confidence });\n }\n\n // Sort for byte-stability (deterministic output).\n edges.sort((x, y) => {\n const cmp = x.fileA.localeCompare(y.fileA);\n if (cmp !== 0) return cmp;\n return x.fileB.localeCompare(y.fileB);\n });\n\n return edges;\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Executor — fetch git log, mine, persist to store\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Mine co-change edges from git history and persist them to the store's\n * `co_changes` table. Idempotent: re-running on unchanged history produces\n * the same edges (the table is cleared + repopulated each run, so stale\n * edges from history changes are pruned automatically).\n */\nexport async function mineAndStoreCoChanges(options: {\n readonly store: GraphStore;\n readonly git: CodingGitInvoker;\n readonly repoRoot: string;\n readonly config?: CoChangeConfig;\n}): Promise<MineCoChangesResult> {\n const { store, git, repoRoot } = options;\n const config = options.config ?? DEFAULT_CO_CHANGE_CONFIG;\n\n const logResult = git.logFiles(repoRoot, config.maxCommits);\n if (!logResult.ok) return logResult;\n\n const edges = mineCoChangeEdges(logResult.entries, config);\n\n // Persist: clear + repopulate in one transaction. Propagate a\n // store_closed failure so the caller does not believe mining\n // succeeded while nothing was persisted (cursor Bugbot: 'Co-change\n // store reports false success').\n const persistResult = await store.upsertCoChanges(edges);\n if (!persistResult.ok) {\n return { ok: false, code: persistResult.code };\n }\n\n return { ok: true, edges };\n}\n","/**\n * Index staleness reporting (issue #1553 done-when: \"a deliberately stale\n * index with `autoIndex: \"manual\"` reports its staleness via `index_status`\n * rather than pretending freshness\").\n *\n * Reports:\n * - `lastIndexedHead` — the HEAD at the last successful reindex.\n * - `currentHead` — the current repo HEAD (null when git unavailable).\n * - `dirty` — true when `lastIndexedHead !== currentHead` (the index\n * is behind the repo and needs a reindex).\n * - `mode` — \"fresh\" | \"stale\" | \"empty\" | \"git_unavailable\".\n *\n * Never throws — a git failure degrades to `mode: \"git_unavailable\"`\n * so callers (remnic doctor / xray) can surface it without crashing.\n */\nimport type { CodingGitInvoker } from \"./git-invoker.js\";\nimport type { GraphStore } from \"./graph-store.js\";\nimport { META_KEY_LAST_HEAD, META_KEY_PENDING_PARSE_FAILURES } from \"./reindex.js\";\n\n// ──────────────────────────────────────────────────────────────────────────\n// Public types\n// ──────────────────────────────────────────────────────────────────────────\n\nexport type IndexStatusMode =\n | \"fresh\" // lastIndexedHead === currentHead\n | \"stale\" // lastIndexedHead !== currentHead\n | \"empty\" // no prior index (lastIndexedHead === null)\n | \"git_unavailable\"; // git failed\n\nexport interface IndexStatus {\n readonly lastIndexedHead: string | null;\n readonly currentHead: string | null;\n readonly dirty: boolean;\n readonly mode: IndexStatusMode;\n readonly fileCount: number;\n readonly nodeCount: number;\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Implementation\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Report the current index status. Pure over the store + git facts — no\n * side effects, never throws.\n */\nexport function getIndexStatus(\n store: GraphStore,\n git: CodingGitInvoker,\n repoRoot: string,\n): IndexStatus {\n // readMeta now returns a tagged result (rule 22). index_status is a\n // best-effort, never-throws display feed (doctor/xray); the load-bearing\n // reindex prune/head-advance path is independently hardened in reindex.ts.\n // Degrade an unreadable head to null (reported as \"empty\"/needs-reindex)\n // rather than crashing the display.\n const lastHeadResult = store.readMeta(META_KEY_LAST_HEAD);\n const lastIndexedHead = lastHeadResult.ok ? lastHeadResult.value : null;\n const headResult = git.revParseHead(repoRoot);\n\n // Gather graph stats for context.\n const stats = store.schemaStats();\n const fileCount = stats.ok ? stats.stats.files : 0;\n const nodeCount = stats.ok ? stats.stats.nodes : 0;\n\n if (!headResult.ok) {\n return {\n lastIndexedHead,\n currentHead: null,\n dirty: true,\n mode: \"git_unavailable\",\n fileCount,\n nodeCount,\n };\n }\n\n const currentHead = headResult.head;\n\n if (lastIndexedHead === null) {\n return {\n lastIndexedHead: null,\n currentHead,\n dirty: currentHead !== null,\n mode: \"empty\",\n fileCount,\n nodeCount,\n };\n }\n\n if (currentHead === null) {\n // Repo currently has no commits (unborn HEAD / reset) but the index\n // still has a last_indexed_head — the index cannot match a repo\n // with no commits, so it is stale, not fresh. Reporting \"fresh\"\n // here would let callers act on a graph that no longer reflects\n // the repo (cursor Bugbot: 'Index status false fresh').\n return {\n lastIndexedHead,\n currentHead: null,\n dirty: true,\n mode: \"stale\",\n fileCount,\n nodeCount,\n };\n }\n\n // Pending parse failures mean some changed paths are missing from the\n // graph even though the heads match. Report stale+dirty so doctor/xray\n // surface it instead of pretending freshness (chatgpt-codex-connector:\n // 'Mark pending parse retries as stale').\n const pendingResult = store.readMeta(META_KEY_PENDING_PARSE_FAILURES);\n const pendingRaw = pendingResult.ok ? pendingResult.value : null;\n let hasPendingFailures = false;\n if (pendingRaw !== null) {\n try {\n const parsed = JSON.parse(pendingRaw);\n hasPendingFailures = Array.isArray(parsed) && parsed.length > 0;\n } catch {\n hasPendingFailures = false;\n }\n }\n const dirty = lastIndexedHead !== currentHead || hasPendingFailures;\n const mode: IndexStatusMode = hasPendingFailures\n ? \"stale\"\n : dirty\n ? \"stale\"\n : \"fresh\";\n return {\n lastIndexedHead,\n currentHead,\n dirty,\n mode,\n fileCount,\n nodeCount,\n };\n}\n","/**\n * Coding-graph git invocation discipline.\n *\n * Mirrors the argv-array + bounded-timeout + never-throws pattern from\n * `packages/remnic-core/src/coding/git-context.ts` (rule 10: every git call\n * is argv + env, never string interpolation; rule 34: tagged failure codes).\n *\n * The coding-graph package owns its own invoker rather than importing\n * core's GitContextResolver because:\n * - The resolver's public surface (resolveGitContext) is project-level\n * detection, not diff/name-status/log plumbing.\n * - The reindex pipeline needs `git diff --name-status`, `git rev-parse`,\n * `git log --name-only`, and `git diff` (hunk headers) — none of which\n * the resolver exposes.\n * - Keeping the invoker local lets the package stay self-contained for\n * its git needs while still consuming the shared `launchProcessSync`\n * from `@remnic/core/runtime/child-process` (rule 23/38 — do not invent\n * a new process-spawning pattern).\n *\n * Never throws. Every failure is a tagged `GitFailure` so callers can\n * surface `git_unavailable` / `unreachable_head` distinctly (rule 34 +\n * issue #1553 pitfall 34).\n */\nimport { launchProcessSync } from \"@remnic/core/runtime/child-process\";\n\n// ──────────────────────────────────────────────────────────────────────────\n// Public types\n// ──────────────────────────────────────────────────────────────────────────\n\n/** A line from `git diff --name-status <range>`. */\nexport interface NameStatusEntry {\n /**\n * `git diff --name-status` status code: `A` (added), `M` (modified),\n * `D` (deleted), `R100` (renamed, 100% similarity), `C75` (copied, 75%),\n * etc. Renames carry the NEW path in `path` and the OLD path in\n * `oldPath`.\n */\n readonly status: string;\n /** Repo-relative forward-slash path of the file in the NEW tree. */\n readonly path: string;\n /** For renames/copies (`R*`/`C*`), the source path. Otherwise `undefined`. */\n readonly oldPath?: string;\n}\n\n/** A hunk header from `git diff --unified=0` (`@@ -a,b +c,d @@`). */\nexport interface DiffHunk {\n /** File path (repo-relative, forward slashes). */\n readonly path: string;\n /**\n * Half-open `[startLine, endLine)` line range in the NEW (post-change)\n * version of the file. `startLine` is 1-based; `endLine` is exclusive.\n * A single-line change has `endLine === startLine + 1`.\n */\n readonly newRange: { readonly startLine: number; readonly endLine: number };\n}\n\n/** A co-change commit entry from `git log --name-only`. */\nexport interface LogFilesEntry {\n /** Full commit SHA (40 hex chars). */\n readonly sha: string;\n /** Files changed in this commit (repo-relative, forward slashes, deduped). */\n readonly files: readonly string[];\n}\n\n/**\n * Tagged failure — `code` is the load-bearing signal for programmatic\n * detection (rule 34). Never carries `error.message` (which may contain\n * absolute paths — rule 11).\n */\nexport interface GitFailure {\n readonly ok: false;\n readonly code: \"git_unavailable\" | \"unreachable_head\" | \"git_error\";\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Invoker\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Injectable git-invocation surface. Each method runs a specific git\n * subcommand with argv arrays (never string interpolation — rule 10) and\n * returns parsed output or a tagged failure. Tests inject a mock to avoid\n * spawning a real git process against synthetic fixture repos.\n *\n * The default implementation uses `launchProcessSync` with a 2-second\n * timeout (matching `git-context.ts`'s `DEFAULT_GIT_TIMEOUT_MS`).\n */\nexport interface CodingGitInvoker {\n /**\n * `git rev-parse HEAD` — the current HEAD SHA. Returns `null` when HEAD\n * does not exist (empty repo / no commits yet).\n */\n revParseHead(cwd: string): { ok: true; head: string | null } | GitFailure;\n\n /**\n * `git rev-parse --verify <ref>^{commit}` — check whether a prior head\n * is still reachable. Used to detect rebase/force-push scenarios\n * (issue #1553 pitfall 34: `unreachable_head`).\n */\n isReachable(\n cwd: string,\n ref: string,\n ): { ok: true; reachable: boolean } | GitFailure;\n\n /**\n * `git diff --name-status <range>` — the changed files between two\n * commits (or between a commit and HEAD). Returns one entry per file.\n */\n diffNameStatus(\n cwd: string,\n range: string,\n ): { ok: true; entries: readonly NameStatusEntry[] } | GitFailure;\n\n /**\n * Working-tree diff hunks with zero context lines. Captures BOTH staged\n * and unstaged changes (compared against HEAD) so `detect_changes` maps\n * every uncommitted edit to a symbol span. Each hunk carries its\n * new-version line range.\n */\n diffHunks(\n cwd: string,\n paths: readonly string[],\n ): { ok: true; hunks: readonly DiffHunk[] } | GitFailure;\n\n /**\n * `git log --name-only --format=%H -n <limit>` — commit SHAs + their\n * changed files over a bounded window. Used by co-change mining.\n */\n logFiles(\n cwd: string,\n limit: number,\n ): { ok: true; entries: readonly LogFilesEntry[] } | GitFailure;\n\n /**\n * `git ls-files` — the repo's tracked files as repo-relative forward-slash\n * paths. Used by the codegraph runtime to source `candidatePaths` for a\n * full reindex (issue #1554: the executor treats an omitted candidate list\n * as non-authoritative and no-ops, so the runtime must supply one).\n * Returns an empty path list for a repo with no tracked files; a git\n * failure degrades to `{ ok: false, code: \"git_unavailable\" | \"git_error\" }`.\n */\n listTrackedFiles(\n cwd: string,\n ): { ok: true; paths: readonly string[] } | GitFailure;\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Default implementation — real git via launchProcessSync\n// ──────────────────────────────────────────────────────────────────────────\n\nconst GIT_TIMEOUT_MS = 2_000;\n\n/**\n * Run `git <args>` synchronously with a bounded timeout. Never throws —\n * spawn failures (git not on PATH, timeout) surface as\n * `{ ok: false, code: \"git_unavailable\" }` so callers can degrade.\n */\nfunction runGit(\n cwd: string,\n args: readonly string[],\n): { ok: true; stdout: string; exitCode: number } | GitFailure {\n const result = launchProcessSync(\"git\", [...args], {\n cwd,\n encoding: \"utf-8\",\n timeout: GIT_TIMEOUT_MS,\n shell: false,\n });\n if (result.error) {\n return { ok: false, code: \"git_unavailable\" };\n }\n return {\n ok: true,\n stdout: typeof result.stdout === \"string\" ? result.stdout : \"\",\n exitCode: typeof result.status === \"number\" ? result.status : 1,\n };\n}\n\n/**\n * Construct the default git invoker that spawns real `git` via\n * `launchProcessSync`. Tests inject a mock instead.\n */\nexport function defaultCodingGitInvoker(): CodingGitInvoker {\n return {\n revParseHead(cwd: string) {\n const r = runGit(cwd, [\"rev-parse\", \"HEAD\"]);\n if (!r.ok) return r;\n const trimmed = r.stdout.trim();\n if (r.exitCode === 0 && trimmed.length > 0) {\n return { ok: true, head: trimmed };\n }\n // Non-zero exit: could be an UNBORN repo (no commits yet, HEAD is a\n // symbolic ref to a non-existent branch) OR a directory that is not\n // a git worktree at all. Distinguish them so a misconfigured workspace\n // surfaces as a git failure instead of silently looking like an empty\n // repo (chatgpt-codex-connector: 'Return a git failure for non-\n // repository HEAD lookups').\n const wt = runGit(cwd, [\"rev-parse\", \"--is-inside-work-tree\"]);\n if (wt.ok && wt.exitCode === 0 && wt.stdout.trim() === \"true\") {\n // Inside a worktree but HEAD has no commits → unborn, head null.\n return { ok: true, head: null };\n }\n // Not a git worktree (or git failed to answer) → degrade visibly.\n return { ok: false, code: \"git_error\" };\n },\n\n isReachable(cwd: string, ref: string) {\n const r = runGit(cwd, [\"rev-parse\", \"--verify\", `${ref}^{commit}`]);\n if (!r.ok) return r;\n return { ok: true, reachable: r.exitCode === 0 };\n },\n\n diffNameStatus(cwd: string, range: string) {\n const r = runGit(cwd, [\"diff\", \"--name-status\", range]);\n if (!r.ok) return r;\n if (r.exitCode !== 0) {\n return { ok: false, code: \"git_error\" };\n }\n return { ok: true, entries: parseNameStatus(r.stdout) };\n },\n\n diffHunks(cwd: string, paths: readonly string[]) {\n // `git diff HEAD` compares the working tree (staged + unstaged)\n // against the last commit, so BOTH staged and unstaged edits are\n // captured (chatgpt-codex-connector: 'Include staged hunks in\n // diffHunks'). Plain `git diff` would miss staged-only changes.\n // --unified=0 → zero context lines so hunks are tight.\n // --no-color → no ANSI escape codes in output.\n // -- separator before paths to disambiguate refs from paths.\n const pathArgs: string[] = [];\n if (paths.length > 0) {\n pathArgs.push(\"--\");\n for (const p of paths) pathArgs.push(p);\n }\n const r = runGit(cwd, [\"diff\", \"HEAD\", \"--unified=0\", \"--no-color\", ...pathArgs]);\n if (!r.ok) return r;\n if (r.exitCode !== 0) {\n // HEAD may not exist (unborn repo, no commits yet). Fall back to\n // unstaged-only diff — an acceptable degradation for a repo that\n // has never been indexed (there are no committed symbols anyway).\n const fallback = runGit(cwd, [\"diff\", \"--unified=0\", \"--no-color\", ...pathArgs]);\n if (!fallback.ok) return fallback;\n if (fallback.exitCode !== 0) {\n return { ok: false, code: \"git_error\" };\n }\n return { ok: true, hunks: parseHunks(fallback.stdout) };\n }\n return { ok: true, hunks: parseHunks(r.stdout) };\n },\n\n logFiles(cwd: string, limit: number) {\n const r = runGit(cwd, [\n \"log\",\n `--format=%H`,\n \"--name-only\",\n `-n`,\n String(limit),\n ]);\n if (!r.ok) return r;\n if (r.exitCode !== 0) {\n return { ok: false, code: \"git_error\" };\n }\n return { ok: true, entries: parseLogFiles(r.stdout) };\n },\n\n listTrackedFiles(cwd: string) {\n // `git ls-files` lists the repo's tracked files (cached in the index)\n // as repo-relative forward-slash paths — exactly the candidatePaths\n // format executeReindex expects. --others is NOT used: only tracked\n // files belong in an authoritative full-reindex candidate set.\n const r = runGit(cwd, [\"ls-files\"]);\n if (!r.ok) return r;\n if (r.exitCode !== 0) {\n return { ok: false, code: \"git_error\" };\n }\n const paths = r.stdout.split(\"\\n\").map((l) => l.trim()).filter((l) => l.length > 0);\n return { ok: true, paths };\n },\n };\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Output parsers — pure functions (unit-testable without git)\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Parse `git diff --name-status` output. Each line is `<status>\\t<path>`\n * for add/modify/delete, or `<status>\\t<old>\\t<new>` for rename/copy.\n *\n * Empty lines are skipped (trailing newline). Lines that do not parse\n * are skipped rather than crashing the whole diff (rule 44 — a single\n * bad line should not make the index go stale silently; but a partial\n * parse is better than a total failure because the executor will fall\n * back to hash-scan if the file count looks wrong).\n */\nexport function parseNameStatus(stdout: string): NameStatusEntry[] {\n const out: NameStatusEntry[] = [];\n const lines = stdout.split(\"\\n\");\n for (const line of lines) {\n if (line.length === 0) continue;\n // Split on the first tab; the path may contain spaces but not tabs.\n const tabIdx = line.indexOf(\"\\t\");\n if (tabIdx < 0) continue;\n const status = line.slice(0, tabIdx);\n const rest = line.slice(tabIdx + 1);\n // Rename/copy: `R100\\told\\tnew` — second tab splits old from new.\n const secondTab = rest.indexOf(\"\\t\");\n if (\n (status.startsWith(\"R\") || status.startsWith(\"C\")) &&\n secondTab >= 0\n ) {\n const oldPath = rest.slice(0, secondTab);\n const newPath = rest.slice(secondTab + 1);\n if (oldPath.length > 0 && newPath.length > 0) {\n out.push({ status, path: newPath, oldPath });\n }\n } else {\n if (rest.length > 0) {\n out.push({ status, path: rest });\n }\n }\n }\n return out;\n}\n\n/**\n * Parse `git diff --unified=0` output into per-file hunks. Each hunk\n * header is `@@ -a,b +c,d @@ <optional section>`. With `--unified=0`\n * the OLD range `(a,b)` is not useful for blast-radius (we want the\n * NEW range). We extract `+c,d` → `{ startLine: c, endLine: c + d }`.\n *\n * The file path comes from `diff --git a/path b/path` or\n * `+++ b/path` lines. We track the current file as we walk.\n */\nexport function parseHunks(stdout: string): DiffHunk[] {\n const out: DiffHunk[] = [];\n let currentPath: string | null = null;\n const lines = stdout.split(\"\\n\");\n for (const line of lines) {\n // `+++ b/path` — the new-version path (post-rename). Prefer this\n // over `diff --git` because it correctly handles renames.\n if (line.startsWith(\"+++ \")) {\n if (line.startsWith(\"+++ /dev/null\")) {\n // The new side of this file is empty → it was DELETED. Clear\n // currentPath so a following `@@` hunk is not mis-attributed to\n // the previous file (chatgpt-codex-connector: 'Clear hunk path\n // for deleted files').\n currentPath = null;\n continue;\n }\n // Strip the leading `+++ b/` (or `+++ ` for non-`b/` forms).\n const raw = line.slice(4);\n currentPath = raw.startsWith(\"b/\") ? raw.slice(2) : raw;\n continue;\n }\n // `@@ -a,b +c,d @@` — hunk header.\n if (line.startsWith(\"@@ \")) {\n const match = line.match(/\\+(\\d+)(?:,(\\d+))?/);\n if (!match) continue;\n // match[1] is guaranteed by the first capture group; match[2] is\n // optional (omitted when the hunk is a single line). Guard both\n // with a fallback to avoid unchecked non-null assertions.\n const startStr = match[1] ?? \"\";\n if (startStr.length === 0) continue;\n const startLine = parseInt(startStr, 10);\n const countStr = match[2] ?? \"\";\n const count = countStr.length > 0 ? parseInt(countStr, 10) : 1;\n if (!currentPath) continue;\n // A deletion-only hunk emits a zero new-count (e.g. `@@ -2 +1,0 @@`).\n // An empty [startLine, startLine) range can never overlap a symbol\n // span, so pure deletions inside a function/class were missed.\n // Treat zero-count as covering the adjacent new line (chatgpt-\n // codex-connector: 'Map deletion-only hunks to a non-empty range').\n out.push({\n path: currentPath,\n newRange: {\n startLine,\n endLine: startLine + Math.max(count, 1),\n },\n });\n }\n }\n return out;\n}\n\n/**\n * Parse `git log --format=%H --name-only` output into one entry per\n * commit. Real git emits a blank line AFTER each SHA (before the file\n * names) AND between commits, so a naive `\\n\\n` block split puts the\n * SHA alone in one block and the file names in the next — the SHA-only\n * block yields a commit with zero files and the file-name block fails\n * SHA validation and is dropped. Walk line-by-line instead: a 40-hex\n * line starts a new commit; subsequent non-empty lines are its files\n * until the next SHA (chatgpt-codex-connector: 'Parse real git log\n * records before mining co-changes'). Robust to both the synthetic\n * (no blank-after-SHA) and real (blank-after-SHA) layouts.\n */\nexport function parseLogFiles(stdout: string): LogFilesEntry[] {\n const out: LogFilesEntry[] = [];\n let currentSha: string | null = null;\n let currentFiles: string[] = [];\n const seenInCommit = new Set<string>();\n const flush = (): void => {\n if (currentSha !== null) {\n out.push({ sha: currentSha, files: currentFiles });\n }\n currentSha = null;\n currentFiles = [];\n seenInCommit.clear();\n };\n for (const line of stdout.split(\"\\n\")) {\n if (/^[0-9a-f]{40}$/.test(line)) {\n flush();\n currentSha = line;\n } else if (line.length > 0 && currentSha !== null) {\n if (!seenInCommit.has(line)) {\n seenInCommit.add(line);\n currentFiles.push(line);\n }\n }\n // blank lines are ignored — they do not terminate a commit; the\n // next SHA does.\n }\n flush();\n return out;\n}\n\n","/**\n * Minimal JSON-RPC-over-stdio LSP client.\n *\n * Implements exactly the protocol subset the resolution pass needs\n * (LSP 3.17): initialize → initialized → didOpen → definition →\n * shutdown → exit. No npm LSP framework — the protocol subset is small\n * and a dependency here would bloat the optional package (issue #1555).\n *\n * Failure discipline (rule 13): every operation degrades to a tagged\n * `LspDegradation` — the client NEVER throws to a caller. Server crashes\n * mid-run, protocol errors, and timeouts all surface as distinct codes.\n *\n * Lifecycle: the client owns the child process. `dispose()` sends\n * shutdown + exit, then hard-kills (SIGKILL) any lingering process —\n * no zombie children survive (tested).\n */\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\nimport {\n encodeLspFrame,\n LspFrameDecoder,\n} from \"./framing.js\";\nimport {\n type JsonRpcRequest,\n type LspInitializeParams,\n type LspInitializeResult,\n type LspLocation,\n type LspTextDocumentItem,\n type LspTextDocumentPositionParams,\n} from \"./types.js\";\nimport {\n lspDegradation,\n type LspDegradation,\n type LspDegradationCode,\n type LspResult,\n} from \"./degradation.js\";\nimport type { LspServerLaunchSpec } from \"./config.js\";\n\n// ──────────────────────────────────────────────────────────────────────────\n// Pending-request entry — stores the resolver pair + timeout handle.\n// ──────────────────────────────────────────────────────────────────────────\n\ninterface PendingRequest {\n readonly resolve: (value: unknown) => void;\n readonly reject: (degradation: LspDegradation) => void;\n readonly timer: ReturnType<typeof setTimeout>;\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Connection options\n// ──────────────────────────────────────────────────────────────────────────\n\nexport interface LspClientOptions {\n readonly launchSpec: LspServerLaunchSpec;\n readonly rootUri: string | null;\n readonly timeoutMs: number;\n /**\n * Optional spawn override — test seam. When provided, the client calls\n * this instead of `child_process.spawn`. Must return a ChildProcess-\n * compatible object with stdin/stdout streams.\n */\n readonly spawnFn?: typeof spawn;\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// LspClient — the connection. One instance per server process.\n// ──────────────────────────────────────────────────────────────────────────\n\nexport class LspClient {\n private readonly child: ChildProcess;\n private readonly decoder = new LspFrameDecoder();\n private readonly rootUri: string | null;\n private readonly timeoutMs: number;\n private nextId = 1;\n private readonly pending = new Map<number, PendingRequest>();\n private disposed = false;\n private crashed = false;\n private crashCode: LspDegradationCode | null = null;\n private serverCapabilities: LspInitializeResult[\"capabilities\"] | null = null;\n\n private constructor(child: ChildProcess, rootUri: string | null, timeoutMs: number) {\n this.child = child;\n this.rootUri = rootUri;\n this.timeoutMs = timeoutMs;\n\n // Wire stdout → decoder → message dispatcher.\n this.child.stdout?.setEncoding(\"utf8\");\n this.child.stdout?.on(\"data\", (chunk: Buffer | string) => this.onStdoutData(chunk));\n\n // Drain stderr so the OS pipe buffer doesn't fill and block the server.\n this.child.stderr?.on(\"data\", () => {});\n\n // Unexpected exit → mark crashed, reject all pending.\n this.child.on(\"exit\", (code, signal) => this.onChildExit(code, signal));\n this.child.on(\"error\", (err) => this.onChildError(err));\n }\n\n /**\n * Spawn the server and perform the initialize handshake. Returns a\n * tagged result — `{ ok: true, client }` on success, or a degradation\n * on failure (server_missing, handshake_timeout, handshake_error).\n */\n static async connect(options: LspClientOptions): Promise<\n | { ok: true; client: LspClient }\n | { ok: false; degradation: LspDegradation }\n > {\n let child: ChildProcess;\n const spawnFn = options.spawnFn ?? spawn;\n try {\n child = spawnFn(options.launchSpec.command, [...options.launchSpec.args], {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n shell: false,\n // The server inherits our cwd so relative rootUri paths resolve.\n cwd: process.cwd(),\n });\n } catch {\n return {\n ok: false,\n degradation: lspDegradation(\n \"server_missing\",\n `failed to spawn ${options.launchSpec.command}`,\n ),\n };\n }\n\n // If spawn emitted 'error' synchronously (ENOENT), treat as missing.\n // We detect this by checking if the child has already exited.\n if (child.exitCode !== null && child.exitCode !== undefined) {\n return {\n ok: false,\n degradation: lspDegradation(\"server_missing\"),\n };\n }\n\n const client = new LspClient(child, options.rootUri, options.timeoutMs);\n\n // If the spawn errored asynchronously (ENOENT fires as 'error' event),\n // the client.crashed flag is set by onChildError. Check after wiring.\n if (client.crashed) {\n return {\n ok: false,\n degradation: lspDegradation(client.crashCode ?? \"server_crashed\"),\n };\n }\n\n // ── initialize handshake ──\n const initParams: LspInitializeParams = {\n processId: process.pid,\n rootUri: options.rootUri,\n capabilities: {},\n };\n const initResult = await client.request(\"initialize\", initParams);\n if (!initResult.ok) {\n // Remap request_timeout → handshake_timeout (the timeout happened\n // during the initialize handshake). protocol_error, server_crashed,\n // and server_missing pass through with their original codes — they\n // describe the specific failure precisely enough.\n await client.dispose();\n const code =\n initResult.degradation.code === \"request_timeout\"\n ? \"handshake_timeout\"\n : initResult.degradation.code;\n return {\n ok: false,\n degradation: lspDegradation(code, initResult.degradation.detail),\n };\n }\n\n if (initResult.value === null || typeof initResult.value !== \"object\") {\n await client.dispose();\n return {\n ok: false,\n degradation: lspDegradation(\"protocol_error\", \"initialize response missing or invalid\"),\n };\n }\n const initResponse = initResult.value as LspInitializeResult;\n client.serverCapabilities = initResponse.capabilities;\n\n // initialized notification (no response expected).\n client.notify(\"initialized\", {});\n\n return { ok: true, client };\n }\n\n /**\n * Send `textDocument/didOpen` — notifies the server about an open\n * document with its full content. No response expected.\n */\n didOpen(item: LspTextDocumentItem): void {\n this.notify(\"textDocument/didOpen\", { textDocument: item });\n }\n\n /**\n * Send `textDocument/definition` for a position in a document. Returns\n * the definition locations (may be empty, a single location, or an\n * array). Degrades on timeout/error/crash — never throws.\n */\n async definition(\n params: LspTextDocumentPositionParams,\n ): Promise<LspResult<{ locations: LspLocation[] }>> {\n if (this.disposed || this.crashed) {\n return {\n ok: false,\n degradation: lspDegradation(\"server_crashed\"),\n };\n }\n const result = await this.request(\"textDocument/definition\", params);\n if (!result.ok) {\n return result;\n }\n // LSP allows Location | Location[] | null for definition.\n const value = result.value;\n let locations: LspLocation[];\n if (value === null || value === undefined) {\n locations = [];\n } else if (Array.isArray(value)) {\n locations = value as LspLocation[];\n } else {\n locations = [value as LspLocation];\n }\n return { ok: true, locations };\n }\n\n /**\n * Send `shutdown`, then `exit`, then SIGKILL if the process lingers.\n * Idempotent — safe to call multiple times. After dispose, no child\n * process remains (tested — zombie cleanup).\n */\n async dispose(): Promise<void> {\n if (this.disposed) return;\n this.disposed = true;\n\n // Reject all pending requests immediately.\n for (const [id, entry] of this.pending) {\n clearTimeout(entry.timer);\n entry.reject(lspDegradation(\"server_crashed\"));\n this.pending.delete(id);\n }\n\n // Try graceful shutdown: send shutdown request, then exit notification.\n if (!this.crashed && this.child.stdin && !this.child.stdin.destroyed) {\n try {\n const promise = new Promise<void>((resolve) => {\n // Send shutdown — don't wait for a response longer than the timeout.\n this.child.stdin?.write(encodeLspFrame({ jsonrpc: \"2.0\", id: 0, method: \"shutdown\" }));\n this.child.stdin?.write(encodeLspFrame({ jsonrpc: \"2.0\", method: \"exit\" }));\n // Give the server a brief window to exit cleanly.\n const timer = setTimeout(resolve, 500);\n this.child.on(\"exit\", () => {\n clearTimeout(timer);\n resolve();\n });\n });\n await promise;\n } catch {\n // Best-effort — if writing fails, hard-kill below.\n }\n }\n\n // Hard kill — ensures no zombie survives even if graceful exit failed.\n this.hardKill();\n }\n\n /** Returns the pid of the child process (for zombie-cleanup tests). */\n get pid(): number | undefined {\n return this.child.pid;\n }\n\n /** True if the server reported definitionProvider capability. */\n get supportsDefinition(): boolean {\n const caps = this.serverCapabilities;\n return caps !== null && Boolean(caps.definitionProvider);\n }\n\n // ──────────────────────────────────────────────────────────────────────\n // Internal — JSON-RPC request/notification machinery\n // ──────────────────────────────────────────────────────────────────────\n\n /**\n * Send a request and await its response. Returns the `result` field\n * on success, or a degradation on timeout/error/crash/protocol-error.\n */\n private request(method: string, params: unknown): Promise<\n | { ok: true; value: unknown }\n | { ok: false; degradation: LspDegradation }\n > {\n if (this.disposed || this.crashed) {\n return Promise.resolve({\n ok: false,\n degradation: lspDegradation(\"server_crashed\"),\n });\n }\n const id = this.nextId++;\n const message: JsonRpcRequest = { jsonrpc: \"2.0\", id, method, params };\n const promise = new Promise<unknown>((resolve, reject) => {\n const timer = setTimeout(() => {\n const entry = this.pending.get(id);\n if (entry) {\n this.pending.delete(id);\n entry.reject(lspDegradation(\"request_timeout\", `method=${method}`));\n }\n }, this.timeoutMs);\n this.pending.set(id, { resolve, reject, timer });\n try {\n const frame = encodeLspFrame(message);\n if (!this.child.stdin || this.child.stdin.destroyed) {\n clearTimeout(timer);\n this.pending.delete(id);\n reject(lspDegradation(\"server_crashed\"));\n return;\n }\n this.child.stdin.write(frame);\n } catch {\n clearTimeout(timer);\n this.pending.delete(id);\n reject(lspDegradation(\"server_crashed\"));\n }\n });\n\n return promise.then(\n (value: unknown) => ({ ok: true as const, value }),\n (degradation: LspDegradation) => ({ ok: false as const, degradation }),\n );\n }\n\n /**\n * Send a notification (no response expected). Best-effort — if the\n * write fails, the next request will surface the crash.\n */\n private notify(method: string, params: unknown): void {\n if (this.disposed || this.crashed) return;\n try {\n const message: JsonRpcRequest = { jsonrpc: \"2.0\", method, params };\n this.child.stdin?.write(encodeLspFrame(message));\n } catch {\n // Best-effort — notifications have no response path.\n }\n }\n\n /**\n * Dispatch a decoded JSON-RPC message. Correlates responses to pending\n * requests by id; ignores server-initiated notifications (we don't\n * need them for the resolution pass).\n */\n /**\n * Dispatch a decoded JSON-RPC message. Correlates responses to pending\n * requests by id; ignores server-initiated notifications.\n */\n private dispatchMessage(msg: unknown): void {\n if (typeof msg !== \"object\" || msg === null) return;\n const rpc = msg as Record<string, unknown>;\n if (rpc.id === undefined || (rpc.result === undefined && rpc.error === undefined)) {\n return;\n }\n const id = typeof rpc.id === \"number\" ? rpc.id : Number(rpc.id);\n const entry = this.pending.get(id);\n if (!entry) return;\n clearTimeout(entry.timer);\n this.pending.delete(id);\n if (rpc.error !== undefined) {\n // Don't echo raw server error text — it may contain absolute paths.\n entry.reject(lspDegradation(\"request_error\", \"server returned an error response\"));\n } else {\n entry.resolve(rpc.result);\n }\n }\n\n /**\n * stdout data handler — feed the decoder, dispatch complete messages,\n * detect protocol errors.\n */\n private onStdoutData(chunk: Buffer | string): void {\n const result = this.decoder.feed(chunk);\n if (!result.ok) {\n this.handleProtocolError(result.error.detail);\n return;\n }\n for (const msg of result.messages) {\n this.dispatchMessage(msg);\n }\n }\n\n /**\n * Handle an unexpected child exit. All pending requests are rejected\n * with server_crashed.\n */\n private onChildExit(code: number | null, signal: NodeJS.Signals | null): void {\n // Normal exit during dispose — don't mark as crashed.\n if (this.disposed) return;\n this.crashed = true;\n this.crashCode = \"server_crashed\";\n const detail =\n code !== null ? `server exited with code ${code}` : `server killed by ${signal}`;\n for (const [id, entry] of this.pending) {\n clearTimeout(entry.timer);\n entry.reject(lspDegradation(\"server_crashed\", detail));\n this.pending.delete(id);\n }\n }\n\n /**\n * Handle a spawn error (ENOENT etc). Marks the server as missing.\n */\n private onChildError(err: Error): void {\n // ENOENT = binary not found; other spawn errors = server present but failing.\n this.crashed = true;\n const isENOENT = err.message.includes(\"ENOENT\");\n this.crashCode = isENOENT ? \"server_missing\" : \"server_crashed\";\n const detail = isENOENT ? undefined : \"spawn error\";\n for (const [id, entry] of this.pending) {\n clearTimeout(entry.timer);\n entry.reject(lspDegradation(this.crashCode, detail));\n this.pending.delete(id);\n }\n }\n\n /**\n * Protocol error — the stream produced a malformed frame. Reject all\n * pending and mark disposed so no further requests can be sent.\n */\n private handleProtocolError(detail: string): void {\n this.crashed = true;\n for (const [id, entry] of this.pending) {\n clearTimeout(entry.timer);\n entry.reject(lspDegradation(\"protocol_error\", detail));\n this.pending.delete(id);\n }\n }\n\n /**\n * Hard-kill the child process: SIGKILL. Called by dispose() as a\n * final cleanup guarantee. Also called if the graceful shutdown path\n * fails. Uses `kill` which is a no-op if the process already exited.\n */\n private hardKill(): void {\n try {\n if (this.child.pid !== undefined && !this.child.killed) {\n this.child.kill(\"SIGKILL\");\n }\n } catch {\n // Best-effort — the process may have already exited.\n }\n }\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// URI helpers — convert between file paths and LSP URIs.\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Convert a repo-relative or absolute file path to a `file://` URI.\n * Handles Windows drive letters (C:\\ → file:///C:/).\n */\nexport function pathToUri(filePath: string): string {\n const abs = path.isAbsolute(filePath) ? filePath : path.resolve(filePath);\n const normalized = abs.replace(/\\\\/g, \"/\");\n // Windows drive paths need a leading slash: C:/foo → /C:/foo → file:///C:/foo\n const withSlash = /^[A-Za-z]:/.test(normalized) ? `/${normalized}` : normalized;\n return `file://${withSlash}`;\n}\n\n/**\n * Convert a `file://` URI back to an absolute file path.\n */\nexport function uriToPath(uri: string): string {\n if (uri.startsWith(\"file://\")) {\n let rest = uri.slice(\"file://\".length);\n // Decode percent-encoded characters (e.g. %20 → space) per RFC 8089.\n try {\n rest = decodeURIComponent(rest);\n } catch {\n // Malformed escape sequence — keep raw path (best-effort).\n }\n // Windows: file:///C:/foo → C:/foo\n if (/^\\/[A-Za-z]:/.test(rest)) {\n return rest.slice(1).replace(/\\//g, path.sep);\n }\n // Unix: file:///foo → /foo\n return rest.replace(/\\//g, path.sep);\n }\n return uri;\n}\n","/**\n * Length-prefixed LSP framing — `Content-Length: <N>\\r\\n\\r\\n` headers\n * followed by exactly `<N>` bytes of JSON body (LSP 3.17 §6.1 — Base Protocol).\n *\n * This is the one module where off-by-one and split-buffer bugs hide.\n * The parser tracks a RUNNING OFFSET — it never re-scans bytes a\n * previous scan already confirmed separator-free (rule 32).\n */\n\n// ──────────────────────────────────────────────────────────────────────────\n// Encoder — serialize a JSON-RPC message into a Content-Length frame.\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Encode a JSON-RPC message as a Content-Length-prefixed frame ready to\n * write to the server's stdin. Uses UTF-8 — the LSP base protocol\n * mandates UTF-8 content encoding (§6.1).\n */\nexport function encodeLspFrame(message: unknown): string {\n const body = JSON.stringify(message);\n // Content-Length counts UTF-8 BYTES, not JS UTF-16 code units. Node's\n // Buffer.byteLength accounts for multi-byte sequences.\n const byteLength = Buffer.byteLength(body, \"utf8\");\n return `Content-Length: ${byteLength}\\r\\n\\r\\n${body}`;\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Decoder — streaming parser that accumulates chunks and yields complete\n// messages. Never throws on partial input — it just returns fewer messages\n// and retains the residual for the next feed() call.\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Reason for a decode failure. A `protocol_error` degradation surfaces\n * the specific reason so the caller can distinguish \"bad header\" from\n * \"unparseable JSON body\".\n */\nexport type FrameDecodeErrorKind =\n | \"malformed_header\" // header line is not `Content-Length: <digits>`\n | \"json_parse_error\"; // body is not valid JSON\n\nexport interface FrameDecodeError {\n readonly kind: FrameDecodeErrorKind;\n readonly detail: string;\n}\n\nexport interface FrameDecodeSuccess {\n readonly ok: true;\n readonly messages: unknown[];\n}\n\nexport type FrameDecodeResult = FrameDecodeSuccess | { readonly ok: false; readonly error: FrameDecodeError };\n\n// Sentinel for the header/body separator — `\\r\\n\\r\\n` (as bytes).\nconst HEADER_SEPARATOR = Buffer.from(\"\\r\\n\\r\\n\");\nconst HEADER_SEPARATOR_LEN = HEADER_SEPARATOR.length;\n\n/**\n * Streaming LSP frame decoder. Feed raw Buffer or string chunks via\n * {@link feed}; each call returns the complete JSON messages parsed\n * since the last call, plus any error if a frame was malformed.\n *\n * Works with BYTES internally because Content-Length counts UTF-8 bytes\n * (LSP 3.17 §6.1), not UTF-16 code units. A string-based buffer would\n * mis-slice any body containing multi-byte characters (𝕏, emoji, CJK).\n *\n * The decoder maintains a running byte-buffer and a scan offset. After\n * each feed, consumed bytes are sliced away so the buffer never grows\n * unbounded across a long session (rule 11 — no unbounded state).\n */\nexport class LspFrameDecoder {\n private buffer = Buffer.alloc(0);\n /**\n * Scan offset into {@link buffer}. The header scan resumes here on\n * the next feed() — never re-scans bytes already confirmed to not\n * contain the separator (rule 32). Reset to 0 after each consumed\n * frame because slicing the buffer discards those bytes.\n */\n private scanOffset = 0;\n\n /**\n * Feed a raw chunk (Buffer or string) from the server's stdout.\n * Returns all complete messages parsed from the accumulated buffer\n * since the last call, or the first decode error encountered (the\n * decoder stops on error — a protocol violation means the stream is\n * corrupt and further parsing is undefined).\n */\n feed(chunk: Buffer | string): FrameDecodeResult {\n const buf = typeof chunk === \"string\" ? Buffer.from(chunk, \"utf8\") : chunk;\n this.buffer = this.buffer.length === 0 ? Buffer.from(buf) : Buffer.concat([this.buffer, buf]);\n const messages: unknown[] = [];\n\n // Loop: extract as many complete frames as the current buffer holds.\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const sepIdx = this.buffer.indexOf(HEADER_SEPARATOR, this.scanOffset);\n if (sepIdx < 0) {\n // No complete header yet. Advance scanOffset past the bytes\n // already confirmed separator-free (rule 32). The separator is\n // 4 bytes, so a partial match could start up to 3 bytes before\n // the current end.\n this.scanOffset = Math.max(0, this.buffer.length - (HEADER_SEPARATOR_LEN - 1));\n return { ok: true, messages };\n }\n\n const headerBlock = this.buffer.subarray(0, sepIdx).toString(\"utf8\");\n const contentLength = parseContentLength(headerBlock);\n if (contentLength === null) {\n return {\n ok: false,\n error: {\n kind: \"malformed_header\",\n detail: `header block has no valid Content-Length: ${JSON.stringify(headerBlock)}`,\n },\n };\n }\n\n const bodyStart = sepIdx + HEADER_SEPARATOR_LEN;\n const bodyEnd = bodyStart + contentLength;\n if (this.buffer.length < bodyEnd) {\n // Body not fully received yet. Back up scanOffset to just before\n // the separator so the next feed re-finds it.\n this.scanOffset = Math.max(0, sepIdx - (HEADER_SEPARATOR_LEN - 1));\n return { ok: true, messages };\n }\n\n const bodyBytes = this.buffer.subarray(bodyStart, bodyEnd);\n let parsed: unknown;\n try {\n parsed = JSON.parse(bodyBytes.toString(\"utf8\"));\n } catch (e) {\n return {\n ok: false,\n error: {\n kind: \"json_parse_error\",\n detail: `body is not valid JSON: ${e instanceof Error ? e.message : String(e)}`,\n },\n };\n }\n messages.push(parsed);\n\n // Slice consumed bytes off the front — subarray returns a view\n // into the same memory; concat on the next feed replaces it.\n this.buffer = this.buffer.subarray(bodyEnd);\n this.scanOffset = 0;\n }\n }\n\n /** True if there is un-consumed residual data in the buffer. */\n get hasResidual(): boolean {\n return this.buffer.length > 0;\n }\n\n /** Reset the decoder to a clean state (test seam). */\n reset(): void {\n this.buffer = Buffer.alloc(0);\n this.scanOffset = 0;\n }\n}\n\n/**\n * Parse the `Content-Length` value from a header block. Returns the byte\n * count or null if the header is absent or malformed. LSP headers are\n * case-insensitive and may appear in any order, but Content-Length is\n * mandatory (§6.1).\n */\nfunction parseContentLength(headerBlock: string): number | null {\n const lines = headerBlock.split(\"\\r\\n\");\n for (const line of lines) {\n const match = /^Content-Length:\\s*(\\d+)\\s*$/i.exec(line);\n if (match) {\n const n = Number(match[1]);\n return Number.isFinite(n) && n >= 0 ? n : null;\n }\n }\n return null;\n}\n","/**\n * LSP degradation codes — shaped like {@link SearchDegradation} from\n * @remnic/core/search/port (issue #1536, CLAUDE.md rule 34).\n *\n * Every failure mode produces a DISTINCT code so callers (index_status,\n * remnic doctor) can render per-language LSP state without conflating\n * \"server not installed\" with \"server timed out\" or \"protocol violation\".\n *\n * The codes are the load-bearing signal — `detail` is optional and never\n * carries `error.message` (which may contain absolute paths — rule 11).\n */\n\n/**\n * Backend identifier — always `\"lsp\"` so callers can distinguish LSP\n * degradations from QMD/search degradations in a unified handler.\n */\nexport type LspBackend = \"lsp\";\n\n/**\n * Distinct failure codes (rule 34 — never `[]`-on-error).\n *\n * - `server_missing` — the configured server binary is not on PATH\n * or is not executable (probe phase).\n * - `handshake_timeout` — `initialize` did not complete within\n * `lsp.timeoutMs`.\n * - `handshake_error` — server responded to `initialize` with an\n * error or a malformed response.\n * - `request_timeout` — a `textDocument/definition` request did\n * not receive a response within the\n * per-request deadline.\n * - `request_error` — server returned a JSON-RPC error response\n * for a resolution request.\n * - `protocol_error` — malformed JSON-RPC frame (bad header,\n * unparseable JSON, unknown method).\n * - `server_crashed` — the child process exited unexpectedly\n * mid-run.\n * - `budget_exhausted` — `lsp.maxRequestsPerRun` reached; remaining\n * call sites keep their Phase A resolution.\n * - `not_enabled` — `lsp.enabled` is `false`; no probe attempted.\n * - `unknown_language` — the file's language has no registered\n * server spec and none was overridden in\n * `lsp.servers`.\n */\nexport type LspDegradationCode =\n | \"server_missing\"\n | \"handshake_timeout\"\n | \"handshake_error\"\n | \"request_timeout\"\n | \"request_error\"\n | \"protocol_error\"\n | \"server_crashed\"\n | \"budget_exhausted\"\n | \"not_enabled\"\n | \"unknown_language\";\n\n/**\n * Tagged degradation — mirrors the shape of {@link SearchDegradation}.\n * `ok: false` results from the client/registry/resolution surface carry\n * this object so consumers can switch on `code` programmatically.\n */\nexport interface LspDegradation {\n readonly backend: LspBackend;\n readonly code: LspDegradationCode;\n readonly detail?: string;\n}\n\n/**\n * Tagged-result helpers — the shared discriminated-union pattern used\n * throughout the coding-graph package (rule 34). Every LSP surface\n * returns `{ ok: true; … }` on success or `{ ok: false; degradation }`\n * on failure — never throws, never returns `[]` to mean \"error\".\n */\nexport type LspResult<T> =\n | ({ readonly ok: true } & T)\n | { readonly ok: false; readonly degradation: LspDegradation };\n\n/**\n * Construct a degradation object. Kept as a factory rather than a class\n * so callers can spread it into a result without `new`.\n */\nexport function lspDegradation(\n code: LspDegradationCode,\n detail?: string,\n): LspDegradation {\n return detail !== undefined ? { backend: \"lsp\", code, detail } : { backend: \"lsp\", code };\n}\n","/**\n * LSP configuration types and defaults (issue #1555 — rule 30/48).\n *\n * `codingGraph.lsp.enabled` defaults `false`. Enabling LSP with zero\n * servers installed must produce a working index identical to Phase A\n * plus visible degradations — the characterization test proves this.\n *\n * Env overrides follow gotcha 9: `REMNIC_CODING_GRAPH_LSP_ENABLED` with\n * `ENGRAM_` fallback.\n */\nimport process from \"node:process\";\n\nimport type { CodingGraphLanguage } from \"@remnic/core\";\n\nimport type { LspDegradation } from \"./degradation.js\";\n\n// ──────────────────────────────────────────────────────────────────────────\n// Server launch spec — argv array end-to-end (rule 10).\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * How to launch a language server: command + argv. Always an array of\n * strings — never a shell string — so injection via config is impossible\n * (rule 10: argv arrays end-to-end).\n */\nexport interface LspServerLaunchSpec {\n readonly command: string;\n readonly args: readonly string[];\n}\n\n/**\n * Per-language server overrides. Keys are language identifiers (matching\n * {@link CodingGraphLanguage}); values are launch specs that REPLACE the\n * default. An unknown language key is an error (rule 51 — list supported\n * languages).\n */\nexport type LspServerOverrides = Partial<Record<CodingGraphLanguage, LspServerLaunchSpec>>;\n\n// ──────────────────────────────────────────────────────────────────────────\n// Configuration shape\n// ──────────────────────────────────────────────────────────────────────────\n\nexport interface LspConfig {\n /** Master switch — default false (rule 30/48). */\n readonly enabled: boolean;\n /** Per-language server overrides (default: empty — use registry defaults). */\n readonly servers: LspServerOverrides;\n /** Handshake + per-request timeout in ms (default 3000). */\n readonly timeoutMs: number;\n /** Max definition requests per index run (default 500). */\n readonly maxRequestsPerRun: number;\n}\n\nexport const DEFAULT_LSP_TIMEOUT_MS = 3_000;\nexport const DEFAULT_LSP_MAX_REQUESTS_PER_RUN = 500;\n\nexport const DEFAULT_LSP_CONFIG: LspConfig = {\n enabled: false,\n servers: {},\n timeoutMs: DEFAULT_LSP_TIMEOUT_MS,\n maxRequestsPerRun: DEFAULT_LSP_MAX_REQUESTS_PER_RUN,\n};\n\n/**\n * Parse and validate user-supplied LSP config. Unknown keys in `servers`\n * are rejected with a degradation listing supported languages (rule 51).\n * Non-executable absolute paths are rejected (rule 24 analog).\n *\n * Returns `{ ok: true, config }` or `{ ok: false, degradation }` — never\n * throws (rule 13).\n */\nexport type LspConfigParseResult =\n | { readonly ok: true; readonly config: LspConfig }\n | { readonly ok: false; readonly degradation: LspDegradation };\n\n/**\n * Parse the `enabled` flag strictly. Boolean values pass through; strings\n * like `\"false\"`, `\"0\"`, `\"no\"`, and `\"\"` are false (so env/CLI overrides\n * work as operators expect). Any other truthy value enables.\n */\nfunction parseEnabledFlag(v: unknown): boolean {\n if (typeof v === \"boolean\") return v;\n if (typeof v === \"string\") {\n const lower = v.trim().toLowerCase();\n return lower !== \"false\" && lower !== \"0\" && lower !== \"no\" && lower !== \"\";\n }\n return Boolean(v);\n}\n\n/**\n * Parse and validate user-supplied LSP config. Unknown keys in `servers`\n * are rejected with a degradation listing supported languages (rule 51).\n * Non-executable absolute paths are rejected (rule 24 analog).\n *\n * Returns `{ ok: true, config }` or `{ ok: false, degradation }` — never\n * throws (rule 13).\n */\nexport function parseLspConfig(\n raw: unknown,\n knownLanguages: readonly CodingGraphLanguage[],\n): LspConfigParseResult {\n if (raw === null || raw === undefined) {\n return { ok: true, config: DEFAULT_LSP_CONFIG };\n }\n if (typeof raw !== \"object\" || Array.isArray(raw)) {\n return {\n ok: false,\n degradation: {\n backend: \"lsp\",\n code: \"protocol_error\",\n detail: \"codingGraph.lsp must be an object\",\n },\n };\n }\n const obj = raw as Record<string, unknown>;\n const knownSet = new Set(knownLanguages);\n\n const enabled = obj.enabled === undefined ? false : parseEnabledFlag(obj.enabled);\n const timeoutMs = obj.timeoutMs === undefined ? DEFAULT_LSP_TIMEOUT_MS : Number(obj.timeoutMs);\n const maxRequestsPerRun =\n obj.maxRequestsPerRun === undefined ? DEFAULT_LSP_MAX_REQUESTS_PER_RUN : Number(obj.maxRequestsPerRun);\n\n if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {\n return {\n ok: false,\n degradation: {\n backend: \"lsp\",\n code: \"protocol_error\",\n detail: `lsp.timeoutMs must be a non-negative number, got ${JSON.stringify(obj.timeoutMs)}`,\n },\n };\n }\n if (!Number.isFinite(maxRequestsPerRun) || maxRequestsPerRun < 0) {\n return {\n ok: false,\n degradation: {\n backend: \"lsp\",\n code: \"protocol_error\",\n detail: `lsp.maxRequestsPerRun must be a non-negative number, got ${JSON.stringify(obj.maxRequestsPerRun)}`,\n },\n };\n }\n\n const servers: Partial<Record<CodingGraphLanguage, LspServerLaunchSpec>> = {};\n if (obj.servers !== undefined && obj.servers !== null) {\n if (typeof obj.servers !== \"object\" || Array.isArray(obj.servers)) {\n return {\n ok: false,\n degradation: {\n backend: \"lsp\",\n code: \"protocol_error\",\n detail: \"lsp.servers must be an object\",\n },\n };\n }\n for (const [lang, spec] of Object.entries(obj.servers as Record<string, unknown>)) {\n if (!knownSet.has(lang as CodingGraphLanguage)) {\n return {\n ok: false,\n degradation: {\n backend: \"lsp\",\n code: \"unknown_language\",\n detail: `unknown language \"${lang}\" in lsp.servers; supported: ${knownLanguages.join(\", \")}`,\n },\n };\n }\n if (typeof spec !== \"object\" || spec === null || Array.isArray(spec)) {\n return {\n ok: false,\n degradation: {\n backend: \"lsp\",\n code: \"protocol_error\",\n detail: `lsp.servers.${lang} must be an object`,\n },\n };\n }\n const s = spec as Record<string, unknown>;\n if (typeof s.command !== \"string\" || s.command.length === 0) {\n return {\n ok: false,\n degradation: {\n backend: \"lsp\",\n code: \"protocol_error\",\n detail: `lsp.servers.${lang}.command must be a non-empty string`,\n },\n };\n }\n if (!Array.isArray(s.args) || s.args.some((a) => typeof a !== \"string\")) {\n return {\n ok: false,\n degradation: {\n backend: \"lsp\",\n code: \"protocol_error\",\n detail: `lsp.servers.${lang}.args must be an array of strings`,\n },\n };\n }\n servers[lang as CodingGraphLanguage] = {\n command: s.command,\n args: [...(s.args as string[])],\n };\n }\n }\n\n return { ok: true, config: { enabled, servers, timeoutMs, maxRequestsPerRun } };\n}\n\n/**\n * Read the env-var override for `lsp.enabled`. Returns the raw string\n * value or null. The caller decides how to interpret it (gotcha 9:\n * `REMNIC_` primary, `ENGRAM_` fallback).\n */\nexport function readLspEnabledEnv(): string | null {\n return (\n process.env.REMNIC_CODING_GRAPH_LSP_ENABLED ??\n process.env.ENGRAM_CODING_GRAPH_LSP_ENABLED ??\n null\n );\n}\n","/**\n * LSP resolution pass — upgrades Phase A heuristic edges with real\n * definition-lookup results from language servers.\n *\n * Two halves (issue #1555 step 4):\n *\n * 1. **Planner** (pure): `planLspUpgrades(unresolvedCallSites, budget)`\n * decides which call sites to query and in what order. Pure function —\n * no side effects, no I/O. Deterministic output for deterministic input.\n *\n * 2. **Executor**: sends `textDocument/didOpen` + `textDocument/definition`\n * for each planned request via the LSP client, maps returned locations\n * back to graph nodes by file + half-open span containment (rule 35),\n * and applies edge upgrades transactionally per batch. A mid-batch\n * failure leaves zero partial upgrades (rule 25 — tested).\n *\n * Budgets (issue #1555): `maxRequestsPerRun` caps the worst case. Remaining\n * call sites keep their Phase A resolution. Everything degrades to Phase A\n * results with a tagged degradation surfaced in index_status.\n *\n * Files whose ingest failed are excluded (rule 44 — the executor never\n * queries for a file that isn't in the store).\n */\n\nimport path from \"node:path\";\n\nimport type { CodingGraphLanguage } from \"@remnic/core\";\n\nimport type { LspClient } from \"./client.js\";\nimport { uriToPath } from \"./client.js\";\nimport type { LspDegradation } from \"./degradation.js\";\nimport {\n buildLineOffsetMap,\n byteOffsetToPosition,\n positionToByteOffset,\n type LineOffsetMap,\n} from \"./byte-position.js\";\nimport type { LspLocation } from \"./types.js\";\n\n// ──────────────────────────────────────────────────────────────────────────\n// Input types — what the resolution pass consumes\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * A call site that Phase A left unresolved or at low confidence. The\n * resolution pass will query the LSP server for its definition.\n */\nexport interface UnresolvedCallSite {\n /** Repo-relative file path of the CALLER (the file containing the call). */\n readonly filePath: string;\n readonly language: CodingGraphLanguage;\n /** Full file content — needed for byte↔position conversion. */\n readonly content: string;\n /** Byte offset of the callee name in the source (for the definition query position). */\n readonly calleeByteOffset: number;\n /** The callee name as extracted by Phase A (for logging/debugging). */\n readonly calleeName: string;\n /** The caller's qualified name (source node for the edge). */\n readonly srcQualifiedName: string;\n}\n\n/**\n * A planned LSP definition request — the planner's output. Each request\n * targets one call site at one position in one file.\n */\nexport interface PlannedLspRequest {\n readonly filePath: string;\n readonly language: CodingGraphLanguage;\n readonly content: string;\n readonly calleeName: string;\n readonly srcQualifiedName: string;\n /** LSP position derived from calleeByteOffset via line-offset map. */\n readonly position: { readonly line: number; readonly character: number };\n}\n\n/**\n * The planner's budget — how many requests can be sent this run.\n */\nexport interface LspBudget {\n readonly maxRequests: number;\n}\n\n/**\n * Result of the planner — the requests to send, plus how many call sites\n * were deferred due to budget exhaustion.\n */\nexport interface PlanResult {\n readonly requests: readonly PlannedLspRequest[];\n readonly budgetExhausted: number;\n}\n\n/**\n * A location that the LSP server returned, mapped to a byte offset in\n * a known file.\n */\nexport interface ResolvedLocation {\n readonly filePath: string;\n readonly startByte: number;\n readonly endByte: number;\n}\n\n/**\n * Result of the resolution pass.\n */\nexport interface ResolutionResult {\n /** Edges upgraded from heuristic → lsp with resolved dst node. */\n readonly upgraded: number;\n /** LSP returned no location or the location didn't map to an indexed node. */\n readonly unresolved: number;\n /** Call sites skipped because maxRequestsPerRun was reached. */\n readonly budgetExhausted: number;\n /** Degradation if the pass could not run (server crashed, protocol error). */\n readonly degradation?: LspDegradation;\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Node-location lookup — the seam between LSP locations and graph nodes.\n// Implemented by the caller (the store-backed resolution executor) so\n// the planner stays pure and testable without a database.\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Look up a graph node by file path + byte span containment (rule 35 —\n * half-open). Returns the node's qualified name if exactly one node's\n * span contains the byte offset, or null if none/ambiguous.\n *\n * The executor provides this closure backed by the GraphStore; tests\n * inject a mock.\n */\nexport type NodeLocator = (\n filePath: string,\n byteOffset: number,\n) => string | null;\n\n/**\n * Context for mapping an LSP location to a graph node. Provides the\n * caller's file path and content (for same-file definitions), plus\n * optional workspace-root normalization and cross-file content resolution.\n */\nexport interface MapLocationContext {\n /** Repo-relative path of the caller file. */\n readonly callerFilePath: string;\n /** Full content of the caller file. */\n readonly callerContent: string;\n /** Workspace root for normalizing absolute LSP URIs to repo-relative paths. */\n readonly workspaceRoot?: string;\n /** Resolve content for a target file path (repo-relative). */\n readonly resolveContent?: (filePath: string) => string | null;\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Planner — pure function, no side effects\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Plan which call sites to resolve via LSP. Pure — deterministic output\n * for deterministic input. Orders requests by file path then byte offset\n * for predictable, reviewable batches (rule 38 — byte-stable ordering).\n *\n * Budget enforcement: at most `budget.maxRequests` requests are planned.\n * Excess call sites are counted in `budgetExhausted` so the caller can\n * surface the degradation.\n */\nexport function planLspUpgrades(\n callSites: readonly UnresolvedCallSite[],\n budget: LspBudget,\n): PlanResult {\n // Sort for deterministic ordering (rule 38): by file path, then byte offset.\n const sorted = [...callSites].sort(\n (a, b) =>\n a.filePath.localeCompare(b.filePath) ||\n a.calleeByteOffset - b.calleeByteOffset,\n );\n\n const max = Math.max(0, budget.maxRequests);\n const planned = sorted.slice(0, max);\n const exhausted = sorted.length - planned.length;\n\n // Pre-compute line-offset maps per unique file to avoid recomputation\n // in the executor. Group by content identity (same content → same map).\n const maps = new Map<string, LineOffsetMap>();\n const requests: PlannedLspRequest[] = planned.map((cs) => {\n let map = maps.get(cs.filePath);\n if (!map) {\n map = buildLineOffsetMap(cs.content);\n maps.set(cs.filePath, map);\n }\n const position = byteOffsetToPosition(cs.content, cs.calleeByteOffset, map);\n return {\n filePath: cs.filePath,\n language: cs.language,\n content: cs.content,\n calleeName: cs.calleeName,\n srcQualifiedName: cs.srcQualifiedName,\n position,\n };\n });\n\n return { requests, budgetExhausted: exhausted };\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Executor — sends definition requests, maps locations, applies upgrades\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Options for the resolution executor.\n */\nexport interface ResolveOptions {\n readonly client: LspClient;\n readonly nodeLocator: NodeLocator;\n /**\n * Apply a batch of edge upgrades atomically. Called once per file batch.\n * MUST be transactional — if it throws, zero upgrades from this batch\n * persist (rule 25). Each upgrade is an edge `{srcQualifiedName,\n * dstQualifiedName, type: \"CALLS\", confidence, provenance: \"lsp\"}`.\n */\n readonly applyUpgrades: (\n upgrades: readonly EdgeUpgrade[],\n ) => Promise<void>;\n /**\n * Workspace root for resolving repo-relative file paths to absolute LSP\n * URIs and normalizing returned URIs back to repo-relative paths.\n */\n readonly workspaceRoot?: string;\n /**\n * Resolve the content of a target file by repo-relative path. Used for\n * cross-file definition positions — without this, cross-file byte-offset\n * conversion falls back to the caller's content (best-effort).\n */\n readonly resolveContent?: (filePath: string) => string | null;\n}\n\n/**\n * A single edge upgrade — the output of a successful definition lookup.\n */\nexport interface EdgeUpgrade {\n readonly srcQualifiedName: string;\n readonly dstQualifiedName: string;\n readonly type: string;\n readonly confidence: number;\n readonly provenance: \"lsp\";\n}\n\n/**\n * Map a {@link CodingGraphLanguage} to its LSP languageId string. Most\n * tier-1 languages use their own name; a few differ (tsx → typescriptreact,\n * bash → shellscript).\n */\nconst LANGUAGE_ID_MAP: Partial<Record<CodingGraphLanguage, string>> = {\n tsx: \"typescriptreact\",\n bash: \"shellscript\",\n};\n\n/**\n * Execute the resolution pass: for each planned request, send a\n * `textDocument/didOpen` + `textDocument/definition` query, map the\n * returned location to a graph node, and collect edge upgrades. Upgrades\n * are applied in file-batched transactions — a mid-batch failure leaves\n * zero partial upgrades.\n *\n * Never throws — degrades to Phase A results with a tagged degradation.\n */\nexport async function executeLspResolution(\n requests: readonly PlannedLspRequest[],\n options: ResolveOptions,\n): Promise<ResolutionResult> {\n const { client, nodeLocator, applyUpgrades } = options;\n\n // Group requests by file for batched transactional application.\n const byFile = new Map<string, PlannedLspRequest[]>();\n for (const req of requests) {\n let batch = byFile.get(req.filePath);\n if (!batch) {\n batch = [];\n byFile.set(req.filePath, batch);\n }\n batch.push(req);\n }\n\n let upgraded = 0;\n let unresolved = 0;\n let degradation: LspDegradation | undefined;\n\n for (const [filePath, batchReqs] of byFile) {\n // Send all definition requests for this file, collecting upgrades.\n const upgrades: EdgeUpgrade[] = [];\n let batchFailed = false;\n\n // Open the document with full text before querying definitions (LSP 3.17).\n // The server needs the content to answer definition requests accurately.\n const firstReq = batchReqs[0];\n client.didOpen({\n uri: filePathToUri(filePath, options.workspaceRoot),\n languageId: LANGUAGE_ID_MAP[firstReq.language] ?? firstReq.language,\n version: 1,\n text: firstReq.content,\n });\n\n for (let i = 0; i < batchReqs.length; i++) {\n const req = batchReqs[i];\n if (degradation) {\n // Server died in a previous batch — count remaining unattempted.\n unresolved += batchReqs.length - i;\n break;\n }\n\n const defResult = await client.definition({\n textDocument: { uri: filePathToUri(req.filePath, options.workspaceRoot) },\n position: req.position,\n });\n\n if (!defResult.ok) {\n // Distinguish \"server problem\" (stop the whole pass) from\n // \"this particular definition returned nothing\" (continue).\n if (\n defResult.degradation.code === \"server_crashed\" ||\n defResult.degradation.code === \"protocol_error\"\n ) {\n degradation = defResult.degradation;\n // Count this request plus all remaining unattempted requests\n // in this batch as unresolved for accurate reporting.\n unresolved += batchReqs.length - i;\n // Mark the batch failed so collected upgrades are NOT committed\n // (rule 25 — per-batch atomicity; a degraded batch must not\n // persist partial LSP edges).\n batchFailed = true;\n break;\n }\n // request_timeout / request_error — count as unresolved, continue.\n unresolved++;\n continue;\n }\n\n const dstQName = mapLocationToNode(\n defResult.locations,\n {\n callerFilePath: req.filePath,\n callerContent: req.content,\n workspaceRoot: options.workspaceRoot,\n resolveContent: options.resolveContent,\n },\n nodeLocator,\n );\n if (dstQName === null) {\n unresolved++;\n continue;\n }\n upgrades.push({\n srcQualifiedName: req.srcQualifiedName,\n dstQualifiedName: dstQName,\n type: \"CALLS\",\n confidence: 0.9,\n provenance: \"lsp\",\n });\n }\n\n if (batchFailed) {\n // The batch was poisoned — collected upgrades must NOT be committed.\n // Count them as unresolved for reporting accuracy.\n unresolved += upgrades.length;\n break;\n }\n\n // Apply upgrades transactionally per file batch. If the apply throws,\n // zero upgrades from this batch persist (rule 25 — the applyUpgrades\n // callback MUST be transactional).\n if (upgrades.length > 0) {\n try {\n await applyUpgrades(upgrades);\n upgraded += upgrades.length;\n } catch {\n // The apply failed — degrade but don't crash. Upgrades from this\n // batch are lost (the callback's transaction rolled back). Edges\n // from already-applied batches survive (they were in separate\n // transactions — this is the documented per-batch isolation).\n // Count the lost upgrades as unresolved for reporting.\n unresolved += upgrades.length;\n }\n }\n }\n\n return {\n upgraded,\n unresolved,\n budgetExhausted: 0, // set by the caller from the planner result\n degradation,\n };\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Location → node mapping (rule 35 — half-open span containment)\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Map an LSP definition location to a graph node's qualified name.\n *\n * Uses half-open span containment (rule 35): the location's start byte\n * must be within `[node.spanStart, node.spanEnd)` for the node to match.\n * If multiple locations are returned, the FIRST one that maps to a node\n * wins (LSP servers typically return the most relevant definition first).\n *\n * For same-file definitions, the caller's content is used for byte-offset\n * conversion (exact). For cross-file definitions, `context.resolveContent`\n * is used to fetch the target file's content; if unavailable, the caller's\n * content is used as a best-effort fallback.\n *\n * Returns null if no location maps to an indexed node.\n */\nexport function mapLocationToNode(\n locations: readonly LspLocation[],\n context: MapLocationContext,\n nodeLocator: NodeLocator,\n): string | null {\n for (const loc of locations) {\n const filePath = normalizeLocationPath(loc.uri, context.workspaceRoot);\n\n // Resolve content for this location's file.\n let content: string;\n if (filePath === context.callerFilePath) {\n // Same file as the caller — use the caller's exact content.\n content = context.callerContent;\n } else if (context.resolveContent) {\n // Cross-file — use the content resolver for exact conversion.\n const resolved = context.resolveContent(filePath);\n if (resolved === null) continue; // file not indexed — skip\n content = resolved;\n } else {\n // Cross-file without a resolver — best-effort fallback to caller\n // content. The byte offset may be imprecise for files with different\n // line lengths or Unicode, but the half-open containment check\n // tolerates small offsets.\n content = context.callerContent;\n }\n\n const map = buildLineOffsetMap(content);\n const startByte = positionToByteOffset(content, loc.range.start, map);\n const qName = nodeLocator(filePath, startByte);\n if (qName !== null) return qName;\n }\n return null;\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// URI helpers\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Convert a repo-relative file path to a `file://` URI for LSP requests.\n * When `workspaceRoot` is provided, relative paths are resolved against\n * it to produce an absolute URI (e.g. `file:///workspace/src/a.ts`). LSP\n * servers do not reinterpret URIs relative to `rootUri`, so the URI must\n * be absolute.\n */\nfunction filePathToUri(filePath: string, workspaceRoot?: string): string {\n const isAbsolute = filePath.startsWith(\"/\") || /^[A-Za-z]:[\\\\/]/.test(filePath);\n if (isAbsolute) {\n return `file://${filePath.replace(/\\\\/g, \"/\")}`;\n }\n if (workspaceRoot) {\n const abs = path.join(workspaceRoot, filePath).replace(/\\\\/g, \"/\");\n return `file://${abs}`;\n }\n // No workspace root — best-effort for backward compatibility.\n return `file:///${filePath.replace(/\\\\/g, \"/\")}`;\n}\n\n/**\n * Normalize an LSP `Location.uri` to a repo-relative file path. Strips the\n * workspace root prefix so the result matches the store's canonical paths.\n * Without `workspaceRoot`, returns the absolute path from the URI.\n */\nfunction normalizeLocationPath(uri: string, workspaceRoot?: string): string {\n const absPath = uriToPath(uri);\n if (workspaceRoot) {\n const root = path.resolve(workspaceRoot);\n const rel = path.relative(root, absPath);\n if (!rel.startsWith(\"..\") && rel !== \"\") {\n return rel.replace(/\\\\/g, \"/\");\n }\n }\n return absPath;\n}\n","/**\n * Byte-offset ↔ LSP position conversion.\n *\n * LSP positions are zero-based {line, character} where `character` is a\n * UTF-16 code-unit offset within the line (LSP 3.17 §3.17). The coding-\n * graph store uses UTF-8 byte spans. This module converts between the two.\n *\n * A {@link LineOffsetMap} pre-computes the UTF-8 BYTE offset of each line\n * start, making both directions O(log n) via binary search for the line,\n * then O(line length) for the character within the line.\n */\n\n/**\n * Pre-computed line-start byte offsets for a single file. Built once per\n * file from its content; reused for all position conversions in that file.\n *\n * `lineStarts[i]` = UTF-8 byte offset of the first character on line `i`.\n * Line 0 always starts at byte 0.\n */\nexport interface LineOffsetMap {\n readonly lineStarts: readonly number[];\n}\n\n/**\n * Compute the UTF-8 byte length of a single UTF-16 code unit or a\n * surrogate pair. Surrogate pairs encode to 4 bytes (U+10000-U+10FFFF).\n */\nfunction utf8ByteLength(code: number, nextCode: number): number {\n if (code >= 0xd800 && code <= 0xdbff && nextCode >= 0xdc00 && nextCode <= 0xdfff) {\n return 4;\n }\n if (code < 0x80) return 1;\n if (code < 0x800) return 2;\n return 3;\n}\n\n/** True if this code unit is the high half of a surrogate pair. */\nfunction isHighSurrogate(code: number, nextCode: number): boolean {\n return code >= 0xd800 && code <= 0xdbff && nextCode >= 0xdc00 && nextCode <= 0xdfff;\n}\n\n/**\n * Build a line-offset map from file content (as a UTF-8 string or Buffer).\n * Records UTF-8 BYTE offsets — not UTF-16 string indices — because\n * Content-Length and the store's span_start/span_end count bytes.\n * Handles `\\n`, `\\r\\n`, and `\\r` line endings.\n */\nexport function buildLineOffsetMap(content: string | Buffer): LineOffsetMap {\n const text = typeof content === \"string\" ? content : content.toString(\"utf8\");\n const lineStarts = [0];\n let byteOffset = 0;\n\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n const nextCode = i + 1 < text.length ? text.charCodeAt(i + 1) : 0;\n\n if (code === 0x0d && nextCode === 0x0a) {\n // \\r\\n — 2 bytes, advance past \\n.\n byteOffset += 2;\n lineStarts.push(byteOffset);\n i++;\n } else if (code === 0x0a || code === 0x0d) {\n byteOffset += 1;\n lineStarts.push(byteOffset);\n } else {\n byteOffset += utf8ByteLength(code, nextCode);\n if (isHighSurrogate(code, nextCode)) i++;\n }\n }\n return { lineStarts };\n}\n\n/**\n * Convert a UTF-8 byte offset to an LSP position {line, character}.\n * `character` is a UTF-16 code-unit count from the line start (surrogates\n * count as 2, matching LSP §3.17).\n */\nexport function byteOffsetToPosition(\n content: string,\n byteOffset: number,\n map: LineOffsetMap,\n): { line: number; character: number } {\n const lineIdx = binarySearchLine(map.lineStarts, byteOffset);\n const lineByteStart = map.lineStarts[lineIdx];\n\n // Find the string index where the line starts (byte offset == lineByteStart).\n let strIdx = 0;\n let byteAccum = 0;\n for (strIdx = 0; strIdx < content.length; strIdx++) {\n if (byteAccum >= lineByteStart) break;\n const code = content.charCodeAt(strIdx);\n const nextCode = strIdx + 1 < content.length ? content.charCodeAt(strIdx + 1) : 0;\n byteAccum += utf8ByteLength(code, nextCode);\n if (isHighSurrogate(code, nextCode)) strIdx++;\n }\n\n // Walk from the line start to byteOffset, counting UTF-16 code units.\n let charCount = 0;\n let currentByte = lineByteStart;\n for (let i = strIdx; i < content.length; i++) {\n if (currentByte >= byteOffset) break;\n const code = content.charCodeAt(i);\n const nextCode = i + 1 < content.length ? content.charCodeAt(i + 1) : 0;\n currentByte += utf8ByteLength(code, nextCode);\n if (isHighSurrogate(code, nextCode)) {\n charCount += 2;\n i++;\n } else {\n charCount++;\n }\n }\n\n return { line: lineIdx, character: charCount };\n}\n\n/**\n * Convert an LSP position {line, character} to a UTF-8 byte offset.\n * `character` is a UTF-16 code-unit count from the line start.\n */\nexport function positionToByteOffset(\n content: string,\n position: { line: number; character: number },\n map: LineOffsetMap,\n): number {\n const lineIdx = Math.min(position.line, map.lineStarts.length - 1);\n const lineByteStart = map.lineStarts[lineIdx];\n\n // Find the string index where the line starts (byte offset == lineByteStart).\n let strIdx = 0;\n let byteAccum = 0;\n for (strIdx = 0; strIdx < content.length; strIdx++) {\n if (byteAccum >= lineByteStart) break;\n const code = content.charCodeAt(strIdx);\n const nextCode = strIdx + 1 < content.length ? content.charCodeAt(strIdx + 1) : 0;\n byteAccum += utf8ByteLength(code, nextCode);\n if (isHighSurrogate(code, nextCode)) strIdx++;\n }\n\n // Walk from the line start, counting UTF-16 code units until position.character.\n let charCount = 0;\n let byteOffset = lineByteStart;\n for (let i = strIdx; i < content.length && charCount < position.character; i++) {\n const code = content.charCodeAt(i);\n const nextCode = i + 1 < content.length ? content.charCodeAt(i + 1) : 0;\n byteOffset += utf8ByteLength(code, nextCode);\n if (isHighSurrogate(code, nextCode)) {\n charCount += 2;\n i++;\n } else {\n charCount++;\n }\n }\n\n return byteOffset;\n}\n\n/**\n * Binary search for the last lineStart that is <= byteOffset.\n * Returns the line index (0-based).\n */\nfunction binarySearchLine(lineStarts: readonly number[], byteOffset: number): number {\n let lo = 0;\n let hi = lineStarts.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n if (lineStarts[mid] <= byteOffset) {\n lo = mid;\n } else {\n hi = mid - 1;\n }\n }\n return lo;\n}\n","/**\n * LSP status surfacing — per-language LSP state for index_status and\n * `remnic doctor` (issue #1555 step 5).\n *\n * Reports per-language: enabled / probed / degraded(code) / requests_used.\n * A missing server is normal, not an error — the status entry shows\n * `probed: false` with a degradation code so the operator knows LSP\n * resolution is available but inactive for that language.\n */\n\nimport type { LspDegradationCode } from \"./degradation.js\";\nimport type { LspConfig } from \"./config.js\";\nimport type { CodingGraphLanguage } from \"@remnic/core\";\nimport type { ResolutionResult } from \"./resolution.js\";\n\n// ──────────────────────────────────────────────────────────────────────────\n// Status entry — one per configured language\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Per-language LSP status. Surfaced in `index_status` and rendered as a\n * single line by `remnic doctor`.\n */\nexport interface LspStatusEntry {\n readonly language: CodingGraphLanguage;\n /** Master switch state (codingGraph.lsp.enabled). */\n readonly enabled: boolean;\n /** Server binary found and initialize handshake succeeded. */\n readonly probed: boolean;\n /** True if the last resolution pass degraded (timeout/crash/protocol). */\n readonly degraded: boolean;\n /** Specific degradation code if `degraded` is true. */\n readonly degradationCode?: LspDegradationCode;\n /** Definition requests sent in the last index run. */\n readonly requestsUsed: number;\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Status computation\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Input for LSP status computation — the probe results and resolution\n * results from the last index run. The caller (the index pipeline)\n * collects these during the run and passes them here.\n */\nexport interface LspStatusInput {\n readonly config: LspConfig;\n /**\n * Per-language probe results from the last run. `true` = server found\n * and handshake succeeded.\n */\n readonly probeResults: ReadonlyMap<CodingGraphLanguage, boolean>;\n /**\n * Per-language degradation codes from the last run. A language in this\n * map with a code means the resolution pass degraded for that language.\n */\n readonly degradations: ReadonlyMap<CodingGraphLanguage, LspDegradationCode>;\n /**\n * Per-language definition request counts from the last run.\n */\n readonly requestCounts: ReadonlyMap<CodingGraphLanguage, number>;\n /**\n * The languages configured for resolution (from the index run's\n * candidate set). Only these languages get status entries.\n */\n readonly languages: readonly CodingGraphLanguage[];\n}\n\n/**\n * Compute per-language LSP status entries. Pure — no side effects.\n * Returns one entry per language in `languages`.\n */\nexport function getLspStatus(input: LspStatusInput): readonly LspStatusEntry[] {\n return input.languages.map((lang) => {\n const probed = input.probeResults.get(lang) ?? false;\n const degradationCode = input.degradations.get(lang);\n const requestsUsed = input.requestCounts.get(lang) ?? 0;\n return {\n language: lang,\n enabled: input.config.enabled,\n probed,\n degraded: degradationCode !== undefined,\n degradationCode,\n requestsUsed,\n };\n });\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Doctor rendering — one line per language\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Render a single LSP status line for `remnic doctor`. Format:\n *\n * typescript: lsp [probed] 12 requests\n * python: lsp [degraded:request_timeout] 3 requests\n * go: lsp [not_probed] 0 requests\n * rust: lsp [disabled]\n */\nexport function formatLspStatusLine(entry: LspStatusEntry): string {\n if (!entry.enabled) {\n return `${entry.language}: lsp [disabled]`;\n }\n const parts: string[] = [];\n if (entry.probed) {\n parts.push(\"probed\");\n } else {\n parts.push(\"not_probed\");\n }\n if (entry.degraded && entry.degradationCode) {\n parts[parts.length - 1] = `degraded:${entry.degradationCode}`;\n }\n const stateStr = parts.join(\" \");\n return `${entry.language}: lsp [${stateStr}] ${entry.requestsUsed} requests`;\n}\n\n// ──────────────────────────────────────────────────────────────────────────\n// Resolution result → status input adapter\n// ──────────────────────────────────────────────────────────────────────────\n\n/**\n * Adapter: convert a {@link ResolutionResult} into the per-language\n * degradation + request-count maps that {@link getLspStatus} consumes.\n *\n * The resolution pass runs once per language (each language has its own\n * server). This helper is called once per language to populate the maps.\n */\nexport function resolutionResultToStatusMaps(\n language: CodingGraphLanguage,\n result: ResolutionResult,\n degradations: Map<CodingGraphLanguage, LspDegradationCode>,\n requestCounts: Map<CodingGraphLanguage, number>,\n): void {\n requestCounts.set(language, result.upgraded + result.unresolved);\n if (result.degradation) {\n degradations.set(language, result.degradation.code);\n }\n}\n","/**\n * Semantic-layer configuration for @remnic/coding-graph (issue #1556).\n *\n * Rule 30/48: the semantic layer is OFF by default. Embedding costs compute\n * and possibly tokens, so nothing in this module touches a provider, writes\n * a vector, or sends symbol text off-machine unless `enabled` is explicitly\n * true. The gate-off characterization test (`gate-off.test.ts`) asserts\n * this end to end.\n *\n * The config object is intentionally self-contained in this package rather\n * than wired into @remnic/core/config.ts — the coding-graph package is an\n * optional peer dep and must compile standalone. Host integrations\n * (core/config.ts + openclaw.plugin.json schema) resolve to this same\n * shape via `resolveSemanticConfig()`, which reads the documented env vars\n * with the `ENGRAM_` fallback (gotcha 9).\n */\nimport type { EdgeProvenance } from \"../graph-schema.js\";\n\n/**\n * Default SIMILAR_TO cosine confirmation threshold (issue #1556 design).\n * 0.92 is the codebase-memory-mcp precedent — high enough to avoid\n * false near-clone pairs across structurally-similar but logically-distinct\n * functions, low enough to catch genuine copy-paste with a renamed\n * variable.\n */\nexport const DEFAULT_SIMILAR_TO_THRESHOLD = 0.92;\n\n/**\n * Default maximum symbols embedded per indexing run. Bounds per-run\n * provider cost. 0 means unlimited (the host budget is the only cap).\n */\nexport const DEFAULT_MAX_SYMBOLS_PER_RUN = 0;\n\n/**\n * Confidence band assigned to MinHash-only SIMILAR_TO edges when no\n * embedding provider is available (deterministic, local). Kept below the\n * embedding-confirmed band so consumers can distinguish provenance quality.\n * The issue designates this a distinct, documented lower band.\n */\nexport const MINHASH_ONLY_CONFIDENCE = 0.5;\n\n/**\n * Confidence band assigned to embedding-confirmed SIMILAR_TO edges. Uses\n * the actual cosine similarity score (≥ similarToThreshold), so this is\n * the FLOOR for that band — the real confidence is the cosine value.\n */\nexport const EMBEDDING_CONFIRMED_MIN_CONFIDENCE = 0.92;\n\n/**\n * Edge type emitted by the SIMILAR_TO pipeline. Lives in the `edges`\n * table with `provenance: \"semantic\"` (already in EDGE_PROVENANCE_VALUES).\n */\nexport const SIMILAR_TO_EDGE_TYPE = \"SIMILAR_TO\";\n\n/**\n * The single provenance tag for every edge this module writes.\n */\nexport const SEMANTIC_PROVENANCE: EdgeProvenance = \"semantic\";\n\n/**\n * Canonical-text body line budget. `signature + doc comment + first N lines\n * of body` per the issue design. N is bounded so a 5k-line function does\n * not dominate the embedded string (and the provider token budget).\n */\nexport const DEFAULT_CANONICAL_BODY_LINES = 16;\n\n/**\n * Token shingle width for MinHash. 3 tokens per shingle is the standard\n * near-duplicate-detection width — small enough to catch renamed-variable\n * clones (most shingles survive a single rename), large enough that\n * boilerplate coincidence does not flood the candidate set.\n */\nexport const MINHASH_SHINGLE_WIDTH = 2;\n\n/**\n * Number of MinHash permutations (hash functions). More = tighter Jaccard\n * estimate = more compute. 128 gives ~±5% Jaccard error at p=0.95 which\n * is well inside the 0.92 cosine confirmation gate's margin.\n */\nexport const MINHASH_NUM_PERMUTATIONS = 128;\n\n/**\n * Number of LSH bands. More bands = more candidate pairs (higher recall,\n * lower precision before the cosine gate). 32 bands of 4 rows each is the\n * banding that pairs a Jaccard ≥ 0.4 with high probability while keeping\n * the candidate set small for typical repos.\n */\nexport const LSH_NUM_BANDS = 32;\n\n/**\n * The LSH banding — derived: rows per band = permutations / bands.\n */\nexport const LSH_ROWS_PER_BAND = MINHASH_NUM_PERMUTATIONS / LSH_NUM_BANDS;\n\n/**\n * LSH candidate-pair Jaccard floor implied by the banding (s ≈ (1/b)^(1/r)).\n * Below this Jaccard similarity a pair is almost never a candidate. Kept as\n * a named constant so the determinism test can assert the candidate set is\n * a pure function of (seeds, inputs) without a hidden threshold drift.\n */\nexport const LSH_CANDIDATE_JACCARD_FLOOR = Math.pow(1 / LSH_NUM_BANDS, 1 / LSH_ROWS_PER_BAND);\n\n/**\n * Self-contained semantic config. Resolved from host config + env.\n *\n * `enabled` is the single gate for the whole layer. When false, every\n * semantic entry point (index-time vector writes, SIMILAR_TO edges,\n * semantic_query) returns a tagged `{ ok: false, code: \"semantic_disabled\" }`\n * WITHOUT touching the provider or the vectors table (gate-off parity).\n */\nexport interface SemanticConfig {\n /** Master gate. Default false (rule 30/48). */\n readonly enabled: boolean;\n /** Cosine threshold for SIMILAR_TO confirmation. Default 0.92. */\n readonly similarToThreshold: number;\n /** Per-run embedding budget (0 = unlimited). Default 0. */\n readonly maxSymbolsPerRun: number;\n /** Canonical-text body line budget. Default 16. */\n readonly canonicalBodyLines: number;\n}\n\n/**\n * Environment variable names. `REMNIC_*` is primary; `ENGRAM_*` is the\n * fallback (gotcha 9 — legacy env names still bind).\n */\nconst ENV_ENABLED = [\"REMNIC_CODING_GRAPH_SEMANTIC_ENABLED\", \"ENGRAM_CODING_GRAPH_SEMANTIC_ENABLED\"];\nconst ENV_THRESHOLD = [\"REMNIC_CODING_GRAPH_SEMANTIC_SIMILAR_TO_THRESHOLD\", \"ENGRAM_CODING_GRAPH_SEMANTIC_SIMILAR_TO_THRESHOLD\"];\nconst ENV_MAX_SYMBOLS = [\"REMNIC_CODING_GRAPH_SEMANTIC_MAX_SYMBOLS_PER_RUN\", \"ENGRAM_CODING_GRAPH_SEMANTIC_MAX_SYMBOLS_PER_RUN\"];\n\n/**\n * Resolve a boolean env var. Accepts true/false/1/0 (case-insensitive).\n */\n/**\n * Coerce a host-provided boolean (which may arrive as a string/number from\n * JSON or CLI config) to a real boolean, so \"false\"/\"0\"/\"no\" do not become\n * truthy and silently enable vector indexing despite an explicit opt-out\n * (chatgpt-codex-connector: 'Coerce host enabled before trusting it').\n * undefined/null → undefined (fall through to env/default).\n */\nfunction coerceHostBool(value: unknown): boolean | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"string\") {\n const v = value.trim().toLowerCase();\n // Fail CLOSED: only explicit affirmatives enable the layer. Any other\n // value (\"false\"/\"0\"/\"no\"/\"off\"/\"disabled\"/\"\"/unknown) is an opt-out,\n // so a malformed or unrecognized host string can never silently enable\n // remote embedding against operator intent (cursor Bugbot: 'Unknown\n // enabled strings enable semantic').\n if (v === \"true\" || v === \"1\" || v === \"yes\" || v === \"on\") return true;\n return false;\n }\n if (typeof value === \"number\") return value !== 0;\n return Boolean(value);\n}\n\n/**\n * Coerce a host-provided number (which may arrive as a numeric string from\n * JSON/CLI config) to a finite number, so a malformed value like\n * maxSymbolsPerRun:\"abc\" cannot become NaN and silently disable the vector\n * budget (NaN > 0 is false → unlimited) or break cosine confirmation\n * (comparisons against NaN are false). undefined/null/non-finite → undefined\n * (fall through to env/default). Negative finite numbers pass through so the\n * downstream clamp still controls range (chatgpt-codex-connector: 'Validate\n * host numeric config before clamping').\n */\nfunction coerceHostNumber(value: unknown): number | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === \"number\") return Number.isFinite(value) ? value : undefined;\n if (typeof value === \"string\") {\n const n = Number(value.trim());\n return Number.isFinite(n) ? n : undefined;\n }\n return undefined;\n}\n\nfunction resolveBoolEnv(names: readonly string[], fallback: boolean, env: NodeJS.ProcessEnv): boolean {\n for (const name of names) {\n const raw = env[name];\n if (raw === undefined) continue;\n const v = raw.trim().toLowerCase();\n if (v === \"true\" || v === \"1\") return true;\n if (v === \"false\" || v === \"0\") return false;\n // Malformed value: ignore (do not throw — a typo must not crash indexing).\n }\n return fallback;\n}\n\n/**\n * Resolve a positive-number env var. Ignores malformed/NaN/negative values\n * rather than throwing (a config typo must not crash the indexer).\n */\nfunction resolveNumberEnv(names: readonly string[], fallback: number, env: NodeJS.ProcessEnv): number {\n for (const name of names) {\n const raw = env[name];\n if (raw === undefined) continue;\n const n = Number(raw);\n if (Number.isFinite(n) && n >= 0) return n;\n }\n return fallback;\n}\n\n/**\n * Resolve the semantic config from an optional host-provided partial plus\n * the environment. Explicit host values win; env vars fill the gaps;\n * documented defaults apply last.\n *\n * `env` defaults to `process.env` but is a parameter so tests can pin the\n * environment deterministically (rule 38 — no implicit process state).\n */\nexport function resolveSemanticConfig(\n host?: Partial<SemanticConfig>,\n env: NodeJS.ProcessEnv = process.env,\n): SemanticConfig {\n const enabled =\n coerceHostBool(host?.enabled) ?? resolveBoolEnv(ENV_ENABLED, false, env);\n const similarToThreshold =\n coerceHostNumber(host?.similarToThreshold) ?? resolveNumberEnv(ENV_THRESHOLD, DEFAULT_SIMILAR_TO_THRESHOLD, env);\n const maxSymbolsPerRun =\n coerceHostNumber(host?.maxSymbolsPerRun) ?? resolveNumberEnv(ENV_MAX_SYMBOLS, DEFAULT_MAX_SYMBOLS_PER_RUN, env);\n const canonicalBodyLines =\n coerceHostNumber(host?.canonicalBodyLines) ?? DEFAULT_CANONICAL_BODY_LINES;\n return {\n enabled,\n // Clamp threshold into [0,1] — a malformed env must not produce an\n // out-of-range confidence gate.\n similarToThreshold: Math.min(1, Math.max(0, similarToThreshold)),\n maxSymbolsPerRun: Math.max(0, Math.floor(maxSymbolsPerRun)),\n // canonicalBodyLines: a negative/zero value must NOT clamp to 0 because\n // extractBodyText treats <= 0 as unlimited — sending full symbol bodies to\n // the embedding provider instead of the bounded excerpt (defeats the\n // privacy/cost cap). Fall back to the default instead (#1680).\n canonicalBodyLines: canonicalBodyLines >= 1 ? Math.floor(canonicalBodyLines) : DEFAULT_CANONICAL_BODY_LINES,\n };\n}\n","/**\n * Canonical-text builder for symbol embeddings (issue #1556).\n *\n * Rule 23/38 — ONE canonical form. The exact string that is embedded is\n * the exact string that is hashed for the embedding cache. Two formatting-\n * variant fixtures of the same function MUST produce the same canonical\n * text; the cache-hash test asserts this end to end.\n *\n * The canonical form (per the issue design):\n * `signature + doc comment + first N lines of body`,\n * normalized (sorted-key serialization where structured, stable truncation).\n *\n * Normalization strategy: ALL whitespace (including newlines) is collapsed\n * to single spaces BEFORE signature/body split. This absorbs every\n * formatting variant (indentation, brace placement, trailing whitespace,\n * tabs vs spaces) so two semantically-identical functions produce IDENTICAL\n * canonical text. The \"first N lines\" budget is then applied as a stable\n * TOKEN budget over the collapsed text — stable because token count is\n * formatting-independent.\n *\n * IMPORTANT: this module takes pre-extracted text spans, not raw source.\n * The caller (the indexer) slices `[startByte, endByte)` from disk and\n * passes the raw symbol text. Canonicalization here is about producing a\n * stable embedding input from that raw text, not about re-parsing.\n */\nimport { createHash } from \"node:crypto\";\n\nimport type { SymbolIR } from \"@remnic/core\";\n\nimport { DEFAULT_CANONICAL_BODY_LINES } from \"./config.js\";\n\n/**\n * Input to the canonical-text builder. `rawText` is the symbol's on-disk\n * source slice `[startByte, endByte)`. `docComment` is the leading\n * comment block immediately above the symbol, if the caller extracted one\n * (the parser does not currently emit doc comments on SymbolIR, so this\n * is optional and may be empty/undefined — the canonical form degrades\n * gracefully to `signature + body`).\n */\nexport interface CanonicalTextInput {\n readonly symbol: SymbolIR;\n /** Raw source text of the symbol span. */\n readonly rawText: string;\n /** Optional leading doc comment (/** … *\\/ or // … lines). */\n readonly docComment?: string;\n /** Body token budget (default {@link DEFAULT_CANONICAL_BODY_LINES}). */\n readonly maxBodyLines?: number;\n}\n\n/**\n * Collapse ALL whitespace runs (including newlines) to single spaces and\n * trim. This is the universal normalization pass: it absorbs indentation,\n * brace placement, trailing whitespace, tabs vs spaces, and line-ending\n * differences. After this pass, two formatting variants of the same\n * function are byte-identical.\n */\nexport function collapseWhitespace(text: string): string {\n return text\n // Insert spaces around punctuation so a,b and a, b canonicalize identically.\n .replace(/([{}()<>\\[\\],;:?!=+\\-*/%&|^~])/g, \" $1 \")\n // Collapse all whitespace runs (including those introduced above) to single spaces.\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n/**\n * Line-oriented whitespace collapse (preserves line structure). Used when\n * line boundaries matter (e.g. the \"first N lines\" budget needs real\n * lines). Collapses intra-line whitespace runs but keeps newlines.\n */\nexport function collapseWhitespaceKeepLines(text: string): string {\n return text\n .split(/\\r?\\n/)\n .map((line) => line.replace(/[ \\t]+/g, \" \").trim())\n .join(\"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\n/**\n * Split normalized symbol text into signature + body at the first body-\n * open marker (`{`, `=>`, `:` preceded by a non-type context). The split\n * is CHARACTER-level on the fully-whitespace-normalized text, so a\n * same-line function `function f() { return 1; }` and a brace-newline\n * variant `function f()\\n{ return 1; }` both split at the same `{`.\n *\n * Returns `{ signature, body }` — both whitespace-normalized. The\n * signature includes the marker char so the canonical form is stable.\n */\nfunction splitSignatureBody(normalized: string): { readonly signature: string; readonly body: string } {\n // Find the body-open marker. `{` is the universal one (functions,\n // classes, blocks). When there is no `{` we fall back to the arrow\n // form so braceless arrow functions (`const f = (x) => x + 1`) split\n // at the arrow instead of collapsing to an empty body.\n //\n // collapseWhitespace's punctuation-spacer turns `=>` into `= >`, so\n // the raw `=>` marker NEVER appears in the normalized text — search\n // the normalized form (`= >`) or this whole branch is dead and\n // braceless arrows lose their body (cursor Bugbot: 'Arrow bodies lost\n // in canonical text'). Embeddings, cache hashes, and MinHash inputs\n // then omitted arrow-function logic.\n const braceIdx = normalized.indexOf(\"{\");\n if (braceIdx >= 0) {\n const signature = normalized.slice(0, braceIdx + 1).trim();\n const body = normalized.slice(braceIdx + 1).trim();\n return { signature, body };\n }\n const arrowIdx = normalized.indexOf(\"=>\");\n const arrowNormIdx = normalized.indexOf(\"= >\");\n if (arrowIdx >= 0) {\n const signature = normalized.slice(0, arrowIdx + 2).trim();\n const body = normalized.slice(arrowIdx + 2).trim();\n return { signature, body };\n }\n if (arrowNormIdx >= 0) {\n // `= >` spans 3 chars (`= `, ` `, `>`); cut after the `>`.\n const bodyStart = arrowNormIdx + 3;\n const signature = normalized.slice(0, bodyStart).trim();\n const body = normalized.slice(bodyStart).trim();\n return { signature, body };\n }\n // No body marker — treat the whole text as signature (e.g. `type Foo = string`).\n return { signature: normalized, body: \"\" };\n}\n\n/**\n * Extract a coarse signature string from raw symbol text. The text is\n * fully whitespace-normalized first, then split at the first body-open\n * marker. The returned signature is stable across all formatting variants\n * of the same function.\n */\nexport function extractSignatureLine(\n rawText: string,\n _kind: SymbolIR[\"kind\"],\n): string {\n const normalized = collapseWhitespace(rawText);\n return splitSignatureBody(normalized).signature;\n}\n\n/**\n * Extract the body (everything after the signature/header marker) from\n * raw symbol text, truncated to the first `maxBodyLines` tokens. Tokens\n * are whitespace-delimited words/operators in the normalized body text —\n * this is a STABLE budget (formatting-independent) unlike line-based\n * truncation. `maxBodyLines` is the config field name; it functions as a\n * token budget here (each \"line\" ≈ one significant token).\n *\n * `maxBodyLines <= 0` means unlimited (return the full normalized body).\n */\nexport function extractBodyText(rawText: string, maxBodyLines: number): string {\n const normalized = collapseWhitespace(rawText);\n const body = splitSignatureBody(normalized).body;\n if (maxBodyLines <= 0 || body.length === 0) return body;\n const tokens = body.split(/\\s+/);\n return tokens.slice(0, maxBodyLines).join(\" \");\n}\n\n/**\n * Build the canonical embedding text for a symbol.\n *\n * The form (rule 23/38 — ONE form, every consumer):\n * KIND:<kind>\\nQNAME:<qualifiedName>\\nSIG:<signature>\\n[DOC:<doc>]\\nBODY:<body>\n *\n * `kind` and `qualifiedName` are included as stable prefix lines so two\n * functions with identical bodies but different names (the \"renamed\n * variable\" clone fixture) embed close but not identically — the qualified\n * name differentiates them at the embedding level while the body dominates\n * the similarity. The cache hash, by contrast, is over the FULL canonical\n * text including the name, so a rename invalidates the cache (rule 37).\n *\n * Normalization:\n * - ALL whitespace collapsed (indentation/brace-style/newlines absorbed)\n * - body truncated to maxBodyLines tokens (stable budget)\n */\nexport function buildCanonicalText(input: CanonicalTextInput): string {\n const { symbol, rawText, docComment, maxBodyLines } = input;\n const budget = maxBodyLines ?? DEFAULT_CANONICAL_BODY_LINES;\n const signature = extractSignatureLine(rawText, symbol.kind);\n const body = extractBodyText(rawText, budget);\n const doc = docComment ? collapseWhitespace(docComment) : \"\";\n const parts = [\n `KIND:${symbol.kind}`,\n `QNAME:${symbol.qualifiedName}`,\n `SIG:${signature}`,\n ];\n if (doc.length > 0) parts.push(`DOC:${doc}`);\n parts.push(`BODY:${body}`);\n return parts.join(\"\\n\");\n}\n\n/**\n * The cache key for a canonical text. This is sha256 over the EXACT\n * canonical text string (rule 23 — the embedded string equals the hashed\n * string). When the canonical text changes (e.g. a rename edits the\n * qualified name, or the body changes), the hash changes, and:\n * 1. the cached vector is invalidated (re-embedded), and\n * 2. any SIMILAR_TO edge derived from it is recomputed.\n *\n * This is the single chokepoint for cache invalidation (rule 37). Every\n * layer that persists a vector persists THIS hash alongside it; every\n * re-index compares THIS hash to decide whether to re-embed.\n */\nexport function canonicalTextHash(canonicalText: string): string {\n return createHash(\"sha256\").update(canonicalText, \"utf8\").digest(\"hex\");\n}\n\n/**\n * Convenience: build canonical text AND its hash in one call. The hash is\n * over the returned text — callers that store both MUST store the exact\n * `text` alongside the `hash` (never re-derive the text from disk and hash\n * separately, or a formatter run between the two would silently\n * re-embed — rule 37).\n */\nexport function buildCanonicalTextAndHash(\n input: CanonicalTextInput,\n): { readonly text: string; readonly hash: string } {\n const text = buildCanonicalText(input);\n return { text, hash: canonicalTextHash(text) };\n}\n","/**\n * MinHash / LSH candidate generation for SIMILAR_TO near-clone detection\n * (issue #1556).\n *\n * Design (per the issue): token-shingle MinHash/LSH over normalized symbol\n * bodies (pure TS, deterministic given fixed seeds). This produces a\n * candidate-pair set cheaply; the cosine-confirmation layer (similarity.ts)\n * then filters candidates by embedding cosine ≥ threshold.\n *\n * Rule 38 — determinism. The seeds are NAMED CONSTANTS (not module-private\n * state, not `Date.now()`/`Math.random()`). Two runs over the same fixture\n * repo produce an identical candidate set. The determinism test asserts\n * this byte-for-byte.\n *\n * Rule 11 — no module-level mutable state. The hash tables live on the\n * MinHasher instance returned by `createMinHasher()`, never at module\n * scope. Two indexers in one process are fully isolated.\n */\nimport { createHash } from \"node:crypto\";\n\nimport { LSH_NUM_BANDS, LSH_ROWS_PER_BAND, MINHASH_NUM_PERMUTATIONS, MINHASH_SHINGLE_WIDTH } from \"./config.js\";\n\n/**\n * Fixed, named 64-bit seeds for the MinHash permutations. These are the\n * ONLY source of randomness in the candidate pipeline. Changing them\n * changes every candidate set, so they are frozen constants — the\n * determinism test pins their exact values.\n *\n * Generated as `sha256(\"remnic:minhash:seed:N\").slice(0,16)` interpreted\n * as a big-endian u64. Using a hash-of-a-string keeps them reproducible\n * (no magic-looking literals) while still being arbitrary fixed values.\n */\nfunction seedFor(index: number): bigint {\n const hex = createHash(\"sha256\")\n .update(`remnic:minhash:seed:${index}`, \"utf8\")\n .digest(\"hex\")\n .slice(0, 16);\n return BigInt(`0x${hex}`);\n}\n\nexport const MINHASH_SEEDS: readonly bigint[] = Object.freeze(\n Array.from({ length: MINHASH_NUM_PERMUTATIONS }, (_, i) => seedFor(i)),\n);\n\n/**\n * A 64-bit MurmurHash3 x64 finalizer — fast, well-distributed, and\n * deterministic. Used as the base hash; MinHash permutations are\n * (a*x + b) mod Mersenne over this base.\n */\nfunction hash64(data: string): bigint {\n const buf = createHash(\"sha256\").update(data, \"utf8\").digest();\n // Read the first 8 bytes as big-endian u64 — sha256 is already a strong\n // mixing function, so we skip the MurmurHash3 body and use sha256 as\n // the universal hash. Determinism is the only hard requirement; speed\n // is secondary (the candidate set is small after LSH banding).\n let h = 0n;\n for (let i = 0; i < 8; i++) {\n h = (h << 8n) | BigInt(buf[i]!);\n }\n return h;\n}\n\n// 2^61 - 1, a Mersenne prime — the MinHash modulus.\nconst MINHASH_MODULUS = (1n << 61n) - 1n;\n\n/**\n * Normalize symbol body text into a token stream for shingling.\n *\n * Normalization:\n * - lowercase (case-insensitive clone detection — `MyFunc` vs `myfunc`)\n * - split on non-alphanumeric (identifiers, numbers, operators become tokens)\n * - drop empty tokens\n *\n * This is deliberately coarse: the goal is Jaccard over token sets, not\n * semantic parsing. A renamed variable changes exactly one token per\n * occurrence, so Jaccard stays high for genuine clones.\n */\nexport function tokenizeForShingling(body: string): string[] {\n return body\n .toLowerCase()\n .split(/[^a-z0-9_]+/)\n .filter((t) => t.length > 0);\n}\n\n/**\n * Build the set of shingles (n-grams of width MINHASH_SHINGLE_WIDTH) from\n * a token stream. Returns a Set so duplicate shingles collapse (Jaccard\n * is over the SET of shingles, not a multiset).\n */\nexport function shingleSet(tokens: readonly string[]): Set<string> {\n if (tokens.length < MINHASH_SHINGLE_WIDTH) {\n // Too few tokens for a full shingle — use the whole token sequence as\n // a single shingle so short symbols still participate.\n return new Set(tokens.length > 0 ? [tokens.join(\" \")] : []);\n }\n const out = new Set<string>();\n for (let i = 0; i <= tokens.length - MINHASH_SHINGLE_WIDTH; i++) {\n out.add(tokens.slice(i, i + MINHASH_SHINGLE_WIDTH).join(\" \"));\n }\n return out;\n}\n\n/**\n * Compute the MinHash signature (array of permutation minima) for a set\n * of shingles. Signature length = MINHASH_NUM_PERMUTATIONS. The estimated\n * Jaccard similarity between two signatures is the fraction of matching\n * positions.\n */\nexport function minHashSignature(shingles: Set<string>): bigint[] {\n const sig: bigint[] = [];\n if (shingles.size === 0) {\n // Empty body → max-value signature so it never collides with anything.\n for (let i = 0; i < MINHASH_NUM_PERMUTATIONS; i++) sig.push(MINHASH_MODULUS);\n return sig;\n }\n const shingleArr = Array.from(shingles);\n for (let p = 0; p < MINHASH_NUM_PERMUTATIONS; p++) {\n const a = MINHASH_SEEDS[p]!;\n const b = MINHASH_SEEDS[(p * 2 + 1) % MINHASH_SEEDS.length]!;\n let min = MINHASH_MODULUS;\n for (const s of shingleArr) {\n const h = (a * hash64(s) + b) % MINHASH_MODULUS;\n if (h < min) min = h;\n }\n sig.push(min);\n }\n return sig;\n}\n\n/**\n * LSH band key — the concatenation of one band's rows from the signature.\n * Two signatures that share at least one band key are a candidate pair.\n */\nexport function lshBandKeys(signature: readonly bigint[]): string[] {\n const keys: string[] = [];\n for (let b = 0; b < LSH_NUM_BANDS; b++) {\n const start = b * LSH_ROWS_PER_BAND;\n const end = start + LSH_ROWS_PER_BAND;\n keys.push(signature.slice(start, end).map((v) => v.toString(16)).join(\"|\"));\n }\n return keys;\n}\n\n/**\n * An indexed symbol body ready for LSH bucketing.\n */\nexport interface LshIndexEntry {\n readonly nodeId: string;\n readonly qualifiedName: string;\n readonly body: string;\n}\n\n/**\n * A MinHash/LSH indexer instance. Owns the band→node-id bucket map on the\n * instance (rule 11). `findCandidates` returns the deduplicated candidate\n * pair set with estimated Jaccard for each pair.\n */\nexport class MinHasher {\n /** band key → set of node ids in that band. */\n private readonly buckets: Map<string, Set<string>> = new Map();\n /** node id → signature (for Jaccard estimation on candidate pairs). */\n private readonly signatures: Map<string, bigint[]> = new Map();\n /** node id → qualified name (for readable candidate output). */\n private readonly qnames: Map<string, string> = new Map();\n\n /**\n * Add a symbol body to the LSH index. Idempotent — re-adding the same\n * (nodeId, body) is a no-op.\n */\n add(entry: LshIndexEntry): void {\n if (this.signatures.has(entry.nodeId)) return;\n const tokens = tokenizeForShingling(entry.body);\n const shingles = shingleSet(tokens);\n // Skip empty bodies — they would all get the same all-max signature\n // and flood the candidate set with false pairs\n // (chatgpt-codex-connector: 'Keep empty bodies out of shared MinHash\n // buckets').\n if (shingles.size === 0) return;\n const sig = minHashSignature(shingles);\n this.signatures.set(entry.nodeId, sig);\n this.qnames.set(entry.nodeId, entry.qualifiedName);\n for (const key of lshBandKeys(sig)) {\n let bucket = this.buckets.get(key);\n if (!bucket) {\n bucket = new Set();\n this.buckets.set(key, bucket);\n }\n bucket.add(entry.nodeId);\n }\n }\n\n /**\n * Find all candidate pairs (pairs sharing at least one LSH band) with\n * their estimated Jaccard similarity. Returns a stable-sorted array\n * (by aNodeId then bNodeId) so the determinism test can compare runs\n * byte-for-byte.\n */\n findCandidates(): { readonly aNodeId: string; readonly bNodeId: string; readonly aQualifiedName: string; readonly bQualifiedName: string; readonly jaccard: number }[] {\n const pairSet = new Set<string>();\n const pairs: { aNodeId: string; bNodeId: string; aQualifiedName: string; bQualifiedName: string; jaccard: number }[] = [];\n for (const bucket of this.buckets.values()) {\n if (bucket.size < 2) continue;\n const ids = Array.from(bucket).sort();\n for (let i = 0; i < ids.length; i++) {\n for (let j = i + 1; j < ids.length; j++) {\n const a = ids[i]!;\n const b = ids[j]!;\n const key = `${a}\\0${b}`;\n if (pairSet.has(key)) continue;\n pairSet.add(key);\n const sigA = this.signatures.get(a)!;\n const sigB = this.signatures.get(b)!;\n let matches = 0;\n for (let k = 0; k < sigA.length; k++) {\n if (sigA[k] === sigB[k]) matches++;\n }\n pairs.push({\n aNodeId: a,\n bNodeId: b,\n aQualifiedName: this.qnames.get(a) ?? a,\n bQualifiedName: this.qnames.get(b) ?? b,\n jaccard: matches / sigA.length,\n });\n }\n }\n }\n pairs.sort((x, y) => {\n if (x.aNodeId !== y.aNodeId) return x.aNodeId < y.aNodeId ? -1 : 1;\n return x.bNodeId < y.bNodeId ? -1 : x.bNodeId > y.bNodeId ? 1 : 0;\n });\n return pairs;\n }\n}\n\n/**\n * Construct a fresh MinHasher (rule 11 — state on the instance).\n */\nexport function createMinHasher(): MinHasher {\n return new MinHasher();\n}\n\n/**\n * Cosine similarity between two equal-length float vectors. Returns 0 for\n * zero-norm vectors (no division-by-zero). This is the brute-force\n * retrieval primitive shared by SIMILAR_TO confirmation and semantic_query.\n */\nexport function cosineSimilarity(a: Float32Array | number[], b: Float32Array | number[]): number {\n const len = Math.min(a.length, b.length);\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < len; i++) {\n const av = a[i]!;\n const bv = b[i]!;\n dot += av * bv;\n normA += av * av;\n normB += bv * bv;\n }\n if (normA === 0 || normB === 0) return 0;\n return dot / (Math.sqrt(normA) * Math.sqrt(normB));\n}\n","/**\n * Symbol-vector indexing (issue #1556 PR1 component).\n *\n * Reads source text from disk, builds canonical text, checks the cache\n * (skip re-embed when content_hash matches), embeds via the host provider,\n * and persists vectors to the `symbol_vectors` table.\n *\n * Rule 30/48: when `config.enabled` is false, this module returns a tagged\n * `{ ok: false, code: \"semantic_disabled\" }` WITHOUT reading source,\n * calling the provider, or writing a vector (gate-off parity test covers\n * this). The gate is checked at this single chokepoint — callers never\n * need to re-check.\n *\n * Rule 44: vectors are written only for symbols that persisted\n * successfully. The indexer reads nodes from the store (which only\n * contains persisted nodes), so a `parse_failed` file contributes zero\n * nodes and thus zero vectors.\n *\n * Rule 37: cache invalidation. When a symbol's canonical text changes,\n * its content_hash changes, the cached row no longer matches, and the\n * vector is re-embedded. The old vector is overwritten (ON CONFLICT\n * UPDATE). Any SIMILAR_TO edge derived from the old vector is recomputed\n * by the similarity pipeline on its next run.\n */\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { HostEmbeddingProvider } from \"@remnic/core/host-embedding-provider\";\nimport { normalizeHostEmbeddingVector } from \"@remnic/core/host-embedding-provider\";\n\nimport type { GraphStore } from \"../graph-store.js\";\nimport { buildCanonicalTextAndHash } from \"./canonical-text.js\";\nimport type { SemanticConfig } from \"./config.js\";\nimport type { IndexVectorsResult, SemanticFailure } from \"./types.js\";\n\n/**\n * Input to {@link indexSymbolVectors}. The store provides node metadata +\n * the vectors table; the provider embeds; repoRoot resolves file paths.\n */\nexport interface IndexVectorsInput {\n readonly store: GraphStore;\n readonly provider: HostEmbeddingProvider | undefined;\n readonly repoRoot: string;\n readonly config: SemanticConfig;\n /**\n * Optional abort signal forwarded to the provider. The indexer does not\n * impose its own timeout (the provider's embed() contract handles that).\n */\n readonly signal?: AbortSignal;\n}\n\n/**\n * The model id used for cache keying. Derives from the provider's `model`\n * (falling back to `id`) so a provider/model swap produces a distinct\n * cache namespace and does not overwrite the prior vectors.\n */\nexport function modelIdFor(provider: HostEmbeddingProvider): string {\n return provider.model ?? provider.id;\n}\n\n/**\n * Index symbol vectors for every persisted node in the store.\n *\n * Flow:\n * 1. Gate: if !config.enabled → tagged semantic_disabled (no work).\n * 2. Provider check: if no provider → tagged provider_unavailable.\n * 3. Read all nodes from the store (persisted only — rule 44).\n * 4. For each node (within maxSymbolsPerRun budget):\n * a. Read source text from disk.\n * b. Build canonical text + hash.\n * c. Cache check: skip if cached row's content_hash matches.\n * d. Embed via provider.\n * e. Normalize (reject malformed → counted as skipped).\n * f. Persist vector.\n * 5. Return counts.\n *\n * Budget order (rule 27): recently-changed symbols first. The store's\n * readNodesForSemantic returns nodes ordered by qualified_name; the\n * indexer applies the caller-supplied priority before slicing. When no\n * priority is given, all nodes are eligible (budget 0 = unlimited).\n */\nexport async function indexSymbolVectors(\n input: IndexVectorsInput,\n): Promise<IndexVectorsResult | SemanticFailure> {\n const { store, provider, repoRoot, config, signal } = input;\n\n // Closed store is a distinct degradation (rule 34) — do not treat it as\n // an empty graph that returns { ok: true } with zero counts (cursor\n // Bugbot: 'Closed store reports success').\n if (store.isClosed) {\n return { ok: false, code: \"store_closed\" };\n }\n\n // Rule 30/48 + rule 39: single gate chokepoint.\n if (!config.enabled) {\n return { ok: false, code: \"semantic_disabled\" };\n }\n if (!provider) {\n return { ok: false, code: \"provider_unavailable\" };\n }\n\n const modelId = modelIdFor(provider);\n // Wrap the store read so a transient SQLite error (SQLITE_BUSY / CORRUPT)\n // maps to a tagged db_error instead of escaping indexSymbolVectors (#1680).\n let nodes: readonly ReturnType<typeof store.readNodesForSemantic>[number][];\n try {\n nodes = store.readNodesForSemantic();\n } catch {\n return { ok: false, code: \"db_error\" };\n }\n // Budget applies to EMBED WORK, not the candidate list. Slicing the\n // full node list (ordered by qualified_name) BEFORE the cache check\n // meant a bounded run kept re-visiting the cached alphabetical prefix\n // and never reached uncached or changed symbols later in the list, so\n // a bounded semantic index could remain permanently incomplete. Now\n // every node gets a cache check and only a successful embed consumes\n // budget, so progress accumulates across runs until every symbol is\n // embedded (cursor Bugbot: 'Embedding budget uses alphabetical order';\n // chatgpt-codex-connector P2: 'Apply vector budget after skipping\n // cached rows'). maxSymbolsPerRun=0 means unlimited.\n const limit = config.maxSymbolsPerRun;\n\n let embedded = 0;\n let cached = 0;\n let skipped = 0;\n // Budget bounds provider CALLS (cost), not successful writes. A degraded\n // provider that throws / returns null / returns a malformed vector for\n // many uncached symbols must not bypass the per-run cost cap by never\n // incrementing `embedded` (chatgpt-codex-connector: 'Count failed embed\n // attempts against the vector budget'). Cached rows do not consume\n // budget (no provider call); source-read failures do not either.\n let embedAttempts = 0;\n\n for (const node of nodes) {\n if (signal?.aborted) break;\n if (limit > 0 && embedAttempts >= limit) break;\n // Read source text from disk.\n const absolutePath = path.resolve(repoRoot, node.filePath);\n let bytes: Buffer;\n try {\n bytes = await readFile(absolutePath);\n } catch {\n skipped += 1;\n continue;\n }\n const start = Math.max(0, node.startByte);\n const end = Math.min(bytes.length, node.endByte);\n if (start > end) {\n skipped += 1;\n continue;\n }\n const rawText = bytes.subarray(start, end).toString(\"utf8\");\n\n // Canonical text + hash (rule 23/37). The embedded string MUST equal\n // the hashed string (rule 23 — one form everywhere).\n const { text: canonicalText, hash } = buildCanonicalTextAndHash({\n symbol: {\n kind: node.kind as never,\n name: node.qualifiedName.split(/[.#:]/).pop() ?? node.qualifiedName,\n qualifiedName: node.qualifiedName,\n span: { startByte: node.startByte, endByte: node.endByte },\n },\n rawText,\n maxBodyLines: config.canonicalBodyLines,\n });\n\n // Cache check: skip re-embed when content_hash matches AND the stored\n // dims still equal the active provider's declared dimensions. A model\n // that keeps the same model_id but changes vector size would otherwise\n // leave stale-dimensionality rows cached (cursor Bugbot: 'Cache ignores\n // embedding dimension changes'). When the provider does not declare\n // dimensions (optional), the dims gate is skipped so caching still\n // works instead of re-embedding every run (cursor Bugbot: 'Cache misses\n // without provider dimensions').\n const cachedRow = store.readSymbolVector(node.nodeId, modelId);\n if (\n cachedRow &&\n cachedRow.contentHash === hash &&\n (provider.dimensions === undefined || cachedRow.dims === provider.dimensions)\n ) {\n cached += 1;\n continue;\n }\n\n // Count the embed attempt (cost) BEFORE the call, regardless of\n // outcome, so a failed/null/malformed result still consumes budget.\n embedAttempts += 1;\n // Embed the CANONICAL text (not the raw span) so the vector\n // corresponds to the content_hash that gates cache hits (rule 23).\n // This also respects canonicalBodyLines as a cost/privacy bound.\n let raw: ArrayLike<number> | null;\n try {\n raw = await provider.embed(canonicalText, {\n signal,\n inputType: \"document\",\n });\n } catch {\n // Stale vector cleanup (rule 37): if a prior vector exists with a\n // different content_hash and we cannot re-embed, delete the stale\n // row so semantic_query/cosine confirmation do not serve it.\n if (cachedRow && cachedRow.contentHash !== hash) {\n await store.deleteSymbolVectors([node.nodeId]);\n }\n skipped += 1;\n continue;\n }\n const vec = normalizeHostEmbeddingVector(raw);\n if (!vec || vec.length === 0) {\n if (cachedRow && cachedRow.contentHash !== hash) {\n await store.deleteSymbolVectors([node.nodeId]);\n }\n skipped += 1;\n continue;\n }\n const float32 = new Float32Array(vec);\n // Only count a persisted embed — writeSymbolVector returns false (and\n // is a no-op) when the store is closing/closed, so progress reporting\n // must not claim an embedding that was dropped (cursor Bugbot: 'Embedded\n // count after dropped writes').\n const persisted = await store.writeSymbolVector({\n nodeId: node.nodeId,\n modelId,\n contentHash: hash,\n dims: float32.length,\n vector: float32,\n });\n if (persisted) {\n embedded += 1;\n } else {\n skipped += 1;\n }\n }\n\n return { ok: true, embedded, cached, skipped };\n}\n","/**\n * SIMILAR_TO near-clone pipeline (issue #1556 PR2 component).\n *\n * Cheap-first, deterministic:\n * 1. MinHash/LSH candidate generation over normalized symbol bodies.\n * 2. Cosine confirmation by embedding ≥ threshold when vectors exist.\n *\n * Edges carry `provenance: \"semantic\"` + the similarity score as\n * confidence. No provider → MinHash-only edges carry a distinct lower\n * confidence band (documented), and the pipeline still runs (MinHash is\n * local and deterministic).\n *\n * Rule 35 spirit: the threshold boundary (≥ vs >) is decided ONCE here\n * (≥ threshold confirms) and documented. The boundary test pins it.\n *\n * Rule 38: the candidate set is a pure function of (seeds, inputs). Two\n * runs over the same fixture produce an identical edge set.\n */\nimport { readFileSync as fsReadFileSync } from \"node:fs\";\nimport path from \"node:path\";\nconst fs = { readFileSync: fsReadFileSync };\nimport type { GraphStore } from \"../graph-store.js\";\nimport type { EdgeIR } from \"../graph-store.js\";\nimport { extractBodyText } from \"./canonical-text.js\";\nimport {\n MINHASH_ONLY_CONFIDENCE,\n SEMANTIC_PROVENANCE,\n SIMILAR_TO_EDGE_TYPE,\n} from \"./config.js\";\nimport type { SemanticConfig } from \"./config.js\";\nimport { cosineSimilarity, createMinHasher, tokenizeForShingling, shingleSet } from \"./minhash.js\";\nimport type { SimilarEdge, SimilarToResult } from \"./types.js\";\nimport type { SemanticFailure } from \"./types.js\";\nimport { modelIdFor } from \"./vectors.js\";\nimport type { HostEmbeddingProvider } from \"@remnic/core/host-embedding-provider\";\n\n/**\n * Input to {@link computeSimilarTo}.\n */\nexport interface SimilarToInput {\n readonly store: GraphStore;\n readonly provider: HostEmbeddingProvider | undefined;\n readonly config: SemanticConfig;\n /**\n * Repo root for reading source text from disk when bodies are not\n * supplied. Required when bodies is absent.\n */\n readonly repoRoot?: string;\n /**\n * Symbol bodies keyed by nodeId. The caller (the indexer or a\n * standalone pass) reads source text and builds canonical bodies. When\n * absent, the pipeline reads nodes from the store + disk itself.\n */\n readonly bodies?: ReadonlyMap<string, { readonly qualifiedName: string; readonly body: string }>;\n /**\n * Vectors keyed by nodeId (the persisted embedding). When absent, the\n * pipeline reads them from the store via readAllSymbolVectors.\n */\n readonly vectors?: ReadonlyMap<string, Float32Array>;\n}\n\n/**\n * The comparison operator for cosine confirmation. Decided ONCE (rule 35\n * spirit): `>= threshold`. A pair at EXACTLY the threshold confirms. The\n * boundary test asserts this.\n */\nexport const CONFIRM_OPERATOR = \">=\" as const;\n\n/**\n * Jaccard gate for MinHash-only SIMILAR_TO edges (no embedding provider).\n * Well below the cosine similarToThreshold (0.92) because token-Jaccard\n * for near-clone code is typically 0.3-0.8. 0.5 catches genuine\n * copy-paste with minor renames while rejecting structurally-similar\n * but logically-unrelated pairs.\n */\nexport const MINHASH_JACCARD_GATE = 0.5;\n\n/**\n * Compute SIMILAR_TO edges.\n *\n * Returns the edges (for the caller to persist via store.upsertEdges) plus\n * counts. The caller persists; this function is pure over its inputs\n * (rule 38 — deterministic given seeds + bodies + vectors).\n *\n * When `config.enabled` is false → tagged semantic_disabled (no work, no\n * candidate generation — gate-off parity).\n */\nexport function computeSimilarTo(input: SimilarToInput): SimilarToResult | SemanticFailure {\n const { store, provider, config } = input;\n if (!config.enabled) {\n return { ok: false, code: \"semantic_disabled\" };\n }\n\n // Without pre-built bodies AND without a repoRoot, the only available\n // text is the qualified name, which MinHashes name similarity rather\n // than body similarity — real copy-paste clones with different names\n // are silently missed. Refuse to guess: require one of the two so the\n // pipeline always MinHashes actual symbol bodies (chatgpt-codex-\n // connector P2: 'Require source bodies before MinHashing').\n if (!input.bodies && !input.repoRoot) {\n return {\n ok: false,\n code: \"repo_root_unset\",\n message: \"computeSimilarTo needs either 'bodies' or 'repoRoot' to read source text\",\n };\n }\n // Closed store is a distinct degradation (rule 34) — readNodesForSemantic\n // would return [] and we would report { ok: true, edges: [] } instead of\n // the documented store_closed code used by the other entry points (cursor\n // Bugbot: 'SimilarTo ignores closed store').\n if (store.isClosed) {\n return { ok: false, code: \"store_closed\" };\n }\n const bodies = input.bodies ?? readBodiesFromStore(store, input.repoRoot);\n const modelId = provider ? modelIdFor(provider) : undefined;\n const vectors = input.vectors ?? (modelId ? readVectorsMap(store, modelId) : new Map<string, Float32Array>());\n\n // Pass 1: MinHash/LSH candidates.\n const hasher = createMinHasher();\n for (const [nodeId, entry] of bodies) {\n hasher.add({ nodeId, qualifiedName: entry.qualifiedName, body: entry.body });\n }\n const candidates = hasher.findCandidates();\n\n // Pass 2: cosine confirmation.\n const edges: SimilarEdge[] = [];\n let confirmed = 0;\n let minhashOnly = 0;\n for (const c of candidates) {\n const va = vectors.get(c.aNodeId);\n const vb = vectors.get(c.bNodeId);\n if (va && vb && va.length === vb.length) {\n // Require matching dimensionality — cosineSimilarity compares over the\n // shorter length, so mismatched-dims rows would get a misleading\n // partial-overlap score (cursor Bugbot: 'SimilarTo skips embedding\n // length check'). Pairs that fail this fall through to the no-provider\n // MinHash-only branch or are skipped.\n const cos = cosineSimilarity(va, vb);\n // rule 35: >= threshold confirms (decided once, here).\n if (cos >= config.similarToThreshold) {\n edges.push({\n srcNodeId: c.aNodeId,\n dstNodeId: c.bNodeId,\n srcQualifiedName: c.aQualifiedName,\n dstQualifiedName: c.bQualifiedName,\n confidence: cos,\n confirmed: true,\n });\n confirmed += 1;\n }\n } else if (!provider) {\n // MinHash-only is the documented fallback for the NO-PROVIDER\n // (local, deterministic) mode. When a provider IS configured we do\n // NOT emit MinHash-only edges for pairs missing a vector — that\n // would bypass the cosine confirmation path during partial / not-\n // yet-indexed state. Such pairs are simply skipped; they will be\n // cosine-confirmed once indexing completes (cursor Bugbot: 'MinHash\n // edges with provider set'). Use a Jaccard gate well below the\n // cosine threshold — MinHash Jaccard for near-clones is typically\n // 0.3-0.8; the MINHASH_JACCARD_GATE is the documented floor.\n if (c.jaccard >= MINHASH_JACCARD_GATE) {\n edges.push({\n srcNodeId: c.aNodeId,\n dstNodeId: c.bNodeId,\n srcQualifiedName: c.aQualifiedName,\n dstQualifiedName: c.bQualifiedName,\n confidence: MINHASH_ONLY_CONFIDENCE,\n confirmed: false,\n });\n minhashOnly += 1;\n }\n }\n // else: provider configured but a vector is missing → skip (await\n // indexing + cosine confirmation; do not bypass with MinHash-only).\n }\n\n // Stable sort: by confidence desc, then src qname, then dst qname.\n edges.sort((a, b) => {\n if (b.confidence !== a.confidence) return b.confidence - a.confidence;\n if (a.srcQualifiedName !== b.srcQualifiedName) return a.srcQualifiedName < b.srcQualifiedName ? -1 : 1;\n return a.dstQualifiedName < b.dstQualifiedName ? -1 : a.dstQualifiedName > b.dstQualifiedName ? 1 : 0;\n });\n\n return { ok: true, edges, candidates: candidates.length, confirmed, minhashOnly };\n}\n\n/**\n * Convert SimilarEdge[] to the store's EdgeIR[] for persistence via\n * upsertEdges. Provenance is always \"semantic\"; type is SIMILAR_TO.\n *\n * Carries the content-derived node ids onto the EdgeIR (issue #1677) so\n * the store resolves each endpoint by `nodes.id` (unique) instead of by\n * qualified name — two symbols that share a qualified name across files\n * get distinct, non-colliding SIMILAR_TO edges instead of being dropped\n * as ambiguous.\n */\nexport function similarEdgesToEdgeIR(edges: readonly SimilarEdge[]): EdgeIR[] {\n return edges.map((e) => ({\n srcQualifiedName: e.srcQualifiedName,\n dstQualifiedName: e.dstQualifiedName,\n type: SIMILAR_TO_EDGE_TYPE,\n confidence: e.confidence,\n provenance: SEMANTIC_PROVENANCE,\n srcNodeId: e.srcNodeId,\n dstNodeId: e.dstNodeId,\n }));\n}\n\n/**\n * Read canonical bodies for every persisted node from the store + disk.\n * Used when the caller does not supply pre-built bodies. Returns a map\n * keyed by nodeId with the canonical body text (the MinHash input).\n *\n * Note: this reads from disk synchronously per node, so callers that\n * already have bodies in memory should pass them via `input.bodies`.\n */\nfunction readBodiesFromStore(store: GraphStore, repoRoot?: string): Map<string, { readonly qualifiedName: string; readonly body: string }> {\n const out = new Map<string, { readonly qualifiedName: string; readonly body: string }>();\n for (const node of store.readNodesForSemantic()) {\n let rawText = \"\";\n if (repoRoot) {\n try {\n const abs = path.resolve(repoRoot, node.filePath);\n const bytes = fs.readFileSync(abs);\n const start = Math.max(0, node.startByte);\n const end = Math.min(bytes.length, node.endByte);\n if (start <= end) rawText = bytes.subarray(start, end).toString(\"utf8\");\n } catch {\n // File not readable — body stays empty, symbol is skipped by MinHasher.\n }\n }\n // MinHash the extracted BODY only, not the full canonical text. The\n // canonical form includes KIND/QNAME/SIG metadata; tokenizing it would\n // let empty-body stubs/declarations emit name-driven candidates among\n // unrelated symbols. extractBodyText returns \"\" for a bodyless symbol,\n // and the hasher skips empty bodies entirely (chatgpt-codex-connector:\n // 'MinHash only the extracted body text').\n const body = extractBodyText(rawText, 0);\n out.set(node.nodeId, { qualifiedName: node.qualifiedName, body });\n }\n return out;\n}\n\n/**\n * Read the vectors table into a nodeId → Float32Array map for cosine\n * confirmation. Uses the model id derived from the provider.\n */\nfunction readVectorsMap(store: GraphStore, modelId: string): Map<string, Float32Array> {\n const out = new Map<string, Float32Array>();\n for (const row of store.readAllSymbolVectors(modelId)) {\n out.set(row.nodeId, row.vector);\n }\n return out;\n}\n\n/**\n * Estimate Jaccard similarity between two bodies directly (no LSH). Used\n * by the hard-negative test to assert two bodies are NOT similar.\n */\nexport function estimateJaccard(bodyA: string, bodyB: string): number {\n const sa = shingleSet(tokenizeForShingling(bodyA));\n const sb = shingleSet(tokenizeForShingling(bodyB));\n if (sa.size === 0 && sb.size === 0) return 1;\n let inter = 0;\n for (const s of sa) if (sb.has(s)) inter += 1;\n const union = sa.size + sb.size - inter;\n return union === 0 ? 0 : inter / union;\n}\n","/**\n * semantic_query — natural-language retrieval over the symbol graph\n * (issue #1556 PR3 component).\n *\n * Embed the query via the host provider / EmbeddingFallback\n * (`mode: \"lookup\"`), top-k symbols by cosine, hydrate each hit with\n * graph context (defining file, direct callers/callees).\n *\n * Rule 34 — degradation matrix. Provider missing / timeout / malformed\n * vector (`normalizeHostEmbeddingVector` returns null) → three distinct\n * `{ok:false}` codes, never an empty result masquerading as \"no matches\".\n * When the provider is available but returns zero hits, `ok:true` with\n * empty hits is the honest answer.\n */\n\n\nimport type { HostEmbeddingProvider } from \"@remnic/core/host-embedding-provider\";\nimport {\n EmbeddingProviderUnavailableError,\n EmbeddingTimeoutError,\n} from \"@remnic/core/embedding-fallback\";\nimport { normalizeHostEmbeddingVector } from \"@remnic/core/host-embedding-provider\";\n\nimport type { GraphStore } from \"../graph-store.js\";\nimport type { SemanticConfig } from \"./config.js\";\nimport { cosineSimilarity } from \"./minhash.js\";\nimport { modelIdFor } from \"./vectors.js\";\nimport type { SemanticQueryHit, SemanticQueryOutcome } from \"./types.js\";\n\n/**\n * Input to {@link semanticQuery}.\n */\nexport interface SemanticQueryInput {\n readonly store: GraphStore;\n readonly provider: HostEmbeddingProvider | undefined;\n readonly repoRoot: string;\n readonly config: SemanticConfig;\n readonly query: string;\n readonly limit?: number;\n readonly signal?: AbortSignal;\n}\n\n/**\n * Default top-k for semantic_query.\n */\nexport const DEFAULT_SEMANTIC_QUERY_LIMIT = 10;\n\n/**\n * Run a semantic query: embed → top-k → hydrate.\n *\n * Degradation matrix (rule 34):\n * - !config.enabled → { ok:false, code:\"semantic_disabled\" }\n * - no provider → { ok:false, code:\"provider_unavailable\" }\n * - provider throws timeout → { ok:false, code:\"provider_timeout\" }\n * - provider returns null/malformed → { ok:false, code:\"malformed_vector\" }\n * - no vectors in table → { ok:false, code:\"no_vectors\" }\n * - ok but zero hits → { ok:true, hits:[] }\n */\nexport async function semanticQuery(input: SemanticQueryInput): Promise<SemanticQueryOutcome> {\n const { store, provider, repoRoot, config, query, signal } = input;\n // Closed store is a distinct degradation (rule 34) — do not treat it as\n // an empty vectors table that returns 'no_vectors' (cursor Bugbot:\n // 'Closed store reports success').\n if (store.isClosed) {\n return { ok: false, code: \"store_closed\" };\n }\n if (!config.enabled) {\n return { ok: false, code: \"semantic_disabled\" };\n }\n if (query.length === 0) {\n return { ok: false, code: \"invalid_query\", message: \"query must be non-empty\" };\n }\n if (!provider) {\n return { ok: false, code: \"provider_unavailable\" };\n }\n\n // Embed the query (lookup mode — short timeout budget).\n let raw: ArrayLike<number> | null;\n try {\n raw = await provider.embed(query, { signal, inputType: \"query\" });\n } catch (error) {\n if (error instanceof EmbeddingTimeoutError) {\n return { ok: false, code: \"provider_timeout\" };\n }\n if (error instanceof EmbeddingProviderUnavailableError) {\n return { ok: false, code: \"provider_unavailable\" };\n }\n // Unknown provider error → treat as unavailable (the provider is broken).\n // Do NOT include the raw error message (may leak paths/stacks to clients).\n return { ok: false, code: \"provider_unavailable\" };\n }\n const queryVec = normalizeHostEmbeddingVector(raw);\n if (!queryVec || queryVec.length === 0) {\n return { ok: false, code: \"malformed_vector\" };\n }\n const queryF32 = new Float32Array(queryVec);\n\n // Brute-force cosine over all vectors for this model.\n const modelId = modelIdFor(provider);\n // Wrap the store read so a transient SQLite error (SQLITE_BUSY / CORRUPT)\n // maps to a tagged db_error instead of escaping semanticQuery (#1680).\n let rows: readonly ReturnType<typeof store.readAllSymbolVectors>[number][];\n try {\n rows = store.readAllSymbolVectors(modelId);\n } catch {\n return { ok: false, code: \"db_error\" };\n }\n if (rows.length === 0) {\n return { ok: false, code: \"no_vectors\" };\n }\n\n // Validate limit: NaN/Infinity/non-numeric must not produce a NaN/Infinity\n // slice (#1680). NaN → slice(0, NaN) returns no hits; Infinity → every row.\n // Coerce to a finite positive integer, falling back to the default.\n const rawLimit = input.limit;\n const limit = (typeof rawLimit === \"number\" && Number.isFinite(rawLimit) && rawLimit > 0)\n ? Math.max(1, Math.floor(rawLimit))\n : DEFAULT_SEMANTIC_QUERY_LIMIT;\n // Only score vectors whose dimensionality matches the query embedding.\n // cosineSimilarity compares over the SHORTER length, so a row from a\n // different model size would otherwise get a misleading partial-overlap\n // score and rank incorrectly instead of being excluded (cursor Bugbot:\n // 'Mismatched embedding lengths scored').\n const queryDims = queryF32.length;\n const scored = rows\n .filter((r) => r.dims === queryDims)\n .map((r) => ({\n nodeId: r.nodeId,\n qualifiedName: r.qualifiedName,\n filePath: r.filePath,\n kind: r.kind,\n dims: r.dims,\n score: cosineSimilarity(queryF32, r.vector),\n }))\n .filter((r) => Number.isFinite(r.score))\n .sort((a, b) => b.score - a.score)\n .slice(0, limit);\n\n // Hydrate each hit with graph context (callers/callees + snippet).\n const hits: SemanticQueryHit[] = [];\n for (const h of scored) {\n const neighbors = store.readNeighborsByNodeId(h.nodeId);\n let snippet = \"\";\n try {\n // Hydrate by the exact node id from the vector row, not the\n // qualified name — a name duplicated across files would otherwise\n // hit 'ambiguous_name' and leave the snippet empty even though the\n // vector row identified the precise node (chatgpt-codex-connector\n // P2: 'Hydrate snippets by node id as well').\n const snippetResult = await store.snippetFor({ nodeId: h.nodeId, repoRoot });\n if (snippetResult.ok) snippet = snippetResult.text;\n } catch {\n // snippetFor returns tagged failures, not throws — this is defensive.\n }\n hits.push({\n qualifiedName: h.qualifiedName,\n filePath: h.filePath,\n kind: h.kind,\n score: h.score,\n snippet,\n callers: neighbors.callers,\n callees: neighbors.callees,\n });\n }\n\n return { ok: true, hits };\n}\n\n/**\n * Read a short snippet (first ~20 lines) from a file for hydration. The\n * full source span is available via store.snippetFor; here we just want\n * enough context for the agent to orient.\n */"],"mappings":"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","names":["CODING_GRAPH_ENGINE_VERSION","TIER_1_LANGUAGES","TIER_1_LANGUAGES","TIER_1_LANGUAGES","path","readFile","path","path","process","process","path","process","path","path","createHash","createHash","createHash","createHash","path","path","path","path","normalizeHostEmbeddingVector","normalizeHostEmbeddingVector","CODING_GRAPH_ENGINE_VERSION"]}
|