@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.
- package/dist/builtin.js +2799 -609
- package/dist/codebase-index/binary-frame.d.ts +43 -0
- package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +1 -0
- package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +1 -0
- package/dist/codebase-index/content-hash.d.ts +66 -0
- package/dist/codebase-index/index.js +1569 -148
- package/dist/codebase-index/parser-worker-pool.d.ts +63 -0
- package/dist/codebase-index/parser-worker-script.d.ts +42 -0
- package/dist/codebase-index/project-server-protocol.d.ts +2 -0
- package/dist/codebase-index/project-server.js +1455 -99
- package/dist/codebase-index/schema.d.ts +7 -0
- package/dist/codebase-index/tree-sitter/queries.d.ts +48 -0
- package/dist/codebase-index/tree-sitter/util.d.ts +31 -0
- package/dist/codebase-index/tree-sitter/visitor.d.ts +47 -0
- package/dist/codebase-index/tree-sitter-parser.d.ts +58 -0
- package/dist/codebase-index/vector-search.d.ts +62 -0
- package/dist/codebase-index/worker-protocol.d.ts +2 -0
- package/dist/codebase-index/worker.js +1424 -68
- package/dist/codebase-index/writer-bulk-insert.d.ts +5 -0
- package/dist/codebase-index/writer-graph-reader.d.ts +39 -0
- package/dist/codebase-index/writer-schema.d.ts +9 -2
- package/dist/codebase-index/writer.d.ts +36 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2785 -634
- package/dist/kanban-task-inputs.d.ts +1 -0
- package/dist/kanban-tool-schema.d.ts +2 -2
- package/dist/kanban-tool-types.d.ts +18 -2
- package/dist/kanban.js +392 -126
- package/dist/pack.js +2799 -609
- package/dist/plan.d.ts +4 -1
- package/dist/plan.js +2380 -9
- package/dist/read.js +1531 -98
- package/dist/session-kanban.d.ts +8 -0
- package/dist/session-kanban.js +111 -17
- package/dist/task.d.ts +5 -4
- package/dist/task.js +2418 -43
- package/dist/todo.d.ts +10 -1
- package/dist/todo.js +2152 -20
- package/dist/tool-tier.js +2799 -609
- 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(
|
|
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 = '
|
|
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
|
-
{
|
|
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(
|
|
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
|
|
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
|
|
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:
|
|
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(
|
|
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(
|
|
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.
|
|
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
|
|
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
|
|
5474
|
-
import { fileURLToPath as
|
|
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
|
|
6557
|
+
import * as fs11 from "node:fs/promises";
|
|
5481
6558
|
import { availableParallelism } from "node:os";
|
|
5482
|
-
import * as
|
|
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:
|
|
5981
|
-
return
|
|
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:
|
|
5985
|
-
return
|
|
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:
|
|
5989
|
-
return
|
|
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:
|
|
5993
|
-
return
|
|
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:
|
|
5997
|
-
return
|
|
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:
|
|
6001
|
-
return
|
|
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:
|
|
6005
|
-
return
|
|
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 =
|
|
6040
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
7463
|
+
const full = path13.resolve(projectRoot, relative2);
|
|
6108
7464
|
if (deleted.has(full)) continue;
|
|
6109
|
-
const ext =
|
|
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
|
|
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 =
|
|
6154
|
-
const rel =
|
|
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 =
|
|
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) =>
|
|
7601
|
+
files = opts.files.map((f) => path13.resolve(projectRoot, f)).filter((f) => {
|
|
6246
7602
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
6247
|
-
const rel =
|
|
6248
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
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
|
|
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
|
|
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
|
-
|
|
6347
|
-
|
|
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
|
-
|
|
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:" &&
|
|
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
|
|
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
|
|
7524
|
-
import * as
|
|
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 =
|
|
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 (
|
|
7577
|
-
return
|
|
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 (
|
|
9031
|
+
if (fs13.existsSync(resolved)) entries.add(resolved);
|
|
7585
9032
|
}
|
|
7586
9033
|
}
|
|
7587
|
-
const rootPkg = tryReadJson(
|
|
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 =
|
|
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 =
|
|
7606
|
-
if (
|
|
7607
|
-
const convention2 =
|
|
7608
|
-
if (
|
|
7609
|
-
const convention3 =
|
|
7610
|
-
if (
|
|
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 =
|
|
7616
|
-
if (
|
|
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) => `${
|
|
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,
|
|
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, `${
|
|
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 &&
|
|
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 &&
|
|
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 &&
|
|
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 (
|
|
9094
|
+
if (fs13.existsSync(resolved)) entries.add(resolved);
|
|
7648
9095
|
const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
|
|
7649
|
-
if (tsResolved !== resolved &&
|
|
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 =
|
|
9140
|
+
const baseDir = path14.resolve(projectRoot, base);
|
|
7694
9141
|
try {
|
|
7695
|
-
const children =
|
|
9142
|
+
const children = fs13.readdirSync(baseDir, { withFileTypes: true });
|
|
7696
9143
|
for (const child of children) {
|
|
7697
9144
|
if (child.isDirectory()) {
|
|
7698
|
-
dirs.push(
|
|
9145
|
+
dirs.push(path14.join(baseDir, child.name));
|
|
7699
9146
|
}
|
|
7700
9147
|
}
|
|
7701
9148
|
} catch {
|
|
7702
9149
|
}
|
|
7703
9150
|
} else {
|
|
7704
|
-
dirs.push(
|
|
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 =
|
|
7722
|
-
if (!
|
|
9168
|
+
const yamlPath = path14.join(projectRoot, "pnpm-workspace.yaml");
|
|
9169
|
+
if (!fs13.existsSync(yamlPath)) return [];
|
|
7723
9170
|
try {
|
|
7724
|
-
const content =
|
|
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 =
|
|
7759
|
-
const base =
|
|
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(
|
|
7772
|
-
results.push(
|
|
7773
|
-
if (indexedFiles.has(
|
|
7774
|
-
results.push(
|
|
7775
|
-
if (indexedFiles.has(
|
|
7776
|
-
results.push(
|
|
7777
|
-
if (indexedFiles.has(
|
|
7778
|
-
results.push(
|
|
7779
|
-
if (indexedFiles.has(
|
|
7780
|
-
results.push(
|
|
7781
|
-
if (indexedFiles.has(
|
|
7782
|
-
results.push(
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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 +
|
|
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(
|
|
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(
|
|
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
|
}
|