@wrongstack/tools 0.302.2 → 0.303.0

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.
Files changed (40) hide show
  1. package/dist/builtin.js +2799 -609
  2. package/dist/codebase-index/binary-frame.d.ts +43 -0
  3. package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +1 -0
  4. package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +1 -0
  5. package/dist/codebase-index/content-hash.d.ts +66 -0
  6. package/dist/codebase-index/index.js +1569 -148
  7. package/dist/codebase-index/parser-worker-pool.d.ts +63 -0
  8. package/dist/codebase-index/parser-worker-script.d.ts +42 -0
  9. package/dist/codebase-index/project-server-protocol.d.ts +2 -0
  10. package/dist/codebase-index/project-server.js +1455 -99
  11. package/dist/codebase-index/schema.d.ts +7 -0
  12. package/dist/codebase-index/tree-sitter/queries.d.ts +48 -0
  13. package/dist/codebase-index/tree-sitter/util.d.ts +31 -0
  14. package/dist/codebase-index/tree-sitter/visitor.d.ts +47 -0
  15. package/dist/codebase-index/tree-sitter-parser.d.ts +58 -0
  16. package/dist/codebase-index/vector-search.d.ts +62 -0
  17. package/dist/codebase-index/worker-protocol.d.ts +2 -0
  18. package/dist/codebase-index/worker.js +1424 -68
  19. package/dist/codebase-index/writer-bulk-insert.d.ts +5 -0
  20. package/dist/codebase-index/writer-graph-reader.d.ts +39 -0
  21. package/dist/codebase-index/writer-schema.d.ts +9 -2
  22. package/dist/codebase-index/writer.d.ts +36 -0
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +2785 -634
  25. package/dist/kanban-task-inputs.d.ts +1 -0
  26. package/dist/kanban-tool-schema.d.ts +2 -2
  27. package/dist/kanban-tool-types.d.ts +18 -2
  28. package/dist/kanban.js +392 -126
  29. package/dist/pack.js +2799 -609
  30. package/dist/plan.d.ts +4 -1
  31. package/dist/plan.js +2380 -9
  32. package/dist/read.js +1531 -98
  33. package/dist/session-kanban.d.ts +8 -0
  34. package/dist/session-kanban.js +111 -17
  35. package/dist/task.d.ts +5 -4
  36. package/dist/task.js +2418 -43
  37. package/dist/todo.d.ts +10 -1
  38. package/dist/todo.js +2152 -20
  39. package/dist/tool-tier.js +2799 -609
  40. package/package.json +8 -4
@@ -2130,10 +2130,582 @@ var init_yaml_parser = __esm({
2130
2130
  }
2131
2131
  });
2132
2132
 
2133
+ // src/codebase-index/tree-sitter/queries.ts
2134
+ function getQueries(lang) {
2135
+ return LANG_QUERIES[lang] ?? DEFAULT_QUERIES;
2136
+ }
2137
+ function readFirstString(node) {
2138
+ if (!node) return null;
2139
+ if (node.type === "string_literal" || node.type === "alias") {
2140
+ return node.text.replace(/^"|"$/g, "");
2141
+ }
2142
+ const child = node.namedChild(0);
2143
+ return child ? readFirstString(child) : null;
2144
+ }
2145
+ var DEFAULT_QUERIES, LANG_QUERIES;
2146
+ var init_queries = __esm({
2147
+ "src/codebase-index/tree-sitter/queries.ts"() {
2148
+ "use strict";
2149
+ DEFAULT_QUERIES = {
2150
+ declKinds: {}
2151
+ };
2152
+ LANG_QUERIES = {
2153
+ // ─── C family ──────────────────────────────────────────────────────────────
2154
+ c: {
2155
+ declKinds: {
2156
+ function_definition: "function",
2157
+ declaration: "function",
2158
+ // K&R-style `int foo(...)` ambiguous w/ local var; the visitor prefers the function branch when the declarator field is present
2159
+ struct_specifier: "struct",
2160
+ union_specifier: "struct",
2161
+ enum_specifier: "enum",
2162
+ type_definition: "type",
2163
+ // `typedef … X;`
2164
+ preproc_def: "const"
2165
+ // `#define NAME …`
2166
+ },
2167
+ nameField: {
2168
+ function_definition: "declarator",
2169
+ declaration: "declarator",
2170
+ struct_specifier: "name",
2171
+ enum_specifier: "name",
2172
+ type_definition: "declarator",
2173
+ preproc_def: "name"
2174
+ },
2175
+ scopeNodes: /* @__PURE__ */ new Set([
2176
+ "translation_unit",
2177
+ "function_definition",
2178
+ "struct_specifier",
2179
+ "union_specifier",
2180
+ "enum_specifier"
2181
+ ])
2182
+ },
2183
+ cpp: {
2184
+ declKinds: {
2185
+ function_definition: "function",
2186
+ template_declaration: "function",
2187
+ // `template<typename T> …`
2188
+ class_specifier: "class",
2189
+ struct_specifier: "struct",
2190
+ union_specifier: "struct",
2191
+ enum_specifier: "enum",
2192
+ namespace_definition: "namespace",
2193
+ type_definition: "type"
2194
+ },
2195
+ nameField: {
2196
+ function_definition: "declarator",
2197
+ template_declaration: "name",
2198
+ class_specifier: "name",
2199
+ struct_specifier: "name",
2200
+ enum_specifier: "name",
2201
+ namespace_definition: "name",
2202
+ type_definition: "declarator"
2203
+ },
2204
+ scopeNodes: /* @__PURE__ */ new Set([
2205
+ "translation_unit",
2206
+ "function_definition",
2207
+ "class_specifier",
2208
+ "struct_specifier",
2209
+ "union_specifier",
2210
+ "enum_specifier",
2211
+ "namespace_definition"
2212
+ ])
2213
+ },
2214
+ java: {
2215
+ declKinds: {
2216
+ class_declaration: "class",
2217
+ interface_declaration: "interface",
2218
+ enum_declaration: "enum",
2219
+ record_declaration: "class",
2220
+ annotation_type_declaration: "interface",
2221
+ method_declaration: "method",
2222
+ constructor_declaration: "method",
2223
+ field_declaration: "property"
2224
+ },
2225
+ nameField: {
2226
+ class_declaration: "name",
2227
+ interface_declaration: "name",
2228
+ enum_declaration: "name",
2229
+ record_declaration: "name",
2230
+ annotation_type_declaration: "name",
2231
+ method_declaration: "name",
2232
+ constructor_declaration: "name"
2233
+ },
2234
+ // `field_declaration` has no single `name` field — it carries a list of
2235
+ // variable declarators. We emit one Symbol per node using the first
2236
+ // identifier-shaped named child (see `extractName` fallback in
2237
+ // `visitor.ts`). `int a, b, c;` therefore indexes only `a` — splitting
2238
+ // multi-declarator fields into separate Symbols is a separate refactor
2239
+ // that needs the visitor to know it has multiple names per node, and no
2240
+ // current test relies on it.
2241
+ scopeNodes: /* @__PURE__ */ new Set([
2242
+ "program",
2243
+ "class_declaration",
2244
+ "interface_declaration",
2245
+ "enum_declaration",
2246
+ "record_declaration"
2247
+ ])
2248
+ },
2249
+ csharp: {
2250
+ // C# 10+ `namespace Foo.Bar;` produces this node type. The legacy block
2251
+ // form `namespace Foo.Bar { ... }` produces `namespace_declaration`. Both
2252
+ // carry a `qualified_name` child whose text already includes the dots.
2253
+ // `using_directive` is intentionally not a declaration. Imports are
2254
+ // extracted separately; indexing a using directive as a namespace makes
2255
+ // the resolver bind it to its own source file before the real declaration.
2256
+ declKinds: {
2257
+ file_scoped_namespace_declaration: "namespace",
2258
+ class_declaration: "class",
2259
+ interface_declaration: "interface",
2260
+ struct_declaration: "struct",
2261
+ enum_declaration: "enum",
2262
+ record_declaration: "class",
2263
+ method_declaration: "method",
2264
+ constructor_declaration: "method",
2265
+ property_declaration: "property",
2266
+ field_declaration: "property",
2267
+ namespace_declaration: "namespace"
2268
+ },
2269
+ // Custom name extractor: take the full dotted name verbatim.
2270
+ nameExtractor: (node) => {
2271
+ const inner = node.namedChild(0);
2272
+ if (inner && (inner.type === "qualified_name" || inner.type === "name")) {
2273
+ return inner.text;
2274
+ }
2275
+ return null;
2276
+ },
2277
+ scopeNodes: /* @__PURE__ */ new Set([
2278
+ "compilation_unit",
2279
+ "namespace_declaration",
2280
+ "class_declaration",
2281
+ "interface_declaration",
2282
+ "struct_declaration",
2283
+ "enum_declaration",
2284
+ "record_declaration"
2285
+ ])
2286
+ },
2287
+ php: {
2288
+ declKinds: {
2289
+ function_definition: "function",
2290
+ method_declaration: "method",
2291
+ class_declaration: "class",
2292
+ interface_declaration: "interface",
2293
+ trait_declaration: "class",
2294
+ enum_declaration: "enum",
2295
+ namespace_definition: "namespace"
2296
+ },
2297
+ nameField: {
2298
+ function_definition: "name",
2299
+ method_declaration: "name",
2300
+ class_declaration: "name",
2301
+ interface_declaration: "name",
2302
+ trait_declaration: "name",
2303
+ enum_declaration: "name",
2304
+ namespace_declaration: "name"
2305
+ },
2306
+ scopeNodes: /* @__PURE__ */ new Set([
2307
+ "program",
2308
+ "namespace_definition",
2309
+ "class_declaration",
2310
+ "interface_declaration",
2311
+ "trait_declaration",
2312
+ "enum_declaration"
2313
+ ])
2314
+ },
2315
+ // ─── Scripting / mobile ────────────────────────────────────────────────────
2316
+ ruby: {
2317
+ declKinds: {
2318
+ method: "function",
2319
+ singleton_method: "method",
2320
+ class: "class",
2321
+ module: "namespace",
2322
+ constant: "const"
2323
+ },
2324
+ nameField: {
2325
+ method: "name",
2326
+ singleton_method: "name",
2327
+ class: "name",
2328
+ module: "name",
2329
+ constant: "name"
2330
+ },
2331
+ scopeNodes: /* @__PURE__ */ new Set(["program", "class", "module", "singleton_method", "method"])
2332
+ },
2333
+ swift: {
2334
+ declKinds: {
2335
+ function_declaration: "function",
2336
+ class_declaration: "class",
2337
+ struct_declaration: "struct",
2338
+ enum_declaration: "enum",
2339
+ protocol_declaration: "interface",
2340
+ actor_declaration: "class",
2341
+ extension_declaration: "class",
2342
+ initializer: "method",
2343
+ property_declaration: "property"
2344
+ },
2345
+ nameField: {
2346
+ function_declaration: "name",
2347
+ class_declaration: "name",
2348
+ struct_declaration: "name",
2349
+ enum_declaration: "name",
2350
+ protocol_declaration: "name",
2351
+ actor_declaration: "name",
2352
+ extension_declaration: "name",
2353
+ initializer: "name",
2354
+ property_declaration: "name"
2355
+ },
2356
+ scopeNodes: /* @__PURE__ */ new Set([
2357
+ "source_file",
2358
+ "class_declaration",
2359
+ "struct_declaration",
2360
+ "enum_declaration",
2361
+ "protocol_declaration",
2362
+ "actor_declaration",
2363
+ "extension_declaration"
2364
+ ])
2365
+ },
2366
+ kotlin: {
2367
+ declKinds: {
2368
+ class_declaration: "class",
2369
+ object_declaration: "class",
2370
+ interface_declaration: "interface",
2371
+ function_declaration: "function",
2372
+ property_declaration: "property",
2373
+ type_alias: "type"
2374
+ },
2375
+ nameField: {
2376
+ class_declaration: "name",
2377
+ object_declaration: "name",
2378
+ interface_declaration: "name",
2379
+ function_declaration: "name",
2380
+ property_declaration: "name",
2381
+ type_alias: "name"
2382
+ },
2383
+ scopeNodes: /* @__PURE__ */ new Set([
2384
+ "source_file",
2385
+ "class_declaration",
2386
+ "object_declaration",
2387
+ "interface_declaration",
2388
+ "function_declaration"
2389
+ ])
2390
+ },
2391
+ elixir: {
2392
+ declKinds: {
2393
+ // `def foo`, `defp foo`, `defmacro foo`, `macrop foo` all surface as
2394
+ // `call` nodes in the tree-sitter grammar — there is no
2395
+ // `function_definition`. The `nameExtractor` walks the call's
2396
+ // children to pick the right sibling identifier.
2397
+ call: "function",
2398
+ module: "namespace"
2399
+ },
2400
+ nameExtractor: (node) => {
2401
+ if (node.type === "module") {
2402
+ const aliasNode = node.childForFieldName("alias");
2403
+ return readFirstString(aliasNode) ?? null;
2404
+ }
2405
+ if (node.type !== "call") return null;
2406
+ const first = node.namedChild(0);
2407
+ if (!first) return null;
2408
+ const target = first.text;
2409
+ if (target !== "def" && target !== "defp" && target !== "defmacro" && target !== "defp_macro" && target !== "macrop" && target !== "defprotocol" && target !== "defguard" && target !== "defguardp") {
2410
+ return null;
2411
+ }
2412
+ const nameNode = node.namedChild(1);
2413
+ return nameNode?.text ?? null;
2414
+ },
2415
+ scopeNodes: /* @__PURE__ */ new Set(["source", "module"])
2416
+ },
2417
+ shell: {
2418
+ declKinds: {
2419
+ function_definition: "function"
2420
+ },
2421
+ nameField: { function_definition: "name" },
2422
+ scopeNodes: /* @__PURE__ */ new Set(["program", "function_definition"])
2423
+ }
2424
+ };
2425
+ }
2426
+ });
2427
+
2428
+ // src/codebase-index/tree-sitter/util.ts
2429
+ function lineColAt2(offsets, index) {
2430
+ let low = 0;
2431
+ let high = offsets.length;
2432
+ while (low < high) {
2433
+ const mid = low + high >>> 1;
2434
+ if ((offsets[mid] ?? 0) < index) low = mid + 1;
2435
+ else high = mid;
2436
+ }
2437
+ const lastNl = low > 0 ? offsets[low - 1] ?? -1 : -1;
2438
+ return { line: low + 1, col: index - lastNl };
2439
+ }
2440
+ function newlineOffsets3(content) {
2441
+ const offsets = [];
2442
+ for (let i = 0; i < content.length; i++) {
2443
+ if (content.charCodeAt(i) === 10) offsets.push(i);
2444
+ }
2445
+ return offsets;
2446
+ }
2447
+ var TREE_SITTER_MAX_FILE_CHARS, TREE_SITTER_MAX_SYMBOLS;
2448
+ var init_util = __esm({
2449
+ "src/codebase-index/tree-sitter/util.ts"() {
2450
+ "use strict";
2451
+ TREE_SITTER_MAX_FILE_CHARS = 512 * 1024;
2452
+ TREE_SITTER_MAX_SYMBOLS = 500;
2453
+ }
2454
+ });
2455
+
2456
+ // src/codebase-index/tree-sitter/visitor.ts
2457
+ function visitTree(tree, content, file, lang, queries) {
2458
+ const boundedContent = content.length > TREE_SITTER_MAX_FILE_CHARS ? content.slice(0, TREE_SITTER_MAX_FILE_CHARS) : content;
2459
+ const nlOffsets = newlineOffsets3(boundedContent);
2460
+ const symbols = [];
2461
+ const scopeStack = [];
2462
+ function visit(node, depth) {
2463
+ if (symbols.length >= TREE_SITTER_MAX_SYMBOLS) return;
2464
+ if (node.isMissing || node.isError) {
2465
+ } else {
2466
+ const kind = queries.declKinds[node.type];
2467
+ if (kind) {
2468
+ const emitted = emitSymbol(
2469
+ node,
2470
+ kind,
2471
+ file,
2472
+ lang,
2473
+ scopeStack,
2474
+ boundedContent,
2475
+ nlOffsets,
2476
+ queries
2477
+ );
2478
+ if (emitted) symbols.push(emitted);
2479
+ }
2480
+ }
2481
+ const pushesScope = queries.scopeNodes?.has(node.type) ?? false;
2482
+ const pushIdx = pushesScope ? pushScope(scopeStack, node, queries) : -1;
2483
+ if (queries.skipNamedChildren) {
2484
+ if (pushIdx !== -1) scopeStack.pop();
2485
+ return;
2486
+ }
2487
+ for (const child of node.namedChildren) {
2488
+ visit(child, depth + 1);
2489
+ if (symbols.length >= TREE_SITTER_MAX_SYMBOLS) {
2490
+ if (pushIdx !== -1) scopeStack.pop();
2491
+ return;
2492
+ }
2493
+ }
2494
+ if (pushIdx !== -1) scopeStack.pop();
2495
+ }
2496
+ visit(tree.rootNode, 0);
2497
+ return { symbols };
2498
+ }
2499
+ function pushScope(scopeStack, node, queries) {
2500
+ const name = extractName(node, queries);
2501
+ if (!name) return -1;
2502
+ scopeStack.push(name);
2503
+ return scopeStack.length - 1;
2504
+ }
2505
+ function extractName(node, queries) {
2506
+ if (queries.nameExtractor) {
2507
+ const extracted = queries.nameExtractor(node);
2508
+ if (extracted) return extracted;
2509
+ }
2510
+ const fieldName = queries.nameField?.[node.type] ?? "name";
2511
+ const field = node.childForFieldName(fieldName);
2512
+ if (field) {
2513
+ if (IDENTIFIER_NODE_TYPES.has(field.type)) {
2514
+ return field.text;
2515
+ }
2516
+ const inner = field.childForFieldName("name") ?? field.namedChild(0);
2517
+ if (inner && IDENTIFIER_NODE_TYPES.has(inner.type)) {
2518
+ return inner.text;
2519
+ }
2520
+ }
2521
+ for (let i = 0; i < node.namedChildCount; i++) {
2522
+ const child = node.namedChild(i);
2523
+ if (child && IDENTIFIER_NODE_TYPES.has(child.type)) {
2524
+ return child.text;
2525
+ }
2526
+ }
2527
+ return null;
2528
+ }
2529
+ function emitSymbol(node, kind, file, lang, scopeStack, content, nlOffsets, queries) {
2530
+ const name = extractName(node, queries);
2531
+ if (!name) return null;
2532
+ const pos = node.startIndex;
2533
+ const { line, col } = lineColAt2(nlOffsets, pos);
2534
+ const end = Math.min(node.endIndex, content.length);
2535
+ const signature = content.slice(pos, end).replace(/\s+/g, " ").trim().slice(0, 500);
2536
+ const scope = scopeStack.join(".");
2537
+ const text = [name, signature].filter(Boolean).join(" | ").trim().slice(0, 1e3);
2538
+ return {
2539
+ id: 0,
2540
+ // caller assigns during bulk insertion
2541
+ lang,
2542
+ kind,
2543
+ name: name.slice(0, 200),
2544
+ file,
2545
+ line,
2546
+ col,
2547
+ signature,
2548
+ docComment: "",
2549
+ // doc-comment extraction lands with ref emission on Day 4
2550
+ scope,
2551
+ text
2552
+ };
2553
+ }
2554
+ var IDENTIFIER_NODE_TYPES;
2555
+ var init_visitor = __esm({
2556
+ "src/codebase-index/tree-sitter/visitor.ts"() {
2557
+ "use strict";
2558
+ init_util();
2559
+ IDENTIFIER_NODE_TYPES = /* @__PURE__ */ new Set([
2560
+ "identifier",
2561
+ "simple_identifier",
2562
+ "type_identifier",
2563
+ "field_identifier",
2564
+ "property_identifier",
2565
+ "name",
2566
+ "word",
2567
+ "variable_name",
2568
+ "constant",
2569
+ "sym"
2570
+ ]);
2571
+ }
2572
+ });
2573
+
2574
+ // src/codebase-index/tree-sitter-parser.ts
2575
+ var tree_sitter_parser_exports = {};
2576
+ __export(tree_sitter_parser_exports, {
2577
+ __smokeRootType: () => __smokeRootType,
2578
+ getGrammarWasmPath: () => getGrammarWasmPath,
2579
+ isTreeSitterSupported: () => isTreeSitterSupported,
2580
+ loadTreeSitterLanguage: () => loadTreeSitterLanguage,
2581
+ parseSymbols: () => parseSymbols8
2582
+ });
2583
+ import * as path12 from "node:path";
2584
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
2585
+ function optInEnabled(env) {
2586
+ return process.env[env] === "1" || process.env[env] === "true";
2587
+ }
2588
+ function getRuntime() {
2589
+ if (!runtimePromise) {
2590
+ runtimePromise = (async () => {
2591
+ const mod = await import("web-tree-sitter");
2592
+ const init = () => mod.Parser.init({ locateFile: () => RUNTIME_WASM });
2593
+ return { Parser: mod.Parser, Language: mod.Language, init };
2594
+ })();
2595
+ }
2596
+ return runtimePromise;
2597
+ }
2598
+ async function loadLanguage(lang) {
2599
+ const existing = languageCache.get(lang);
2600
+ if (existing) return existing;
2601
+ const promise = (async () => {
2602
+ const grammarName = resolveGrammarName(lang);
2603
+ if (!grammarName) {
2604
+ throw new Error(`tree-sitter: no grammar registered for lang "${lang}"`);
2605
+ }
2606
+ const wasmPath = path12.join(WASM_DIR, grammarName, `tree-sitter-${grammarName}.wasm`);
2607
+ const { Language, init } = await getRuntime();
2608
+ await init();
2609
+ const languageObj = await Language.load(wasmPath);
2610
+ return { lang, Language: languageObj };
2611
+ })();
2612
+ languageCache.set(lang, promise);
2613
+ return promise;
2614
+ }
2615
+ function resolveGrammarName(lang) {
2616
+ if (lang === "go" && optInEnabled(GO_OPT_IN)) return "go";
2617
+ if (lang === "py" && optInEnabled(PY_OPT_IN)) return "python";
2618
+ if (lang === "rs" && optInEnabled(RS_OPT_IN)) return "rust";
2619
+ return LANG_TO_GRAMMAR[lang];
2620
+ }
2621
+ function isTreeSitterSupported(lang) {
2622
+ return resolveGrammarName(lang) !== void 0;
2623
+ }
2624
+ function getGrammarWasmPath(lang) {
2625
+ const name = resolveGrammarName(lang);
2626
+ if (!name) return void 0;
2627
+ return path12.join(WASM_DIR, name, `tree-sitter-${name}.wasm`);
2628
+ }
2629
+ async function parseSymbols8(opts) {
2630
+ const { file, content, lang } = opts;
2631
+ if (!isTreeSitterSupported(lang)) {
2632
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
2633
+ }
2634
+ try {
2635
+ const { Parser } = await getRuntime();
2636
+ const cached = await loadLanguage(lang);
2637
+ const parser = new Parser();
2638
+ parser.setLanguage(cached.Language);
2639
+ const tree = parser.parse(content);
2640
+ if (!tree) {
2641
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
2642
+ }
2643
+ const { symbols } = visitTree(tree, content, file, lang, getQueries(lang));
2644
+ parser.delete();
2645
+ tree.delete();
2646
+ return { file, lang, symbols, refs: [], mtimeMs: Date.now() };
2647
+ } catch {
2648
+ return { file, lang, symbols: [], mtimeMs: Date.now() };
2649
+ }
2650
+ }
2651
+ async function loadTreeSitterLanguage(lang) {
2652
+ const cached = await loadLanguage(lang);
2653
+ return cached.Language;
2654
+ }
2655
+ async function __smokeRootType(opts) {
2656
+ if (!isTreeSitterSupported(opts.lang)) {
2657
+ throw new Error(`tree-sitter: no grammar registered for lang "${opts.lang}"`);
2658
+ }
2659
+ const { Parser } = await getRuntime();
2660
+ const cached = await loadLanguage(opts.lang);
2661
+ const parser = new Parser();
2662
+ parser.setLanguage(cached.Language);
2663
+ let tree = null;
2664
+ try {
2665
+ tree = parser.parse(opts.content);
2666
+ if (!tree) throw new Error("tree-sitter: parser.parse returned null");
2667
+ return tree.rootNode.type;
2668
+ } finally {
2669
+ tree?.delete();
2670
+ parser.delete();
2671
+ }
2672
+ }
2673
+ var WASM_DIR, RUNTIME_WASM, LANG_TO_GRAMMAR, GO_OPT_IN, PY_OPT_IN, RS_OPT_IN, runtimePromise, languageCache;
2674
+ var init_tree_sitter_parser = __esm({
2675
+ "src/codebase-index/tree-sitter-parser.ts"() {
2676
+ "use strict";
2677
+ init_queries();
2678
+ init_visitor();
2679
+ WASM_DIR = fileURLToPath3(new URL("./wasm/", import.meta.url));
2680
+ RUNTIME_WASM = path12.join(WASM_DIR, "tree-sitter-runtime.wasm");
2681
+ LANG_TO_GRAMMAR = {
2682
+ c: "c",
2683
+ cpp: "cpp",
2684
+ java: "java",
2685
+ csharp: "c_sharp",
2686
+ // tree-sitter directory uses underscore
2687
+ php: "php",
2688
+ ruby: "ruby",
2689
+ swift: "swift",
2690
+ kotlin: "kotlin",
2691
+ shell: "bash",
2692
+ // we treat `.sh` / `.bash` / `.zsh` via the bash grammar
2693
+ // Go/Python/Rust are opt-in only (see goOptIn / pyOptIn / rsOptIn below)
2694
+ elixir: "elixir"
2695
+ };
2696
+ GO_OPT_IN = "WRONGSTACK_USE_TS_GO";
2697
+ PY_OPT_IN = "WRONGSTACK_USE_TS_PY";
2698
+ RS_OPT_IN = "WRONGSTACK_USE_TS_RS";
2699
+ runtimePromise = null;
2700
+ languageCache = /* @__PURE__ */ new Map();
2701
+ }
2702
+ });
2703
+
2133
2704
  // src/codebase-index/project-server-client.ts
