@wrongstack/tools 0.302.2 → 0.305.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.d.ts +6 -0
- package/dist/builtin.js +3213 -686
- 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-index-tool.d.ts +6 -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 +1597 -153
- package/dist/codebase-index/indexer.d.ts +6 -0
- 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 +1483 -104
- package/dist/codebase-index/schema.d.ts +18 -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 +1452 -73
- 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 +4 -3
- package/dist/index.js +3258 -727
- package/dist/kanban-contract-actions.d.ts +7 -0
- package/dist/kanban-task-inputs.d.ts +16 -2
- package/dist/kanban-tool-schema.d.ts +2 -2
- package/dist/kanban-tool-types.d.ts +39 -2
- package/dist/kanban.js +580 -193
- package/dist/pack.js +3212 -686
- package/dist/plan.d.ts +4 -1
- package/dist/plan.js +2701 -18
- package/dist/read.js +1559 -103
- package/dist/session-kanban.d.ts +94 -1
- package/dist/session-kanban.js +308 -44
- package/dist/task.d.ts +5 -4
- package/dist/task.js +2825 -138
- package/dist/todo.d.ts +10 -1
- package/dist/todo.js +2474 -30
- package/dist/tool-tier.js +3212 -686
- 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?.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,39 @@ 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) => {
|
|
5162
|
+
bm25Rank.set(r.id, i);
|
|
5163
|
+
});
|
|
5164
|
+
const vecRank = /* @__PURE__ */ new Map();
|
|
5165
|
+
vecScores.forEach((r, i) => {
|
|
5166
|
+
vecRank.set(r.id, i);
|
|
5167
|
+
});
|
|
5168
|
+
const fused = reciprocalRankFusion(bm25Rank, vecRank, 60);
|
|
5169
|
+
const fusedScore = new Map(fused);
|
|
5170
|
+
const sorted = [...bm25Rows].sort(
|
|
5171
|
+
(a, b) => (fusedScore.get(b.id) ?? 0) - (fusedScore.get(a.id) ?? 0)
|
|
5172
|
+
);
|
|
5173
|
+
return {
|
|
5174
|
+
results: sorted.map(
|
|
5175
|
+
(row) => mapWriterSearchRow(row, filter?.lspKind, Math.max(1e-4, row.score), row.snippet)
|
|
5176
|
+
),
|
|
5177
|
+
total
|
|
5178
|
+
};
|
|
5179
|
+
}
|
|
4222
5180
|
return {
|
|
4223
|
-
results:
|
|
5181
|
+
results: bm25Rows.map(
|
|
4224
5182
|
(row) => mapWriterSearchRow(row, filter?.lspKind, Math.max(1e-4, row.score), row.snippet)
|
|
4225
5183
|
),
|
|
4226
5184
|
total
|
|
@@ -4348,6 +5306,7 @@ var IndexStore = class _IndexStore {
|
|
|
4348
5306
|
this.db.exec("DROP TABLE IF EXISTS files");
|
|
4349
5307
|
this.db.exec("DROP TABLE IF EXISTS metadata");
|
|
4350
5308
|
if (this.ftsAvailable) this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
5309
|
+
this.db.exec("DROP TABLE IF EXISTS symbol_vectors");
|
|
4351
5310
|
this.db.exec("COMMIT");
|
|
4352
5311
|
this.stmtCache.clear();
|
|
4353
5312
|
this.initSchema();
|
|
@@ -4435,6 +5394,11 @@ var IndexStore = class _IndexStore {
|
|
|
4435
5394
|
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
4436
5395
|
).run(...options.deleteForFiles);
|
|
4437
5396
|
}
|
|
5397
|
+
if (this.vectorsAvailable) {
|
|
5398
|
+
this.stmt(
|
|
5399
|
+
`DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
5400
|
+
).run(...options.deleteForFiles);
|
|
5401
|
+
}
|
|
4438
5402
|
this.stmt(
|
|
4439
5403
|
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
4440
5404
|
).run(...options.deleteForFiles);
|
|
@@ -4448,6 +5412,7 @@ var IndexStore = class _IndexStore {
|
|
|
4448
5412
|
const refsToInsert = [];
|
|
4449
5413
|
const bulkSyms = [];
|
|
4450
5414
|
const ftsRows = [];
|
|
5415
|
+
const vectorRows = [];
|
|
4451
5416
|
for (const entry of entries) {
|
|
4452
5417
|
const insertedForEntry = [];
|
|
4453
5418
|
for (const s of entry.symbols) {
|
|
@@ -4471,6 +5436,10 @@ var IndexStore = class _IndexStore {
|
|
|
4471
5436
|
text: buildIndexableText(s.name, s.signature, s.docComment)
|
|
4472
5437
|
});
|
|
4473
5438
|
}
|
|
5439
|
+
vectorRows.push({
|
|
5440
|
+
id,
|
|
5441
|
+
vector: encodeVector(embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment)))
|
|
5442
|
+
});
|
|
4474
5443
|
const inserted = { ...s, id };
|
|
4475
5444
|
allInserted.push(inserted);
|
|
4476
5445
|
insertedForEntry.push(inserted);
|
|
@@ -4484,19 +5453,34 @@ var IndexStore = class _IndexStore {
|
|
|
4484
5453
|
this.ftsAvailable,
|
|
4485
5454
|
ftsRows
|
|
4486
5455
|
);
|
|
5456
|
+
if (this.vectorsAvailable) {
|
|
5457
|
+
bulkInsertVectorsWithStatement(
|
|
5458
|
+
(sql) => this.stmt(sql),
|
|
5459
|
+
_IndexStore.MAX_SQL_VARS,
|
|
5460
|
+
vectorRows
|
|
5461
|
+
);
|
|
5462
|
+
}
|
|
4487
5463
|
bulkInsertRefsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, refsToInsert);
|
|
4488
5464
|
const upsertStmt = this.stmt(
|
|
4489
|
-
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
4490
|
-
VALUES (?, ?, ?, ?, ?)
|
|
5465
|
+
`INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
|
|
5466
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
4491
5467
|
ON CONFLICT(file) DO UPDATE SET
|
|
4492
5468
|
lang = excluded.lang,
|
|
4493
5469
|
mtime_ms = excluded.mtime_ms,
|
|
5470
|
+
content_hash = excluded.content_hash,
|
|
4494
5471
|
symbol_count = excluded.symbol_count,
|
|
4495
5472
|
last_indexed = excluded.last_indexed`
|
|
4496
5473
|
);
|
|
4497
5474
|
const now = Date.now();
|
|
4498
5475
|
for (const entry of entries) {
|
|
4499
|
-
upsertStmt.run(
|
|
5476
|
+
upsertStmt.run(
|
|
5477
|
+
entry.file,
|
|
5478
|
+
entry.lang,
|
|
5479
|
+
entry.mtimeMs,
|
|
5480
|
+
entry.contentHash ?? "",
|
|
5481
|
+
entry.symbolCount,
|
|
5482
|
+
now
|
|
5483
|
+
);
|
|
4500
5484
|
}
|
|
4501
5485
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
4502
5486
|
this.db.exec("COMMIT");
|
|
@@ -4587,19 +5571,32 @@ var IndexStore = class _IndexStore {
|
|
|
4587
5571
|
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
4588
5572
|
).run(meta.file);
|
|
4589
5573
|
}
|
|
5574
|
+
if (this.vectorsAvailable) {
|
|
5575
|
+
this.stmt(
|
|
5576
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
5577
|
+
).run(meta.file);
|
|
5578
|
+
}
|
|
4590
5579
|
this.stmt(
|
|
4591
5580
|
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
4592
5581
|
).run(meta.file);
|
|
4593
5582
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(meta.file);
|
|
4594
5583
|
this.stmt(
|
|
4595
|
-
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
4596
|
-
VALUES (?, ?, ?, ?, ?)
|
|
5584
|
+
`INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
|
|
5585
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
4597
5586
|
ON CONFLICT(file) DO UPDATE SET
|
|
4598
5587
|
lang = excluded.lang,
|
|
4599
5588
|
mtime_ms = excluded.mtime_ms,
|
|
5589
|
+
content_hash = excluded.content_hash,
|
|
4600
5590
|
symbol_count = excluded.symbol_count,
|
|
4601
5591
|
last_indexed = excluded.last_indexed`
|
|
4602
|
-
).run(
|
|
5592
|
+
).run(
|
|
5593
|
+
meta.file,
|
|
5594
|
+
meta.lang,
|
|
5595
|
+
meta.mtimeMs,
|
|
5596
|
+
meta.contentHash ?? "",
|
|
5597
|
+
meta.symbolCount,
|
|
5598
|
+
meta.lastIndexed
|
|
5599
|
+
);
|
|
4603
5600
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
4604
5601
|
this.db.exec("COMMIT");
|
|
4605
5602
|
} catch (err) {
|
|
@@ -4662,6 +5659,31 @@ var IndexStore = class _IndexStore {
|
|
|
4662
5659
|
findOutgoingCallsByName(symbolName, file, limit = 100) {
|
|
4663
5660
|
return findOutgoingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
4664
5661
|
}
|
|
5662
|
+
/**
|
|
5663
|
+
* Transitive incoming-call tree: all symbols that transitively call the
|
|
5664
|
+
* target, to an unbounded depth (cycle-safe via SQL UNION deduplication).
|
|
5665
|
+
* Used by `codebase-incoming-calls` when the caller wants the full call
|
|
5666
|
+
* chain rather than just direct callers.
|
|
5667
|
+
*/
|
|
5668
|
+
findTransitiveIncomingCallsByName(symbolName, file, limit = 200) {
|
|
5669
|
+
return findTransitiveIncomingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
5670
|
+
}
|
|
5671
|
+
/**
|
|
5672
|
+
* Transitive outgoing-call tree: all symbols the target transitively calls.
|
|
5673
|
+
* Used by `codebase-outgoing-calls` when the caller wants the full
|
|
5674
|
+
* dependency chain rather than just direct callees.
|
|
5675
|
+
*/
|
|
5676
|
+
findTransitiveOutgoingCallsByName(symbolName, file, limit = 200) {
|
|
5677
|
+
return findTransitiveOutgoingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
5678
|
+
}
|
|
5679
|
+
/**
|
|
5680
|
+
* Compute the set of symbol IDs reachable from the given seed IDs using a
|
|
5681
|
+
* native SQLite recursive CTE. Used by dead-code detection to replace the
|
|
5682
|
+
* in-memory BFS.
|
|
5683
|
+
*/
|
|
5684
|
+
findReachableSymbolIds(seedIds) {
|
|
5685
|
+
return findReachableSymbolIds((sql) => this.stmt(sql), seedIds);
|
|
5686
|
+
}
|
|
4665
5687
|
/**
|
|
4666
5688
|
* Find all references TO a given symbol (who calls / uses this symbol?).
|
|
4667
5689
|
*/
|
|
@@ -4924,6 +5946,12 @@ var ProjectServerConnection = class {
|
|
|
4924
5946
|
endpoint;
|
|
4925
5947
|
socket = null;
|
|
4926
5948
|
buffer = "";
|
|
5949
|
+
/** P6: binary frame buffer — accumulates raw bytes when in binary mode. */
|
|
5950
|
+
binaryBuffer = [];
|
|
5951
|
+
/** P6: StringDecoder for safe UTF-8 multibyte handling in JSON mode. */
|
|
5952
|
+
textDecoder = null;
|
|
5953
|
+
/** P6: true once the server advertises binary support and client accepts. */
|
|
5954
|
+
useBinary = false;
|
|
4927
5955
|
info = null;
|
|
4928
5956
|
activity = null;
|
|
4929
5957
|
health = null;
|
|
@@ -5062,6 +6090,8 @@ var ProjectServerConnection = class {
|
|
|
5062
6090
|
this.info = null;
|
|
5063
6091
|
this.activity = null;
|
|
5064
6092
|
this.health = null;
|
|
6093
|
+
this.useBinary = false;
|
|
6094
|
+
this.binaryBuffer = [];
|
|
5065
6095
|
this.connectReject?.(new Error("codebase-index client disconnected"));
|
|
5066
6096
|
this.connectResolve = null;
|
|
5067
6097
|
this.connectReject = null;
|
|
@@ -5190,10 +6220,12 @@ var ProjectServerConnection = class {
|
|
|
5190
6220
|
this.activity = null;
|
|
5191
6221
|
this.health = null;
|
|
5192
6222
|
this.buffer = "";
|
|
6223
|
+
this.binaryBuffer = [];
|
|
6224
|
+
this.useBinary = false;
|
|
6225
|
+
this.textDecoder = null;
|
|
5193
6226
|
return new Promise((resolve4, reject) => {
|
|
5194
6227
|
const socket = net.createConnection(this.endpoint);
|
|
5195
6228
|
this.socket = socket;
|
|
5196
|
-
socket.setEncoding("utf8");
|
|
5197
6229
|
const timer = setTimeout(() => {
|
|
5198
6230
|
reject(new Error("codebase-index server handshake timed out"));
|
|
5199
6231
|
socket.destroy();
|
|
@@ -5222,7 +6254,12 @@ var ProjectServerConnection = class {
|
|
|
5222
6254
|
}
|
|
5223
6255
|
onData(socket, chunk) {
|
|
5224
6256
|
if (socket !== this.socket) return;
|
|
5225
|
-
this.
|
|
6257
|
+
if (this.useBinary) {
|
|
6258
|
+
this.onBinaryData(socket, chunk);
|
|
6259
|
+
return;
|
|
6260
|
+
}
|
|
6261
|
+
if (!this.textDecoder) this.textDecoder = new StringDecoder("utf8");
|
|
6262
|
+
this.buffer += this.textDecoder.write(chunk);
|
|
5226
6263
|
while (true) {
|
|
5227
6264
|
const newline = this.buffer.indexOf("\n");
|
|
5228
6265
|
if (newline < 0) {
|
|
@@ -5248,6 +6285,44 @@ var ProjectServerConnection = class {
|
|
|
5248
6285
|
this.onMessage(message);
|
|
5249
6286
|
}
|
|
5250
6287
|
}
|
|
6288
|
+
/**
|
|
6289
|
+
* P6: Parse binary frames from the raw buffer.
|
|
6290
|
+
*
|
|
6291
|
+
* Each frame is: [1 byte magic 0x57] [4 bytes uint32 BE length] [payload].
|
|
6292
|
+
* The magic byte distinguishes binary from JSON — a JSON frame's first byte
|
|
6293
|
+
* is `{` (0x7B), so there is no ambiguity even in a mixed-mode buffer.
|
|
6294
|
+
*/
|
|
6295
|
+
onBinaryData(socket, chunk) {
|
|
6296
|
+
this.binaryBuffer.push(chunk);
|
|
6297
|
+
const all = Buffer.concat(this.binaryBuffer);
|
|
6298
|
+
let offset = 0;
|
|
6299
|
+
while (offset + 5 <= all.length) {
|
|
6300
|
+
if (!isBinaryFrame(all[offset])) {
|
|
6301
|
+
this.useBinary = false;
|
|
6302
|
+
this.buffer += all.subarray(offset).toString("utf8");
|
|
6303
|
+
this.binaryBuffer = [];
|
|
6304
|
+
return;
|
|
6305
|
+
}
|
|
6306
|
+
const frameLen = all.readUInt32BE(offset + 1);
|
|
6307
|
+
if (frameLen > 256 * 1024 * 1024) {
|
|
6308
|
+
socket.destroy();
|
|
6309
|
+
this.transition("offline", { error: "binary frame length exceeds 256MB limit" });
|
|
6310
|
+
return;
|
|
6311
|
+
}
|
|
6312
|
+
const totalLen = 5 + frameLen;
|
|
6313
|
+
if (offset + totalLen > all.length) break;
|
|
6314
|
+
const payload = all.subarray(offset + 5, offset + 5 + frameLen);
|
|
6315
|
+
try {
|
|
6316
|
+
const message = decodeBinaryFrame(payload);
|
|
6317
|
+
this.onMessage(message);
|
|
6318
|
+
} catch {
|
|
6319
|
+
socket.destroy(new Error("invalid binary codebase-index server response"));
|
|
6320
|
+
return;
|
|
6321
|
+
}
|
|
6322
|
+
offset += totalLen;
|
|
6323
|
+
}
|
|
6324
|
+
this.binaryBuffer = offset < all.length ? [all.subarray(offset)] : [];
|
|
6325
|
+
}
|
|
5251
6326
|
onMessage(message) {
|
|
5252
6327
|
if (message.type === "hello") {
|
|
5253
6328
|
if (message.protocolVersion !== PROJECT_INDEX_SERVER_PROTOCOL_VERSION) {
|
|
@@ -5267,6 +6342,7 @@ var ProjectServerConnection = class {
|
|
|
5267
6342
|
}
|
|
5268
6343
|
this.info = message;
|
|
5269
6344
|
this.markResponsive();
|
|
6345
|
+
if (message.binarySupported) this.useBinary = true;
|
|
5270
6346
|
this.transition("connected", { pid: message.pid });
|
|
5271
6347
|
ensureHeartbeatLoop();
|
|
5272
6348
|
this.connectResolve?.();
|
|
@@ -5325,7 +6401,12 @@ var ProjectServerConnection = class {
|
|
|
5325
6401
|
}
|
|
5326
6402
|
write(message) {
|
|
5327
6403
|
const socket = this.socket;
|
|
5328
|
-
if (socket
|
|
6404
|
+
if (!socket || socket.destroyed) return;
|
|
6405
|
+
if (this.useBinary) {
|
|
6406
|
+
socket.write(encodeBinaryFrame(message));
|
|
6407
|
+
} else {
|
|
6408
|
+
socket.write(encodeProjectServerMessage(message));
|
|
6409
|
+
}
|
|
5329
6410
|
}
|
|
5330
6411
|
rejectStaleServer(message, reason) {
|
|
5331
6412
|
const socket = this.socket;
|
|
@@ -5470,22 +6551,102 @@ function closeProjectIndexServerClients() {
|
|
|
5470
6551
|
}
|
|
5471
6552
|
|
|
5472
6553
|
// src/codebase-index/background-indexer.ts
|
|
5473
|
-
import * as
|
|
5474
|
-
import { fileURLToPath as
|
|
5475
|
-
import { Worker } from "node:worker_threads";
|
|
6554
|
+
import * as fs12 from "node:fs";
|
|
6555
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
6556
|
+
import { Worker as Worker2 } from "node:worker_threads";
|
|
5476
6557
|
|
|
5477
6558
|
// src/codebase-index/indexer.ts
|
|
5478
6559
|
import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
|
|
5479
6560
|
import { execFile } from "node:child_process";
|
|
5480
|
-
import * as
|
|
6561
|
+
import * as fs11 from "node:fs/promises";
|
|
5481
6562
|
import { availableParallelism } from "node:os";
|
|
5482
|
-
import * as
|
|
6563
|
+
import * as path13 from "node:path";
|
|
5483
6564
|
import {
|
|
5484
6565
|
DEFAULT_WALK_IGNORE_DIRS,
|
|
5485
6566
|
indexParallelBatchSize,
|
|
5486
6567
|
isFrugalPerf
|
|
5487
6568
|
} from "@wrongstack/core/utils";
|
|
5488
6569
|
|
|
6570
|
+
// src/codebase-index/content-hash.ts
|
|
6571
|
+
var PRIME64_1 = 0x9e3779b185ebca87n;
|
|
6572
|
+
var PRIME64_2 = 0xc2b2ae3d27d4eb4fn;
|
|
6573
|
+
var PRIME64_3 = 0x165667b19e3779f9n;
|
|
6574
|
+
var PRIME64_4 = 0x85ebca77c2b2ae63n;
|
|
6575
|
+
var PRIME64_5 = 0x27d4eb2f165667c5n;
|
|
6576
|
+
var MASK64 = 0xffffffffffffffffn;
|
|
6577
|
+
function mul64(a, b) {
|
|
6578
|
+
return (a & MASK64) * (b & MASK64) & MASK64;
|
|
6579
|
+
}
|
|
6580
|
+
function rotl64(x, n) {
|
|
6581
|
+
const v = x & MASK64;
|
|
6582
|
+
return (v << BigInt(n) | v >> BigInt(64 - n)) & MASK64;
|
|
6583
|
+
}
|
|
6584
|
+
function readU64LE(buf, off) {
|
|
6585
|
+
let v = 0n;
|
|
6586
|
+
for (let i = 7; i >= 0; i--) {
|
|
6587
|
+
v = v << 8n | BigInt(buf[off + i] ?? 0);
|
|
6588
|
+
}
|
|
6589
|
+
return v & MASK64;
|
|
6590
|
+
}
|
|
6591
|
+
function readU32LE(buf, off) {
|
|
6592
|
+
return (BigInt(buf[off] ?? 0) | BigInt(buf[off + 1] ?? 0) << 8n | BigInt(buf[off + 2] ?? 0) << 16n | BigInt(buf[off + 3] ?? 0) << 24n) & MASK64;
|
|
6593
|
+
}
|
|
6594
|
+
function xxh64Round(acc, lane) {
|
|
6595
|
+
return mul64(rotl64(acc + mul64(lane, PRIME64_2) & MASK64, 31), PRIME64_1);
|
|
6596
|
+
}
|
|
6597
|
+
function xxh64MergeRound(acc, val) {
|
|
6598
|
+
return mul64(acc ^ xxh64Round(0n, val), PRIME64_1) + PRIME64_4 & MASK64;
|
|
6599
|
+
}
|
|
6600
|
+
function xxhash64Hex(buf, explicitLen) {
|
|
6601
|
+
const length = explicitLen ?? buf.length;
|
|
6602
|
+
let h;
|
|
6603
|
+
let off = 0;
|
|
6604
|
+
if (length >= 32) {
|
|
6605
|
+
let v1 = PRIME64_1 + PRIME64_2 & MASK64;
|
|
6606
|
+
let v2 = PRIME64_2;
|
|
6607
|
+
let v3 = 0n;
|
|
6608
|
+
let v4 = 0n - PRIME64_1 & MASK64;
|
|
6609
|
+
const end32 = length - 32;
|
|
6610
|
+
while (off <= end32) {
|
|
6611
|
+
v1 = xxh64Round(v1, readU64LE(buf, off));
|
|
6612
|
+
v2 = xxh64Round(v2, readU64LE(buf, off + 8));
|
|
6613
|
+
v3 = xxh64Round(v3, readU64LE(buf, off + 16));
|
|
6614
|
+
v4 = xxh64Round(v4, readU64LE(buf, off + 24));
|
|
6615
|
+
off += 32;
|
|
6616
|
+
}
|
|
6617
|
+
h = rotl64(v1, 1) + rotl64(v2, 7) + rotl64(v3, 12) + rotl64(v4, 18) & MASK64;
|
|
6618
|
+
h = xxh64MergeRound(h, v1);
|
|
6619
|
+
h = xxh64MergeRound(h, v2);
|
|
6620
|
+
h = xxh64MergeRound(h, v3);
|
|
6621
|
+
h = xxh64MergeRound(h, v4);
|
|
6622
|
+
} else {
|
|
6623
|
+
h = PRIME64_5;
|
|
6624
|
+
}
|
|
6625
|
+
h = h + BigInt(length) & MASK64;
|
|
6626
|
+
while (off + 8 <= length) {
|
|
6627
|
+
const k1 = xxh64Round(0n, readU64LE(buf, off));
|
|
6628
|
+
h = mul64(rotl64(h ^ k1, 27), PRIME64_1) + PRIME64_4 & MASK64;
|
|
6629
|
+
off += 8;
|
|
6630
|
+
}
|
|
6631
|
+
if (off + 4 <= length) {
|
|
6632
|
+
h = mul64(rotl64(h ^ mul64(readU32LE(buf, off), PRIME64_1), 23), PRIME64_2) + PRIME64_3 & MASK64;
|
|
6633
|
+
off += 4;
|
|
6634
|
+
}
|
|
6635
|
+
while (off < length) {
|
|
6636
|
+
h = mul64(rotl64(h ^ mul64(BigInt(buf[off] ?? 0), PRIME64_5), 11), PRIME64_1) & MASK64;
|
|
6637
|
+
off += 1;
|
|
6638
|
+
}
|
|
6639
|
+
h = (h ^ h >> 33n) & MASK64;
|
|
6640
|
+
h = mul64(h, PRIME64_2);
|
|
6641
|
+
h = (h ^ h >> 29n) & MASK64;
|
|
6642
|
+
h = mul64(h, PRIME64_3);
|
|
6643
|
+
h = (h ^ h >> 32n) & MASK64;
|
|
6644
|
+
return h.toString(16).padStart(16, "0");
|
|
6645
|
+
}
|
|
6646
|
+
function xxhash64String(content) {
|
|
6647
|
+
return xxhash64Hex(new TextEncoder().encode(content));
|
|
6648
|
+
}
|
|
6649
|
+
|
|
5489
6650
|
// src/codebase-index/gitignore.ts
|
|
5490
6651
|
import * as fs6 from "node:fs/promises";
|
|
5491
6652
|
import * as path6 from "node:path";
|
|
@@ -5977,32 +7138,56 @@ async function dispatch(file, content, lang) {
|
|
|
5977
7138
|
case "tsx":
|
|
5978
7139
|
case "js":
|
|
5979
7140
|
case "jsx": {
|
|
5980
|
-
const { parseSymbols:
|
|
5981
|
-
return
|
|
7141
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
|
|
7142
|
+
return parseSymbols9({ file, content, lang });
|
|
5982
7143
|
}
|
|
5983
7144
|
case "go": {
|
|
5984
|
-
const { parseSymbols:
|
|
5985
|
-
return
|
|
7145
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
|
|
7146
|
+
return parseSymbols9({ file, content, lang: "go" });
|
|
5986
7147
|
}
|
|
5987
7148
|
case "py": {
|
|
5988
|
-
const { parseSymbols:
|
|
5989
|
-
return
|
|
7149
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
|
|
7150
|
+
return parseSymbols9({ file, content, lang: "py" });
|
|
5990
7151
|
}
|
|
5991
7152
|
case "rs": {
|
|
5992
|
-
const { parseSymbols:
|
|
5993
|
-
return
|
|
7153
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
|
|
7154
|
+
return parseSymbols9({ file, content, lang: "rs" });
|
|
5994
7155
|
}
|
|
5995
7156
|
case "json": {
|
|
5996
|
-
const { parseSymbols:
|
|
5997
|
-
return
|
|
7157
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
|
|
7158
|
+
return parseSymbols9({ file, content, lang: "json" });
|
|
5998
7159
|
}
|
|
5999
7160
|
case "yaml": {
|
|
6000
|
-
const { parseSymbols:
|
|
6001
|
-
return
|
|
7161
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
|
|
7162
|
+
return parseSymbols9({ file, content, lang: "yaml" });
|
|
7163
|
+
}
|
|
7164
|
+
// Phase 1: ten languages now route through the Tree-Sitter WASM
|
|
7165
|
+
// universal parser (`tree-sitter-parser.ts`). The dispatch falls back to
|
|
7166
|
+
// the regex extractor in `generic-parser.ts` whenever WASM loading fails
|
|
7167
|
+
// or the parser returns zero symbols — preserving the indexable-file
|
|
7168
|
+
// contract that "missing a parser must never mean skipping the file".
|
|
7169
|
+
case "c":
|
|
7170
|
+
case "cpp":
|
|
7171
|
+
case "java":
|
|
7172
|
+
case "csharp":
|
|
7173
|
+
case "php":
|
|
7174
|
+
case "ruby":
|
|
7175
|
+
case "swift":
|
|
7176
|
+
case "kotlin":
|
|
7177
|
+
case "shell":
|
|
7178
|
+
case "elixir": {
|
|
7179
|
+
try {
|
|
7180
|
+
const { parseSymbols: parseSymbols10 } = await Promise.resolve().then(() => (init_tree_sitter_parser(), tree_sitter_parser_exports));
|
|
7181
|
+
const parsed = await parseSymbols10({ file, content, lang });
|
|
7182
|
+
if (parsed.symbols.length > 0) return parsed;
|
|
7183
|
+
} catch {
|
|
7184
|
+
}
|
|
7185
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
7186
|
+
return parseSymbols9({ file, content, lang });
|
|
6002
7187
|
}
|
|
6003
7188
|
default: {
|
|
6004
|
-
const { parseSymbols:
|
|
6005
|
-
return
|
|
7189
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
7190
|
+
return parseSymbols9({ file, content, lang });
|
|
6006
7191
|
}
|
|
6007
7192
|
}
|
|
6008
7193
|
}
|
|
@@ -6014,11 +7199,189 @@ function withRelations(parsed, content, lang) {
|
|
|
6014
7199
|
return { ...parsed, refs: refs.map((ref) => ref.lang ? ref : { ...ref, lang }) };
|
|
6015
7200
|
}
|
|
6016
7201
|
|
|
7202
|
+
// src/codebase-index/parser-worker-pool.ts
|
|
7203
|
+
import { Worker } from "node:worker_threads";
|
|
7204
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
7205
|
+
import * as fs10 from "node:fs";
|
|
7206
|
+
var WORKER_POOL_THRESHOLD = 500;
|
|
7207
|
+
var ParserWorkerPool = class {
|
|
7208
|
+
constructor(maxWorkers = defaultWorkerCount()) {
|
|
7209
|
+
this.maxWorkers = maxWorkers;
|
|
7210
|
+
}
|
|
7211
|
+
maxWorkers;
|
|
7212
|
+
workers = [];
|
|
7213
|
+
nextBatchId = 1;
|
|
7214
|
+
pending = /* @__PURE__ */ new Map();
|
|
7215
|
+
creating = false;
|
|
7216
|
+
unavailable = false;
|
|
7217
|
+
/**
|
|
7218
|
+
* True if the pool is available for use. Returns false when:
|
|
7219
|
+
* - Worker threads aren't supported (sandbox, exotic runtime)
|
|
7220
|
+
* - The built worker script can't be found
|
|
7221
|
+
* - Pool creation was attempted and failed
|
|
7222
|
+
*/
|
|
7223
|
+
isAvailable() {
|
|
7224
|
+
return !this.unavailable && this.workers.length > 0;
|
|
7225
|
+
}
|
|
7226
|
+
/**
|
|
7227
|
+
* Lazily create the worker pool. Returns true if the pool is ready, false
|
|
7228
|
+
* if it's unavailable (caller should fall back to inline parsing).
|
|
7229
|
+
*/
|
|
7230
|
+
async ensureReady() {
|
|
7231
|
+
if (this.isAvailable()) return true;
|
|
7232
|
+
if (this.unavailable) return false;
|
|
7233
|
+
if (this.creating) {
|
|
7234
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
7235
|
+
return this.isAvailable();
|
|
7236
|
+
}
|
|
7237
|
+
this.creating = true;
|
|
7238
|
+
try {
|
|
7239
|
+
const url = resolveWorkerScriptUrl();
|
|
7240
|
+
if (!url) {
|
|
7241
|
+
this.unavailable = true;
|
|
7242
|
+
return false;
|
|
7243
|
+
}
|
|
7244
|
+
for (let i = 0; i < this.maxWorkers; i++) {
|
|
7245
|
+
try {
|
|
7246
|
+
const w = new Worker(url, { name: `wstack-parser-${i}` });
|
|
7247
|
+
w.unref();
|
|
7248
|
+
w.on("message", (msg) => this.handleMessage(msg));
|
|
7249
|
+
w.on("error", (err) => this.handleError(err, w));
|
|
7250
|
+
this.workers.push({ worker: w, busy: false });
|
|
7251
|
+
} catch {
|
|
7252
|
+
if (this.workers.length === 0) {
|
|
7253
|
+
this.unavailable = true;
|
|
7254
|
+
return false;
|
|
7255
|
+
}
|
|
7256
|
+
break;
|
|
7257
|
+
}
|
|
7258
|
+
}
|
|
7259
|
+
return this.workers.length > 0;
|
|
7260
|
+
} finally {
|
|
7261
|
+
this.creating = false;
|
|
7262
|
+
}
|
|
7263
|
+
}
|
|
7264
|
+
/**
|
|
7265
|
+
* Parse files in parallel across the worker pool. Returns a flat
|
|
7266
|
+
* `FileSymbols[]` in completion order (caller sorts if needed).
|
|
7267
|
+
*
|
|
7268
|
+
* Content is pre-read by the main thread (for the content-hash check)
|
|
7269
|
+
* and passed to workers to avoid a second disk read. Files are
|
|
7270
|
+
* distributed round-robin across workers.
|
|
7271
|
+
*/
|
|
7272
|
+
async parseFiles(files) {
|
|
7273
|
+
if (!this.isAvailable()) {
|
|
7274
|
+
throw new Error("ParserWorkerPool.parseFiles called before ensureReady() succeeded");
|
|
7275
|
+
}
|
|
7276
|
+
if (files.length === 0) return [];
|
|
7277
|
+
const batchId = this.nextBatchId++;
|
|
7278
|
+
const workerCount = Math.min(this.workers.length, files.length);
|
|
7279
|
+
const chunks = Array.from(
|
|
7280
|
+
{ length: workerCount },
|
|
7281
|
+
() => []
|
|
7282
|
+
);
|
|
7283
|
+
for (let i = 0; i < files.length; i++) {
|
|
7284
|
+
chunks[i % workerCount].push(files[i]);
|
|
7285
|
+
}
|
|
7286
|
+
return new Promise((resolve4, reject) => {
|
|
7287
|
+
this.pending.set(batchId, {
|
|
7288
|
+
resolve: resolve4,
|
|
7289
|
+
reject,
|
|
7290
|
+
accumulated: [],
|
|
7291
|
+
expectedWorkers: workerCount,
|
|
7292
|
+
completedWorkers: 0
|
|
7293
|
+
});
|
|
7294
|
+
for (let i = 0; i < workerCount; i++) {
|
|
7295
|
+
const pw = this.workers[i];
|
|
7296
|
+
pw.busy = true;
|
|
7297
|
+
pw.worker.postMessage({
|
|
7298
|
+
type: "parse",
|
|
7299
|
+
id: batchId,
|
|
7300
|
+
files: chunks[i]
|
|
7301
|
+
});
|
|
7302
|
+
}
|
|
7303
|
+
});
|
|
7304
|
+
}
|
|
7305
|
+
/** Shut down all workers. Safe to call multiple times. */
|
|
7306
|
+
async shutdown() {
|
|
7307
|
+
const workers = this.workers.map((w) => w.worker);
|
|
7308
|
+
this.workers = [];
|
|
7309
|
+
this.unavailable = false;
|
|
7310
|
+
for (const w of workers) {
|
|
7311
|
+
try {
|
|
7312
|
+
w.postMessage({ type: "shutdown" });
|
|
7313
|
+
} catch {
|
|
7314
|
+
}
|
|
7315
|
+
}
|
|
7316
|
+
await Promise.allSettled(
|
|
7317
|
+
workers.map(
|
|
7318
|
+
(w) => Promise.race([
|
|
7319
|
+
new Promise((resolve4) => {
|
|
7320
|
+
w.once("exit", () => resolve4());
|
|
7321
|
+
}),
|
|
7322
|
+
new Promise((resolve4) => setTimeout(() => resolve4(), 2e3))
|
|
7323
|
+
]).then(() => {
|
|
7324
|
+
if (!w.threadId) return;
|
|
7325
|
+
return w.terminate().catch(() => {
|
|
7326
|
+
});
|
|
7327
|
+
})
|
|
7328
|
+
)
|
|
7329
|
+
);
|
|
7330
|
+
for (const [, p] of this.pending) p.reject(new Error("ParserWorkerPool shut down"));
|
|
7331
|
+
this.pending.clear();
|
|
7332
|
+
}
|
|
7333
|
+
handleMessage(msg) {
|
|
7334
|
+
const batch = this.pending.get(msg.id);
|
|
7335
|
+
if (!batch) return;
|
|
7336
|
+
batch.accumulated.push(...msg.results);
|
|
7337
|
+
batch.completedWorkers++;
|
|
7338
|
+
const freeWorker = this.workers.find((w) => w.busy);
|
|
7339
|
+
if (freeWorker) freeWorker.busy = false;
|
|
7340
|
+
if (batch.completedWorkers >= batch.expectedWorkers) {
|
|
7341
|
+
this.pending.delete(msg.id);
|
|
7342
|
+
batch.resolve(batch.accumulated);
|
|
7343
|
+
}
|
|
7344
|
+
}
|
|
7345
|
+
handleError(err, source) {
|
|
7346
|
+
this.workers = this.workers.filter((w) => w.worker !== source);
|
|
7347
|
+
if (this.workers.length === 0) {
|
|
7348
|
+
for (const [, p] of this.pending) p.reject(err);
|
|
7349
|
+
this.pending.clear();
|
|
7350
|
+
this.unavailable = true;
|
|
7351
|
+
}
|
|
7352
|
+
}
|
|
7353
|
+
};
|
|
7354
|
+
function defaultWorkerCount() {
|
|
7355
|
+
const cores = globalThis.navigator?.hardwareConcurrency ?? 4;
|
|
7356
|
+
return Math.max(1, Math.min(4, cores - 1));
|
|
7357
|
+
}
|
|
7358
|
+
function resolveWorkerScriptUrl() {
|
|
7359
|
+
for (const rel of [
|
|
7360
|
+
"./parser-worker-script.js",
|
|
7361
|
+
"./codebase-index/parser-worker-script.js"
|
|
7362
|
+
]) {
|
|
7363
|
+
try {
|
|
7364
|
+
const url = new URL(rel, import.meta.url);
|
|
7365
|
+
if (url.protocol === "file:" && fs10.existsSync(fileURLToPath4(url))) return url;
|
|
7366
|
+
} catch {
|
|
7367
|
+
}
|
|
7368
|
+
}
|
|
7369
|
+
return null;
|
|
7370
|
+
}
|
|
7371
|
+
var _pool = null;
|
|
7372
|
+
function getParserPool() {
|
|
7373
|
+
_pool ??= new ParserWorkerPool();
|
|
7374
|
+
return _pool;
|
|
7375
|
+
}
|
|
7376
|
+
|
|
6017
7377
|
// src/codebase-index/indexer.ts
|
|
6018
7378
|
var YIELD_EVERY_N = 50;
|
|
6019
7379
|
function resolveParallelBatch() {
|
|
6020
7380
|
return indexParallelBatchSize(availableParallelism());
|
|
6021
7381
|
}
|
|
7382
|
+
function shouldUseParserWorkerPool(candidateFileCount, parseBatchCount) {
|
|
7383
|
+
return !isFrugalPerf() && candidateFileCount >= WORKER_POOL_THRESHOLD && parseBatchCount > 1;
|
|
7384
|
+
}
|
|
6022
7385
|
function yieldEventLoop() {
|
|
6023
7386
|
return new Promise((resolve4) => setImmediate(resolve4));
|
|
6024
7387
|
}
|
|
@@ -6036,15 +7399,15 @@ var INDEXABLE_EXTENSION_SET = new Set(INDEXABLE_EXTENSIONS);
|
|
|
6036
7399
|
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
6037
7400
|
var MAX_GIT_FILE_LIST_BYTES = 64 * 1024 * 1024;
|
|
6038
7401
|
function isWithinProject(projectRoot, file) {
|
|
6039
|
-
const rel =
|
|
6040
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
7402
|
+
const rel = path13.relative(projectRoot, file);
|
|
7403
|
+
return rel !== "" && !rel.startsWith(`..${path13.sep}`) && rel !== ".." && !path13.isAbsolute(rel);
|
|
6041
7404
|
}
|
|
6042
7405
|
function isMissingPathError(err) {
|
|
6043
7406
|
const code = err?.code;
|
|
6044
7407
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
6045
7408
|
}
|
|
6046
7409
|
function normalizeComparablePath(value) {
|
|
6047
|
-
const resolved =
|
|
7410
|
+
const resolved = path13.resolve(value);
|
|
6048
7411
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
6049
7412
|
}
|
|
6050
7413
|
function gitOutput(projectRoot, args) {
|
|
@@ -6089,24 +7452,24 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
|
|
|
6089
7452
|
const record = statusRecords[i];
|
|
6090
7453
|
if (!record) continue;
|
|
6091
7454
|
const status = record.slice(0, 2);
|
|
6092
|
-
const changedPath =
|
|
7455
|
+
const changedPath = path13.resolve(projectRoot, record.slice(3));
|
|
6093
7456
|
dirty.add(changedPath);
|
|
6094
7457
|
if (status.includes("D")) deleted.add(changedPath);
|
|
6095
7458
|
if (status.includes("R") || status.includes("C")) {
|
|
6096
7459
|
const source = statusRecords[++i];
|
|
6097
|
-
if (source) dirty.add(
|
|
7460
|
+
if (source) dirty.add(path13.resolve(projectRoot, source));
|
|
6098
7461
|
}
|
|
6099
7462
|
}
|
|
6100
7463
|
const files = [];
|
|
6101
7464
|
for (const relative2 of output.toString("utf8").split("\0")) {
|
|
6102
7465
|
if (!relative2) continue;
|
|
6103
7466
|
const portable = relative2.replace(/\\/g, "/");
|
|
6104
|
-
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(
|
|
7467
|
+
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path13.posix.basename(portable))) {
|
|
6105
7468
|
continue;
|
|
6106
7469
|
}
|
|
6107
|
-
const full =
|
|
7470
|
+
const full = path13.resolve(projectRoot, relative2);
|
|
6108
7471
|
if (deleted.has(full)) continue;
|
|
6109
|
-
const ext =
|
|
7472
|
+
const ext = path13.extname(relative2).toLowerCase();
|
|
6110
7473
|
if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
|
|
6111
7474
|
}
|
|
6112
7475
|
return {
|
|
@@ -6141,7 +7504,7 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
6141
7504
|
}
|
|
6142
7505
|
let entries;
|
|
6143
7506
|
try {
|
|
6144
|
-
entries = await
|
|
7507
|
+
entries = await fs11.readdir(dir, { withFileTypes: true });
|
|
6145
7508
|
} catch (err) {
|
|
6146
7509
|
complete = false;
|
|
6147
7510
|
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -6150,14 +7513,14 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
|
|
|
6150
7513
|
dirCount++;
|
|
6151
7514
|
for (const e of entries) {
|
|
6152
7515
|
if (ignoreSet.has(e.name)) continue;
|
|
6153
|
-
const full =
|
|
6154
|
-
const rel =
|
|
7516
|
+
const full = path13.join(dir, e.name);
|
|
7517
|
+
const rel = path13.relative(projectRoot, full).replace(/\\/g, "/");
|
|
6155
7518
|
if (e.isDirectory()) {
|
|
6156
7519
|
if (isGitIgnored(rel, true)) continue;
|
|
6157
7520
|
await walk(full);
|
|
6158
7521
|
} else if (e.isFile()) {
|
|
6159
7522
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
6160
|
-
const ext =
|
|
7523
|
+
const ext = path13.extname(e.name).toLowerCase();
|
|
6161
7524
|
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
6162
7525
|
results.push(full);
|
|
6163
7526
|
}
|
|
@@ -6195,11 +7558,7 @@ async function resolveProjectRelations(store, projectRoot, opts) {
|
|
|
6195
7558
|
const structure = await detectModuleRoots(projectRoot, indexedFiles);
|
|
6196
7559
|
if (opts.signal?.aborted) return;
|
|
6197
7560
|
store.setFilePackages(assignPackageLabels(structure, indexedFiles));
|
|
6198
|
-
const resolver = new ModuleResolver(
|
|
6199
|
-
structure,
|
|
6200
|
-
indexedFiles,
|
|
6201
|
-
store.getNamespaceDeclarations()
|
|
6202
|
-
);
|
|
7561
|
+
const resolver = new ModuleResolver(structure, indexedFiles, store.getNamespaceDeclarations());
|
|
6203
7562
|
const pending2 = store.getUnresolvedImports(opts.onlyFiles);
|
|
6204
7563
|
const resolutions = [];
|
|
6205
7564
|
for (const entry of pending2) {
|
|
@@ -6235,6 +7594,10 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6235
7594
|
const errors = [];
|
|
6236
7595
|
const langStats = {};
|
|
6237
7596
|
let filesIndexed = 0;
|
|
7597
|
+
let filesParsed = 0;
|
|
7598
|
+
let filesSkipped = 0;
|
|
7599
|
+
let filesEmpty = 0;
|
|
7600
|
+
let filesFailed = 0;
|
|
6238
7601
|
let symbolsIndexed = 0;
|
|
6239
7602
|
const isGitIgnored = await loadGitignoreMatcher(projectRoot);
|
|
6240
7603
|
let files;
|
|
@@ -6242,10 +7605,10 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6242
7605
|
let discoveryComplete = true;
|
|
6243
7606
|
let trustedUnchanged;
|
|
6244
7607
|
if (opts.files && opts.files.length > 0) {
|
|
6245
|
-
files = opts.files.map((f) =>
|
|
7608
|
+
files = opts.files.map((f) => path13.resolve(projectRoot, f)).filter((f) => {
|
|
6246
7609
|
if (!isWithinProject(projectRoot, f)) return false;
|
|
6247
|
-
const rel =
|
|
6248
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
7610
|
+
const rel = path13.relative(projectRoot, f).replace(/\\/g, "/");
|
|
7611
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path13.basename(f)) && !isGitIgnored(rel, false);
|
|
6249
7612
|
});
|
|
6250
7613
|
} else {
|
|
6251
7614
|
const discovery = await findSourceFiles(projectRoot, ignore, isGitIgnored, signal);
|
|
@@ -6276,12 +7639,14 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6276
7639
|
langStats[meta.lang] = (langStats[meta.lang] ?? 0) + meta.symbolCount;
|
|
6277
7640
|
symbolsIndexed += meta.symbolCount;
|
|
6278
7641
|
filesIndexed++;
|
|
7642
|
+
filesSkipped++;
|
|
6279
7643
|
filesPreSkipped++;
|
|
6280
7644
|
return false;
|
|
6281
7645
|
});
|
|
6282
7646
|
if (filesPreSkipped > 0) opts.onProgress?.(filesPreSkipped, totalFilesForProgress);
|
|
6283
7647
|
}
|
|
6284
7648
|
const parallelBatch = resolveParallelBatch();
|
|
7649
|
+
const parserPoolCandidateCount = files.length;
|
|
6285
7650
|
let filesSinceLastYield = 0;
|
|
6286
7651
|
for (let batchStart = 0; batchStart < files.length; batchStart += parallelBatch) {
|
|
6287
7652
|
const batchEnd = Math.min(batchStart + parallelBatch, files.length);
|
|
@@ -6302,7 +7667,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6302
7667
|
async (file) => {
|
|
6303
7668
|
let stat2;
|
|
6304
7669
|
try {
|
|
6305
|
-
stat2 = await
|
|
7670
|
+
stat2 = await fs11.stat(file, statOpts);
|
|
6306
7671
|
} catch (e) {
|
|
6307
7672
|
if (isAbortError(e)) throw e;
|
|
6308
7673
|
return {
|
|
@@ -6332,7 +7697,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6332
7697
|
}
|
|
6333
7698
|
let content;
|
|
6334
7699
|
try {
|
|
6335
|
-
content = await
|
|
7700
|
+
content = await fs11.readFile(file, { encoding: "utf8", signal });
|
|
6336
7701
|
} catch (e) {
|
|
6337
7702
|
if (isAbortError(e)) throw e;
|
|
6338
7703
|
return {
|
|
@@ -6343,22 +7708,78 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6343
7708
|
error: `read error: ${e instanceof Error ? e.message : String(e)}`
|
|
6344
7709
|
};
|
|
6345
7710
|
}
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
parsed = await parseFileContent(file, content, lang);
|
|
6349
|
-
} catch (e) {
|
|
7711
|
+
const contentHash = xxhash64String(content);
|
|
7712
|
+
if (!force && meta && meta.contentHash && contentHash === meta.contentHash) {
|
|
6350
7713
|
return {
|
|
6351
7714
|
file,
|
|
6352
7715
|
stat: stat2,
|
|
6353
7716
|
lang,
|
|
6354
7717
|
parsed: null,
|
|
6355
|
-
|
|
7718
|
+
content,
|
|
7719
|
+
contentHash,
|
|
7720
|
+
skippedMeta: { ...meta, mtimeMs: Math.floor(stat2.mtimeMs) }
|
|
6356
7721
|
};
|
|
6357
7722
|
}
|
|
6358
|
-
return { file, stat: stat2, lang, parsed, content };
|
|
7723
|
+
return { file, stat: stat2, lang, parsed: null, content, contentHash };
|
|
6359
7724
|
}
|
|
6360
7725
|
)
|
|
6361
7726
|
);
|
|
7727
|
+
const toParse = [];
|
|
7728
|
+
for (let pi = 0; pi < statReadParse.length; pi++) {
|
|
7729
|
+
const s = statReadParse[pi];
|
|
7730
|
+
if (s.status !== "fulfilled") continue;
|
|
7731
|
+
const r = s.value;
|
|
7732
|
+
if (r.error || r.skippedMeta || !r.lang || r.parsed) continue;
|
|
7733
|
+
if (r.content === void 0) continue;
|
|
7734
|
+
toParse.push({
|
|
7735
|
+
index: pi,
|
|
7736
|
+
file: batchFiles[pi],
|
|
7737
|
+
content: r.content,
|
|
7738
|
+
lang: r.lang
|
|
7739
|
+
});
|
|
7740
|
+
}
|
|
7741
|
+
if (toParse.length > 0) {
|
|
7742
|
+
let pool = shouldUseParserWorkerPool(parserPoolCandidateCount, toParse.length) ? getParserPool() : null;
|
|
7743
|
+
if (pool) {
|
|
7744
|
+
try {
|
|
7745
|
+
await pool.ensureReady();
|
|
7746
|
+
const parsedResults = await pool.parseFiles(
|
|
7747
|
+
toParse.map((p) => ({ file: p.file, content: p.content, lang: p.lang }))
|
|
7748
|
+
);
|
|
7749
|
+
const byFile = new Map(parsedResults.map((r) => [r.file, r]));
|
|
7750
|
+
for (const item of toParse) {
|
|
7751
|
+
const parsed = byFile.get(item.file);
|
|
7752
|
+
const settled = statReadParse[item.index];
|
|
7753
|
+
if (settled.status !== "fulfilled") continue;
|
|
7754
|
+
if (parsed) {
|
|
7755
|
+
settled.value.parsed = parsed;
|
|
7756
|
+
} else {
|
|
7757
|
+
settled.value.error = `parse error: worker returned no result for ${item.file}`;
|
|
7758
|
+
}
|
|
7759
|
+
}
|
|
7760
|
+
} catch {
|
|
7761
|
+
pool = null;
|
|
7762
|
+
}
|
|
7763
|
+
}
|
|
7764
|
+
if (!pool) {
|
|
7765
|
+
await Promise.all(
|
|
7766
|
+
toParse.map(async (item) => {
|
|
7767
|
+
try {
|
|
7768
|
+
const parsed = await parseFileContent(item.file, item.content, item.lang);
|
|
7769
|
+
const settled = statReadParse[item.index];
|
|
7770
|
+
if (settled.status === "fulfilled") {
|
|
7771
|
+
settled.value.parsed = parsed;
|
|
7772
|
+
}
|
|
7773
|
+
} catch (e) {
|
|
7774
|
+
const settled = statReadParse[item.index];
|
|
7775
|
+
if (settled.status === "fulfilled") {
|
|
7776
|
+
settled.value.error = `parse error: ${e instanceof Error ? e.message : String(e)}`;
|
|
7777
|
+
}
|
|
7778
|
+
}
|
|
7779
|
+
})
|
|
7780
|
+
);
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
6362
7783
|
const batchEntries = [];
|
|
6363
7784
|
const deleteForFiles = [];
|
|
6364
7785
|
for (let fi = 0; fi < statReadParse.length; fi++) {
|
|
@@ -6368,12 +7789,14 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6368
7789
|
const err = settled.reason;
|
|
6369
7790
|
if (err instanceof Error && isAbortError(err)) throw err;
|
|
6370
7791
|
errors.push(`batch error: ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7792
|
+
filesFailed++;
|
|
6371
7793
|
continue;
|
|
6372
7794
|
}
|
|
6373
7795
|
const result = settled.value;
|
|
6374
7796
|
if (result.error) {
|
|
6375
7797
|
if (result.missing) store.deleteFile(file);
|
|
6376
7798
|
errors.push(`${file}: ${result.error}`);
|
|
7799
|
+
filesFailed++;
|
|
6377
7800
|
continue;
|
|
6378
7801
|
}
|
|
6379
7802
|
const { stat: stat2, lang, parsed } = result;
|
|
@@ -6381,6 +7804,18 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6381
7804
|
langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
|
|
6382
7805
|
symbolsIndexed += result.skippedMeta.symbolCount;
|
|
6383
7806
|
filesIndexed++;
|
|
7807
|
+
filesSkipped++;
|
|
7808
|
+
const stored = existingMeta.get(file);
|
|
7809
|
+
if (stored && stored.mtimeMs !== result.skippedMeta.mtimeMs) {
|
|
7810
|
+
store.upsertFile({
|
|
7811
|
+
file,
|
|
7812
|
+
lang,
|
|
7813
|
+
mtimeMs: result.skippedMeta.mtimeMs,
|
|
7814
|
+
symbolCount: result.skippedMeta.symbolCount,
|
|
7815
|
+
lastIndexed: Date.now(),
|
|
7816
|
+
contentHash: result.skippedMeta.contentHash
|
|
7817
|
+
});
|
|
7818
|
+
}
|
|
6384
7819
|
continue;
|
|
6385
7820
|
}
|
|
6386
7821
|
if (!lang || !parsed) {
|
|
@@ -6390,9 +7825,11 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6390
7825
|
lang,
|
|
6391
7826
|
mtimeMs: Math.floor(stat2.mtimeMs),
|
|
6392
7827
|
symbolCount: 0,
|
|
6393
|
-
lastIndexed: Date.now()
|
|
7828
|
+
lastIndexed: Date.now(),
|
|
7829
|
+
contentHash: result.contentHash ?? ""
|
|
6394
7830
|
});
|
|
6395
7831
|
filesIndexed++;
|
|
7832
|
+
filesEmpty++;
|
|
6396
7833
|
}
|
|
6397
7834
|
continue;
|
|
6398
7835
|
}
|
|
@@ -6402,9 +7839,11 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6402
7839
|
lang,
|
|
6403
7840
|
mtimeMs: Math.floor(stat2.mtimeMs),
|
|
6404
7841
|
symbolCount: 0,
|
|
6405
|
-
lastIndexed: Date.now()
|
|
7842
|
+
lastIndexed: Date.now(),
|
|
7843
|
+
contentHash: result.contentHash ?? ""
|
|
6406
7844
|
});
|
|
6407
7845
|
filesIndexed++;
|
|
7846
|
+
filesEmpty++;
|
|
6408
7847
|
continue;
|
|
6409
7848
|
}
|
|
6410
7849
|
batchEntries.push({
|
|
@@ -6413,7 +7852,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6413
7852
|
symbols: parsed.symbols,
|
|
6414
7853
|
refs: parsed.refs ?? [],
|
|
6415
7854
|
mtimeMs: Math.floor(stat2.mtimeMs),
|
|
6416
|
-
symbolCount: parsed.symbols.length
|
|
7855
|
+
symbolCount: parsed.symbols.length,
|
|
7856
|
+
contentHash: result.contentHash ?? ""
|
|
6417
7857
|
});
|
|
6418
7858
|
deleteForFiles.push(file);
|
|
6419
7859
|
}
|
|
@@ -6425,6 +7865,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6425
7865
|
symbolsIndexed += count;
|
|
6426
7866
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + count;
|
|
6427
7867
|
filesIndexed++;
|
|
7868
|
+
filesParsed++;
|
|
6428
7869
|
}
|
|
6429
7870
|
} catch (err) {
|
|
6430
7871
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -6437,6 +7878,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6437
7878
|
symbolsIndexed += symbolsWithIds.length;
|
|
6438
7879
|
langStats[entry.lang] = (langStats[entry.lang] ?? 0) + symbolsWithIds.length;
|
|
6439
7880
|
filesIndexed++;
|
|
7881
|
+
filesParsed++;
|
|
6440
7882
|
if (entry.refs.length > 0 && symbolsWithIds.length > 0) {
|
|
6441
7883
|
const fallbackBatch = assignRefsToSymbols2(entry.refs, symbolsWithIds);
|
|
6442
7884
|
if (fallbackBatch.length > 0) store.insertRefsBatch(fallbackBatch);
|
|
@@ -6450,9 +7892,11 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6450
7892
|
lang: entry.lang,
|
|
6451
7893
|
mtimeMs: entry.mtimeMs,
|
|
6452
7894
|
symbolCount: entry.symbolCount,
|
|
6453
|
-
lastIndexed: Date.now()
|
|
7895
|
+
lastIndexed: Date.now(),
|
|
7896
|
+
contentHash: entry.contentHash
|
|
6454
7897
|
});
|
|
6455
7898
|
} catch (innerErr) {
|
|
7899
|
+
filesFailed++;
|
|
6456
7900
|
errors.push(
|
|
6457
7901
|
`fallback write failed: ${entry.file}: ${innerErr instanceof Error ? innerErr.message : String(innerErr)}`
|
|
6458
7902
|
);
|
|
@@ -6485,6 +7929,12 @@ async function runIndexerWithStore(store, opts) {
|
|
|
6485
7929
|
const durationMs = Date.now() - startMs;
|
|
6486
7930
|
return {
|
|
6487
7931
|
filesIndexed,
|
|
7932
|
+
fileOutcomes: {
|
|
7933
|
+
parsed: filesParsed,
|
|
7934
|
+
skipped: filesSkipped,
|
|
7935
|
+
empty: filesEmpty,
|
|
7936
|
+
failed: filesFailed
|
|
7937
|
+
},
|
|
6488
7938
|
symbolsIndexed,
|
|
6489
7939
|
langStats,
|
|
6490
7940
|
durationMs,
|
|
@@ -6562,6 +8012,9 @@ function symbolGraphService(args) {
|
|
|
6562
8012
|
function incomingCallsService(args) {
|
|
6563
8013
|
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
6564
8014
|
try {
|
|
8015
|
+
if (args.transitive) {
|
|
8016
|
+
return store.findTransitiveIncomingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
8017
|
+
}
|
|
6565
8018
|
return store.findIncomingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
6566
8019
|
} finally {
|
|
6567
8020
|
indexStorePool.release(store);
|
|
@@ -6570,6 +8023,9 @@ function incomingCallsService(args) {
|
|
|
6570
8023
|
function outgoingCallsService(args) {
|
|
6571
8024
|
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
6572
8025
|
try {
|
|
8026
|
+
if (args.transitive) {
|
|
8027
|
+
return store.findTransitiveOutgoingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
8028
|
+
}
|
|
6573
8029
|
return store.findOutgoingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
6574
8030
|
} finally {
|
|
6575
8031
|
indexStorePool.release(store);
|
|
@@ -6632,7 +8088,7 @@ function resolveWorkerUrl() {
|
|
|
6632
8088
|
for (const rel of ["./worker.js", "./codebase-index/worker.js"]) {
|
|
6633
8089
|
try {
|
|
6634
8090
|
const url = new URL(rel, import.meta.url);
|
|
6635
|
-
if (url.protocol === "file:" &&
|
|
8091
|
+
if (url.protocol === "file:" && fs12.existsSync(fileURLToPath5(url))) return url;
|
|
6636
8092
|
} catch {
|
|
6637
8093
|
}
|
|
6638
8094
|
}
|
|
@@ -6652,7 +8108,7 @@ function ensureWorker() {
|
|
|
6652
8108
|
return null;
|
|
6653
8109
|
}
|
|
6654
8110
|
try {
|
|
6655
|
-
const w = new
|
|
8111
|
+
const w = new Worker2(url, { name: "wstack-codebase-index" });
|
|
6656
8112
|
w.unref();
|
|
6657
8113
|
w.on("message", (msg) => {
|
|
6658
8114
|
if (msg.type === "progress") {
|
|
@@ -7125,6 +8581,11 @@ var codebaseIncomingCallsTool = {
|
|
|
7125
8581
|
description: "Maximum call sites to return (default 50, max 200)",
|
|
7126
8582
|
minimum: 1,
|
|
7127
8583
|
maximum: 200
|
|
8584
|
+
},
|
|
8585
|
+
transitive: {
|
|
8586
|
+
type: "boolean",
|
|
8587
|
+
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.",
|
|
8588
|
+
default: false
|
|
7128
8589
|
}
|
|
7129
8590
|
},
|
|
7130
8591
|
required: ["symbol"]
|
|
@@ -7150,13 +8611,15 @@ var codebaseIncomingCallsTool = {
|
|
|
7150
8611
|
};
|
|
7151
8612
|
}
|
|
7152
8613
|
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
|
|
8614
|
+
const transitive = input.transitive === true;
|
|
7153
8615
|
const { calls, symbolFound, ambiguous, totalMatches } = await incomingCallsService2(
|
|
7154
8616
|
{
|
|
7155
8617
|
projectRoot: ctx.projectRoot,
|
|
7156
8618
|
indexDir: codebaseIndexDirOverride(ctx),
|
|
7157
8619
|
symbol: input.symbol,
|
|
7158
8620
|
file: input.file,
|
|
7159
|
-
limit
|
|
8621
|
+
limit,
|
|
8622
|
+
transitive
|
|
7160
8623
|
}
|
|
7161
8624
|
);
|
|
7162
8625
|
if (!symbolFound) {
|
|
@@ -7229,6 +8692,11 @@ var codebaseOutgoingCallsTool = {
|
|
|
7229
8692
|
description: "Maximum call sites to return (default 50, max 200)",
|
|
7230
8693
|
minimum: 1,
|
|
7231
8694
|
maximum: 200
|
|
8695
|
+
},
|
|
8696
|
+
transitive: {
|
|
8697
|
+
type: "boolean",
|
|
8698
|
+
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.",
|
|
8699
|
+
default: false
|
|
7232
8700
|
}
|
|
7233
8701
|
},
|
|
7234
8702
|
required: ["symbol"]
|
|
@@ -7254,13 +8722,15 @@ var codebaseOutgoingCallsTool = {
|
|
|
7254
8722
|
};
|
|
7255
8723
|
}
|
|
7256
8724
|
const limit = Math.max(1, Math.min(Math.trunc(input.limit ?? 50), 200));
|
|
8725
|
+
const transitive = input.transitive === true;
|
|
7257
8726
|
const { calls, symbolFound, unresolvedCount, totalMatches } = await outgoingCallsService2(
|
|
7258
8727
|
{
|
|
7259
8728
|
projectRoot: ctx.projectRoot,
|
|
7260
8729
|
indexDir: codebaseIndexDirOverride(ctx),
|
|
7261
8730
|
symbol: input.symbol,
|
|
7262
8731
|
file: input.file,
|
|
7263
|
-
limit
|
|
8732
|
+
limit,
|
|
8733
|
+
transitive
|
|
7264
8734
|
}
|
|
7265
8735
|
);
|
|
7266
8736
|
if (!symbolFound) {
|
|
@@ -7520,8 +8990,8 @@ var codebaseStatsTool = {
|
|
|
7520
8990
|
|
|
7521
8991
|
// src/codebase-index/dead-code-scan.ts
|
|
7522
8992
|
init_languages();
|
|
7523
|
-
import * as
|
|
7524
|
-
import * as
|
|
8993
|
+
import * as fs13 from "node:fs";
|
|
8994
|
+
import * as path14 from "node:path";
|
|
7525
8995
|
var deadCodeScanTool = {
|
|
7526
8996
|
name: "dead-code-scan",
|
|
7527
8997
|
category: "Project",
|
|
@@ -7566,25 +9036,25 @@ var deadCodeScanTool = {
|
|
|
7566
9036
|
};
|
|
7567
9037
|
function tryReadJson(filePath) {
|
|
7568
9038
|
try {
|
|
7569
|
-
const raw =
|
|
9039
|
+
const raw = fs13.readFileSync(filePath, "utf8");
|
|
7570
9040
|
return JSON.parse(raw);
|
|
7571
9041
|
} catch {
|
|
7572
9042
|
return null;
|
|
7573
9043
|
}
|
|
7574
9044
|
}
|
|
7575
9045
|
function resolveAgainst(base, relative2) {
|
|
7576
|
-
if (
|
|
7577
|
-
return
|
|
9046
|
+
if (path14.isAbsolute(relative2)) return relative2;
|
|
9047
|
+
return path14.resolve(base, relative2);
|
|
7578
9048
|
}
|
|
7579
9049
|
function discoverEntryPoints(projectRoot, userEntryPoints) {
|
|
7580
9050
|
const entries = /* @__PURE__ */ new Set();
|
|
7581
9051
|
if (userEntryPoints) {
|
|
7582
9052
|
for (const ep of userEntryPoints) {
|
|
7583
9053
|
const resolved = resolveAgainst(projectRoot, ep);
|
|
7584
|
-
if (
|
|
9054
|
+
if (fs13.existsSync(resolved)) entries.add(resolved);
|
|
7585
9055
|
}
|
|
7586
9056
|
}
|
|
7587
|
-
const rootPkg = tryReadJson(
|
|
9057
|
+
const rootPkg = tryReadJson(path14.join(projectRoot, "package.json"));
|
|
7588
9058
|
if (rootPkg) {
|
|
7589
9059
|
addPkgJsonEntryPoints(projectRoot, rootPkg, entries);
|
|
7590
9060
|
}
|
|
@@ -7598,45 +9068,45 @@ function discoverEntryPoints(projectRoot, userEntryPoints) {
|
|
|
7598
9068
|
workspaces = [];
|
|
7599
9069
|
}
|
|
7600
9070
|
for (const wsDir of workspaces) {
|
|
7601
|
-
const pkgJsonPath =
|
|
9071
|
+
const pkgJsonPath = path14.join(wsDir, "package.json");
|
|
7602
9072
|
const pkg = tryReadJson(pkgJsonPath);
|
|
7603
9073
|
if (pkg) {
|
|
7604
9074
|
addPkgJsonEntryPoints(wsDir, pkg, entries);
|
|
7605
|
-
const convention1 =
|
|
7606
|
-
if (
|
|
7607
|
-
const convention2 =
|
|
7608
|
-
if (
|
|
7609
|
-
const convention3 =
|
|
7610
|
-
if (
|
|
9075
|
+
const convention1 = path14.join(wsDir, "src", "index.ts");
|
|
9076
|
+
if (fs13.existsSync(convention1)) entries.add(convention1);
|
|
9077
|
+
const convention2 = path14.join(wsDir, "src", "main.ts");
|
|
9078
|
+
if (fs13.existsSync(convention2)) entries.add(convention2);
|
|
9079
|
+
const convention3 = path14.join(wsDir, "index.ts");
|
|
9080
|
+
if (fs13.existsSync(convention3)) entries.add(convention3);
|
|
7611
9081
|
}
|
|
7612
9082
|
}
|
|
7613
9083
|
if (!rootPkg || !workspaces.length) {
|
|
7614
9084
|
for (const name of ["src/index.ts", "src/main.ts", "index.ts"]) {
|
|
7615
|
-
const convention =
|
|
7616
|
-
if (
|
|
9085
|
+
const convention = path14.join(projectRoot, name);
|
|
9086
|
+
if (fs13.existsSync(convention)) entries.add(convention);
|
|
7617
9087
|
}
|
|
7618
9088
|
}
|
|
7619
9089
|
return [...entries];
|
|
7620
9090
|
}
|
|
7621
9091
|
var BUILD_OUTPUT_DIRS = ["dist", "out", "build", "release"];
|
|
7622
|
-
var BUILD_OUTPUT_DIR_NAMES = BUILD_OUTPUT_DIRS.map((d) => `${
|
|
9092
|
+
var BUILD_OUTPUT_DIR_NAMES = BUILD_OUTPUT_DIRS.map((d) => `${path14.sep}${d}${path14.sep}`);
|
|
7623
9093
|
function trySourceEquivalent(resolved) {
|
|
7624
|
-
resolved = resolved.replace(/[/\\]/g,
|
|
9094
|
+
resolved = resolved.replace(/[/\\]/g, path14.sep);
|
|
7625
9095
|
for (const marker of BUILD_OUTPUT_DIR_NAMES) {
|
|
7626
9096
|
const idx = resolved.indexOf(marker);
|
|
7627
9097
|
if (idx === -1) continue;
|
|
7628
|
-
const base = resolved.replace(marker, `${
|
|
9098
|
+
const base = resolved.replace(marker, `${path14.sep}src${path14.sep}`);
|
|
7629
9099
|
const candidate = base.replace(/\.(js|mjs|cjs)$/, ".ts");
|
|
7630
|
-
if (candidate !== base &&
|
|
9100
|
+
if (candidate !== base && fs13.existsSync(candidate)) {
|
|
7631
9101
|
return candidate;
|
|
7632
9102
|
}
|
|
7633
9103
|
const dtsStripped = base.replace(/\.d\.ts$/, "");
|
|
7634
9104
|
const candidateDts = dtsStripped + ".ts";
|
|
7635
|
-
if (candidateDts !== base && candidateDts !== candidate &&
|
|
9105
|
+
if (candidateDts !== base && candidateDts !== candidate && fs13.existsSync(candidateDts)) {
|
|
7636
9106
|
return candidateDts;
|
|
7637
9107
|
}
|
|
7638
9108
|
const candidateNoExt = base + ".ts";
|
|
7639
|
-
if (candidate !== candidateNoExt && candidateNoExt !== candidateDts &&
|
|
9109
|
+
if (candidate !== candidateNoExt && candidateNoExt !== candidateDts && fs13.existsSync(candidateNoExt)) {
|
|
7640
9110
|
return candidateNoExt;
|
|
7641
9111
|
}
|
|
7642
9112
|
}
|
|
@@ -7644,9 +9114,9 @@ function trySourceEquivalent(resolved) {
|
|
|
7644
9114
|
}
|
|
7645
9115
|
function tryAddEntryPath(pkgDir, rawPath, entries) {
|
|
7646
9116
|
const resolved = resolveAgainst(pkgDir, rawPath);
|
|
7647
|
-
if (
|
|
9117
|
+
if (fs13.existsSync(resolved)) entries.add(resolved);
|
|
7648
9118
|
const tsResolved = resolved.replace(/\.(js|mjs|cjs)$/, ".ts");
|
|
7649
|
-
if (tsResolved !== resolved &&
|
|
9119
|
+
if (tsResolved !== resolved && fs13.existsSync(tsResolved)) {
|
|
7650
9120
|
entries.add(tsResolved);
|
|
7651
9121
|
}
|
|
7652
9122
|
const srcAlt = trySourceEquivalent(resolved);
|
|
@@ -7690,18 +9160,18 @@ function expandGlobPattern(entry, projectRoot) {
|
|
|
7690
9160
|
const dirs = [];
|
|
7691
9161
|
if (entry.includes("*")) {
|
|
7692
9162
|
const base = entry.replace(/\/\*+$/, "");
|
|
7693
|
-
const baseDir =
|
|
9163
|
+
const baseDir = path14.resolve(projectRoot, base);
|
|
7694
9164
|
try {
|
|
7695
|
-
const children =
|
|
9165
|
+
const children = fs13.readdirSync(baseDir, { withFileTypes: true });
|
|
7696
9166
|
for (const child of children) {
|
|
7697
9167
|
if (child.isDirectory()) {
|
|
7698
|
-
dirs.push(
|
|
9168
|
+
dirs.push(path14.join(baseDir, child.name));
|
|
7699
9169
|
}
|
|
7700
9170
|
}
|
|
7701
9171
|
} catch {
|
|
7702
9172
|
}
|
|
7703
9173
|
} else {
|
|
7704
|
-
dirs.push(
|
|
9174
|
+
dirs.push(path14.resolve(projectRoot, entry));
|
|
7705
9175
|
}
|
|
7706
9176
|
return dirs;
|
|
7707
9177
|
}
|
|
@@ -7718,10 +9188,10 @@ function extractWorkspaceGlobs(pkg, projectRoot) {
|
|
|
7718
9188
|
return dirs;
|
|
7719
9189
|
}
|
|
7720
9190
|
function extractPnpmWorkspaceDirs(projectRoot) {
|
|
7721
|
-
const yamlPath =
|
|
7722
|
-
if (!
|
|
9191
|
+
const yamlPath = path14.join(projectRoot, "pnpm-workspace.yaml");
|
|
9192
|
+
if (!fs13.existsSync(yamlPath)) return [];
|
|
7723
9193
|
try {
|
|
7724
|
-
const content =
|
|
9194
|
+
const content = fs13.readFileSync(yamlPath, "utf8");
|
|
7725
9195
|
const dirs = [];
|
|
7726
9196
|
let inPackages = false;
|
|
7727
9197
|
const lines = content.split("\n");
|
|
@@ -7755,8 +9225,8 @@ function extractPnpmWorkspaceDirs(projectRoot) {
|
|
|
7755
9225
|
}
|
|
7756
9226
|
function resolveModulePath(importerPath, moduleSpecifier, indexedFiles) {
|
|
7757
9227
|
if (!moduleSpecifier.startsWith(".")) return [];
|
|
7758
|
-
const dir =
|
|
7759
|
-
const base =
|
|
9228
|
+
const dir = path14.dirname(importerPath);
|
|
9229
|
+
const base = path14.resolve(dir, moduleSpecifier);
|
|
7760
9230
|
const results = [];
|
|
7761
9231
|
const stripped = base.replace(/\.(ts|tsx|js|jsx|mjs|cjs)$/, "");
|
|
7762
9232
|
const skipBase = stripped !== base && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(base);
|
|
@@ -7768,18 +9238,18 @@ function resolveModulePath(importerPath, moduleSpecifier, indexedFiles) {
|
|
|
7768
9238
|
if (indexedFiles.has(candidate + ".jsx")) results.push(candidate + ".jsx");
|
|
7769
9239
|
if (indexedFiles.has(candidate + ".mjs")) results.push(candidate + ".mjs");
|
|
7770
9240
|
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(
|
|
9241
|
+
if (indexedFiles.has(path14.join(candidate, "index.ts")))
|
|
9242
|
+
results.push(path14.join(candidate, "index.ts"));
|
|
9243
|
+
if (indexedFiles.has(path14.join(candidate, "index.tsx")))
|
|
9244
|
+
results.push(path14.join(candidate, "index.tsx"));
|
|
9245
|
+
if (indexedFiles.has(path14.join(candidate, "index.js")))
|
|
9246
|
+
results.push(path14.join(candidate, "index.js"));
|
|
9247
|
+
if (indexedFiles.has(path14.join(candidate, "index.jsx")))
|
|
9248
|
+
results.push(path14.join(candidate, "index.jsx"));
|
|
9249
|
+
if (indexedFiles.has(path14.join(candidate, "index.mjs")))
|
|
9250
|
+
results.push(path14.join(candidate, "index.mjs"));
|
|
9251
|
+
if (indexedFiles.has(path14.join(candidate, "index.cjs")))
|
|
9252
|
+
results.push(path14.join(candidate, "index.cjs"));
|
|
7783
9253
|
}
|
|
7784
9254
|
return [...new Set(results)];
|
|
7785
9255
|
}
|
|
@@ -7806,22 +9276,12 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
7806
9276
|
const store = opts.store ?? indexStorePool.acquire(projectRoot, { indexDir: opts.indexDir });
|
|
7807
9277
|
try {
|
|
7808
9278
|
const allSymbols = store.getAllSymbols();
|
|
7809
|
-
const allRefs = store.getAllResolvedRefs();
|
|
7810
9279
|
const symbolById = /* @__PURE__ */ new Map();
|
|
7811
9280
|
for (const s of allSymbols) {
|
|
7812
9281
|
symbolById.set(s.id, s);
|
|
7813
9282
|
}
|
|
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
9283
|
const discoveredFiles = discoverEntryPoints(projectRoot, opts.userEntryPoints);
|
|
7824
|
-
const entryFileSet = new Set(discoveredFiles.map((f) =>
|
|
9284
|
+
const entryFileSet = new Set(discoveredFiles.map((f) => path14.resolve(f)));
|
|
7825
9285
|
const indexedFiles = /* @__PURE__ */ new Set();
|
|
7826
9286
|
for (const s of allSymbols) indexedFiles.add(s.file);
|
|
7827
9287
|
for (const fm of store.getAllFileMetas()) indexedFiles.add(fm.file);
|
|
@@ -7847,7 +9307,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
7847
9307
|
if (scannedBarrels.has(epFile)) continue;
|
|
7848
9308
|
scannedBarrels.add(epFile);
|
|
7849
9309
|
try {
|
|
7850
|
-
const content =
|
|
9310
|
+
const content = fs13.readFileSync(epFile, "utf8");
|
|
7851
9311
|
const strippedContent = content.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/\/\/[^\n]*/g, (m) => " ".repeat(m.length));
|
|
7852
9312
|
const reExportRe = /export\s+(?:(?:type\s+)?\{[\s\S]*?\}\s+from|\*\s+as\s+\w+\s+from|\*\s+from)\s+['"]([^'"]+)['"]/g;
|
|
7853
9313
|
for (; ; ) {
|
|
@@ -7888,23 +9348,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
7888
9348
|
}
|
|
7889
9349
|
}
|
|
7890
9350
|
}
|
|
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
|
-
}
|
|
9351
|
+
const alive = store.findReachableSymbolIds([...seedIds]);
|
|
7908
9352
|
const dead = [];
|
|
7909
9353
|
const symbolsByFile = /* @__PURE__ */ new Map();
|
|
7910
9354
|
const usedFiles = /* @__PURE__ */ new Set();
|
|
@@ -7953,7 +9397,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
7953
9397
|
const deadPackages = [];
|
|
7954
9398
|
const pkgEntries = findPackageEntries(projectRoot);
|
|
7955
9399
|
for (const [pkgName, pkgDir] of pkgEntries) {
|
|
7956
|
-
const pkgFiles = allSymbols.filter((s) => s.file.startsWith(pkgDir +
|
|
9400
|
+
const pkgFiles = allSymbols.filter((s) => s.file.startsWith(pkgDir + path14.sep));
|
|
7957
9401
|
if (pkgFiles.length === 0) continue;
|
|
7958
9402
|
const pkgUsed = pkgFiles.filter((s) => alive.has(s.id));
|
|
7959
9403
|
if (pkgUsed.length === 0) {
|
|
@@ -7989,7 +9433,7 @@ function runDeadCodeScan(projectRoot, opts = {}) {
|
|
|
7989
9433
|
}
|
|
7990
9434
|
function findPackageEntries(projectRoot) {
|
|
7991
9435
|
const pkgMap = /* @__PURE__ */ new Map();
|
|
7992
|
-
const rootPkg = tryReadJson(
|
|
9436
|
+
const rootPkg = tryReadJson(path14.join(projectRoot, "package.json"));
|
|
7993
9437
|
if (rootPkg && typeof rootPkg.name === "string") {
|
|
7994
9438
|
pkgMap.set(rootPkg.name, projectRoot);
|
|
7995
9439
|
}
|
|
@@ -7999,7 +9443,7 @@ function findPackageEntries(projectRoot) {
|
|
|
7999
9443
|
wsDirs = extractPnpmWorkspaceDirs(projectRoot);
|
|
8000
9444
|
}
|
|
8001
9445
|
for (const wsDir of wsDirs) {
|
|
8002
|
-
const wsPkg = tryReadJson(
|
|
9446
|
+
const wsPkg = tryReadJson(path14.join(wsDir, "package.json"));
|
|
8003
9447
|
if (wsPkg && typeof wsPkg.name === "string") {
|
|
8004
9448
|
pkgMap.set(wsPkg.name, wsDir);
|
|
8005
9449
|
}
|