2134
2705
  import { spawn } from "node:child_process";
2135
2706
  import * as fs5 from "node:fs";
2136
2707
  import * as net from "node:net";
2708
+ import { StringDecoder } from "node:string_decoder";
2137
2709
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2138
2710
  import { checkUnixSocketPath } from "@wrongstack/core/utils";
2139
2711
 
@@ -2220,6 +2792,23 @@ function resetIndexCircuitBreaker() {
2220
2792
  indexCircuitBreaker.reset();
2221
2793
  }
2222
2794
 
2795
+ // src/codebase-index/binary-frame.ts
2796
+ import { decode, encode } from "@msgpack/msgpack";
2797
+ var BINARY_FRAME_MAGIC = 87;
2798
+ function isBinaryFrame(firstByte) {
2799
+ return firstByte === BINARY_FRAME_MAGIC;
2800
+ }
2801
+ function encodeBinaryFrame(message) {
2802
+ const payload = encode(message);
2803
+ const header = Buffer.allocUnsafe(5);
2804
+ header[0] = BINARY_FRAME_MAGIC;
2805
+ header.writeUInt32BE(payload.length, 1);
2806
+ return Buffer.concat([header, payload], 5 + payload.length);
2807
+ }
2808
+ function decodeBinaryFrame(payload) {
2809
+ return decode(payload);
2810
+ }
2811
+
2223
2812
  // src/codebase-index/project-server-endpoint.ts
2224
2813
  import { createHash } from "node:crypto";
2225
2814
  import * as fs4 from "node:fs";
@@ -2495,7 +3084,7 @@ function getMetadataWithStatement(stmt, key) {
2495
3084
  }
2496
3085
  function getFileMetaWithStatement(stmt, file) {
2497
3086
  const rows = stmt(
2498
- "SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files WHERE file = ?"
3087
+ "SELECT file, lang, mtime_ms, symbol_count, last_indexed, content_hash FROM files WHERE file = ?"
2499
3088
  ).all(file);
2500
3089
  const r = rows[0];
2501
3090
  if (!r) return null;
@@ -2504,16 +3093,20 @@ function getFileMetaWithStatement(stmt, file) {
2504
3093
  lang: r.lang,
2505
3094
  mtimeMs: r.mtime_ms,
2506
3095
  symbolCount: r.symbol_count,
2507
- lastIndexed: r.last_indexed
3096
+ lastIndexed: r.last_indexed,
3097
+ contentHash: r.content_hash
2508
3098
  };
2509
3099
  }
2510
3100
  function getAllFileMetasWithStatement(stmt) {
2511
- return stmt("SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files").all().map((r) => ({
3101
+ return stmt(
3102
+ "SELECT file, lang, mtime_ms, symbol_count, last_indexed, content_hash FROM files"
3103
+ ).all().map((r) => ({
2512
3104
  file: r.file,
2513
3105
  lang: r.lang,
2514
3106
  mtimeMs: r.mtime_ms,
2515
3107
  symbolCount: r.symbol_count,
2516
- lastIndexed: r.last_indexed
3108
+ lastIndexed: r.last_indexed,
3109
+ contentHash: r.content_hash
2517
3110
  }));
2518
3111
  }
2519
3112
  function getIndexDbSizeBytes(indexDir) {
@@ -2567,6 +3160,18 @@ function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
2567
3160
  insert.run(...binds);
2568
3161
  }
2569
3162
  }
3163
+ function bulkInsertVectorsWithStatement(stmt, maxSqlVars, rows) {
3164
+ if (rows.length === 0) return;
3165
+ const chunkSize = Math.max(1, Math.floor(maxSqlVars / 2));
3166
+ for (let i = 0; i < rows.length; i += chunkSize) {
3167
+ const chunk = rows.slice(i, i + chunkSize);
3168
+ const placeholders = chunk.map(() => "(?, ?)").join(", ");
3169
+ const insert = stmt(`INSERT INTO symbol_vectors(symbol_id, vector) VALUES ${placeholders}`);
3170
+ const binds = [];
3171
+ for (const r of chunk) binds.push(r.id, r.vector);
3172
+ insert.run(...binds);
3173
+ }
3174
+ }
2570
3175
  function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
2571
3176
  if (refs.length === 0) return;
2572
3177
  const chunkSize = Math.max(1, Math.floor(maxSqlVars / 8));
@@ -3173,6 +3778,171 @@ function findOutgoingCallsByName(stmt, symbolName, file, limit) {
3173
3778
  const calls = rows.map(mapCallSiteRow).slice(0, limit);
3174
3779
  return { calls, symbolFound: true, unresolvedCount, totalMatches: rows.length };
3175
3780
  }
3781
+ function runCteWithSeeds(stmt, seedIds, buildSql) {
3782
+ if (seedIds.length <= 900) {
3783
+ const ph = seedIds.map(() => "?").join(",");
3784
+ return stmt(buildSql(ph)).all(...seedIds);
3785
+ }
3786
+ stmt("DROP TABLE IF EXISTS _cte_seeds").run();
3787
+ try {
3788
+ stmt("CREATE TEMP TABLE _cte_seeds (id INTEGER PRIMARY KEY)").run();
3789
+ for (let i = 0; i < seedIds.length; i += 500) {
3790
+ const chunk = seedIds.slice(i, i + 500);
3791
+ const ph = chunk.map(() => "(?)").join(",");
3792
+ stmt(`INSERT OR IGNORE INTO _cte_seeds (id) VALUES ${ph}`).run(...chunk);
3793
+ }
3794
+ return stmt(buildSql("SELECT id FROM _cte_seeds")).all();
3795
+ } finally {
3796
+ stmt("DROP TABLE IF EXISTS _cte_seeds").run();
3797
+ }
3798
+ }
3799
+ function findTransitiveIncomingCallsByName(stmt, symbolName, file, limit) {
3800
+ const targetIds = resolveSymbolIds(stmt, symbolName, file);
3801
+ if (targetIds.length === 0)
3802
+ return { calls: [], symbolFound: false, ambiguous: false, totalMatches: 0 };
3803
+ let matchIds = targetIds;
3804
+ let ambiguous = false;
3805
+ if (file !== void 0) {
3806
+ const allNamedIds = resolveSymbolIds(stmt, symbolName, void 0);
3807
+ if (allNamedIds.length > targetIds.length) {
3808
+ matchIds = allNamedIds;
3809
+ ambiguous = true;
3810
+ }
3811
+ }
3812
+ const cteSql = (seedSource) => `WITH RECURSIVE incoming_tree(from_id) AS (
3813
+ SELECT r.from_id
3814
+ FROM refs r
3815
+ WHERE r.to_id IN (${seedSource})
3816
+
3817
+ UNION
3818
+
3819
+ SELECT r.from_id
3820
+ FROM refs r
3821
+ JOIN incoming_tree it ON r.to_id = it.from_id
3822
+ )
3823
+ SELECT
3824
+ s.id AS sym_id,
3825
+ s.name AS sym_name,
3826
+ s.kind AS sym_kind,
3827
+ s.lang AS sym_lang,
3828
+ s.file AS sym_file,
3829
+ s.line AS sym_line,
3830
+ s.signature AS sym_signature,
3831
+ '' AS call_type,
3832
+ 0 AS ref_line
3833
+ FROM incoming_tree it
3834
+ JOIN symbols s ON s.id = it.from_id
3835
+ GROUP BY s.id
3836
+ ORDER BY s.file, s.line`;
3837
+ const rows = runCteWithSeeds(stmt, matchIds, cteSql);
3838
+ if (!file) {
3839
+ const fallbackRows = stmt(
3840
+ `SELECT
3841
+ s.id AS sym_id,
3842
+ s.name AS sym_name,
3843
+ s.kind AS sym_kind,
3844
+ s.lang AS sym_lang,
3845
+ s.file AS sym_file,
3846
+ s.line AS sym_line,
3847
+ s.signature AS sym_signature,
3848
+ r.call_type,
3849
+ r.line AS ref_line
3850
+ FROM refs r
3851
+ JOIN symbols s ON s.id = r.from_id
3852
+ WHERE r.to_id IS NULL AND r.to_name = ?
3853
+ ORDER BY r.line, r.id`
3854
+ ).all(symbolName);
3855
+ rows.push(...fallbackRows);
3856
+ }
3857
+ rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
3858
+ const allCalls = rows.map(mapCallSiteRow);
3859
+ return {
3860
+ calls: allCalls.slice(0, limit),
3861
+ symbolFound: true,
3862
+ ambiguous,
3863
+ totalMatches: allCalls.length
3864
+ };
3865
+ }
3866
+ function findTransitiveOutgoingCallsByName(stmt, symbolName, file, limit) {
3867
+ const sourceIds = resolveSymbolIds(stmt, symbolName, file);
3868
+ if (sourceIds.length === 0)
3869
+ return { calls: [], symbolFound: false, unresolvedCount: 0, totalMatches: 0 };
3870
+ const unresolvedCount = chunkedIdScalar(
3871
+ stmt,
3872
+ sourceIds,
3873
+ (ph) => `SELECT COUNT(*) AS n FROM refs WHERE from_id IN (${ph}) AND to_id IS NULL`
3874
+ );
3875
+ const cteSql = (seedSource) => `WITH RECURSIVE outgoing_tree(to_id) AS (
3876
+ SELECT r.to_id
3877
+ FROM refs r
3878
+ WHERE r.from_id IN (${seedSource}) AND r.to_id IS NOT NULL
3879
+
3880
+ UNION
3881
+
3882
+ SELECT r.to_id
3883
+ FROM refs r
3884
+ JOIN outgoing_tree ot ON r.from_id = ot.to_id
3885
+ WHERE r.to_id IS NOT NULL
3886
+ )
3887
+ SELECT
3888
+ s.id AS sym_id,
3889
+ s.name AS sym_name,
3890
+ s.kind AS sym_kind,
3891
+ s.lang AS sym_lang,
3892
+ s.file AS sym_file,
3893
+ s.line AS sym_line,
3894
+ s.signature AS sym_signature,
3895
+ '' AS call_type,
3896
+ 0 AS ref_line
3897
+ FROM outgoing_tree ot
3898
+ JOIN symbols s ON s.id = ot.to_id
3899
+ GROUP BY s.id
3900
+ ORDER BY s.file, s.line`;
3901
+ const rows = runCteWithSeeds(stmt, sourceIds, cteSql);
3902
+ const calls = rows.map(mapCallSiteRow).slice(0, limit);
3903
+ return { calls, symbolFound: true, unresolvedCount, totalMatches: rows.length };
3904
+ }
3905
+ function findReachableSymbolIds(stmt, seedIds) {
3906
+ if (seedIds.length === 0) return /* @__PURE__ */ new Set();
3907
+ if (seedIds.length > 900) {
3908
+ stmt("DROP TABLE IF EXISTS _seeds").run();
3909
+ try {
3910
+ stmt("CREATE TEMP TABLE _seeds (id INTEGER PRIMARY KEY)").run();
3911
+ for (let i = 0; i < seedIds.length; i += 500) {
3912
+ const chunk = seedIds.slice(i, i + 500);
3913
+ const ph2 = chunk.map(() => "(?)").join(",");
3914
+ stmt(`INSERT OR IGNORE INTO _seeds (id) VALUES ${ph2}`).run(...chunk);
3915
+ }
3916
+ const rows2 = stmt(
3917
+ `WITH RECURSIVE reachable(id) AS (
3918
+ SELECT id FROM _seeds
3919
+ UNION
3920
+ SELECT r.to_id
3921
+ FROM refs r
3922
+ JOIN reachable ON r.from_id = reachable.id
3923
+ WHERE r.to_id IS NOT NULL
3924
+ )
3925
+ SELECT DISTINCT id FROM reachable`
3926
+ ).all();
3927
+ return new Set(rows2.map((r) => r.id));
3928
+ } finally {
3929
+ stmt("DROP TABLE IF EXISTS _seeds").run();
3930
+ }
3931
+ }
3932
+ const ph = seedIds.map(() => "?").join(",");
3933
+ const rows = stmt(
3934
+ `WITH RECURSIVE reachable(id) AS (
3935
+ SELECT id FROM symbols WHERE id IN (${ph})
3936
+ UNION
3937
+ SELECT r.to_id
3938
+ FROM refs r
3939
+ JOIN reachable ON r.from_id = reachable.id
3940
+ WHERE r.to_id IS NOT NULL
3941
+ )
3942
+ SELECT DISTINCT id FROM reachable`
3943
+ ).all(...seedIds);
3944
+ return new Set(rows.map((r) => r.id));
3945
+ }
3176
3946
  function findRefsToWithStatement(stmt, symbolId) {
3177
3947
  return stmt(
3178
3948
  "SELECT id, from_id, to_name, to_id, call_type, line FROM refs WHERE to_id = ? OR to_name = (SELECT name FROM symbols WHERE id = ?)"
@@ -3416,6 +4186,12 @@ var CORE_TABLES_SQL = `
3416
4186
  file TEXT PRIMARY KEY,
3417
4187
  lang TEXT NOT NULL,
3418
4188
  mtime_ms INTEGER NOT NULL,
4189
+ -- Phase 2: xxHash64 of the file's UTF-8 bytes. Empty string when the
4190
+ -- indexer hasn't populated it yet (legacy rows, schema repaired by
4191
+ -- repairMissingColumns). Compared on incremental re-index so that a
4192
+ -- touch or branch-switch that leaves content byte-identical skips the
4193
+ -- expensive parse phase entirely (refactoring proposal Phase 2).
4194
+ content_hash TEXT NOT NULL DEFAULT '',
3419
4195
  symbol_count INTEGER NOT NULL DEFAULT 0,
3420
4196
  last_indexed INTEGER NOT NULL,
3421
4197
  -- Code Atlas grouping label, computed at index time from the ecosystem's
@@ -3483,7 +4259,14 @@ var LANG_FAMILY_TABLE_SQL = `
3483
4259
  );
3484
4260
  `;
3485
4261
  var LANG_FAMILY_WILDCARD = "*";
3486
- var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'unicode61')";
4262
+ var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'trigram')";
4263
+ var SYMBOL_VECTORS_TABLE_SQL = `
4264
+ CREATE TABLE IF NOT EXISTS symbol_vectors (
4265
+ symbol_id INTEGER PRIMARY KEY,
4266
+ vector BLOB NOT NULL,
4267
+ FOREIGN KEY (symbol_id) REFERENCES symbols(id) ON DELETE CASCADE
4268
+ );
4269
+ `;
3487
4270
 
3488
4271
  // src/codebase-index/writer-search-helpers.ts
3489
4272
  var SEARCH_CANDIDATE_SCAN_CAP = 5e3;
@@ -3624,6 +4407,85 @@ var StorePool = class {
3624
4407
  }
3625
4408
  };
3626
4409
 
4410
+ // src/codebase-index/vector-search.ts
4411
+ var RRF_K = 60;
4412
+ var VECTOR_DIMENSIONS = 384;
4413
+ var NGRAM_SIZE = 3;
4414
+ function embedText(text) {
4415
+ const vec = new Float32Array(VECTOR_DIMENSIONS);
4416
+ const normalized = text.toLowerCase().trim();
4417
+ if (normalized.length < NGRAM_SIZE) {
4418
+ const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
4419
+ for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
4420
+ const ngram = padded.slice(i, i + NGRAM_SIZE);
4421
+ const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
4422
+ vec[bucket] += 1;
4423
+ }
4424
+ } else {
4425
+ for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
4426
+ const ngram = normalized.slice(i, i + NGRAM_SIZE);
4427
+ const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
4428
+ vec[bucket] += 1;
4429
+ }
4430
+ }
4431
+ let norm = 0;
4432
+ for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
4433
+ norm += vec[i] * vec[i];
4434
+ }
4435
+ norm = Math.sqrt(norm);
4436
+ if (norm > 0) {
4437
+ for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
4438
+ vec[i] /= norm;
4439
+ }
4440
+ }
4441
+ return vec;
4442
+ }
4443
+ function hashNgram(str) {
4444
+ let hash = 2166136261;
4445
+ for (let i = 0; i < str.length; i++) {
4446
+ hash ^= str.charCodeAt(i);
4447
+ hash = Math.imul(hash, 16777619);
4448
+ }
4449
+ return hash >>> 0;
4450
+ }
4451
+ function cosineSimilarity(a, b) {
4452
+ let dot = 0;
4453
+ const len = Math.min(a.length, b.length);
4454
+ for (let i = 0; i < len; i++) {
4455
+ dot += a[i] * b[i];
4456
+ }
4457
+ return dot;
4458
+ }
4459
+ function encodeVector(vec) {
4460
+ return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
4461
+ }
4462
+ function decodeVector(buf) {
4463
+ const view = new DataView(
4464
+ buf.buffer,
4465
+ buf.byteOffset,
4466
+ buf.byteLength
4467
+ );
4468
+ const copy = new Float32Array(buf.byteLength / 4);
4469
+ for (let i = 0; i < copy.length; i++) {
4470
+ copy[i] = view.getFloat32(i * 4, true);
4471
+ }
4472
+ return copy;
4473
+ }
4474
+ function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
4475
+ const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
4476
+ const scored = [];
4477
+ for (const id of allIds) {
4478
+ const bm25Rank = bm25Ranks.get(id);
4479
+ const vecRank = vectorRanks.get(id);
4480
+ let score = 0;
4481
+ if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
4482
+ if (vecRank !== void 0) score += 1 / (k + vecRank);
4483
+ scored.push([id, score]);
4484
+ }
4485
+ scored.sort((a, b) => b[1] - a[1]);
4486
+ return scored;
4487
+ }
4488
+
3627
4489
  // src/codebase-index/writer.ts
3628
4490
  var DB_FILE2 = "index.db";
3629
4491
  var MAX_STATEMENT_CACHE = 128;
@@ -3636,6 +4498,12 @@ var IndexStore = class _IndexStore {
3636
4498
  * When false, ranked search falls back to the LIKE + in-process BM25 path.
3637
4499
  */
3638
4500
  ftsAvailable = false;
4501
+ /**
4502
+ * Phase 3: true when the `symbol_vectors` table was created successfully.
4503
+ * When false, hybrid search skips the vector pass and falls back to FTS5
4504
+ * (or LIKE) only.
4505
+ */
4506
+ vectorsAvailable = false;
3639
4507
  /**
3640
4508
  * Cache of prepared statements keyed by their SQL text. `DatabaseSync`
3641
4509
  * compiles SQL on every `.prepare()` call; for the fixed-SQL methods
@@ -3734,7 +4602,13 @@ var IndexStore = class _IndexStore {
3734
4602
  */
3735
4603
  repairMissingColumns() {
3736
4604
  const expected = [
3737
- { table: "files", columns: [["package", "TEXT NOT NULL DEFAULT ''"]] },
4605
+ {
4606
+ table: "files",
4607
+ columns: [
4608
+ ["package", "TEXT NOT NULL DEFAULT ''"],
4609
+ ["content_hash", "TEXT NOT NULL DEFAULT ''"]
4610
+ ]
4611
+ },
3738
4612
  {
3739
4613
  table: "refs",
3740
4614
  columns: [
@@ -3766,6 +4640,7 @@ var IndexStore = class _IndexStore {
3766
4640
  DROP TABLE IF EXISTS symbols;
3767
4641
  DROP TABLE IF EXISTS files;
3768
4642
  DROP TABLE IF EXISTS refs;
4643
+ DROP TABLE IF EXISTS symbol_vectors;
3769
4644
  `);
3770
4645
  this.db.exec("DROP TABLE IF EXISTS symbols_fts");
3771
4646
  this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(
@@ -3787,6 +4662,12 @@ var IndexStore = class _IndexStore {
3787
4662
  this.db.exec(LANG_FAMILY_TABLE_SQL);
3788
4663
  this.seedLangFamilies();
3789
4664
  try {
4665
+ const ftsSchema = this.stmt(
4666
+ "SELECT sql FROM sqlite_master WHERE type='table' AND name='symbols_fts'"
4667
+ ).get();
4668
+ if (ftsSchema?.sql && ftsSchema.sql.includes("unicode61")) {
4669
+ this.db.exec("DROP TABLE IF EXISTS symbols_fts");
4670
+ }
3790
4671
  this.db.exec(SYMBOLS_FTS_SQL);
3791
4672
  this.ftsAvailable = true;
3792
4673
  const symbolCount = Number(
@@ -3797,6 +4678,7 @@ var IndexStore = class _IndexStore {
3797
4678
  );
3798
4679
  if (symbolCount !== ftsCount) {
3799
4680
  this.db.exec("DELETE FROM symbols_fts");
4681
+ if (this.vectorsAvailable) this.db.exec("DELETE FROM symbol_vectors");
3800
4682
  const rows = this.stmt(
3801
4683
  "SELECT id, name, signature, doc_comment FROM symbols ORDER BY id"
3802
4684
  ).all();
@@ -3814,6 +4696,12 @@ var IndexStore = class _IndexStore {
3814
4696
  } catch {
3815
4697
  this.ftsAvailable = false;
3816
4698
  }
4699
+ try {
4700
+ this.db.exec(SYMBOL_VECTORS_TABLE_SQL);
4701
+ this.vectorsAvailable = true;
4702
+ } catch {
4703
+ this.vectorsAvailable = false;
4704
+ }
3817
4705
  this.ensureNextSymbolIdSeeded();
3818
4706
  }
3819
4707
  // ─── ID allocation & bulk helpers ────────────────────────────────────────────
@@ -3923,6 +4811,7 @@ var IndexStore = class _IndexStore {
3923
4811
  const result = [];
3924
4812
  const bulk = [];
3925
4813
  const ftsRows = [];
4814
+ const vectorRows = [];
3926
4815
  for (const s of symbols) {
3927
4816
  const id = nextId++;
3928
4817
  bulk.push({
@@ -3941,6 +4830,10 @@ var IndexStore = class _IndexStore {
3941
4830
  if (this.ftsAvailable) {
3942
4831
  ftsRows.push({ id, text: buildIndexableText(s.name, s.signature, s.docComment) });
3943
4832
  }
4833
+ vectorRows.push({
4834
+ id,
4835
+ vector: encodeVector(embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment)))
4836
+ });
3944
4837
  result.push({ ...s, id });
3945
4838
  }
3946
4839
  bulkInsertSymbolsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, bulk);
@@ -3950,6 +4843,13 @@ var IndexStore = class _IndexStore {
3950
4843
  this.ftsAvailable,
3951
4844
  ftsRows
3952
4845
  );
4846
+ if (this.vectorsAvailable) {
4847
+ bulkInsertVectorsWithStatement(
4848
+ (sql) => this.stmt(sql),
4849
+ _IndexStore.MAX_SQL_VARS,
4850
+ vectorRows
4851
+ );
4852
+ }
3953
4853
  this.db.exec("COMMIT");
3954
4854
  return result;
3955
4855
  } catch (err) {
@@ -3969,6 +4869,11 @@ var IndexStore = class _IndexStore {
3969
4869
  "DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
3970
4870
  ).run(file);
3971
4871
  }
4872
+ if (this.vectorsAvailable) {
4873
+ this.stmt(
4874
+ "DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
4875
+ ).run(file);
4876
+ }
3972
4877
  this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
3973
4878
  this.resolveRefsForNamesUnsafe(affectedNames);
3974
4879
  this.db.exec("COMMIT");
@@ -3994,6 +4899,11 @@ var IndexStore = class _IndexStore {
3994
4899
  "DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
3995
4900
  ).run(file);
3996
4901
  }
4902
+ if (this.vectorsAvailable) {
4903
+ this.stmt(
4904
+ "DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
4905
+ ).run(file);
4906
+ }
3997
4907
  this.stmt(
3998
4908
  "DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
3999
4909
  ).run(file);
@@ -4011,14 +4921,22 @@ var IndexStore = class _IndexStore {
4011
4921
  upsertFile(meta) {
4012
4922
  this.runWithRetry(() => {
4013
4923
  this.stmt(
4014
- `INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
4015
- VALUES (?, ?, ?, ?, ?)
4924
+ `INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
4925
+ VALUES (?, ?, ?, ?, ?, ?)
4016
4926
  ON CONFLICT(file) DO UPDATE SET
4017
4927
  lang = excluded.lang,
4018
4928
  mtime_ms = excluded.mtime_ms,
4929
+ content_hash = excluded.content_hash,
4019
4930
  symbol_count = excluded.symbol_count,
4020
4931
  last_indexed = excluded.last_indexed`
4021
- ).run(meta.file, meta.lang, meta.mtimeMs, meta.symbolCount, meta.lastIndexed);
4932
+ ).run(
4933
+ meta.file,
4934
+ meta.lang,
4935
+ meta.mtimeMs,
4936
+ meta.contentHash ?? "",
4937
+ meta.symbolCount,
4938
+ meta.lastIndexed
4939
+ );
4022
4940
  });
4023
4941
  }
4024
4942
  getFileMeta(file) {
@@ -4185,9 +5103,18 @@ var IndexStore = class _IndexStore {
4185
5103
  if (mapped === null) return { results: [], total: 0 };
4186
5104
  effectiveKind = mapped;
4187
5105
  }
4188
- const match = tokens.map((t) => `"${t.replaceAll('"', "")}"*`).join(" OR ");
5106
+ const longTokens = tokens.filter((t) => t.length >= 3);
5107
+ const shortTokens = tokens.filter((t) => t.length < 3);
5108
+ if (longTokens.length === 0) {
5109
+ return this.searchRankedFallback(query, filter, safeLimit);
5110
+ }
5111
+ const match = longTokens.map((t) => `"${t.replaceAll('"', "")}"`).join(" OR ");
4189
5112
  const conditions = ["symbols_fts MATCH ?"];
4190
5113
  const values = [match];
5114
+ for (const shortTok of shortTokens) {
5115
+ conditions.push("s.text LIKE ? ESCAPE '\\'");
5116
+ values.push(`%${escapeLike(shortTok)}%`);
5117
+ }
4191
5118
  if (effectiveKind) {
4192
5119
  conditions.push("s.kind = ?");
4193
5120
  values.push(effectiveKind);
@@ -4206,7 +5133,7 @@ var IndexStore = class _IndexStore {
4206
5133
  ).all(...values);
4207
5134
  const total = countRows[0] ? Number(countRows[0].n) : 0;
4208
5135
  if (total === 0) return { results: [], total: 0 };
4209
- const rows = this.stmt(
5136
+ const bm25Rows = this.stmt(
4210
5137
  `SELECT s.id, s.lang, s.kind, s.name, s.file, s.line, s.col, s.signature, s.doc_comment,
4211
5138
  -bm25(symbols_fts) AS score,
4212
5139
  snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
@@ -4219,8 +5146,35 @@ var IndexStore = class _IndexStore {
4219
5146
  bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
4220
5147
  LIMIT ?`
4221
5148
  ).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
5149
+ if (this.vectorsAvailable && bm25Rows.length > 0) {
5150
+ const queryVec = embedText(query);
5151
+ const candidateIds = bm25Rows.map((r) => r.id);
5152
+ const placeholders = candidateIds.map(() => "?").join(",");
5153
+ const vecRows = this.stmt(
5154
+ `SELECT sv.symbol_id, sv.vector FROM symbol_vectors sv WHERE sv.symbol_id IN (${placeholders})`
5155
+ ).all(...candidateIds);
5156
+ const vecScores = vecRows.map((r) => ({
5157
+ id: r.symbol_id,
5158
+ sim: cosineSimilarity(queryVec, decodeVector(r.vector))
5159
+ })).sort((a, b) => b.sim - a.sim);
5160
+ const bm25Rank = /* @__PURE__ */ new Map();
5161
+ bm25Rows.forEach((r, i) => bm25Rank.set(r.id, i));
5162
+ const vecRank = /* @__PURE__ */ new Map();
5163
+ vecScores.forEach((r, i) => vecRank.set(r.id, i));
5164
+ const fused = reciprocalRankFusion(bm25Rank, vecRank, 60);
5165
+ const fusedScore = new Map(fused);
5166
+ const sorted = [...bm25Rows].sort(
5167
+ (a, b) => (fusedScore.get(b.id) ?? 0) - (fusedScore.get(a.id) ?? 0)
5168
+ );
5169
+ return {
5170
+ results: sorted.map(
5171
+ (row) => mapWriterSearchRow(row, filter?.lspKind, Math.max(1e-4, row.score), row.snippet)
5172
+ ),
5173
+ total
5174
+ };
5175
+ }
4222
5176
  return {
4223
- results: rows.map(
5177
+ results: bm25Rows.map(
4224
5178
  (row) => mapWriterSearchRow(row, filter?.lspKind, Math.max(1e-4, row.score), row.snippet)
4225
5179
  ),
4226
5180
  total
@@ -4348,6 +5302,7 @@ var IndexStore = class _IndexStore {
4348
5302
  this.db.exec("DROP TABLE IF EXISTS files");
4349
5303
  this.db.exec("DROP TABLE IF EXISTS metadata");
4350
5304
  if (this.ftsAvailable) this.db.exec("DROP TABLE IF EXISTS symbols_fts");
5305
+ this.db.exec("DROP TABLE IF EXISTS symbol_vectors");
4351
5306
  this.db.exec("COMMIT");
4352
5307
  this.stmtCache.clear();
4353
5308
  this.initSchema();
@@ -4435,6 +5390,11 @@ var IndexStore = class _IndexStore {
4435
5390
  `DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
4436
5391
  ).run(...options.deleteForFiles);
4437
5392
  }
5393
+ if (this.vectorsAvailable) {
5394
+ this.stmt(
5395
+ `DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
5396
+ ).run(...options.deleteForFiles);
5397
+ }
4438
5398
  this.stmt(
4439
5399
  `DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
4440
5400
  ).run(...options.deleteForFiles);
@@ -4448,6 +5408,7 @@ var IndexStore = class _IndexStore {
4448
5408
  const refsToInsert = [];
4449
5409
  const bulkSyms = [];
4450
5410
  const ftsRows = [];
5411
+ const vectorRows = [];
4451
5412
  for (const entry of entries) {
4452
5413
  const insertedForEntry = [];
4453
5414
  for (const s of entry.symbols) {
@@ -4471,6 +5432,10 @@ var IndexStore = class _IndexStore {
4471
5432
  text: buildIndexableText(s.name, s.signature, s.docComment)
4472
5433
  });
4473
5434
  }
5435
+ vectorRows.push({
5436
+ id,
5437
+ vector: encodeVector(embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment)))
5438
+ });
4474
5439
  const inserted = { ...s, id };
4475
5440
  allInserted.push(inserted);
4476
5441
  insertedForEntry.push(inserted);
@@ -4484,19 +5449,34 @@ var IndexStore = class _IndexStore {
4484
5449
  this.ftsAvailable,
4485
5450
  ftsRows
4486
5451
  );
5452
+ if (this.vectorsAvailable) {
5453
+ bulkInsertVectorsWithStatement(
5454
+ (sql) => this.stmt(sql),
5455
+ _IndexStore.MAX_SQL_VARS,
5456
+ vectorRows
5457
+ );
5458
+ }
4487
5459
  bulkInsertRefsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, refsToInsert);
4488
5460
  const upsertStmt = this.stmt(
4489
- `INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
4490
- VALUES (?, ?, ?, ?, ?)
5461
+ `INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
5462
+ VALUES (?, ?, ?, ?, ?, ?)
4491
5463
  ON CONFLICT(file) DO UPDATE SET
4492
5464
  lang = excluded.lang,
4493
5465
  mtime_ms = excluded.mtime_ms,
5466
+ content_hash = excluded.content_hash,
4494
5467
  symbol_count = excluded.symbol_count,
4495
5468
  last_indexed = excluded.last_indexed`
4496
5469
  );
4497
5470
  const now = Date.now();
4498
5471
  for (const entry of entries) {
4499
- upsertStmt.run(entry.file, entry.lang, entry.mtimeMs, entry.symbolCount, now);
5472
+ upsertStmt.run(
5473
+ entry.file,
5474
+ entry.lang,
5475
+ entry.mtimeMs,
5476
+ entry.contentHash ?? "",
5477
+ entry.symbolCount,
5478
+ now
5479
+ );
4500
5480
  }
4501
5481
  this.resolveRefsForNamesUnsafe(affectedNames);
4502
5482
  this.db.exec("COMMIT");
@@ -4587,19 +5567,32 @@ var IndexStore = class _IndexStore {
4587
5567
  "DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
4588
5568
  ).run(meta.file);
4589
5569
  }
5570
+ if (this.vectorsAvailable) {
5571
+ this.stmt(
5572
+ "DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
5573
+ ).run(meta.file);
5574
+ }
4590
5575
  this.stmt(
4591
5576
  "DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
4592
5577
  ).run(meta.file);
4593
5578
  this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(meta.file);
4594
5579
  this.stmt(
4595
- `INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
4596
- VALUES (?, ?, ?, ?, ?)
5580
+ `INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
5581
+ VALUES (?, ?, ?, ?, ?, ?)
4597
5582
  ON CONFLICT(file) DO UPDATE SET
4598
5583
  lang = excluded.lang,
4599
5584
  mtime_ms = excluded.mtime_ms,
5585
+ content_hash = excluded.content_hash,
4600
5586
  symbol_count = excluded.symbol_count,
4601
5587
  last_indexed = excluded.last_indexed`
4602
- ).run(meta.file, meta.lang, meta.mtimeMs, meta.symbolCount, meta.lastIndexed);
5588
+ ).run(
5589
+ meta.file,
5590
+ meta.lang,
5591
+ meta.mtimeMs,
5592
+ meta.contentHash ?? "",
5593
+ meta.symbolCount,
5594
+ meta.lastIndexed
5595
+ );
4603
5596
  this.resolveRefsForNamesUnsafe(affectedNames);
4604
5597
  this.db.exec("COMMIT");
4605
5598
  } catch (err) {
@@ -4662,6 +5655,31 @@ var IndexStore = class _IndexStore {
4662
5655
  findOutgoingCallsByName(symbolName, file, limit = 100) {
4663
5656
  return findOutgoingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
4664
5657
  }
5658
+ /**
5659
+ * Transitive incoming-call tree: all symbols that transitively call the
5660
+ * target, to an unbounded depth (cycle-safe via SQL UNION deduplication).
5661
+ * Used by `codebase-incoming-calls` when the caller wants the full call
5662
+ * chain rather than just direct callers.
5663
+ */
5664
+ findTransitiveIncomingCallsByName(symbolName, file, limit = 200) {
5665
+ return findTransitiveIncomingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
5666
+ }
5667
+ /**
5668
+ * Transitive outgoing-call tree: all symbols the target transitively calls.
5669
+ * Used by `codebase-outgoing-calls` when the caller wants the full
5670
+ * dependency chain rather than just direct callees.
5671
+ */
5672
+ findTransitiveOutgoingCallsByName(symbolName, file, limit = 200) {
5673
+ return findTransitiveOutgoingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
5674
+ }
5675
+ /**
5676
+ * Compute the set of symbol IDs reachable from the given seed IDs using a
5677
+ * native SQLite recursive CTE. Used by dead-code detection to replace the
5678
+ * in-memory BFS.
5679
+ */
5680
+ findReachableSymbolIds(seedIds) {
5681
+ return findReachableSymbolIds((sql) => this.stmt(sql), seedIds);
5682
+ }
4665
5683
  /**
4666
5684
  * Find all references TO a given symbol (who calls / uses this symbol?).
4667
5685
  */
@@ -4924,6 +5942,12 @@ var ProjectServerConnection = class {
4924
5942
  endpoint;
4925
5943
  socket = null;
4926
5944
  buffer = "";
5945
+ /** P6: binary frame buffer — accumulates raw bytes when in binary mode. */
5946
+ binaryBuffer = [];
5947
+ /** P6: StringDecoder for safe UTF-8 multibyte handling in JSON mode. */
5948
+ textDecoder = null;
5949
+ /** P6: true once the server advertises binary support and client accepts. */
5950
+ useBinary = false;
4927
5951
  info = null;
4928
5952
  activity = null;
4929
5953
  health = null;
@@ -5062,6 +6086,8 @@ var ProjectServerConnection = class {
5062
6086
  this.info = null;
5063
6087
  this.activity = null;
5064
6088
  this.health = null;
6089
+ this.useBinary = false;
6090
+ this.binaryBuffer = [];
5065
6091
  this.connectReject?.(new Error("codebase-index client disconnected"));
5066
6092
  this.connectResolve = null;
5067
6093
  this.connectReject = null;
@@ -5190,10 +6216,12 @@ var ProjectServerConnection = class {
5190
6216
  this.activity = null;
5191
6217
  this.health = null;
5192
6218
  this.buffer = "";
6219
+ this.binaryBuffer = [];
6220
+ this.useBinary = false;
6221
+ this.textDecoder = null;
5193
6222
  return new Promise((resolve4, reject) => {
5194
6223
  const socket = net.createConnection(this.endpoint);
5195
6224
  this.socket = socket;
5196
- socket.setEncoding("utf8");
5197
6225
  const timer = setTimeout(() => {
5198
6226
  reject(new Error("codebase-index server handshake timed out"));
5199
6227
  socket.destroy();
@@ -5222,7 +6250,12 @@ var ProjectServerConnection = class {
5222
6250
  }
5223
6251
  onData(socket, chunk) {
5224
6252
  if (socket !== this.socket) return;
5225
- this.buffer += chunk;
6253
+ if (this.useBinary) {
6254
+ this.onBinaryData(socket, chunk);
6255
+ return;
6256
+ }
6257
+ if (!this.textDecoder) this.textDecoder = new StringDecoder("utf8");
6258
+ this.buffer += this.textDecoder.write(chunk);
5226
6259
  while (true) {
5227
6260
  const newline = this.buffer.indexOf("\n");
5228
6261
  if (newline < 0) {
@@ -5248,6 +6281,44 @@ var ProjectServerConnection = class {
5248
6281
  this.onMessage(message);
5249
6282
  }
5250
6283
  }
6284
+ /**
6285
+ * P6: Parse binary frames from the raw buffer.
6286
+ *
6287
+ * Each frame is: [1 byte magic 0x57] [4 bytes uint32 BE length] [payload].
6288
+ * The magic byte distinguishes binary from JSON — a JSON frame's first byte
6289
+ * is `{` (0x7B), so there is no ambiguity even in a mixed-mode buffer.
6290
+ */
6291
+ onBinaryData(socket, chunk) {
6292
+ this.binaryBuffer.push(chunk);
6293
+ const all = Buffer.concat(this.binaryBuffer);
6294
+ let offset = 0;
6295
+ while (offset + 5 <= all.length) {
6296
+ if (!isBinaryFrame(all[offset])) {
6297
+ this.useBinary = false;
6298
+ this.buffer += all.subarray(offset).toString("utf8");
6299
+ this.binaryBuffer = [];
6300
+ return;
6301
+ }
6302
+ const frameLen = all.readUInt32BE(offset + 1);
6303
+ if (frameLen > 256 * 1024 * 1024) {
6304
+ socket.destroy();
6305
+ this.transition("offline", { error: "binary frame length exceeds 256MB limit" });
6306
+ return;
6307
+ }
6308
+ const totalLen = 5 + frameLen;
6309
+ if (offset + totalLen > all.length) break;
6310
+ const payload = all.subarray(offset + 5, offset + 5 + frameLen);
6311
+ try {
6312
+ const message = decodeBinaryFrame(payload);
6313
+ this.onMessage(message);
6314
+ } catch {
6315
+ socket.destroy(new Error("invalid binary codebase-index server response"));
6316
+ return;
6317
+ }
6318
+ offset += totalLen;
6319
+ }
6320
+ this.binaryBuffer = offset < all.length ? [all.subarray(offset)] : [];
6321
+ }
5251
6322
  onMessage(message) {
5252
6323
  if (message.type === "hello") {
5253
6324
  if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
@@ -5267,6 +6338,7 @@ var ProjectServerConnection = class {
5267
6338
  }
5268
6339
  this.info = message;
5269
6340
  this.markResponsive();
6341
+ if (message.binarySupported) this.useBinary = true;
5270
6342
  this.transition("connected", { pid: message.pid });
5271
6343
  ensureHeartbeatLoop();
5272
6344
  this.connectResolve?.();
@@ -5325,7 +6397,12 @@ var ProjectServerConnection = class {
5325
6397
  }
5326
6398
  write(message) {
5327
6399
  const socket = this.socket;
5328
- if (socket && !socket.destroyed) socket.write(encodeProjectServerMessage(message));
6400
+ if (!socket || socket.destroyed) return;
6401
+ if (this.useBinary) {
6402
+ socket.write(encodeBinaryFrame(message));
6403
+ } else {
6404
+ socket.write(encodeProjectServerMessage(message));
6405
+ }
5329
6406
  }
5330
6407
  rejectStaleServer(message, reason) {
5331
6408
  const socket = this.socket;
@@ -5470,16 +6547,16 @@ function closeProjectIndexServerClients() {
5470
6547
  }
5471
6548
 
5472
6549
  // src/codebase-index/background-indexer.ts
5473
- import * as fs11 from "node:fs";
5474
- import { fileURLToPath as fileURLToPath3 } from "node:url";
5475
- import { Worker } from "node:worker_threads";
6550
+ import * as fs12 from "node:fs";
6551
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
6552
+ import { Worker as Worker2 } from "node:worker_threads";
5476
6553
 
5477
6554
  // src/codebase-index/indexer.ts
5478
6555
  import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
5479
6556
  import { execFile } from "node:child_process";
5480
- import * as fs10 from "node:fs/promises";
6557
+ import * as fs11 from "node:fs/promises";
5481
6558
  import { availableParallelism } from "node:os";
5482
- import * as path12 from "node:path";
6559
+ import * as path13 from "node:path";
5483
6560
  import {
5484
6561
  DEFAULT_WALK_IGNORE_DIRS,
5485
6562
  indexParallelBatchSize,
@@ -5977,32 +7054,56 @@ async function dispatch(file, content, lang) {
5977
7054
  case "tsx":
5978
7055
  case "js":
5979
7056
  case "jsx": {
5980
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
5981
- return parseSymbols8({ file, content, lang });
7057
+ const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
7058
+ return parseSymbols9({ file, content, lang });
5982
7059
  }
5983
7060
  case "go": {
5984
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
5985
- return parseSymbols8({ file, content, lang: "go" });
7061
+ const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
7062
+ return parseSymbols9({ file, content, lang: "go" });
5986
7063
  }
5987
7064
  case "py": {
5988
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
5989
- return parseSymbols8({ file, content, lang: "py" });
7065
+ const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
7066
+ return parseSymbols9({ file, content, lang: "py" });
5990
7067
  }
5991
7068
  case "rs": {
5992
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
5993
- return parseSymbols8({ file, content, lang: "rs" });
7069
+ const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
7070
+ return parseSymbols9({ file, content, lang: "rs" });
5994
7071
  }
5995
7072
  case "json": {
5996
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
5997
- return parseSymbols8({ file, content, lang: "json" });
7073
+ const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
7074
+ return parseSymbols9({ file, content, lang: "json" });
5998
7075
  }
5999
7076
  case "yaml": {
6000
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
6001
- return parseSymbols8({ file, content, lang: "yaml" });
7077
+ const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
7078
+ return parseSymbols9({ file, content, lang: "yaml" });
7079
+ }
7080
+ // Phase 1: ten languages now route through the Tree-Sitter WASM
7081
+ // universal parser (`tree-sitter-parser.ts`). The dispatch falls back to
7082
+ // the regex extractor in `generic-parser.ts` whenever WASM loading fails
7083
+ // or the parser returns zero symbols — preserving the indexable-file
7084
+ // contract that "missing a parser must never mean skipping the file".
7085
+ case "c":
7086
+ case "cpp":
7087
+ case "java":
7088
+ case "csharp":
7089
+ case "php":
7090
+ case "ruby":
7091
+ case "swift":
7092
+ case "kotlin":
7093
+ case "shell":
7094
+ case "elixir": {
7095
+ try {
7096
+ const { parseSymbols: parseSymbols10 } = await Promise.resolve().then(() => (init_tree_sitter_parser(), tree_sitter_parser_exports));
7097
+ const parsed = await parseSymbols10({ file, content, lang });
7098
+ if (parsed.symbols.length > 0) return parsed;
7099
+ } catch {
7100
+ }
7101
+ const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
7102
+ return parseSymbols9({ file, content, lang });
6002
7103
  }
6003
7104
  default: {
6004
- const { parseSymbols: parseSymbols8 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
6005
- return parseSymbols8({ file, content, lang });
7105
+ const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
7106
+ return parseSymbols9({ file, content, lang });
6006
7107
  }
6007
7108
  }
6008
7109
  }
@@ -6014,6 +7115,261 @@ function withRelations(parsed, content, lang) {
6014
7115
  return { ...parsed, refs: refs.map((ref) => ref.lang ? ref : { ...ref, lang }) };
6015
7116
  }
6016
7117
 
7118
+ // src/codebase-index/parser-worker-pool.ts
7119
+ import { Worker } from "node:worker_threads";
7120
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
7121
+ import * as fs10 from "node:fs";
7122
+ var WORKER_POOL_THRESHOLD = 500;
7123
+ var ParserWorkerPool = class {
7124
+ constructor(maxWorkers = defaultWorkerCount()) {
7125
+ this.maxWorkers = maxWorkers;
7126
+ }
7127
+ maxWorkers;
7128
+ workers = [];
7129
+ nextBatchId = 1;
7130
+ pending = /* @__PURE__ */ new Map();
7131
+ creating = false;
7132
+ unavailable = false;
7133
+ /**
7134
+ * True if the pool is available for use. Returns false when:
7135
+ * - Worker threads aren't supported (sandbox, exotic runtime)
7136
+ * - The built worker script can't be found
7137
+ * - Pool creation was attempted and failed
7138
+ */
7139
+ isAvailable() {
7140
+ return !this.unavailable && this.workers.length > 0;
7141
+ }
7142
+ /**
7143
+ * Lazily create the worker pool. Returns true if the pool is ready, false
7144
+ * if it's unavailable (caller should fall back to inline parsing).
7145
+ */
7146
+ async ensureReady() {
7147
+ if (this.isAvailable()) return true;
7148
+ if (this.unavailable) return false;
7149
+ if (this.creating) {
7150
+ await new Promise((r) => setTimeout(r, 50));
7151
+ return this.isAvailable();
7152
+ }
7153
+ this.creating = true;
7154
+ try {
7155
+ const url = resolveWorkerScriptUrl();
7156
+ if (!url) {
7157
+ this.unavailable = true;
7158
+ return false;
7159
+ }
7160
+ for (let i = 0; i < this.maxWorkers; i++) {
7161
+ try {
7162
+ const w = new Worker(url, { name: `wstack-parser-${i}` });
7163
+ w.unref();
7164
+ w.on("message", (msg) => this.handleMessage(msg));
7165
+ w.on("error", (err) => this.handleError(err, w));
7166
+ this.workers.push({ worker: w, busy: false });
7167
+ } catch {
7168
+ if (this.workers.length === 0) {
7169
+ this.unavailable = true;
7170
+ return false;
7171
+ }
7172
+ break;
7173
+ }
7174
+ }
7175
+ return this.workers.length > 0;
7176
+ } finally {
7177
+ this.creating = false;
7178
+ }
7179
+ }
7180
+ /**
7181
+ * Parse files in parallel across the worker pool. Returns a flat
7182
+ * `FileSymbols[]` in completion order (caller sorts if needed).
7183
+ *
7184
+ * Content is pre-read by the main thread (for the content-hash check)
7185
+ * and passed to workers to avoid a second disk read. Files are
7186
+ * distributed round-robin across workers.
7187
+ */
7188
+ async parseFiles(files) {
7189
+ if (!this.isAvailable()) {
7190
+ throw new Error("ParserWorkerPool.parseFiles called before ensureReady() succeeded");
7191
+ }
7192
+ if (files.length === 0) return [];
7193
+ const batchId = this.nextBatchId++;
7194
+ const workerCount = Math.min(this.workers.length, files.length);
7195
+ const chunks = Array.from(
7196
+ { length: workerCount },
7197
+ () => []
7198
+ );
7199
+ for (let i = 0; i < files.length; i++) {
7200
+ chunks[i % workerCount].push(files[i]);
7201
+ }
7202
+ return new Promise((resolve4, reject) => {
7203
+ this.pending.set(batchId, {
7204
+ resolve: resolve4,
7205
+ reject,
7206
+ accumulated: [],
7207
+ expectedWorkers: workerCount,
7208
+ completedWorkers: 0
7209
+ });
7210
+ for (let i = 0; i < workerCount; i++) {
7211
+ const pw = this.workers[i];
7212
+ pw.busy = true;
7213
+ pw.worker.postMessage({
7214
+ type: "parse",
7215
+ id: batchId,
7216
+ files: chunks[i]
7217
+ });
7218
+ }
7219
+ });
7220
+ }
7221
+ /** Shut down all workers. Safe to call multiple times. */
7222
+ async shutdown() {
7223
+ const workers = this.workers.map((w) => w.worker);
7224
+ this.workers = [];
7225
+ this.unavailable = false;
7226
+ for (const w of workers) {
7227
+ try {
7228
+ w.postMessage({ type: "shutdown" });
7229
+ } catch {
7230
+ }
7231
+ }
7232
+ await Promise.allSettled(
7233
+ workers.map(
7234
+ (w) => Promise.race([
7235
+ new Promise((resolve4) => {
7236
+ w.once("exit", () => resolve4());
7237
+ }),
7238
+ new Promise((resolve4) => setTimeout(() => resolve4(), 2e3))
7239
+ ]).then(() => {
7240
+ if (!w.threadId) return;
7241
+ return w.terminate().catch(() => {
7242
+ });
7243
+ })
7244
+ )
7245
+ );
7246
+ for (const [, p] of this.pending) p.reject(new Error("ParserWorkerPool shut down"));
7247
+ this.pending.clear();
7248
+ }
7249
+ handleMessage(msg) {
7250
+ const batch = this.pending.get(msg.id);
7251
+ if (!batch) return;
7252
+ batch.accumulated.push(...msg.results);
7253
+ batch.completedWorkers++;
7254
+ const freeWorker = this.workers.find((w) => w.busy);
7255
+ if (freeWorker) freeWorker.busy = false;
7256
+ if (batch.completedWorkers >= batch.expectedWorkers) {
7257
+ this.pending.delete(msg.id);
7258
+ batch.resolve(batch.accumulated);
7259
+ }
7260
+ }
7261
+ handleError(err, source) {
7262
+ this.workers = this.workers.filter((w) => w.worker !== source);
7263
+ if (this.workers.length === 0) {
7264
+ for (const [, p] of this.pending) p.reject(err);
7265
+ this.pending.clear();
7266
+ this.unavailable = true;
7267
+ }
7268
+ }
7269
+ };
7270
+ function defaultWorkerCount() {
7271
+ const cores = globalThis.navigator?.hardwareConcurrency ?? 4;
7272
+ return Math.max(1, Math.min(4, cores - 1));
7273
+ }
7274
+ function resolveWorkerScriptUrl() {
7275
+ for (const rel of [
7276
+ "./parser-worker-script.js",
7277
+ "./codebase-index/parser-worker-script.js"
7278
+ ]) {
7279
+ try {
7280
+ const url = new URL(rel, import.meta.url);
7281
+ if (url.protocol === "file:" && fs10.existsSync(fileURLToPath4(url))) return url;
7282
+ } catch {
7283
+ }
7284
+ }
7285
+ return null;
7286
+ }
7287
+ var _pool = null;
7288
+ function getParserPool() {
7289
+ _pool ??= new ParserWorkerPool();
7290
+ return _pool;
7291
+ }
7292
+
7293
+ // src/codebase-index/content-hash.ts
7294
+ var PRIME64_1 = 0x9e3779b185ebca87n;
7295
+ var PRIME64_2 = 0xc2b2ae3d27d4eb4fn;
7296
+ var PRIME64_3 = 0x165667b19e3779f9n;
7297
+ var PRIME64_4 = 0x85ebca77c2b2ae63n;
7298
+ var PRIME64_5 = 0x27d4eb2f165667c5n;
7299
+ var MASK64 = 0xffffffffffffffffn;
7300
+ function mul64(a, b) {
7301
+ return (a & MASK64) * (b & MASK64) & MASK64;
7302
+ }
7303
+ function rotl64(x, n) {
7304
+ const v = x & MASK64;
7305
+ return (v << BigInt(n) | v >> BigInt(64 - n)) & MASK64;
7306
+ }
7307
+ function readU64LE(buf, off) {
7308
+ let v = 0n;
7309
+ for (let i = 7; i >= 0; i--) {
7310
+ v = v << 8n | BigInt(buf[off + i] ?? 0);
7311
+ }
7312
+ return v & MASK64;
7313
+ }
7314
+ function readU32LE(buf, off) {
7315
+ return (BigInt(buf[off] ?? 0) | BigInt(buf[off + 1] ?? 0) << 8n | BigInt(buf[off + 2] ?? 0) << 16n | BigInt(buf[off + 3] ?? 0) << 24n) & MASK64;
7316
+ }
7317
+ function xxh64Round(acc, lane) {
7318
+ return mul64(rotl64(acc + mul64(lane, PRIME64_2) & MASK64, 31), PRIME64_1);
7319
+ }
7320
+ function xxh64MergeRound(acc, val) {
7321
+ return mul64(acc ^ xxh64Round(0n, val), PRIME64_1) + PRIME64_4 & MASK64;
7322
+ }
7323
+ function xxhash64Hex(buf, explicitLen) {
7324
+ const length = explicitLen ?? buf.length;
7325
+ let h;
7326
+ let off = 0;
7327
+ if (length >= 32) {
7328
+ let v1 = PRIME64_1 + PRIME64_2 & MASK64;
7329
+ let v2 = PRIME64_2;
7330
+ let v3 = 0n;
7331
+ let v4 = 0n - PRIME64_1 & MASK64;
7332
+ const end32 = length - 32;
7333
+ while (off <= end32) {
7334
+ v1 = xxh64Round(v1, readU64LE(buf, off));
7335
+ v2 = xxh64Round(v2, readU64LE(buf, off + 8));
7336
+ v3 = xxh64Round(v3, readU64LE(buf, off + 16));
7337
+ v4 = xxh64Round(v4, readU64LE(buf, off + 24));
7338
+ off += 32;
7339
+ }
7340
+ h = rotl64(v1, 1) + rotl64(v2, 7) + rotl64(v3, 12) + rotl64(v4, 18) & MASK64;
7341
+ h = xxh64MergeRound(h, v1);
7342
+ h = xxh64MergeRound(h, v2);
7343
+ h = xxh64MergeRound(h, v3);
7344
+ h = xxh64MergeRound(h, v4);
7345
+ } else {
7346
+ h = PRIME64_5;
7347
+ }
7348
+ h = h + BigInt(length) & MASK64;
7349
+ while (off + 8 <= length) {
7350
+ const k1 = xxh64Round(0n, readU64LE(buf, off));
7351
+ h = mul64(rotl64(h ^ k1, 27), PRIME64_1) + PRIME64_4 & MASK64;
7352
+ off += 8;
7353
+ }
7354
+ if (off + 4 <= length) {
7355
+ h = mul64(rotl64(h ^ mul64(readU32LE(buf, off), PRIME64_1), 23), PRIME64_2) + PRIME64_3 & MASK64;
7356
+ off += 4;
7357
+ }
7358
+ while (off < length) {
7359
+ h = mul64(rotl64(h ^ mul64(BigInt(buf[off] ?? 0), PRIME64_5), 11), PRIME64_1) & MASK64;
7360
+ off += 1;
7361
+ }
7362
+ h = (h ^ h >> 33n) & MASK64;
7363
+ h = mul64(h, PRIME64_2);
7364
+ h = (h ^ h >> 29n) & MASK64;
7365
+ h = mul64(h, PRIME64_3);
7366
+ h = (h ^ h >> 32n) & MASK64;
7367
+ return h.toString(16).padStart(16, "0");
7368
+ }
7369
+ function xxhash64String(content) {
7370
+ return xxhash64Hex(new TextEncoder().encode(content));
7371
+ }
7372
+
6017
7373
  // src/codebase-index/indexer.ts
6018
7374
  var YIELD_EVERY_N = 50;
6019
7375
  function resolveParallelBatch() {
@@ -6036,15 +7392,15 @@ var INDEXABLE_EXTENSION_SET = new Set(INDEXABLE_EXTENSIONS);
6036
7392
  var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
6037
7393
  var MAX_GIT_FILE_LIST_BYTES = 64 * 1024 * 1024;
6038
7394
  function isWithinProject(projectRoot, file) {
6039
- const rel = path12.relative(projectRoot, file);
6040
- return rel !== "" && !rel.startsWith(`..${path12.sep}`) && rel !== ".." && !path12.isAbsolute(rel);
7395
+ const rel = path13.relative(projectRoot, file);
7396
+ return rel !== "" && !rel.startsWith(`..${path13.sep}`) && rel !== ".." && !path13.isAbsolute(rel);
6041
7397
  }
6042
7398
  function isMissingPathError(err) {
6043
7399
  const code = err?.code;
6044
7400
  return code === "ENOENT" || code === "ENOTDIR";
6045
7401
  }
6046
7402
  function normalizeComparablePath(value) {
6047
- const resolved = path12.resolve(value);
7403
+ const resolved = path13.resolve(value);
6048
7404
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
6049
7405
  }
6050
7406
  function gitOutput(projectRoot, args) {
@@ -6089,24 +7445,24 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
6089
7445
  const record = statusRecords[i];
6090
7446
  if (!record) continue;
6091
7447
  const status = record.slice(0, 2);
6092
- const changedPath = path12.resolve(projectRoot, record.slice(3));
7448
+ const changedPath = path13.resolve(projectRoot, record.slice(3));
6093
7449
  dirty.add(changedPath);
6094
7450
  if (status.includes("D")) deleted.add(changedPath);
6095
7451
  if (status.includes("R") || status.includes("C")) {
6096
7452
  const source = statusRecords[++i];
6097
- if (source) dirty.add(path12.resolve(projectRoot, source));
7453
+ if (source) dirty.add(path13.resolve(projectRoot, source));
6098
7454
  }
6099
7455
  }
6100
7456
  const files = [];
6101
7457
  for (const relative2 of output.toString("utf8").split("\0")) {
6102
7458
  if (!relative2) continue;
6103
7459
  const portable = relative2.replace(/\\/g, "/");
6104
- if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path12.posix.basename(portable))) {
7460
+ if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path13.posix.basename(portable))) {
6105
7461
  continue;
6106
7462
  }
6107
- const full = path12.resolve(projectRoot, relative2);
7463
+ const full = path13.resolve(projectRoot, relative2);
6108
7464
  if (deleted.has(full)) continue;
6109
- const ext = path12.extname(relative2).toLowerCase();
7465
+ const ext = path13.extname(relative2).toLowerCase();
6110
7466
  if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
6111
7467
  }
6112
7468
  return {
@@ -6141,7 +7497,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
6141
7497
  }
6142
7498
  let entries;
6143
7499
  try {
6144
- entries = await fs10.readdir(dir, { withFileTypes: true });
7500
+ entries = await fs11.readdir(dir, { withFileTypes: true });
6145
7501
  } catch (err) {
6146
7502
  complete = false;
6147
7503
  errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
@@ -6150,14 +7506,14 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
6150
7506
  dirCount++;
6151
7507
  for (const e of entries) {
6152
7508
  if (ignoreSet.has(e.name)) continue;
6153
- const full = path12.join(dir, e.name);
6154
- const rel = path12.relative(projectRoot, full).replace(/\\/g, "/");
7509
+ const full = path13.join(dir, e.name);
7510
+ const rel = path13.relative(projectRoot, full).replace(/\\/g, "/");
6155
7511
  if (e.isDirectory()) {
6156
7512
  if (isGitIgnored(rel, true)) continue;
6157
7513
  await walk(full);
6158
7514
  } else if (e.isFile()) {
6159
7515
  if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
6160
- const ext = path12.extname(e.name).toLowerCase();
7516
+ const ext = path13.extname(e.name).toLowerCase();
6161
7517
  if (indexableExts.has(ext) || detectLang(full) !== null) {
6162
7518
  results.push(full);
6163
7519
  }
@@ -6242,10 +7598,10 @@ async function runIndexerWithStore(store, opts) {
6242
7598
  let discoveryComplete = true;
6243
7599
  let trustedUnchanged;
6244
7600
  if (opts.files && opts.files.length > 0) {
6245
- files = opts.files.map((f) => path12.resolve(projectRoot, f)).filter((f) => {
7601
+ files = opts.files.map((f) => path13.resolve(projectRoot, f)).filter((f) => {
6246
7602
  if (!isWithinProject(projectRoot, f)) return false;
6247
- const rel = path12.relative(projectRoot, f).replace(/\\/g, "/");
6248
- return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path12.basename(f)) && !isGitIgnored(rel, false);
7603
+ const rel = path13.relative(projectRoot, f).replace(/\\/g, "/");
7604
+ return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path13.basename(f)) && !isGitIgnored(rel, false);
6249
7605
  });
6250
7606
  } else {
6251
7607
  const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
@@ -6302,7 +7658,7 @@ async function runIndexerWithStore(store, opts) {
6302
7658
  async (file) => {
6303
7659
  let stat2;
6304
7660
  try {
6305
- stat2 = await fs10.stat(file, statOpts);
7661
+ stat2 = await fs11.stat(file, statOpts);
6306
7662
  } catch (e) {
6307
7663
  if (isAbortError(e)) throw e;
6308
7664
  return {
@@ -6332,7 +7688,7 @@ async function runIndexerWithStore(store, opts) {
6332
7688
  }
6333
7689
  let content;
6334
7690
  try {
6335
- content = await fs10.readFile(file, { encoding: "utf8", signal });
7691
+ content = await fs11.readFile(file, { encoding: "utf8", signal });
6336
7692
  } catch (e) {
6337
7693
  if (isAbortError(e)) throw e;
6338
7694
  return {
@@ -6343,22 +7699,78 @@ async function runIndexerWithStore(store, opts) {
6343
7699
  error: `read error: ${e instanceof Error ? e.message : String(e)}`
6344
7700
  };
6345
7701
  }
6346
- let parsed;
6347
- try {
6348
- parsed = await parseFileContent(file, content, lang);
6349
- } catch (e) {
7702
+ const contentHash = xxhash64String(content);
7703
+ if (!force && meta && meta.contentHash && contentHash === meta.contentHash) {
6350
7704
  return {
6351
7705
  file,
6352
7706
  stat: stat2,
6353
7707
  lang,
6354
7708
  parsed: null,
6355
- error: `parse error: ${e instanceof Error ? e.message : String(e)}`
7709
+ content,
7710
+ contentHash,
7711
+ skippedMeta: { ...meta, mtimeMs: Math.floor(stat2.mtimeMs) }
6356
7712
  };
6357
7713
  }
6358
- return { file, stat: stat2, lang, parsed, content };
7714
+ return { file, stat: stat2, lang, parsed: null, content, contentHash };
6359
7715
  }
6360
7716
  )
6361
7717
  );
7718
+ const toParse = [];
7719
+ for (let pi = 0; pi < statReadParse.length; pi++) {
7720
+ const s = statReadParse[pi];
7721
+ if (s.status !== "fulfilled") continue;
7722
+ const r = s.value;
7723
+ if (r.error || r.skippedMeta || !r.lang || r.parsed) continue;
7724
+ if (r.content === void 0) continue;
7725
+ toParse.push({
7726
+ index: pi,
7727
+ file: batchFiles[pi],
7728
+ content: r.content,
7729
+ lang: r.lang
7730
+ });
7731
+ }
7732
+ if (toParse.length > 0) {
7733
+ let pool = toParse.length >= WORKER_POOL_THRESHOLD ? getParserPool() : null;
7734
+ if (pool) {
7735
+ try {
7736
+ await pool.ensureReady();
7737
+ const parsedResults = await pool.parseFiles(
7738
+ toParse.map((p) => ({ file: p.file, content: p.content, lang: p.lang }))
7739
+ );
7740
+ const byFile = new Map(parsedResults.map((r) => [r.file, r]));
7741
+ for (const item of toParse) {
7742
+ const parsed = byFile.get(item.file);
7743
+ const settled = statReadParse[item.index];
7744
+ if (settled.status !== "fulfilled") continue;
7745
+ if (parsed) {
7746
+ settled.value.parsed = parsed;
7747
+ } else {
7748
+ settled.value.error = `parse error: worker returned no result for ${item.file}`;
7749
+ }
7750
+ }
7751
+ } catch {
7752
+ pool = null;
7753
+ }
7754
+ }
7755
+ if (!pool) {
7756
+ await Promise.all(
7757
+ toParse.map(async (item) => {
7758
+ try {
7759
+ const parsed = await parseFileContent(item.file, item.content, item.lang);
7760
+ const settled = statReadParse[item.index];
7761
+ if (settled.status === "fulfilled") {
7762
+ settled.value.parsed = parsed;
7763
+ }
7764
+ } catch (e) {
7765
+ const settled = statReadParse[item.index];
7766
+ if (settled.status === "fulfilled") {
7767
+ settled.value.error = `parse error: ${e instanceof Error ? e.message : String(e)}`;
7768
+ }
7769
+ }
7770
+ })
7771
+ );
7772
+ }
7773
+ }
6362
7774
  const batchEntries = [];
6363
7775
  const deleteForFiles = [];
6364
7776
  for (let fi = 0; fi < statReadParse.length; fi++) {
@@ -6381,6 +7793,17 @@ async function runIndexerWithStore(store, opts) {
6381
7793
  langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
6382
7794
  symbolsIndexed += result.skippedMeta.symbolCount;
6383
7795
  filesIndexed++;
7796
+ const stored = existingMeta.get(file);
7797
+ if (stored && stored.mtimeMs !== result.skippedMeta.mtimeMs) {
7798
+ store.upsertFile({
7799
+ file,
7800
+ lang,
7801
+ mtimeMs: result.skippedMeta.mtimeMs,
7802
+ symbolCount: result.skippedMeta.symbolCount,
7803
+ lastIndexed: Date.now(),
7804
+ contentHash: result.skippedMeta.contentHash
7805
+ });
7806
+ }
6384
7807
  continue;
6385
7808
  }
6386
7809
  if (!lang || !parsed) {
@@ -6390,7 +7813,8 @@ async function runIndexerWithStore(store, opts) {
6390
7813
  lang,
6391
7814
  mtimeMs: Math.floor(stat2.mtimeMs),
6392
7815
  symbolCount: 0,
6393
- lastIndexed: Date.now()
7816
+ lastIndexed: Date.now(),
7817
+ contentHash: result.contentHash ?? ""
6394
7818
  });
6395
7819
  filesIndexed++;
6396
7820
  }
@@ -6402,7 +7826,8 @@ async function runIndexerWithStore(store, opts) {
6402
7826
  lang,
6403
7827
  mtimeMs: Math.floor(stat2.mtimeMs),
6404
7828
  symbolCount: 0,
6405
- lastIndexed: Date.now()
7829
+ lastIndexed: Date.now(),
7830
+ contentHash: result.contentHash ?? ""
6406
7831
  });
6407
7832
  filesIndexed++;
6408
7833
  continue;
@@ -6413,7 +7838,8 @@ async function runIndexerWithStore(store, opts) {
6413
7838
  symbols: parsed.symbols,
6414
7839
  refs: parsed.refs ?? [],
6415
7840
  mtimeMs: Math.floor(stat2.mtimeMs),
6416
- symbolCount: parsed.symbols.length
7841
+ symbolCount: parsed.symbols.length,
7842
+ contentHash: result.contentHash ?? ""
6417
7843
  });
6418
7844
  deleteForFiles.push(file);
6419
7845
  }
@@ -6450,7 +7876,8 @@ async function runIndexerWithStore(store, opts) {
6450
7876
  lang: entry.lang,
6451
7877
  mtimeMs: entry.mtimeMs,
6452
7878
  symbolCount: entry.symbolCount,
6453
- lastIndexed: Date.now()
7879
+ lastIndexed: Date.now(),
7880
+ contentHash: entry.contentHash
6454
7881
  });
6455
7882
  } catch (innerErr) {
6456
7883
  errors.push(
@@ -6562,6 +7989,9 @@ function symbolGraphService(args) {
6562
7989
  function incomingCallsService(args) {
6563
7990
  const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
6564
7991
  try {
7992
+ if (args.transitive) {
7993
+ return store.findTransitiveIncomingCallsByName(args.symbol, args.file, args.limit ?? 100);
7994
+ }
6565
7995
  return store.findIncomingCallsByName(args.symbol, args.file, args.limit ?? 100);
6566
7996
  } finally {
6567
7997
  indexStorePool.release(store);
@@ -6570,6 +8000,9 @@ function incomingCallsService(args) {
6570
8000
  function outgoingCallsService(args) {
6571
8001
  const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
6572
8002
  try {
8003
+ if (args.transitive) {
8004
+ return store.findTransitiveOutgoingCallsByName(args.symbol, args.file, args.limit ?? 100);
8005
+ }
6573
8006
  return store.findOutgoingCallsByName(args.symbol, args.file, args.limit ?? 100);
6574
8007
  } finally {
6575
8008
  indexStorePool.release(store);
@@ -6632,7 +8065,7 @@ function resolveWorkerUrl() {
6632
8065
  for (const rel of ["./worker.js", "./codebase-index/worker.js"]) {
6633
8066
  try {
6634
8067
  const url = new URL(rel, import.meta.url);
6635
- if (url.protocol === "file:" && fs11.existsSync(fileURLToPath3(url))) return url;
8068
+ if (url.protocol === "file:" && fs12.existsSync(fileURLToPath5(url))) return url;
6636
8069
  } catch {
6637
8070
  }
6638
8071
  }
@@ -6652,7 +8085,7 @@ function ensureWorker() {
6652
8085
  return null;
6653
8086
  }
6654
8087
  try {
6655
- const w = new Worker(url, { name: "wstack-codebase-index" });
8088
+ const w = new Worker2(url, { name: "wstack-codebase-index" });
6656
8089
  w.unref();
6657
8090
  w.on("message", (msg) => {
6658
8091
  if (msg.type === "progress") {
@@ -7125,6 +8558,11 @@ var codebaseIncomingCallsTool = {
7125
8558
  description: "Maximum call sites to return (default 50, max 200)",
7126
8559
  minimum: 1,
7127
8560
  maximum: 200
8561
+ },
8562
+ transitive: {
8563
+ type: "boolean",
8564
+ description: "When true, traverse the full transitive call chain (callers of callers, to unlimited depth). Default false: return only direct callers. Cycle-safe via SQL recursive CTE.",
8565
+ default: false
7128
8566
  }
7129
8567
  },
7130
8568
  required: ["symbol"]
@@ -7150,13 +8588,15 @@ var codebaseIncomingCallsTool = {
7150
8588
  };
7151
8589
  }
7152
8590
  const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
8591
+ const transitive = input.transitive === true;
7153
8592
  const { calls, symbolFound, ambiguous, totalMatches } = await incomingCallsService2(
7154
8593
  {
7155
8594
  projectRoot: ctx.projectRoot,
7156
8595
  indexDir: codebaseIndexDirOverride(ctx),
7157
8596
  symbol: input.symbol,
7158
8597
  file: input.file,
7159
- limit
8598
+ limit,
8599
+ transitive
7160
8600
  }
7161
8601
  );
7162
8602
  if (!symbolFound) {
@@ -7229,6 +8669,11 @@ var codebaseOutgoingCallsTool = {
7229
8669
  description: "Maximum call sites to return (default 50, max 200)",
7230
8670
  minimum: 1,
7231
8671
  maximum: 200
8672
+ },
8673
+ transitive: {
8674
+ type: "boolean",
8675
+ description: "When true, traverse the full transitive dependency chain (callees of callees, to unlimited depth). Default false: return only direct callees. Cycle-safe via SQL recursive CTE.",
8676
+ default: false
7232
8677
  }
7233
8678
  },
7234
8679
  required: ["symbol"]
@@ -7254,13 +8699,15 @@ var codebaseOutgoingCallsTool = {
7254
8699
  };
7255
8700
  }
7256
8701
  const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
8702
+ const transitive = input.transitive === true;
7257
8703
  const { calls, symbolFound, unresolvedCount, totalMatches } = await outgoingCallsService2(
7258
8704
  {
7259
8705
  projectRoot: ctx.projectRoot,
7260
8706
  indexDir: codebaseIndexDirOverride(ctx),
7261
8707
  symbol: input.symbol,
7262
8708
  file: input.file,
7263
- limit
8709
+ limit,
8710
+ transitive
7264
8711
  }
7265
8712
  );
7266
8713
  if (!symbolFound) {
@@ -7520,8 +8967,8 @@ var codebaseStatsTool = {
7520
8967
 
7521
8968
  // src/codebase-index/dead-code-scan.ts
7522
8969
  init_languages();
7523
- import * as fs12 from "node:fs";
7524
- import * as path13 from "node:path";
8970
+ import * as fs13 from "node:fs";
8971
+ import * as path14 from "node:path";
7525
8972
  var deadCodeScanTool = {
7526
8973
  name: "dead-code-scan",
7527
8974
  category: "Project",
@@ -7566,25 +9013,25 @@ var deadCodeScanTool = {
7566
9013
  };
7567
9014
  function tryReadJson(filePath) {
7568
9015
  try {
7569
- const raw = fs12.readFileSync(filePath, "utf8");
9016
+ const raw = fs13.readFileSync(filePath, "utf8");
7570
9017
  return JSON.parse(raw);
7571
9018
  } catch {
7572
9019
  return null;
7573
9020
  }
7574
9021
  }
7575
9022
  function resolveAgainst(base, relative2) {
7576
- if (path13.isAbsolute(relative2)) return relative2;
7577
- return path13.resolve(base, relative2);
9023
+ if (path14.isAbsolute(relative2)) return relative2;
9024
+ return path14.resolve(base, relative2);
7578
9025
  }
7579
9026
  function discoverEntryPoints(projectRoot, userEntryPoints) {
7580
9027
  const entries = /* @__PURE__ */ new Set();
7581
9028
  if (userEntryPoints) {
7582
9029
  for (const ep of userEntryPoints) {
7583
9030
  const resolved = resolveAgainst(projectRoot, ep);
7584
- if (fs12.existsSync(resolved)) entries.add(resolved);
9031
+ if (fs13.existsSync(resolved)) entries.add(resolved);
7585
9032
  }
7586
9033
  }
7587
- const rootPkg = tryReadJson(path13.join(projectRoot, "package.json"));
9034
+ const rootPkg = tryReadJson(path14.join(projectRoot, "package.json"));
7588
9035
  if (rootPkg) {
7589
9036
  addPkgJsonEntryPoints(projectRoot, rootPkg, entries);
7590
9037
  }
@@ -7598,45 +9045,45 @@ function discoverEntryPoints(projectRoot, userEntryPoints) {
7598
9045
  workspaces = [];
7599
9046
  }
7600
9047
  for (const wsDir of workspaces) {
7601
- const pkgJsonPath = path13.join(wsDir, "package.json");
9048
+ const pkgJsonPath = path14.join(wsDir, "package.json");
7602
9049
  const pkg = tryReadJson(pkgJsonPath);
7603
9050
  if (pkg) {
7604
9051
  addPkgJsonEntryPoints(wsDir, pkg, entries);
7605
- const convention1 = path13.join(wsDir, "src", "index.ts");
7606
- if (fs12.existsSync(convention1)) entries.add(convention1);
7607
- const convention2 = path13.join(wsDir, "src", "main.ts");
7608
- if (fs12.existsSync(convention2)) entries.add(convention2);
7609
- const convention3 = path13.join(wsDir, "index.ts");
7610
- if (fs12.existsSync(convention3)) entries.add(convention3);
9052
+ const convention1 = path14.join(wsDir, "src", "index.ts");
9053
+ if (fs13.existsSync(convention1)) entries.add(convention1);
9054
+ const convention2 = path14.join(wsDir, "src", "main.ts");
9055
+ if (fs13.existsSync(convention2)) entries.add(convention2);
9056
+ const convention3 = path14.join(wsDir, "index.ts");
9057
+ if (fs13.existsSync(convention3)) entries.add(convention3);
7611
9058
  }
7612
9059
  }
7613
9060
  if (!rootPkg || !workspaces.length) {
7614
9061
  for (const name of ["src/index.ts", "src/main.ts", "index.ts"]) {
7615
- const convention = path13.join(projectRoot, name);
7616
- if (fs12.existsSync(convention)) entries.add(convention);
9062
+ const convention = path14.join(projectRoot, name);
9063
+ if (fs13.existsSync(convention)) entries.add(convention);
7617
9064
  }
7618
9065
  }
7619
9066
  return [...entries];
7620
9067
  }
7621
9068
  var BUILD_OUTPUT_DIRS = ["dist", "out", "build", "release"];
7622
- var BUILD_OUTPUT_DIR_NAMES = BUILD_OUTPUT_DIRS.map((d) => `${path13.sep}${d}${path13.sep}`);
9069
+ var BUILD_OUTPUT_DIR_NAMES = BUILD_OUTPUT_DIRS.map((d) => `${path14.sep}${d}${path14.sep}`);
7623
9070
  function trySourceEquivalent(resolved) {
7624
- resolved = resolved.replace(/[/\\]/g, path13.sep);
9071
+ resolved = resolved.replace(/[/\\]/g, path14.sep);
7625
9072
  for (const marker of BUILD_OUTPUT_DIR_NAMES) {
7626
9073
  const idx = resolved.indexOf(marker);
7627
9074
  if (idx === -1) continue;
7628
- const base = resolved.replace(marker, `${path13.sep}src${path13.sep}`);
9075
+ const base = resolved.replace(marker, `${path14.sep}src${path14.sep}`);
7629
9076
  const candidate = base.replace(/\.(js|mjs|cjs)$/, ".ts");
7630
- if (candidate !== base && fs12.existsSync(candidate)) {
9077
+ if (candidate !== base && fs13.existsSync(candidate)) {
7631
9078
  return candidate;
7632
9079
  }
7633
9080
  const dtsStripped = base.replace(/\.d\.ts$/, "");
7634
9081
  const candidateDts = dtsStripped + ".ts";
7635
- if (candidateDts !== base && candidateDts !== candidate && fs12.existsSync(candidateDts)) {
9082
+ if (candidateDts !== base && candidateDts !== candidate && fs13.existsSync(candidateDts)) {
7636
9083
  return candidateDts;
7637
9084
  }
7638
9085
  const candidateNoExt = base + ".ts";
7639
- if (candidate !== candidateNoExt && candidateNoExt !== candidateDts && fs12.existsSync(candidateNoExt)) {
9086
+ if (candidate !== candidateNoExt && candidateNoExt !== candidateDts && fs13.existsSync(candidateNoExt)) {
7640
9087
  return candidateNoExt;
7641
9088
  }
7642
9089
  }
@@ -7644,9 +9091,9 @@ function trySourceEquivalent(resolved) {
7644
9091
  }
7645
9092
  function tryAddEntryPath(pkgDir, rawPath, entries) {
7646
9093
  const resolved = resolveAgainst(pkgDir, rawPath);
7647
- if (fs12.existsSync(resolved)) entries.add(resolved);
9094
+ if (fs13.existsSync(resolved)) entries.add(resolved);
7648
9095
  const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
7649
- if (tsResolved !== resolved && fs12.existsSync(tsResolved)) {
9096
+ if (tsResolved !== resolved && fs13.existsSync(tsResolved)) {
7650
9097
  entries.add(tsResolved);
7651
9098
  }
7652
9099
  const srcAlt = trySourceEquivalent(resolved);
@@ -7690,18 +9137,18 @@ function expandGlobPattern(entry, projectRoot) {
7690
9137
  const dirs = [];
7691
9138
  if (entry.includes("*")) {
7692
9139
  const base = entry.replace(/\/\*+$/, "");
7693
- const baseDir = path13.resolve(projectRoot, base);
9140
+ const baseDir = path14.resolve(projectRoot, base);
7694
9141
  try {
7695
- const children = fs12.readdirSync(baseDir, { withFileTypes: true });
9142
+ const children = fs13.readdirSync(baseDir, { withFileTypes: true });
7696
9143
  for (const child of children) {
7697
9144
  if (child.isDirectory()) {
7698
- dirs.push(path13.join(baseDir, child.name));
9145
+ dirs.push(path14.join(baseDir, child.name));
7699
9146
  }
7700
9147
  }
7701
9148
  } catch {
7702
9149
  }
7703
9150
  } else {
7704
- dirs.push(path13.resolve(projectRoot, entry));
9151
+ dirs.push(path14.resolve(projectRoot, entry));
7705
9152
  }
7706
9153
  return dirs;
7707
9154
  }
@@ -7718,10 +9165,10 @@ function extractWorkspaceGlobs(pkg, projectRoot) {
7718
9165
  return dirs;
7719
9166
  }
7720
9167
  function extractPnpmWorkspaceDirs(projectRoot) {
7721
- const yamlPath = path13.join(projectRoot, "pnpm-workspace.yaml");
7722
- if (!fs12.existsSync(yamlPath)) return [];
9168
+ const yamlPath = path14.join(projectRoot, "pnpm-workspace.yaml");
9169
+ if (!fs13.existsSync(yamlPath)) return [];
7723
9170
  try {
7724
- const content = fs12.readFileSync(yamlPath, "utf8");
9171
+ const content = fs13.readFileSync(yamlPath, "utf8");
7725
9172
  const dirs = [];
7726
9173
  let inPackages = false;
7727
9174
  const lines = content.split("\n");
@@ -7755,8 +9202,8 @@ function extractPnpmWorkspaceDirs(projectRoot) {
7755
9202
  }
7756
9203
  function resolveModulePath(importerPath, moduleSpecifier, indexedFiles) {
7757
9204
  if (!moduleSpecifier.startsWith(".")) return [];
7758
- const dir = path13.dirname(importerPath);
7759
- const base = path13.resolve(dir, moduleSpecifier);
9205
+ const dir = path14.dirname(importerPath);
9206
+ const base = path14.resolve(dir, moduleSpecifier);
7760
9207
  const results = [];
7761
9208
  const stripped = base.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
7762
9209
  const skipBase = stripped !== base && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(base);
@@ -7768,18 +9215,18 @@ function resolveModulePath(importerPath, moduleSpecifier, indexedFiles) {
7768
9215
  if (indexedFiles.has(candidate + ".jsx")) results.push(candidate + ".jsx");
7769
9216
  if (indexedFiles.has(candidate + ".mjs")) results.push(candidate + ".mjs");
7770
9217
  if (indexedFiles.has(candidate + ".cjs")) results.push(candidate + ".cjs");
7771
- if (indexedFiles.has(path13.join(candidate, "index.ts")))
7772
- results.push(path13.join(candidate, "index.ts"));
7773
- if (indexedFiles.has(path13.join(candidate, "index.tsx")))
7774
- results.push(path13.join(candidate, "index.tsx"));
7775
- if (indexedFiles.has(path13.join(candidate, "index.js")))
7776
- results.push(path13.join(candidate, "index.js"));
7777
- if (indexedFiles.has(path13.join(candidate, "index.jsx")))
7778
- results.push(path13.join(candidate, "index.jsx"));
7779
- if (indexedFiles.has(path13.join(candidate, "index.mjs")))
7780
- results.push(path13.join(candidate, "index.mjs"));
7781
- if (indexedFiles.has(path13.join(candidate, "index.cjs")))
7782
- results.push(path13.join(candidate, "index.cjs"));
9218
+ if (indexedFiles.has(path14.join(candidate, "index.ts")))
9219
+ results.push(path14.join(candidate, "index.ts"));
9220
+ if (indexedFiles.has(path14.join(candidate, "index.tsx")))
9221
+ results.push(path14.join(candidate, "index.tsx"));
9222
+ if (indexedFiles.has(path14.join(candidate, "index.js")))
9223
+ results.push(path14.join(candidate, "index.js"));
9224
+ if (indexedFiles.has(path14.join(candidate, "index.jsx")))
9225
+ results.push(path14.join(candidate, "index.jsx"));
9226
+ if (indexedFiles.has(path14.join(candidate, "index.mjs")))
9227
+ results.push(path14.join(candidate, "index.mjs"));
9228
+ if (indexedFiles.has(path14.join(candidate, "index.cjs")))
9229
+ results.push(path14.join(candidate, "index.cjs"));
7783
9230
  }
7784
9231
  return [...new Set(results)];
7785
9232
  }
@@ -7806,22 +9253,12 @@ function runDeadCodeScan(projectRoot, opts = {}) {
7806
9253
  const store = opts.store ?? indexStorePool.acquire(projectRoot, { indexDir: opts.indexDir });
7807
9254
  try {
7808
9255
  const allSymbols = store.getAllSymbols();
7809
- const allRefs = store.getAllResolvedRefs();
7810
9256
  const symbolById = /* @__PURE__ */ new Map();
7811
9257
  for (const s of allSymbols) {
7812
9258
  symbolById.set(s.id, s);
7813
9259
  }
7814
- const references = /* @__PURE__ */ new Map();
7815
- for (const ref of allRefs) {
7816
- let set = references.get(ref.fromId);
7817
- if (!set) {
7818
- set = /* @__PURE__ */ new Set();
7819
- references.set(ref.fromId, set);
7820
- }
7821
- set.add(ref.toId);
7822
- }
7823
9260
  const discoveredFiles = discoverEntryPoints(projectRoot, opts.userEntryPoints);
7824
- const entryFileSet = new Set(discoveredFiles.map((f) => path13.resolve(f)));
9261
+ const entryFileSet = new Set(discoveredFiles.map((f) => path14.resolve(f)));
7825
9262
  const indexedFiles = /* @__PURE__ */ new Set();
7826
9263
  for (const s of allSymbols) indexedFiles.add(s.file);
7827
9264
  for (const fm of store.getAllFileMetas()) indexedFiles.add(fm.file);
@@ -7847,7 +9284,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
7847
9284
  if (scannedBarrels.has(epFile)) continue;
7848
9285
  scannedBarrels.add(epFile);
7849
9286
  try {
7850
- const content = fs12.readFileSync(epFile, "utf8");
9287
+ const content = fs13.readFileSync(epFile, "utf8");
7851
9288
  const strippedContent = content.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/\/\/[^\n]*/g, (m) => " ".repeat(m.length));
7852
9289
  const reExportRe = /export\s+(?:(?:type\s+)?\{[\s\S]*?\}\s+from|\*\s+as\s+\w+\s+from|\*\s+from)\s+['"]([^'"]+)['"]/g;
7853
9290
  for (; ; ) {
@@ -7888,23 +9325,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
7888
9325
  }
7889
9326
  }
7890
9327
  }
7891
- const alive = new Set(seedIds);
7892
- const frontier = [...seedIds];
7893
- const visitedEdges = /* @__PURE__ */ new Set();
7894
- while (frontier.length > 0) {
7895
- const current = frontier.pop();
7896
- const outgoing = references.get(current);
7897
- if (!outgoing) continue;
7898
- for (const toId of outgoing) {
7899
- const edgeKey = `${current}->${toId}`;
7900
- if (visitedEdges.has(edgeKey)) continue;
7901
- visitedEdges.add(edgeKey);
7902
- if (!alive.has(toId)) {
7903
- alive.add(toId);
7904
- frontier.push(toId);
7905
- }
7906
- }
7907
- }
9328
+ const alive = store.findReachableSymbolIds([...seedIds]);
7908
9329
  const dead = [];
7909
9330
  const symbolsByFile = /* @__PURE__ */ new Map();
7910
9331
  const usedFiles = /* @__PURE__ */ new Set();
@@ -7953,7 +9374,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
7953
9374
  const deadPackages = [];
7954
9375
  const pkgEntries = findPackageEntries(projectRoot);
7955
9376
  for (const [pkgName, pkgDir] of pkgEntries) {
7956
- const pkgFiles = allSymbols.filter((s) => s.file.startsWith(pkgDir + path13.sep));
9377
+ const pkgFiles = allSymbols.filter((s) => s.file.startsWith(pkgDir + path14.sep));
7957
9378
  if (pkgFiles.length === 0) continue;
7958
9379
  const pkgUsed = pkgFiles.filter((s) => alive.has(s.id));
7959
9380
  if (pkgUsed.length === 0) {
@@ -7989,7 +9410,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
7989
9410
  }
7990
9411
  function findPackageEntries(projectRoot) {
7991
9412
  const pkgMap = /* @__PURE__ */ new Map();
7992
- const rootPkg = tryReadJson(path13.join(projectRoot, "package.json"));
9413
+ const rootPkg = tryReadJson(path14.join(projectRoot, "package.json"));
7993
9414
  if (rootPkg && typeof rootPkg.name === "string") {
7994
9415
  pkgMap.set(rootPkg.name, projectRoot);
7995
9416
  }
@@ -7999,7 +9420,7 @@ function findPackageEntries(projectRoot) {
7999
9420
  wsDirs = extractPnpmWorkspaceDirs(projectRoot);
8000
9421
  }
8001
9422
  for (const wsDir of wsDirs) {
8002
- const wsPkg = tryReadJson(path13.join(wsDir, "package.json"));
9423
+ const wsPkg = tryReadJson(path14.join(wsDir, "package.json"));
8003
9424
  if (wsPkg && typeof wsPkg.name === "string") {
8004
9425
  pkgMap.set(wsPkg.name, wsDir);
8005
9426
  }