@wrongstack/tools 0.302.2 → 0.303.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builtin.js +2799 -609
- package/dist/codebase-index/binary-frame.d.ts +43 -0
- package/dist/codebase-index/codebase-incoming-calls-tool.d.ts +1 -0
- package/dist/codebase-index/codebase-outgoing-calls-tool.d.ts +1 -0
- package/dist/codebase-index/content-hash.d.ts +66 -0
- package/dist/codebase-index/index.js +1569 -148
- package/dist/codebase-index/parser-worker-pool.d.ts +63 -0
- package/dist/codebase-index/parser-worker-script.d.ts +42 -0
- package/dist/codebase-index/project-server-protocol.d.ts +2 -0
- package/dist/codebase-index/project-server.js +1455 -99
- package/dist/codebase-index/schema.d.ts +7 -0
- package/dist/codebase-index/tree-sitter/queries.d.ts +48 -0
- package/dist/codebase-index/tree-sitter/util.d.ts +31 -0
- package/dist/codebase-index/tree-sitter/visitor.d.ts +47 -0
- package/dist/codebase-index/tree-sitter-parser.d.ts +58 -0
- package/dist/codebase-index/vector-search.d.ts +62 -0
- package/dist/codebase-index/worker-protocol.d.ts +2 -0
- package/dist/codebase-index/worker.js +1424 -68
- package/dist/codebase-index/writer-bulk-insert.d.ts +5 -0
- package/dist/codebase-index/writer-graph-reader.d.ts +39 -0
- package/dist/codebase-index/writer-schema.d.ts +9 -2
- package/dist/codebase-index/writer.d.ts +36 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2785 -634
- package/dist/kanban-task-inputs.d.ts +1 -0
- package/dist/kanban-tool-schema.d.ts +2 -2
- package/dist/kanban-tool-types.d.ts +18 -2
- package/dist/kanban.js +392 -126
- package/dist/pack.js +2799 -609
- package/dist/plan.d.ts +4 -1
- package/dist/plan.js +2380 -9
- package/dist/read.js +1531 -98
- package/dist/session-kanban.d.ts +8 -0
- package/dist/session-kanban.js +111 -17
- package/dist/task.d.ts +5 -4
- package/dist/task.js +2418 -43
- package/dist/todo.d.ts +10 -1
- package/dist/todo.js +2152 -20
- package/dist/tool-tier.js +2799 -609
- package/package.json +8 -4
|
@@ -2131,11 +2131,582 @@ var init_yaml_parser = __esm({
|
|
|
2131
2131
|
}
|
|
2132
2132
|
});
|
|
2133
2133
|
|
|
2134
|
+
// src/codebase-index/tree-sitter/queries.ts
|
|
2135
|
+
function getQueries(lang) {
|
|
2136
|
+
return LANG_QUERIES[lang] ?? DEFAULT_QUERIES;
|
|
2137
|
+
}
|
|
2138
|
+
function readFirstString(node) {
|
|
2139
|
+
if (!node) return null;
|
|
2140
|
+
if (node.type === "string_literal" || node.type === "alias") {
|
|
2141
|
+
return node.text.replace(/^"|"$/g, "");
|
|
2142
|
+
}
|
|
2143
|
+
const child = node.namedChild(0);
|
|
2144
|
+
return child ? readFirstString(child) : null;
|
|
2145
|
+
}
|
|
2146
|
+
var DEFAULT_QUERIES, LANG_QUERIES;
|
|
2147
|
+
var init_queries = __esm({
|
|
2148
|
+
"src/codebase-index/tree-sitter/queries.ts"() {
|
|
2149
|
+
"use strict";
|
|
2150
|
+
DEFAULT_QUERIES = {
|
|
2151
|
+
declKinds: {}
|
|
2152
|
+
};
|
|
2153
|
+
LANG_QUERIES = {
|
|
2154
|
+
// ─── C family ──────────────────────────────────────────────────────────────
|
|
2155
|
+
c: {
|
|
2156
|
+
declKinds: {
|
|
2157
|
+
function_definition: "function",
|
|
2158
|
+
declaration: "function",
|
|
2159
|
+
// K&R-style `int foo(...)` ambiguous w/ local var; the visitor prefers the function branch when the declarator field is present
|
|
2160
|
+
struct_specifier: "struct",
|
|
2161
|
+
union_specifier: "struct",
|
|
2162
|
+
enum_specifier: "enum",
|
|
2163
|
+
type_definition: "type",
|
|
2164
|
+
// `typedef … X;`
|
|
2165
|
+
preproc_def: "const"
|
|
2166
|
+
// `#define NAME …`
|
|
2167
|
+
},
|
|
2168
|
+
nameField: {
|
|
2169
|
+
function_definition: "declarator",
|
|
2170
|
+
declaration: "declarator",
|
|
2171
|
+
struct_specifier: "name",
|
|
2172
|
+
enum_specifier: "name",
|
|
2173
|
+
type_definition: "declarator",
|
|
2174
|
+
preproc_def: "name"
|
|
2175
|
+
},
|
|
2176
|
+
scopeNodes: /* @__PURE__ */ new Set([
|
|
2177
|
+
"translation_unit",
|
|
2178
|
+
"function_definition",
|
|
2179
|
+
"struct_specifier",
|
|
2180
|
+
"union_specifier",
|
|
2181
|
+
"enum_specifier"
|
|
2182
|
+
])
|
|
2183
|
+
},
|
|
2184
|
+
cpp: {
|
|
2185
|
+
declKinds: {
|
|
2186
|
+
function_definition: "function",
|
|
2187
|
+
template_declaration: "function",
|
|
2188
|
+
// `template<typename T> …`
|
|
2189
|
+
class_specifier: "class",
|
|
2190
|
+
struct_specifier: "struct",
|
|
2191
|
+
union_specifier: "struct",
|
|
2192
|
+
enum_specifier: "enum",
|
|
2193
|
+
namespace_definition: "namespace",
|
|
2194
|
+
type_definition: "type"
|
|
2195
|
+
},
|
|
2196
|
+
nameField: {
|
|
2197
|
+
function_definition: "declarator",
|
|
2198
|
+
template_declaration: "name",
|
|
2199
|
+
class_specifier: "name",
|
|
2200
|
+
struct_specifier: "name",
|
|
2201
|
+
enum_specifier: "name",
|
|
2202
|
+
namespace_definition: "name",
|
|
2203
|
+
type_definition: "declarator"
|
|
2204
|
+
},
|
|
2205
|
+
scopeNodes: /* @__PURE__ */ new Set([
|
|
2206
|
+
"translation_unit",
|
|
2207
|
+
"function_definition",
|
|
2208
|
+
"class_specifier",
|
|
2209
|
+
"struct_specifier",
|
|
2210
|
+
"union_specifier",
|
|
2211
|
+
"enum_specifier",
|
|
2212
|
+
"namespace_definition"
|
|
2213
|
+
])
|
|
2214
|
+
},
|
|
2215
|
+
java: {
|
|
2216
|
+
declKinds: {
|
|
2217
|
+
class_declaration: "class",
|
|
2218
|
+
interface_declaration: "interface",
|
|
2219
|
+
enum_declaration: "enum",
|
|
2220
|
+
record_declaration: "class",
|
|
2221
|
+
annotation_type_declaration: "interface",
|
|
2222
|
+
method_declaration: "method",
|
|
2223
|
+
constructor_declaration: "method",
|
|
2224
|
+
field_declaration: "property"
|
|
2225
|
+
},
|
|
2226
|
+
nameField: {
|
|
2227
|
+
class_declaration: "name",
|
|
2228
|
+
interface_declaration: "name",
|
|
2229
|
+
enum_declaration: "name",
|
|
2230
|
+
record_declaration: "name",
|
|
2231
|
+
annotation_type_declaration: "name",
|
|
2232
|
+
method_declaration: "name",
|
|
2233
|
+
constructor_declaration: "name"
|
|
2234
|
+
},
|
|
2235
|
+
// `field_declaration` has no single `name` field — it carries a list of
|
|
2236
|
+
// variable declarators. We emit one Symbol per node using the first
|
|
2237
|
+
// identifier-shaped named child (see `extractName` fallback in
|
|
2238
|
+
// `visitor.ts`). `int a, b, c;` therefore indexes only `a` — splitting
|
|
2239
|
+
// multi-declarator fields into separate Symbols is a separate refactor
|
|
2240
|
+
// that needs the visitor to know it has multiple names per node, and no
|
|
2241
|
+
// current test relies on it.
|
|
2242
|
+
scopeNodes: /* @__PURE__ */ new Set([
|
|
2243
|
+
"program",
|
|
2244
|
+
"class_declaration",
|
|
2245
|
+
"interface_declaration",
|
|
2246
|
+
"enum_declaration",
|
|
2247
|
+
"record_declaration"
|
|
2248
|
+
])
|
|
2249
|
+
},
|
|
2250
|
+
csharp: {
|
|
2251
|
+
// C# 10+ `namespace Foo.Bar;` produces this node type. The legacy block
|
|
2252
|
+
// form `namespace Foo.Bar { ... }` produces `namespace_declaration`. Both
|
|
2253
|
+
// carry a `qualified_name` child whose text already includes the dots.
|
|
2254
|
+
// `using_directive` is intentionally not a declaration. Imports are
|
|
2255
|
+
// extracted separately; indexing a using directive as a namespace makes
|
|
2256
|
+
// the resolver bind it to its own source file before the real declaration.
|
|
2257
|
+
declKinds: {
|
|
2258
|
+
file_scoped_namespace_declaration: "namespace",
|
|
2259
|
+
class_declaration: "class",
|
|
2260
|
+
interface_declaration: "interface",
|
|
2261
|
+
struct_declaration: "struct",
|
|
2262
|
+
enum_declaration: "enum",
|
|
2263
|
+
record_declaration: "class",
|
|
2264
|
+
method_declaration: "method",
|
|
2265
|
+
constructor_declaration: "method",
|
|
2266
|
+
property_declaration: "property",
|
|
2267
|
+
field_declaration: "property",
|
|
2268
|
+
namespace_declaration: "namespace"
|
|
2269
|
+
},
|
|
2270
|
+
// Custom name extractor: take the full dotted name verbatim.
|
|
2271
|
+
nameExtractor: (node) => {
|
|
2272
|
+
const inner = node.namedChild(0);
|
|
2273
|
+
if (inner && (inner.type === "qualified_name" || inner.type === "name")) {
|
|
2274
|
+
return inner.text;
|
|
2275
|
+
}
|
|
2276
|
+
return null;
|
|
2277
|
+
},
|
|
2278
|
+
scopeNodes: /* @__PURE__ */ new Set([
|
|
2279
|
+
"compilation_unit",
|
|
2280
|
+
"namespace_declaration",
|
|
2281
|
+
"class_declaration",
|
|
2282
|
+
"interface_declaration",
|
|
2283
|
+
"struct_declaration",
|
|
2284
|
+
"enum_declaration",
|
|
2285
|
+
"record_declaration"
|
|
2286
|
+
])
|
|
2287
|
+
},
|
|
2288
|
+
php: {
|
|
2289
|
+
declKinds: {
|
|
2290
|
+
function_definition: "function",
|
|
2291
|
+
method_declaration: "method",
|
|
2292
|
+
class_declaration: "class",
|
|
2293
|
+
interface_declaration: "interface",
|
|
2294
|
+
trait_declaration: "class",
|
|
2295
|
+
enum_declaration: "enum",
|
|
2296
|
+
namespace_definition: "namespace"
|
|
2297
|
+
},
|
|
2298
|
+
nameField: {
|
|
2299
|
+
function_definition: "name",
|
|
2300
|
+
method_declaration: "name",
|
|
2301
|
+
class_declaration: "name",
|
|
2302
|
+
interface_declaration: "name",
|
|
2303
|
+
trait_declaration: "name",
|
|
2304
|
+
enum_declaration: "name",
|
|
2305
|
+
namespace_declaration: "name"
|
|
2306
|
+
},
|
|
2307
|
+
scopeNodes: /* @__PURE__ */ new Set([
|
|
2308
|
+
"program",
|
|
2309
|
+
"namespace_definition",
|
|
2310
|
+
"class_declaration",
|
|
2311
|
+
"interface_declaration",
|
|
2312
|
+
"trait_declaration",
|
|
2313
|
+
"enum_declaration"
|
|
2314
|
+
])
|
|
2315
|
+
},
|
|
2316
|
+
// ─── Scripting / mobile ────────────────────────────────────────────────────
|
|
2317
|
+
ruby: {
|
|
2318
|
+
declKinds: {
|
|
2319
|
+
method: "function",
|
|
2320
|
+
singleton_method: "method",
|
|
2321
|
+
class: "class",
|
|
2322
|
+
module: "namespace",
|
|
2323
|
+
constant: "const"
|
|
2324
|
+
},
|
|
2325
|
+
nameField: {
|
|
2326
|
+
method: "name",
|
|
2327
|
+
singleton_method: "name",
|
|
2328
|
+
class: "name",
|
|
2329
|
+
module: "name",
|
|
2330
|
+
constant: "name"
|
|
2331
|
+
},
|
|
2332
|
+
scopeNodes: /* @__PURE__ */ new Set(["program", "class", "module", "singleton_method", "method"])
|
|
2333
|
+
},
|
|
2334
|
+
swift: {
|
|
2335
|
+
declKinds: {
|
|
2336
|
+
function_declaration: "function",
|
|
2337
|
+
class_declaration: "class",
|
|
2338
|
+
struct_declaration: "struct",
|
|
2339
|
+
enum_declaration: "enum",
|
|
2340
|
+
protocol_declaration: "interface",
|
|
2341
|
+
actor_declaration: "class",
|
|
2342
|
+
extension_declaration: "class",
|
|
2343
|
+
initializer: "method",
|
|
2344
|
+
property_declaration: "property"
|
|
2345
|
+
},
|
|
2346
|
+
nameField: {
|
|
2347
|
+
function_declaration: "name",
|
|
2348
|
+
class_declaration: "name",
|
|
2349
|
+
struct_declaration: "name",
|
|
2350
|
+
enum_declaration: "name",
|
|
2351
|
+
protocol_declaration: "name",
|
|
2352
|
+
actor_declaration: "name",
|
|
2353
|
+
extension_declaration: "name",
|
|
2354
|
+
initializer: "name",
|
|
2355
|
+
property_declaration: "name"
|
|
2356
|
+
},
|
|
2357
|
+
scopeNodes: /* @__PURE__ */ new Set([
|
|
2358
|
+
"source_file",
|
|
2359
|
+
"class_declaration",
|
|
2360
|
+
"struct_declaration",
|
|
2361
|
+
"enum_declaration",
|
|
2362
|
+
"protocol_declaration",
|
|
2363
|
+
"actor_declaration",
|
|
2364
|
+
"extension_declaration"
|
|
2365
|
+
])
|
|
2366
|
+
},
|
|
2367
|
+
kotlin: {
|
|
2368
|
+
declKinds: {
|
|
2369
|
+
class_declaration: "class",
|
|
2370
|
+
object_declaration: "class",
|
|
2371
|
+
interface_declaration: "interface",
|
|
2372
|
+
function_declaration: "function",
|
|
2373
|
+
property_declaration: "property",
|
|
2374
|
+
type_alias: "type"
|
|
2375
|
+
},
|
|
2376
|
+
nameField: {
|
|
2377
|
+
class_declaration: "name",
|
|
2378
|
+
object_declaration: "name",
|
|
2379
|
+
interface_declaration: "name",
|
|
2380
|
+
function_declaration: "name",
|
|
2381
|
+
property_declaration: "name",
|
|
2382
|
+
type_alias: "name"
|
|
2383
|
+
},
|
|
2384
|
+
scopeNodes: /* @__PURE__ */ new Set([
|
|
2385
|
+
"source_file",
|
|
2386
|
+
"class_declaration",
|
|
2387
|
+
"object_declaration",
|
|
2388
|
+
"interface_declaration",
|
|
2389
|
+
"function_declaration"
|
|
2390
|
+
])
|
|
2391
|
+
},
|
|
2392
|
+
elixir: {
|
|
2393
|
+
declKinds: {
|
|
2394
|
+
// `def foo`, `defp foo`, `defmacro foo`, `macrop foo` all surface as
|
|
2395
|
+
// `call` nodes in the tree-sitter grammar — there is no
|
|
2396
|
+
// `function_definition`. The `nameExtractor` walks the call's
|
|
2397
|
+
// children to pick the right sibling identifier.
|
|
2398
|
+
call: "function",
|
|
2399
|
+
module: "namespace"
|
|
2400
|
+
},
|
|
2401
|
+
nameExtractor: (node) => {
|
|
2402
|
+
if (node.type === "module") {
|
|
2403
|
+
const aliasNode = node.childForFieldName("alias");
|
|
2404
|
+
return readFirstString(aliasNode) ?? null;
|
|
2405
|
+
}
|
|
2406
|
+
if (node.type !== "call") return null;
|
|
2407
|
+
const first = node.namedChild(0);
|
|
2408
|
+
if (!first) return null;
|
|
2409
|
+
const target = first.text;
|
|
2410
|
+
if (target !== "def" && target !== "defp" && target !== "defmacro" && target !== "defp_macro" && target !== "macrop" && target !== "defprotocol" && target !== "defguard" && target !== "defguardp") {
|
|
2411
|
+
return null;
|
|
2412
|
+
}
|
|
2413
|
+
const nameNode = node.namedChild(1);
|
|
2414
|
+
return nameNode?.text ?? null;
|
|
2415
|
+
},
|
|
2416
|
+
scopeNodes: /* @__PURE__ */ new Set(["source", "module"])
|
|
2417
|
+
},
|
|
2418
|
+
shell: {
|
|
2419
|
+
declKinds: {
|
|
2420
|
+
function_definition: "function"
|
|
2421
|
+
},
|
|
2422
|
+
nameField: { function_definition: "name" },
|
|
2423
|
+
scopeNodes: /* @__PURE__ */ new Set(["program", "function_definition"])
|
|
2424
|
+
}
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
});
|
|
2428
|
+
|
|
2429
|
+
// src/codebase-index/tree-sitter/util.ts
|
|
2430
|
+
function lineColAt2(offsets, index) {
|
|
2431
|
+
let low = 0;
|
|
2432
|
+
let high = offsets.length;
|
|
2433
|
+
while (low < high) {
|
|
2434
|
+
const mid = low + high >>> 1;
|
|
2435
|
+
if ((offsets[mid] ?? 0) < index) low = mid + 1;
|
|
2436
|
+
else high = mid;
|
|
2437
|
+
}
|
|
2438
|
+
const lastNl = low > 0 ? offsets[low - 1] ?? -1 : -1;
|
|
2439
|
+
return { line: low + 1, col: index - lastNl };
|
|
2440
|
+
}
|
|
2441
|
+
function newlineOffsets3(content) {
|
|
2442
|
+
const offsets = [];
|
|
2443
|
+
for (let i = 0; i < content.length; i++) {
|
|
2444
|
+
if (content.charCodeAt(i) === 10) offsets.push(i);
|
|
2445
|
+
}
|
|
2446
|
+
return offsets;
|
|
2447
|
+
}
|
|
2448
|
+
var TREE_SITTER_MAX_FILE_CHARS, TREE_SITTER_MAX_SYMBOLS;
|
|
2449
|
+
var init_util = __esm({
|
|
2450
|
+
"src/codebase-index/tree-sitter/util.ts"() {
|
|
2451
|
+
"use strict";
|
|
2452
|
+
TREE_SITTER_MAX_FILE_CHARS = 512 * 1024;
|
|
2453
|
+
TREE_SITTER_MAX_SYMBOLS = 500;
|
|
2454
|
+
}
|
|
2455
|
+
});
|
|
2456
|
+
|
|
2457
|
+
// src/codebase-index/tree-sitter/visitor.ts
|
|
2458
|
+
function visitTree(tree, content, file, lang, queries) {
|
|
2459
|
+
const boundedContent = content.length > TREE_SITTER_MAX_FILE_CHARS ? content.slice(0, TREE_SITTER_MAX_FILE_CHARS) : content;
|
|
2460
|
+
const nlOffsets = newlineOffsets3(boundedContent);
|
|
2461
|
+
const symbols = [];
|
|
2462
|
+
const scopeStack = [];
|
|
2463
|
+
function visit(node, depth) {
|
|
2464
|
+
if (symbols.length >= TREE_SITTER_MAX_SYMBOLS) return;
|
|
2465
|
+
if (node.isMissing || node.isError) {
|
|
2466
|
+
} else {
|
|
2467
|
+
const kind = queries.declKinds[node.type];
|
|
2468
|
+
if (kind) {
|
|
2469
|
+
const emitted = emitSymbol(
|
|
2470
|
+
node,
|
|
2471
|
+
kind,
|
|
2472
|
+
file,
|
|
2473
|
+
lang,
|
|
2474
|
+
scopeStack,
|
|
2475
|
+
boundedContent,
|
|
2476
|
+
nlOffsets,
|
|
2477
|
+
queries
|
|
2478
|
+
);
|
|
2479
|
+
if (emitted) symbols.push(emitted);
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
const pushesScope = queries.scopeNodes?.has(node.type) ?? false;
|
|
2483
|
+
const pushIdx = pushesScope ? pushScope(scopeStack, node, queries) : -1;
|
|
2484
|
+
if (queries.skipNamedChildren) {
|
|
2485
|
+
if (pushIdx !== -1) scopeStack.pop();
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
for (const child of node.namedChildren) {
|
|
2489
|
+
visit(child, depth + 1);
|
|
2490
|
+
if (symbols.length >= TREE_SITTER_MAX_SYMBOLS) {
|
|
2491
|
+
if (pushIdx !== -1) scopeStack.pop();
|
|
2492
|
+
return;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
if (pushIdx !== -1) scopeStack.pop();
|
|
2496
|
+
}
|
|
2497
|
+
visit(tree.rootNode, 0);
|
|
2498
|
+
return { symbols };
|
|
2499
|
+
}
|
|
2500
|
+
function pushScope(scopeStack, node, queries) {
|
|
2501
|
+
const name = extractName(node, queries);
|
|
2502
|
+
if (!name) return -1;
|
|
2503
|
+
scopeStack.push(name);
|
|
2504
|
+
return scopeStack.length - 1;
|
|
2505
|
+
}
|
|
2506
|
+
function extractName(node, queries) {
|
|
2507
|
+
if (queries.nameExtractor) {
|
|
2508
|
+
const extracted = queries.nameExtractor(node);
|
|
2509
|
+
if (extracted) return extracted;
|
|
2510
|
+
}
|
|
2511
|
+
const fieldName = queries.nameField?.[node.type] ?? "name";
|
|
2512
|
+
const field = node.childForFieldName(fieldName);
|
|
2513
|
+
if (field) {
|
|
2514
|
+
if (IDENTIFIER_NODE_TYPES.has(field.type)) {
|
|
2515
|
+
return field.text;
|
|
2516
|
+
}
|
|
2517
|
+
const inner = field.childForFieldName("name") ?? field.namedChild(0);
|
|
2518
|
+
if (inner && IDENTIFIER_NODE_TYPES.has(inner.type)) {
|
|
2519
|
+
return inner.text;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
2523
|
+
const child = node.namedChild(i);
|
|
2524
|
+
if (child && IDENTIFIER_NODE_TYPES.has(child.type)) {
|
|
2525
|
+
return child.text;
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
return null;
|
|
2529
|
+
}
|
|
2530
|
+
function emitSymbol(node, kind, file, lang, scopeStack, content, nlOffsets, queries) {
|
|
2531
|
+
const name = extractName(node, queries);
|
|
2532
|
+
if (!name) return null;
|
|
2533
|
+
const pos = node.startIndex;
|
|
2534
|
+
const { line, col } = lineColAt2(nlOffsets, pos);
|
|
2535
|
+
const end = Math.min(node.endIndex, content.length);
|
|
2536
|
+
const signature = content.slice(pos, end).replace(/\s+/g, " ").trim().slice(0, 500);
|
|
2537
|
+
const scope = scopeStack.join(".");
|
|
2538
|
+
const text = [name, signature].filter(Boolean).join(" | ").trim().slice(0, 1e3);
|
|
2539
|
+
return {
|
|
2540
|
+
id: 0,
|
|
2541
|
+
// caller assigns during bulk insertion
|
|
2542
|
+
lang,
|
|
2543
|
+
kind,
|
|
2544
|
+
name: name.slice(0, 200),
|
|
2545
|
+
file,
|
|
2546
|
+
line,
|
|
2547
|
+
col,
|
|
2548
|
+
signature,
|
|
2549
|
+
docComment: "",
|
|
2550
|
+
// doc-comment extraction lands with ref emission on Day 4
|
|
2551
|
+
scope,
|
|
2552
|
+
text
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
var IDENTIFIER_NODE_TYPES;
|
|
2556
|
+
var init_visitor = __esm({
|
|
2557
|
+
"src/codebase-index/tree-sitter/visitor.ts"() {
|
|
2558
|
+
"use strict";
|
|
2559
|
+
init_util();
|
|
2560
|
+
IDENTIFIER_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
2561
|
+
"identifier",
|
|
2562
|
+
"simple_identifier",
|
|
2563
|
+
"type_identifier",
|
|
2564
|
+
"field_identifier",
|
|
2565
|
+
"property_identifier",
|
|
2566
|
+
"name",
|
|
2567
|
+
"word",
|
|
2568
|
+
"variable_name",
|
|
2569
|
+
"constant",
|
|
2570
|
+
"sym"
|
|
2571
|
+
]);
|
|
2572
|
+
}
|
|
2573
|
+
});
|
|
2574
|
+
|
|
2575
|
+
// src/codebase-index/tree-sitter-parser.ts
|
|
2576
|
+
var tree_sitter_parser_exports = {};
|
|
2577
|
+
__export(tree_sitter_parser_exports, {
|
|
2578
|
+
__smokeRootType: () => __smokeRootType,
|
|
2579
|
+
getGrammarWasmPath: () => getGrammarWasmPath,
|
|
2580
|
+
isTreeSitterSupported: () => isTreeSitterSupported,
|
|
2581
|
+
loadTreeSitterLanguage: () => loadTreeSitterLanguage,
|
|
2582
|
+
parseSymbols: () => parseSymbols8
|
|
2583
|
+
});
|
|
2584
|
+
import * as path9 from "node:path";
|
|
2585
|
+
import { fileURLToPath } from "node:url";
|
|
2586
|
+
function optInEnabled(env) {
|
|
2587
|
+
return process.env[env] === "1" || process.env[env] === "true";
|
|
2588
|
+
}
|
|
2589
|
+
function getRuntime() {
|
|
2590
|
+
if (!runtimePromise) {
|
|
2591
|
+
runtimePromise = (async () => {
|
|
2592
|
+
const mod = await import("web-tree-sitter");
|
|
2593
|
+
const init = () => mod.Parser.init({ locateFile: () => RUNTIME_WASM });
|
|
2594
|
+
return { Parser: mod.Parser, Language: mod.Language, init };
|
|
2595
|
+
})();
|
|
2596
|
+
}
|
|
2597
|
+
return runtimePromise;
|
|
2598
|
+
}
|
|
2599
|
+
async function loadLanguage(lang) {
|
|
2600
|
+
const existing = languageCache.get(lang);
|
|
2601
|
+
if (existing) return existing;
|
|
2602
|
+
const promise = (async () => {
|
|
2603
|
+
const grammarName = resolveGrammarName(lang);
|
|
2604
|
+
if (!grammarName) {
|
|
2605
|
+
throw new Error(`tree-sitter: no grammar registered for lang "${lang}"`);
|
|
2606
|
+
}
|
|
2607
|
+
const wasmPath = path9.join(WASM_DIR, grammarName, `tree-sitter-${grammarName}.wasm`);
|
|
2608
|
+
const { Language, init } = await getRuntime();
|
|
2609
|
+
await init();
|
|
2610
|
+
const languageObj = await Language.load(wasmPath);
|
|
2611
|
+
return { lang, Language: languageObj };
|
|
2612
|
+
})();
|
|
2613
|
+
languageCache.set(lang, promise);
|
|
2614
|
+
return promise;
|
|
2615
|
+
}
|
|
2616
|
+
function resolveGrammarName(lang) {
|
|
2617
|
+
if (lang === "go" && optInEnabled(GO_OPT_IN)) return "go";
|
|
2618
|
+
if (lang === "py" && optInEnabled(PY_OPT_IN)) return "python";
|
|
2619
|
+
if (lang === "rs" && optInEnabled(RS_OPT_IN)) return "rust";
|
|
2620
|
+
return LANG_TO_GRAMMAR[lang];
|
|
2621
|
+
}
|
|
2622
|
+
function isTreeSitterSupported(lang) {
|
|
2623
|
+
return resolveGrammarName(lang) !== void 0;
|
|
2624
|
+
}
|
|
2625
|
+
function getGrammarWasmPath(lang) {
|
|
2626
|
+
const name = resolveGrammarName(lang);
|
|
2627
|
+
if (!name) return void 0;
|
|
2628
|
+
return path9.join(WASM_DIR, name, `tree-sitter-${name}.wasm`);
|
|
2629
|
+
}
|
|
2630
|
+
async function parseSymbols8(opts) {
|
|
2631
|
+
const { file, content, lang } = opts;
|
|
2632
|
+
if (!isTreeSitterSupported(lang)) {
|
|
2633
|
+
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2634
|
+
}
|
|
2635
|
+
try {
|
|
2636
|
+
const { Parser } = await getRuntime();
|
|
2637
|
+
const cached = await loadLanguage(lang);
|
|
2638
|
+
const parser = new Parser();
|
|
2639
|
+
parser.setLanguage(cached.Language);
|
|
2640
|
+
const tree = parser.parse(content);
|
|
2641
|
+
if (!tree) {
|
|
2642
|
+
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2643
|
+
}
|
|
2644
|
+
const { symbols } = visitTree(tree, content, file, lang, getQueries(lang));
|
|
2645
|
+
parser.delete();
|
|
2646
|
+
tree.delete();
|
|
2647
|
+
return { file, lang, symbols, refs: [], mtimeMs: Date.now() };
|
|
2648
|
+
} catch {
|
|
2649
|
+
return { file, lang, symbols: [], mtimeMs: Date.now() };
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
async function loadTreeSitterLanguage(lang) {
|
|
2653
|
+
const cached = await loadLanguage(lang);
|
|
2654
|
+
return cached.Language;
|
|
2655
|
+
}
|
|
2656
|
+
async function __smokeRootType(opts) {
|
|
2657
|
+
if (!isTreeSitterSupported(opts.lang)) {
|
|
2658
|
+
throw new Error(`tree-sitter: no grammar registered for lang "${opts.lang}"`);
|
|
2659
|
+
}
|
|
2660
|
+
const { Parser } = await getRuntime();
|
|
2661
|
+
const cached = await loadLanguage(opts.lang);
|
|
2662
|
+
const parser = new Parser();
|
|
2663
|
+
parser.setLanguage(cached.Language);
|
|
2664
|
+
let tree = null;
|
|
2665
|
+
try {
|
|
2666
|
+
tree = parser.parse(opts.content);
|
|
2667
|
+
if (!tree) throw new Error("tree-sitter: parser.parse returned null");
|
|
2668
|
+
return tree.rootNode.type;
|
|
2669
|
+
} finally {
|
|
2670
|
+
tree?.delete();
|
|
2671
|
+
parser.delete();
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
var WASM_DIR, RUNTIME_WASM, LANG_TO_GRAMMAR, GO_OPT_IN, PY_OPT_IN, RS_OPT_IN, runtimePromise, languageCache;
|
|
2675
|
+
var init_tree_sitter_parser = __esm({
|
|
2676
|
+
"src/codebase-index/tree-sitter-parser.ts"() {
|
|
2677
|
+
"use strict";
|
|
2678
|
+
init_queries();
|
|
2679
|
+
init_visitor();
|
|
2680
|
+
WASM_DIR = fileURLToPath(new URL("./wasm/", import.meta.url));
|
|
2681
|
+
RUNTIME_WASM = path9.join(WASM_DIR, "tree-sitter-runtime.wasm");
|
|
2682
|
+
LANG_TO_GRAMMAR = {
|
|
2683
|
+
c: "c",
|
|
2684
|
+
cpp: "cpp",
|
|
2685
|
+
java: "java",
|
|
2686
|
+
csharp: "c_sharp",
|
|
2687
|
+
// tree-sitter directory uses underscore
|
|
2688
|
+
php: "php",
|
|
2689
|
+
ruby: "ruby",
|
|
2690
|
+
swift: "swift",
|
|
2691
|
+
kotlin: "kotlin",
|
|
2692
|
+
shell: "bash",
|
|
2693
|
+
// we treat `.sh` / `.bash` / `.zsh` via the bash grammar
|
|
2694
|
+
// Go/Python/Rust are opt-in only (see goOptIn / pyOptIn / rsOptIn below)
|
|
2695
|
+
elixir: "elixir"
|
|
2696
|
+
};
|
|
2697
|
+
GO_OPT_IN = "WRONGSTACK_USE_TS_GO";
|
|
2698
|
+
PY_OPT_IN = "WRONGSTACK_USE_TS_PY";
|
|
2699
|
+
RS_OPT_IN = "WRONGSTACK_USE_TS_RS";
|
|
2700
|
+
runtimePromise = null;
|
|
2701
|
+
languageCache = /* @__PURE__ */ new Map();
|
|
2702
|
+
}
|
|
2703
|
+
});
|
|
2704
|
+
|
|
2134
2705
|
// src/codebase-index/project-server.ts
|
|
2135
2706
|
import { randomBytes } from "node:crypto";
|
|
2136
|
-
import * as
|
|
2707
|
+
import * as fs11 from "node:fs";
|
|
2137
2708
|
import * as net from "node:net";
|
|
2138
|
-
import * as
|
|
2709
|
+
import * as path14 from "node:path";
|
|
2139
2710
|
import {
|
|
2140
2711
|
DEFAULT_WALK_IGNORE_SET,
|
|
2141
2712
|
startSharedHeapWatchdog,
|
|
@@ -2146,9 +2717,9 @@ import {
|
|
|
2146
2717
|
// src/codebase-index/indexer.ts
|
|
2147
2718
|
import { expectDefined as expectDefined5 } from "@wrongstack/core/utils";
|
|
2148
2719
|
import { execFile } from "node:child_process";
|
|
2149
|
-
import * as
|
|
2720
|
+
import * as fs9 from "node:fs/promises";
|
|
2150
2721
|
import { availableParallelism } from "node:os";
|
|
2151
|
-
import * as
|
|
2722
|
+
import * as path12 from "node:path";
|
|
2152
2723
|
import {
|
|
2153
2724
|
DEFAULT_WALK_IGNORE_DIRS,
|
|
2154
2725
|
indexParallelBatchSize,
|
|
@@ -2950,32 +3521,56 @@ async function dispatch(file, content, lang) {
|
|
|
2950
3521
|
case "tsx":
|
|
2951
3522
|
case "js":
|
|
2952
3523
|
case "jsx": {
|
|
2953
|
-
const { parseSymbols:
|
|
2954
|
-
return
|
|
3524
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_ts_parser(), ts_parser_exports));
|
|
3525
|
+
return parseSymbols9({ file, content, lang });
|
|
2955
3526
|
}
|
|
2956
3527
|
case "go": {
|
|
2957
|
-
const { parseSymbols:
|
|
2958
|
-
return
|
|
3528
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_go_parser(), go_parser_exports));
|
|
3529
|
+
return parseSymbols9({ file, content, lang: "go" });
|
|
2959
3530
|
}
|
|
2960
3531
|
case "py": {
|
|
2961
|
-
const { parseSymbols:
|
|
2962
|
-
return
|
|
3532
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_py_parser(), py_parser_exports));
|
|
3533
|
+
return parseSymbols9({ file, content, lang: "py" });
|
|
2963
3534
|
}
|
|
2964
3535
|
case "rs": {
|
|
2965
|
-
const { parseSymbols:
|
|
2966
|
-
return
|
|
3536
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_rs_parser(), rs_parser_exports));
|
|
3537
|
+
return parseSymbols9({ file, content, lang: "rs" });
|
|
2967
3538
|
}
|
|
2968
3539
|
case "json": {
|
|
2969
|
-
const { parseSymbols:
|
|
2970
|
-
return
|
|
3540
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_json_parser(), json_parser_exports));
|
|
3541
|
+
return parseSymbols9({ file, content, lang: "json" });
|
|
2971
3542
|
}
|
|
2972
3543
|
case "yaml": {
|
|
2973
|
-
const { parseSymbols:
|
|
2974
|
-
return
|
|
3544
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_yaml_parser(), yaml_parser_exports));
|
|
3545
|
+
return parseSymbols9({ file, content, lang: "yaml" });
|
|
3546
|
+
}
|
|
3547
|
+
// Phase 1: ten languages now route through the Tree-Sitter WASM
|
|
3548
|
+
// universal parser (`tree-sitter-parser.ts`). The dispatch falls back to
|
|
3549
|
+
// the regex extractor in `generic-parser.ts` whenever WASM loading fails
|
|
3550
|
+
// or the parser returns zero symbols — preserving the indexable-file
|
|
3551
|
+
// contract that "missing a parser must never mean skipping the file".
|
|
3552
|
+
case "c":
|
|
3553
|
+
case "cpp":
|
|
3554
|
+
case "java":
|
|
3555
|
+
case "csharp":
|
|
3556
|
+
case "php":
|
|
3557
|
+
case "ruby":
|
|
3558
|
+
case "swift":
|
|
3559
|
+
case "kotlin":
|
|
3560
|
+
case "shell":
|
|
3561
|
+
case "elixir": {
|
|
3562
|
+
try {
|
|
3563
|
+
const { parseSymbols: parseSymbols10 } = await Promise.resolve().then(() => (init_tree_sitter_parser(), tree_sitter_parser_exports));
|
|
3564
|
+
const parsed2 = await parseSymbols10({ file, content, lang });
|
|
3565
|
+
if (parsed2.symbols.length > 0) return parsed2;
|
|
3566
|
+
} catch {
|
|
3567
|
+
}
|
|
3568
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
3569
|
+
return parseSymbols9({ file, content, lang });
|
|
2975
3570
|
}
|
|
2976
3571
|
default: {
|
|
2977
|
-
const { parseSymbols:
|
|
2978
|
-
return
|
|
3572
|
+
const { parseSymbols: parseSymbols9 } = await Promise.resolve().then(() => (init_generic_parser(), generic_parser_exports));
|
|
3573
|
+
return parseSymbols9({ file, content, lang });
|
|
2979
3574
|
}
|
|
2980
3575
|
}
|
|
2981
3576
|
}
|
|
@@ -2987,10 +3582,265 @@ function withRelations(parsed2, content, lang) {
|
|
|
2987
3582
|
return { ...parsed2, refs: refs.map((ref) => ref.lang ? ref : { ...ref, lang }) };
|
|
2988
3583
|
}
|
|
2989
3584
|
|
|
3585
|
+
// src/codebase-index/parser-worker-pool.ts
|
|
3586
|
+
import { Worker } from "node:worker_threads";
|
|
3587
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3588
|
+
import * as fs6 from "node:fs";
|
|
3589
|
+
var WORKER_POOL_THRESHOLD = 500;
|
|
3590
|
+
var ParserWorkerPool = class {
|
|
3591
|
+
constructor(maxWorkers = defaultWorkerCount()) {
|
|
3592
|
+
this.maxWorkers = maxWorkers;
|
|
3593
|
+
}
|
|
3594
|
+
maxWorkers;
|
|
3595
|
+
workers = [];
|
|
3596
|
+
nextBatchId = 1;
|
|
3597
|
+
pending = /* @__PURE__ */ new Map();
|
|
3598
|
+
creating = false;
|
|
3599
|
+
unavailable = false;
|
|
3600
|
+
/**
|
|
3601
|
+
* True if the pool is available for use. Returns false when:
|
|
3602
|
+
* - Worker threads aren't supported (sandbox, exotic runtime)
|
|
3603
|
+
* - The built worker script can't be found
|
|
3604
|
+
* - Pool creation was attempted and failed
|
|
3605
|
+
*/
|
|
3606
|
+
isAvailable() {
|
|
3607
|
+
return !this.unavailable && this.workers.length > 0;
|
|
3608
|
+
}
|
|
3609
|
+
/**
|
|
3610
|
+
* Lazily create the worker pool. Returns true if the pool is ready, false
|
|
3611
|
+
* if it's unavailable (caller should fall back to inline parsing).
|
|
3612
|
+
*/
|
|
3613
|
+
async ensureReady() {
|
|
3614
|
+
if (this.isAvailable()) return true;
|
|
3615
|
+
if (this.unavailable) return false;
|
|
3616
|
+
if (this.creating) {
|
|
3617
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
3618
|
+
return this.isAvailable();
|
|
3619
|
+
}
|
|
3620
|
+
this.creating = true;
|
|
3621
|
+
try {
|
|
3622
|
+
const url = resolveWorkerScriptUrl();
|
|
3623
|
+
if (!url) {
|
|
3624
|
+
this.unavailable = true;
|
|
3625
|
+
return false;
|
|
3626
|
+
}
|
|
3627
|
+
for (let i = 0; i < this.maxWorkers; i++) {
|
|
3628
|
+
try {
|
|
3629
|
+
const w = new Worker(url, { name: `wstack-parser-${i}` });
|
|
3630
|
+
w.unref();
|
|
3631
|
+
w.on("message", (msg) => this.handleMessage(msg));
|
|
3632
|
+
w.on("error", (err) => this.handleError(err, w));
|
|
3633
|
+
this.workers.push({ worker: w, busy: false });
|
|
3634
|
+
} catch {
|
|
3635
|
+
if (this.workers.length === 0) {
|
|
3636
|
+
this.unavailable = true;
|
|
3637
|
+
return false;
|
|
3638
|
+
}
|
|
3639
|
+
break;
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
return this.workers.length > 0;
|
|
3643
|
+
} finally {
|
|
3644
|
+
this.creating = false;
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
/**
|
|
3648
|
+
* Parse files in parallel across the worker pool. Returns a flat
|
|
3649
|
+
* `FileSymbols[]` in completion order (caller sorts if needed).
|
|
3650
|
+
*
|
|
3651
|
+
* Content is pre-read by the main thread (for the content-hash check)
|
|
3652
|
+
* and passed to workers to avoid a second disk read. Files are
|
|
3653
|
+
* distributed round-robin across workers.
|
|
3654
|
+
*/
|
|
3655
|
+
async parseFiles(files) {
|
|
3656
|
+
if (!this.isAvailable()) {
|
|
3657
|
+
throw new Error("ParserWorkerPool.parseFiles called before ensureReady() succeeded");
|
|
3658
|
+
}
|
|
3659
|
+
if (files.length === 0) return [];
|
|
3660
|
+
const batchId = this.nextBatchId++;
|
|
3661
|
+
const workerCount = Math.min(this.workers.length, files.length);
|
|
3662
|
+
const chunks = Array.from(
|
|
3663
|
+
{ length: workerCount },
|
|
3664
|
+
() => []
|
|
3665
|
+
);
|
|
3666
|
+
for (let i = 0; i < files.length; i++) {
|
|
3667
|
+
chunks[i % workerCount].push(files[i]);
|
|
3668
|
+
}
|
|
3669
|
+
return new Promise((resolve4, reject) => {
|
|
3670
|
+
this.pending.set(batchId, {
|
|
3671
|
+
resolve: resolve4,
|
|
3672
|
+
reject,
|
|
3673
|
+
accumulated: [],
|
|
3674
|
+
expectedWorkers: workerCount,
|
|
3675
|
+
completedWorkers: 0
|
|
3676
|
+
});
|
|
3677
|
+
for (let i = 0; i < workerCount; i++) {
|
|
3678
|
+
const pw = this.workers[i];
|
|
3679
|
+
pw.busy = true;
|
|
3680
|
+
pw.worker.postMessage({
|
|
3681
|
+
type: "parse",
|
|
3682
|
+
id: batchId,
|
|
3683
|
+
files: chunks[i]
|
|
3684
|
+
});
|
|
3685
|
+
}
|
|
3686
|
+
});
|
|
3687
|
+
}
|
|
3688
|
+
/** Shut down all workers. Safe to call multiple times. */
|
|
3689
|
+
async shutdown() {
|
|
3690
|
+
const workers = this.workers.map((w) => w.worker);
|
|
3691
|
+
this.workers = [];
|
|
3692
|
+
this.unavailable = false;
|
|
3693
|
+
for (const w of workers) {
|
|
3694
|
+
try {
|
|
3695
|
+
w.postMessage({ type: "shutdown" });
|
|
3696
|
+
} catch {
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
await Promise.allSettled(
|
|
3700
|
+
workers.map(
|
|
3701
|
+
(w) => Promise.race([
|
|
3702
|
+
new Promise((resolve4) => {
|
|
3703
|
+
w.once("exit", () => resolve4());
|
|
3704
|
+
}),
|
|
3705
|
+
new Promise((resolve4) => setTimeout(() => resolve4(), 2e3))
|
|
3706
|
+
]).then(() => {
|
|
3707
|
+
if (!w.threadId) return;
|
|
3708
|
+
return w.terminate().catch(() => {
|
|
3709
|
+
});
|
|
3710
|
+
})
|
|
3711
|
+
)
|
|
3712
|
+
);
|
|
3713
|
+
for (const [, p] of this.pending) p.reject(new Error("ParserWorkerPool shut down"));
|
|
3714
|
+
this.pending.clear();
|
|
3715
|
+
}
|
|
3716
|
+
handleMessage(msg) {
|
|
3717
|
+
const batch = this.pending.get(msg.id);
|
|
3718
|
+
if (!batch) return;
|
|
3719
|
+
batch.accumulated.push(...msg.results);
|
|
3720
|
+
batch.completedWorkers++;
|
|
3721
|
+
const freeWorker = this.workers.find((w) => w.busy);
|
|
3722
|
+
if (freeWorker) freeWorker.busy = false;
|
|
3723
|
+
if (batch.completedWorkers >= batch.expectedWorkers) {
|
|
3724
|
+
this.pending.delete(msg.id);
|
|
3725
|
+
batch.resolve(batch.accumulated);
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
handleError(err, source) {
|
|
3729
|
+
this.workers = this.workers.filter((w) => w.worker !== source);
|
|
3730
|
+
if (this.workers.length === 0) {
|
|
3731
|
+
for (const [, p] of this.pending) p.reject(err);
|
|
3732
|
+
this.pending.clear();
|
|
3733
|
+
this.unavailable = true;
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
};
|
|
3737
|
+
function defaultWorkerCount() {
|
|
3738
|
+
const cores = globalThis.navigator?.hardwareConcurrency ?? 4;
|
|
3739
|
+
return Math.max(1, Math.min(4, cores - 1));
|
|
3740
|
+
}
|
|
3741
|
+
function resolveWorkerScriptUrl() {
|
|
3742
|
+
for (const rel of [
|
|
3743
|
+
"./parser-worker-script.js",
|
|
3744
|
+
"./codebase-index/parser-worker-script.js"
|
|
3745
|
+
]) {
|
|
3746
|
+
try {
|
|
3747
|
+
const url = new URL(rel, import.meta.url);
|
|
3748
|
+
if (url.protocol === "file:" && fs6.existsSync(fileURLToPath2(url))) return url;
|
|
3749
|
+
} catch {
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
return null;
|
|
3753
|
+
}
|
|
3754
|
+
var _pool = null;
|
|
3755
|
+
function getParserPool() {
|
|
3756
|
+
_pool ??= new ParserWorkerPool();
|
|
3757
|
+
return _pool;
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
// src/codebase-index/content-hash.ts
|
|
3761
|
+
var PRIME64_1 = 0x9e3779b185ebca87n;
|
|
3762
|
+
var PRIME64_2 = 0xc2b2ae3d27d4eb4fn;
|
|
3763
|
+
var PRIME64_3 = 0x165667b19e3779f9n;
|
|
3764
|
+
var PRIME64_4 = 0x85ebca77c2b2ae63n;
|
|
3765
|
+
var PRIME64_5 = 0x27d4eb2f165667c5n;
|
|
3766
|
+
var MASK64 = 0xffffffffffffffffn;
|
|
3767
|
+
function mul64(a, b) {
|
|
3768
|
+
return (a & MASK64) * (b & MASK64) & MASK64;
|
|
3769
|
+
}
|
|
3770
|
+
function rotl64(x, n) {
|
|
3771
|
+
const v = x & MASK64;
|
|
3772
|
+
return (v << BigInt(n) | v >> BigInt(64 - n)) & MASK64;
|
|
3773
|
+
}
|
|
3774
|
+
function readU64LE(buf, off) {
|
|
3775
|
+
let v = 0n;
|
|
3776
|
+
for (let i = 7; i >= 0; i--) {
|
|
3777
|
+
v = v << 8n | BigInt(buf[off + i] ?? 0);
|
|
3778
|
+
}
|
|
3779
|
+
return v & MASK64;
|
|
3780
|
+
}
|
|
3781
|
+
function readU32LE(buf, off) {
|
|
3782
|
+
return (BigInt(buf[off] ?? 0) | BigInt(buf[off + 1] ?? 0) << 8n | BigInt(buf[off + 2] ?? 0) << 16n | BigInt(buf[off + 3] ?? 0) << 24n) & MASK64;
|
|
3783
|
+
}
|
|
3784
|
+
function xxh64Round(acc, lane) {
|
|
3785
|
+
return mul64(rotl64(acc + mul64(lane, PRIME64_2) & MASK64, 31), PRIME64_1);
|
|
3786
|
+
}
|
|
3787
|
+
function xxh64MergeRound(acc, val) {
|
|
3788
|
+
return mul64(acc ^ xxh64Round(0n, val), PRIME64_1) + PRIME64_4 & MASK64;
|
|
3789
|
+
}
|
|
3790
|
+
function xxhash64Hex(buf, explicitLen) {
|
|
3791
|
+
const length = explicitLen ?? buf.length;
|
|
3792
|
+
let h;
|
|
3793
|
+
let off = 0;
|
|
3794
|
+
if (length >= 32) {
|
|
3795
|
+
let v1 = PRIME64_1 + PRIME64_2 & MASK64;
|
|
3796
|
+
let v2 = PRIME64_2;
|
|
3797
|
+
let v3 = 0n;
|
|
3798
|
+
let v4 = 0n - PRIME64_1 & MASK64;
|
|
3799
|
+
const end32 = length - 32;
|
|
3800
|
+
while (off <= end32) {
|
|
3801
|
+
v1 = xxh64Round(v1, readU64LE(buf, off));
|
|
3802
|
+
v2 = xxh64Round(v2, readU64LE(buf, off + 8));
|
|
3803
|
+
v3 = xxh64Round(v3, readU64LE(buf, off + 16));
|
|
3804
|
+
v4 = xxh64Round(v4, readU64LE(buf, off + 24));
|
|
3805
|
+
off += 32;
|
|
3806
|
+
}
|
|
3807
|
+
h = rotl64(v1, 1) + rotl64(v2, 7) + rotl64(v3, 12) + rotl64(v4, 18) & MASK64;
|
|
3808
|
+
h = xxh64MergeRound(h, v1);
|
|
3809
|
+
h = xxh64MergeRound(h, v2);
|
|
3810
|
+
h = xxh64MergeRound(h, v3);
|
|
3811
|
+
h = xxh64MergeRound(h, v4);
|
|
3812
|
+
} else {
|
|
3813
|
+
h = PRIME64_5;
|
|
3814
|
+
}
|
|
3815
|
+
h = h + BigInt(length) & MASK64;
|
|
3816
|
+
while (off + 8 <= length) {
|
|
3817
|
+
const k1 = xxh64Round(0n, readU64LE(buf, off));
|
|
3818
|
+
h = mul64(rotl64(h ^ k1, 27), PRIME64_1) + PRIME64_4 & MASK64;
|
|
3819
|
+
off += 8;
|
|
3820
|
+
}
|
|
3821
|
+
if (off + 4 <= length) {
|
|
3822
|
+
h = mul64(rotl64(h ^ mul64(readU32LE(buf, off), PRIME64_1), 23), PRIME64_2) + PRIME64_3 & MASK64;
|
|
3823
|
+
off += 4;
|
|
3824
|
+
}
|
|
3825
|
+
while (off < length) {
|
|
3826
|
+
h = mul64(rotl64(h ^ mul64(BigInt(buf[off] ?? 0), PRIME64_5), 11), PRIME64_1) & MASK64;
|
|
3827
|
+
off += 1;
|
|
3828
|
+
}
|
|
3829
|
+
h = (h ^ h >> 33n) & MASK64;
|
|
3830
|
+
h = mul64(h, PRIME64_2);
|
|
3831
|
+
h = (h ^ h >> 29n) & MASK64;
|
|
3832
|
+
h = mul64(h, PRIME64_3);
|
|
3833
|
+
h = (h ^ h >> 32n) & MASK64;
|
|
3834
|
+
return h.toString(16).padStart(16, "0");
|
|
3835
|
+
}
|
|
3836
|
+
function xxhash64String(content) {
|
|
3837
|
+
return xxhash64Hex(new TextEncoder().encode(content));
|
|
3838
|
+
}
|
|
3839
|
+
|
|
2990
3840
|
// src/codebase-index/writer.ts
|
|
2991
3841
|
import { expectDefined as expectDefined4 } from "@wrongstack/core/utils";
|
|
2992
|
-
import * as
|
|
2993
|
-
import * as
|
|
3842
|
+
import * as fs8 from "node:fs";
|
|
3843
|
+
import * as path11 from "node:path";
|
|
2994
3844
|
|
|
2995
3845
|
// src/codebase-index/bm25.ts
|
|
2996
3846
|
var K1 = 1.5;
|
|
@@ -3264,8 +4114,8 @@ function runSqliteWithRetry(fn) {
|
|
|
3264
4114
|
}
|
|
3265
4115
|
|
|
3266
4116
|
// src/codebase-index/writer-admin.ts
|
|
3267
|
-
import * as
|
|
3268
|
-
import * as
|
|
4117
|
+
import * as fs7 from "node:fs";
|
|
4118
|
+
import * as path10 from "node:path";
|
|
3269
4119
|
var DB_FILE = "index.db";
|
|
3270
4120
|
function getAllIndexableWithStatement(stmt) {
|
|
3271
4121
|
return stmt("SELECT id, text FROM symbols").all().map(({ id, text }) => ({ id, text }));
|
|
@@ -3301,7 +4151,7 @@ function getMetadataWithStatement(stmt, key) {
|
|
|
3301
4151
|
}
|
|
3302
4152
|
function getFileMetaWithStatement(stmt, file) {
|
|
3303
4153
|
const rows = stmt(
|
|
3304
|
-
"SELECT file, lang, mtime_ms, symbol_count, last_indexed FROM files WHERE file = ?"
|
|
4154
|
+
"SELECT file, lang, mtime_ms, symbol_count, last_indexed, content_hash FROM files WHERE file = ?"
|
|
3305
4155
|
).all(file);
|
|
3306
4156
|
const r = rows[0];
|
|
3307
4157
|
if (!r) return null;
|
|
@@ -3310,21 +4160,25 @@ function getFileMetaWithStatement(stmt, file) {
|
|
|
3310
4160
|
lang: r.lang,
|
|
3311
4161
|
mtimeMs: r.mtime_ms,
|
|
3312
4162
|
symbolCount: r.symbol_count,
|
|
3313
|
-
lastIndexed: r.last_indexed
|
|
4163
|
+
lastIndexed: r.last_indexed,
|
|
4164
|
+
contentHash: r.content_hash
|
|
3314
4165
|
};
|
|
3315
4166
|
}
|
|
3316
4167
|
function getAllFileMetasWithStatement(stmt) {
|
|
3317
|
-
return stmt(
|
|
4168
|
+
return stmt(
|
|
4169
|
+
"SELECT file, lang, mtime_ms, symbol_count, last_indexed, content_hash FROM files"
|
|
4170
|
+
).all().map((r) => ({
|
|
3318
4171
|
file: r.file,
|
|
3319
4172
|
lang: r.lang,
|
|
3320
4173
|
mtimeMs: r.mtime_ms,
|
|
3321
4174
|
symbolCount: r.symbol_count,
|
|
3322
|
-
lastIndexed: r.last_indexed
|
|
4175
|
+
lastIndexed: r.last_indexed,
|
|
4176
|
+
contentHash: r.content_hash
|
|
3323
4177
|
}));
|
|
3324
4178
|
}
|
|
3325
4179
|
function getIndexDbSizeBytes(indexDir2) {
|
|
3326
4180
|
try {
|
|
3327
|
-
return
|
|
4181
|
+
return fs7.statSync(path10.join(indexDir2, DB_FILE)).size;
|
|
3328
4182
|
} catch {
|
|
3329
4183
|
return 0;
|
|
3330
4184
|
}
|
|
@@ -3373,6 +4227,18 @@ function bulkInsertFtsWithStatement(stmt, maxSqlVars, ftsAvailable, rows) {
|
|
|
3373
4227
|
insert.run(...binds);
|
|
3374
4228
|
}
|
|
3375
4229
|
}
|
|
4230
|
+
function bulkInsertVectorsWithStatement(stmt, maxSqlVars, rows) {
|
|
4231
|
+
if (rows.length === 0) return;
|
|
4232
|
+
const chunkSize = Math.max(1, Math.floor(maxSqlVars / 2));
|
|
4233
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
4234
|
+
const chunk = rows.slice(i, i + chunkSize);
|
|
4235
|
+
const placeholders = chunk.map(() => "(?, ?)").join(", ");
|
|
4236
|
+
const insert = stmt(`INSERT INTO symbol_vectors(symbol_id, vector) VALUES ${placeholders}`);
|
|
4237
|
+
const binds = [];
|
|
4238
|
+
for (const r of chunk) binds.push(r.id, r.vector);
|
|
4239
|
+
insert.run(...binds);
|
|
4240
|
+
}
|
|
4241
|
+
}
|
|
3376
4242
|
function bulkInsertRefsWithStatement(stmt, maxSqlVars, refs) {
|
|
3377
4243
|
if (refs.length === 0) return;
|
|
3378
4244
|
const chunkSize = Math.max(1, Math.floor(maxSqlVars / 8));
|
|
@@ -3677,6 +4543,171 @@ function findOutgoingCallsByName(stmt, symbolName, file, limit) {
|
|
|
3677
4543
|
const calls = rows.map(mapCallSiteRow).slice(0, limit);
|
|
3678
4544
|
return { calls, symbolFound: true, unresolvedCount, totalMatches: rows.length };
|
|
3679
4545
|
}
|
|
4546
|
+
function runCteWithSeeds(stmt, seedIds, buildSql) {
|
|
4547
|
+
if (seedIds.length <= 900) {
|
|
4548
|
+
const ph = seedIds.map(() => "?").join(",");
|
|
4549
|
+
return stmt(buildSql(ph)).all(...seedIds);
|
|
4550
|
+
}
|
|
4551
|
+
stmt("DROP TABLE IF EXISTS _cte_seeds").run();
|
|
4552
|
+
try {
|
|
4553
|
+
stmt("CREATE TEMP TABLE _cte_seeds (id INTEGER PRIMARY KEY)").run();
|
|
4554
|
+
for (let i = 0; i < seedIds.length; i += 500) {
|
|
4555
|
+
const chunk = seedIds.slice(i, i + 500);
|
|
4556
|
+
const ph = chunk.map(() => "(?)").join(",");
|
|
4557
|
+
stmt(`INSERT OR IGNORE INTO _cte_seeds (id) VALUES ${ph}`).run(...chunk);
|
|
4558
|
+
}
|
|
4559
|
+
return stmt(buildSql("SELECT id FROM _cte_seeds")).all();
|
|
4560
|
+
} finally {
|
|
4561
|
+
stmt("DROP TABLE IF EXISTS _cte_seeds").run();
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
function findTransitiveIncomingCallsByName(stmt, symbolName, file, limit) {
|
|
4565
|
+
const targetIds = resolveSymbolIds(stmt, symbolName, file);
|
|
4566
|
+
if (targetIds.length === 0)
|
|
4567
|
+
return { calls: [], symbolFound: false, ambiguous: false, totalMatches: 0 };
|
|
4568
|
+
let matchIds = targetIds;
|
|
4569
|
+
let ambiguous = false;
|
|
4570
|
+
if (file !== void 0) {
|
|
4571
|
+
const allNamedIds = resolveSymbolIds(stmt, symbolName, void 0);
|
|
4572
|
+
if (allNamedIds.length > targetIds.length) {
|
|
4573
|
+
matchIds = allNamedIds;
|
|
4574
|
+
ambiguous = true;
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
const cteSql = (seedSource) => `WITH RECURSIVE incoming_tree(from_id) AS (
|
|
4578
|
+
SELECT r.from_id
|
|
4579
|
+
FROM refs r
|
|
4580
|
+
WHERE r.to_id IN (${seedSource})
|
|
4581
|
+
|
|
4582
|
+
UNION
|
|
4583
|
+
|
|
4584
|
+
SELECT r.from_id
|
|
4585
|
+
FROM refs r
|
|
4586
|
+
JOIN incoming_tree it ON r.to_id = it.from_id
|
|
4587
|
+
)
|
|
4588
|
+
SELECT
|
|
4589
|
+
s.id AS sym_id,
|
|
4590
|
+
s.name AS sym_name,
|
|
4591
|
+
s.kind AS sym_kind,
|
|
4592
|
+
s.lang AS sym_lang,
|
|
4593
|
+
s.file AS sym_file,
|
|
4594
|
+
s.line AS sym_line,
|
|
4595
|
+
s.signature AS sym_signature,
|
|
4596
|
+
'' AS call_type,
|
|
4597
|
+
0 AS ref_line
|
|
4598
|
+
FROM incoming_tree it
|
|
4599
|
+
JOIN symbols s ON s.id = it.from_id
|
|
4600
|
+
GROUP BY s.id
|
|
4601
|
+
ORDER BY s.file, s.line`;
|
|
4602
|
+
const rows = runCteWithSeeds(stmt, matchIds, cteSql);
|
|
4603
|
+
if (!file) {
|
|
4604
|
+
const fallbackRows = stmt(
|
|
4605
|
+
`SELECT
|
|
4606
|
+
s.id AS sym_id,
|
|
4607
|
+
s.name AS sym_name,
|
|
4608
|
+
s.kind AS sym_kind,
|
|
4609
|
+
s.lang AS sym_lang,
|
|
4610
|
+
s.file AS sym_file,
|
|
4611
|
+
s.line AS sym_line,
|
|
4612
|
+
s.signature AS sym_signature,
|
|
4613
|
+
r.call_type,
|
|
4614
|
+
r.line AS ref_line
|
|
4615
|
+
FROM refs r
|
|
4616
|
+
JOIN symbols s ON s.id = r.from_id
|
|
4617
|
+
WHERE r.to_id IS NULL AND r.to_name = ?
|
|
4618
|
+
ORDER BY r.line, r.id`
|
|
4619
|
+
).all(symbolName);
|
|
4620
|
+
rows.push(...fallbackRows);
|
|
4621
|
+
}
|
|
4622
|
+
rows.sort((a, b) => a.ref_line - b.ref_line || a.sym_id - b.sym_id);
|
|
4623
|
+
const allCalls = rows.map(mapCallSiteRow);
|
|
4624
|
+
return {
|
|
4625
|
+
calls: allCalls.slice(0, limit),
|
|
4626
|
+
symbolFound: true,
|
|
4627
|
+
ambiguous,
|
|
4628
|
+
totalMatches: allCalls.length
|
|
4629
|
+
};
|
|
4630
|
+
}
|
|
4631
|
+
function findTransitiveOutgoingCallsByName(stmt, symbolName, file, limit) {
|
|
4632
|
+
const sourceIds = resolveSymbolIds(stmt, symbolName, file);
|
|
4633
|
+
if (sourceIds.length === 0)
|
|
4634
|
+
return { calls: [], symbolFound: false, unresolvedCount: 0, totalMatches: 0 };
|
|
4635
|
+
const unresolvedCount = chunkedIdScalar(
|
|
4636
|
+
stmt,
|
|
4637
|
+
sourceIds,
|
|
4638
|
+
(ph) => `SELECT COUNT(*) AS n FROM refs WHERE from_id IN (${ph}) AND to_id IS NULL`
|
|
4639
|
+
);
|
|
4640
|
+
const cteSql = (seedSource) => `WITH RECURSIVE outgoing_tree(to_id) AS (
|
|
4641
|
+
SELECT r.to_id
|
|
4642
|
+
FROM refs r
|
|
4643
|
+
WHERE r.from_id IN (${seedSource}) AND r.to_id IS NOT NULL
|
|
4644
|
+
|
|
4645
|
+
UNION
|
|
4646
|
+
|
|
4647
|
+
SELECT r.to_id
|
|
4648
|
+
FROM refs r
|
|
4649
|
+
JOIN outgoing_tree ot ON r.from_id = ot.to_id
|
|
4650
|
+
WHERE r.to_id IS NOT NULL
|
|
4651
|
+
)
|
|
4652
|
+
SELECT
|
|
4653
|
+
s.id AS sym_id,
|
|
4654
|
+
s.name AS sym_name,
|
|
4655
|
+
s.kind AS sym_kind,
|
|
4656
|
+
s.lang AS sym_lang,
|
|
4657
|
+
s.file AS sym_file,
|
|
4658
|
+
s.line AS sym_line,
|
|
4659
|
+
s.signature AS sym_signature,
|
|
4660
|
+
'' AS call_type,
|
|
4661
|
+
0 AS ref_line
|
|
4662
|
+
FROM outgoing_tree ot
|
|
4663
|
+
JOIN symbols s ON s.id = ot.to_id
|
|
4664
|
+
GROUP BY s.id
|
|
4665
|
+
ORDER BY s.file, s.line`;
|
|
4666
|
+
const rows = runCteWithSeeds(stmt, sourceIds, cteSql);
|
|
4667
|
+
const calls = rows.map(mapCallSiteRow).slice(0, limit);
|
|
4668
|
+
return { calls, symbolFound: true, unresolvedCount, totalMatches: rows.length };
|
|
4669
|
+
}
|
|
4670
|
+
function findReachableSymbolIds(stmt, seedIds) {
|
|
4671
|
+
if (seedIds.length === 0) return /* @__PURE__ */ new Set();
|
|
4672
|
+
if (seedIds.length > 900) {
|
|
4673
|
+
stmt("DROP TABLE IF EXISTS _seeds").run();
|
|
4674
|
+
try {
|
|
4675
|
+
stmt("CREATE TEMP TABLE _seeds (id INTEGER PRIMARY KEY)").run();
|
|
4676
|
+
for (let i = 0; i < seedIds.length; i += 500) {
|
|
4677
|
+
const chunk = seedIds.slice(i, i + 500);
|
|
4678
|
+
const ph2 = chunk.map(() => "(?)").join(",");
|
|
4679
|
+
stmt(`INSERT OR IGNORE INTO _seeds (id) VALUES ${ph2}`).run(...chunk);
|
|
4680
|
+
}
|
|
4681
|
+
const rows2 = stmt(
|
|
4682
|
+
`WITH RECURSIVE reachable(id) AS (
|
|
4683
|
+
SELECT id FROM _seeds
|
|
4684
|
+
UNION
|
|
4685
|
+
SELECT r.to_id
|
|
4686
|
+
FROM refs r
|
|
4687
|
+
JOIN reachable ON r.from_id = reachable.id
|
|
4688
|
+
WHERE r.to_id IS NOT NULL
|
|
4689
|
+
)
|
|
4690
|
+
SELECT DISTINCT id FROM reachable`
|
|
4691
|
+
).all();
|
|
4692
|
+
return new Set(rows2.map((r) => r.id));
|
|
4693
|
+
} finally {
|
|
4694
|
+
stmt("DROP TABLE IF EXISTS _seeds").run();
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
const ph = seedIds.map(() => "?").join(",");
|
|
4698
|
+
const rows = stmt(
|
|
4699
|
+
`WITH RECURSIVE reachable(id) AS (
|
|
4700
|
+
SELECT id FROM symbols WHERE id IN (${ph})
|
|
4701
|
+
UNION
|
|
4702
|
+
SELECT r.to_id
|
|
4703
|
+
FROM refs r
|
|
4704
|
+
JOIN reachable ON r.from_id = reachable.id
|
|
4705
|
+
WHERE r.to_id IS NOT NULL
|
|
4706
|
+
)
|
|
4707
|
+
SELECT DISTINCT id FROM reachable`
|
|
4708
|
+
).all(...seedIds);
|
|
4709
|
+
return new Set(rows.map((r) => r.id));
|
|
4710
|
+
}
|
|
3680
4711
|
function findRefsToWithStatement(stmt, symbolId) {
|
|
3681
4712
|
return stmt(
|
|
3682
4713
|
"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 = ?)"
|
|
@@ -3916,6 +4947,12 @@ var CORE_TABLES_SQL = `
|
|
|
3916
4947
|
file TEXT PRIMARY KEY,
|
|
3917
4948
|
lang TEXT NOT NULL,
|
|
3918
4949
|
mtime_ms INTEGER NOT NULL,
|
|
4950
|
+
-- Phase 2: xxHash64 of the file's UTF-8 bytes. Empty string when the
|
|
4951
|
+
-- indexer hasn't populated it yet (legacy rows, schema repaired by
|
|
4952
|
+
-- repairMissingColumns). Compared on incremental re-index so that a
|
|
4953
|
+
-- touch or branch-switch that leaves content byte-identical skips the
|
|
4954
|
+
-- expensive parse phase entirely (refactoring proposal Phase 2).
|
|
4955
|
+
content_hash TEXT NOT NULL DEFAULT '',
|
|
3919
4956
|
symbol_count INTEGER NOT NULL DEFAULT 0,
|
|
3920
4957
|
last_indexed INTEGER NOT NULL,
|
|
3921
4958
|
-- Code Atlas grouping label, computed at index time from the ecosystem's
|
|
@@ -3983,7 +5020,14 @@ var LANG_FAMILY_TABLE_SQL = `
|
|
|
3983
5020
|
);
|
|
3984
5021
|
`;
|
|
3985
5022
|
var LANG_FAMILY_WILDCARD = "*";
|
|
3986
|
-
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = '
|
|
5023
|
+
var SYMBOLS_FTS_SQL = "CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(text, tokenize = 'trigram')";
|
|
5024
|
+
var SYMBOL_VECTORS_TABLE_SQL = `
|
|
5025
|
+
CREATE TABLE IF NOT EXISTS symbol_vectors (
|
|
5026
|
+
symbol_id INTEGER PRIMARY KEY,
|
|
5027
|
+
vector BLOB NOT NULL,
|
|
5028
|
+
FOREIGN KEY (symbol_id) REFERENCES symbols(id) ON DELETE CASCADE
|
|
5029
|
+
);
|
|
5030
|
+
`;
|
|
3987
5031
|
|
|
3988
5032
|
// src/codebase-index/writer-search-helpers.ts
|
|
3989
5033
|
var SEARCH_CANDIDATE_SCAN_CAP = 5e3;
|
|
@@ -4124,6 +5168,85 @@ var StorePool = class {
|
|
|
4124
5168
|
}
|
|
4125
5169
|
};
|
|
4126
5170
|
|
|
5171
|
+
// src/codebase-index/vector-search.ts
|
|
5172
|
+
var RRF_K = 60;
|
|
5173
|
+
var VECTOR_DIMENSIONS = 384;
|
|
5174
|
+
var NGRAM_SIZE = 3;
|
|
5175
|
+
function embedText(text) {
|
|
5176
|
+
const vec = new Float32Array(VECTOR_DIMENSIONS);
|
|
5177
|
+
const normalized = text.toLowerCase().trim();
|
|
5178
|
+
if (normalized.length < NGRAM_SIZE) {
|
|
5179
|
+
const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
|
|
5180
|
+
for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
|
|
5181
|
+
const ngram = padded.slice(i, i + NGRAM_SIZE);
|
|
5182
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5183
|
+
vec[bucket] += 1;
|
|
5184
|
+
}
|
|
5185
|
+
} else {
|
|
5186
|
+
for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
|
|
5187
|
+
const ngram = normalized.slice(i, i + NGRAM_SIZE);
|
|
5188
|
+
const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
|
|
5189
|
+
vec[bucket] += 1;
|
|
5190
|
+
}
|
|
5191
|
+
}
|
|
5192
|
+
let norm = 0;
|
|
5193
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5194
|
+
norm += vec[i] * vec[i];
|
|
5195
|
+
}
|
|
5196
|
+
norm = Math.sqrt(norm);
|
|
5197
|
+
if (norm > 0) {
|
|
5198
|
+
for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
|
|
5199
|
+
vec[i] /= norm;
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
return vec;
|
|
5203
|
+
}
|
|
5204
|
+
function hashNgram(str) {
|
|
5205
|
+
let hash = 2166136261;
|
|
5206
|
+
for (let i = 0; i < str.length; i++) {
|
|
5207
|
+
hash ^= str.charCodeAt(i);
|
|
5208
|
+
hash = Math.imul(hash, 16777619);
|
|
5209
|
+
}
|
|
5210
|
+
return hash >>> 0;
|
|
5211
|
+
}
|
|
5212
|
+
function cosineSimilarity(a, b) {
|
|
5213
|
+
let dot = 0;
|
|
5214
|
+
const len = Math.min(a.length, b.length);
|
|
5215
|
+
for (let i = 0; i < len; i++) {
|
|
5216
|
+
dot += a[i] * b[i];
|
|
5217
|
+
}
|
|
5218
|
+
return dot;
|
|
5219
|
+
}
|
|
5220
|
+
function encodeVector(vec) {
|
|
5221
|
+
return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
|
|
5222
|
+
}
|
|
5223
|
+
function decodeVector(buf) {
|
|
5224
|
+
const view = new DataView(
|
|
5225
|
+
buf.buffer,
|
|
5226
|
+
buf.byteOffset,
|
|
5227
|
+
buf.byteLength
|
|
5228
|
+
);
|
|
5229
|
+
const copy = new Float32Array(buf.byteLength / 4);
|
|
5230
|
+
for (let i = 0; i < copy.length; i++) {
|
|
5231
|
+
copy[i] = view.getFloat32(i * 4, true);
|
|
5232
|
+
}
|
|
5233
|
+
return copy;
|
|
5234
|
+
}
|
|
5235
|
+
function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
|
|
5236
|
+
const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
|
|
5237
|
+
const scored = [];
|
|
5238
|
+
for (const id of allIds) {
|
|
5239
|
+
const bm25Rank = bm25Ranks.get(id);
|
|
5240
|
+
const vecRank = vectorRanks.get(id);
|
|
5241
|
+
let score = 0;
|
|
5242
|
+
if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
|
|
5243
|
+
if (vecRank !== void 0) score += 1 / (k + vecRank);
|
|
5244
|
+
scored.push([id, score]);
|
|
5245
|
+
}
|
|
5246
|
+
scored.sort((a, b) => b[1] - a[1]);
|
|
5247
|
+
return scored;
|
|
5248
|
+
}
|
|
5249
|
+
|
|
4127
5250
|
// src/codebase-index/writer.ts
|
|
4128
5251
|
var DB_FILE2 = "index.db";
|
|
4129
5252
|
var MAX_STATEMENT_CACHE = 128;
|
|
@@ -4136,6 +5259,12 @@ var IndexStore = class _IndexStore {
|
|
|
4136
5259
|
* When false, ranked search falls back to the LIKE + in-process BM25 path.
|
|
4137
5260
|
*/
|
|
4138
5261
|
ftsAvailable = false;
|
|
5262
|
+
/**
|
|
5263
|
+
* Phase 3: true when the `symbol_vectors` table was created successfully.
|
|
5264
|
+
* When false, hybrid search skips the vector pass and falls back to FTS5
|
|
5265
|
+
* (or LIKE) only.
|
|
5266
|
+
*/
|
|
5267
|
+
vectorsAvailable = false;
|
|
4139
5268
|
/**
|
|
4140
5269
|
* Cache of prepared statements keyed by their SQL text. `DatabaseSync`
|
|
4141
5270
|
* compiles SQL on every `.prepare()` call; for the fixed-SQL methods
|
|
@@ -4194,9 +5323,9 @@ var IndexStore = class _IndexStore {
|
|
|
4194
5323
|
}
|
|
4195
5324
|
constructor(projectRoot2, opts = {}) {
|
|
4196
5325
|
this.indexDir = resolveIndexDir(projectRoot2, opts.indexDir);
|
|
4197
|
-
|
|
5326
|
+
fs8.mkdirSync(this.indexDir, { recursive: true });
|
|
4198
5327
|
const Database = loadDatabaseSync();
|
|
4199
|
-
this.db = new Database(
|
|
5328
|
+
this.db = new Database(path11.join(this.indexDir, DB_FILE2));
|
|
4200
5329
|
applyIndexStorePragmas(this.db);
|
|
4201
5330
|
this.initSchema();
|
|
4202
5331
|
}
|
|
@@ -4234,7 +5363,13 @@ var IndexStore = class _IndexStore {
|
|
|
4234
5363
|
*/
|
|
4235
5364
|
repairMissingColumns() {
|
|
4236
5365
|
const expected = [
|
|
4237
|
-
{
|
|
5366
|
+
{
|
|
5367
|
+
table: "files",
|
|
5368
|
+
columns: [
|
|
5369
|
+
["package", "TEXT NOT NULL DEFAULT ''"],
|
|
5370
|
+
["content_hash", "TEXT NOT NULL DEFAULT ''"]
|
|
5371
|
+
]
|
|
5372
|
+
},
|
|
4238
5373
|
{
|
|
4239
5374
|
table: "refs",
|
|
4240
5375
|
columns: [
|
|
@@ -4266,6 +5401,7 @@ var IndexStore = class _IndexStore {
|
|
|
4266
5401
|
DROP TABLE IF EXISTS symbols;
|
|
4267
5402
|
DROP TABLE IF EXISTS files;
|
|
4268
5403
|
DROP TABLE IF EXISTS refs;
|
|
5404
|
+
DROP TABLE IF EXISTS symbol_vectors;
|
|
4269
5405
|
`);
|
|
4270
5406
|
this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
4271
5407
|
this.stmt("UPDATE metadata SET value = ? WHERE key = ?").run(
|
|
@@ -4287,6 +5423,12 @@ var IndexStore = class _IndexStore {
|
|
|
4287
5423
|
this.db.exec(LANG_FAMILY_TABLE_SQL);
|
|
4288
5424
|
this.seedLangFamilies();
|
|
4289
5425
|
try {
|
|
5426
|
+
const ftsSchema = this.stmt(
|
|
5427
|
+
"SELECT sql FROM sqlite_master WHERE type='table' AND name='symbols_fts'"
|
|
5428
|
+
).get();
|
|
5429
|
+
if (ftsSchema?.sql && ftsSchema.sql.includes("unicode61")) {
|
|
5430
|
+
this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
5431
|
+
}
|
|
4290
5432
|
this.db.exec(SYMBOLS_FTS_SQL);
|
|
4291
5433
|
this.ftsAvailable = true;
|
|
4292
5434
|
const symbolCount = Number(
|
|
@@ -4297,6 +5439,7 @@ var IndexStore = class _IndexStore {
|
|
|
4297
5439
|
);
|
|
4298
5440
|
if (symbolCount !== ftsCount) {
|
|
4299
5441
|
this.db.exec("DELETE FROM symbols_fts");
|
|
5442
|
+
if (this.vectorsAvailable) this.db.exec("DELETE FROM symbol_vectors");
|
|
4300
5443
|
const rows = this.stmt(
|
|
4301
5444
|
"SELECT id, name, signature, doc_comment FROM symbols ORDER BY id"
|
|
4302
5445
|
).all();
|
|
@@ -4314,6 +5457,12 @@ var IndexStore = class _IndexStore {
|
|
|
4314
5457
|
} catch {
|
|
4315
5458
|
this.ftsAvailable = false;
|
|
4316
5459
|
}
|
|
5460
|
+
try {
|
|
5461
|
+
this.db.exec(SYMBOL_VECTORS_TABLE_SQL);
|
|
5462
|
+
this.vectorsAvailable = true;
|
|
5463
|
+
} catch {
|
|
5464
|
+
this.vectorsAvailable = false;
|
|
5465
|
+
}
|
|
4317
5466
|
this.ensureNextSymbolIdSeeded();
|
|
4318
5467
|
}
|
|
4319
5468
|
// ─── ID allocation & bulk helpers ────────────────────────────────────────────
|
|
@@ -4423,6 +5572,7 @@ var IndexStore = class _IndexStore {
|
|
|
4423
5572
|
const result = [];
|
|
4424
5573
|
const bulk = [];
|
|
4425
5574
|
const ftsRows = [];
|
|
5575
|
+
const vectorRows = [];
|
|
4426
5576
|
for (const s of symbols) {
|
|
4427
5577
|
const id = nextId++;
|
|
4428
5578
|
bulk.push({
|
|
@@ -4441,6 +5591,10 @@ var IndexStore = class _IndexStore {
|
|
|
4441
5591
|
if (this.ftsAvailable) {
|
|
4442
5592
|
ftsRows.push({ id, text: buildIndexableText(s.name, s.signature, s.docComment) });
|
|
4443
5593
|
}
|
|
5594
|
+
vectorRows.push({
|
|
5595
|
+
id,
|
|
5596
|
+
vector: encodeVector(embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment)))
|
|
5597
|
+
});
|
|
4444
5598
|
result.push({ ...s, id });
|
|
4445
5599
|
}
|
|
4446
5600
|
bulkInsertSymbolsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, bulk);
|
|
@@ -4450,6 +5604,13 @@ var IndexStore = class _IndexStore {
|
|
|
4450
5604
|
this.ftsAvailable,
|
|
4451
5605
|
ftsRows
|
|
4452
5606
|
);
|
|
5607
|
+
if (this.vectorsAvailable) {
|
|
5608
|
+
bulkInsertVectorsWithStatement(
|
|
5609
|
+
(sql) => this.stmt(sql),
|
|
5610
|
+
_IndexStore.MAX_SQL_VARS,
|
|
5611
|
+
vectorRows
|
|
5612
|
+
);
|
|
5613
|
+
}
|
|
4453
5614
|
this.db.exec("COMMIT");
|
|
4454
5615
|
return result;
|
|
4455
5616
|
} catch (err) {
|
|
@@ -4469,6 +5630,11 @@ var IndexStore = class _IndexStore {
|
|
|
4469
5630
|
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
4470
5631
|
).run(file);
|
|
4471
5632
|
}
|
|
5633
|
+
if (this.vectorsAvailable) {
|
|
5634
|
+
this.stmt(
|
|
5635
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
5636
|
+
).run(file);
|
|
5637
|
+
}
|
|
4472
5638
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
|
|
4473
5639
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
4474
5640
|
this.db.exec("COMMIT");
|
|
@@ -4494,6 +5660,11 @@ var IndexStore = class _IndexStore {
|
|
|
4494
5660
|
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
4495
5661
|
).run(file);
|
|
4496
5662
|
}
|
|
5663
|
+
if (this.vectorsAvailable) {
|
|
5664
|
+
this.stmt(
|
|
5665
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
5666
|
+
).run(file);
|
|
5667
|
+
}
|
|
4497
5668
|
this.stmt(
|
|
4498
5669
|
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
4499
5670
|
).run(file);
|
|
@@ -4511,14 +5682,22 @@ var IndexStore = class _IndexStore {
|
|
|
4511
5682
|
upsertFile(meta) {
|
|
4512
5683
|
this.runWithRetry(() => {
|
|
4513
5684
|
this.stmt(
|
|
4514
|
-
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
4515
|
-
VALUES (?, ?, ?, ?, ?)
|
|
5685
|
+
`INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
|
|
5686
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
4516
5687
|
ON CONFLICT(file) DO UPDATE SET
|
|
4517
5688
|
lang = excluded.lang,
|
|
4518
5689
|
mtime_ms = excluded.mtime_ms,
|
|
5690
|
+
content_hash = excluded.content_hash,
|
|
4519
5691
|
symbol_count = excluded.symbol_count,
|
|
4520
5692
|
last_indexed = excluded.last_indexed`
|
|
4521
|
-
).run(
|
|
5693
|
+
).run(
|
|
5694
|
+
meta.file,
|
|
5695
|
+
meta.lang,
|
|
5696
|
+
meta.mtimeMs,
|
|
5697
|
+
meta.contentHash ?? "",
|
|
5698
|
+
meta.symbolCount,
|
|
5699
|
+
meta.lastIndexed
|
|
5700
|
+
);
|
|
4522
5701
|
});
|
|
4523
5702
|
}
|
|
4524
5703
|
getFileMeta(file) {
|
|
@@ -4685,9 +5864,18 @@ var IndexStore = class _IndexStore {
|
|
|
4685
5864
|
if (mapped === null) return { results: [], total: 0 };
|
|
4686
5865
|
effectiveKind = mapped;
|
|
4687
5866
|
}
|
|
4688
|
-
const
|
|
5867
|
+
const longTokens = tokens.filter((t) => t.length >= 3);
|
|
5868
|
+
const shortTokens = tokens.filter((t) => t.length < 3);
|
|
5869
|
+
if (longTokens.length === 0) {
|
|
5870
|
+
return this.searchRankedFallback(query, filter, safeLimit);
|
|
5871
|
+
}
|
|
5872
|
+
const match = longTokens.map((t) => `"${t.replaceAll('"', "")}"`).join(" OR ");
|
|
4689
5873
|
const conditions = ["symbols_fts MATCH ?"];
|
|
4690
5874
|
const values = [match];
|
|
5875
|
+
for (const shortTok of shortTokens) {
|
|
5876
|
+
conditions.push("s.text LIKE ? ESCAPE '\\'");
|
|
5877
|
+
values.push(`%${escapeLike(shortTok)}%`);
|
|
5878
|
+
}
|
|
4691
5879
|
if (effectiveKind) {
|
|
4692
5880
|
conditions.push("s.kind = ?");
|
|
4693
5881
|
values.push(effectiveKind);
|
|
@@ -4706,7 +5894,7 @@ var IndexStore = class _IndexStore {
|
|
|
4706
5894
|
).all(...values);
|
|
4707
5895
|
const total = countRows[0] ? Number(countRows[0].n) : 0;
|
|
4708
5896
|
if (total === 0) return { results: [], total: 0 };
|
|
4709
|
-
const
|
|
5897
|
+
const bm25Rows = this.stmt(
|
|
4710
5898
|
`SELECT s.id, s.lang, s.kind, s.name, s.file, s.line, s.col, s.signature, s.doc_comment,
|
|
4711
5899
|
-bm25(symbols_fts) AS score,
|
|
4712
5900
|
snippet(symbols_fts, 0, '', '', '\u2026', 12) AS snippet
|
|
@@ -4719,8 +5907,35 @@ var IndexStore = class _IndexStore {
|
|
|
4719
5907
|
bm25(symbols_fts), lower(s.name), s.file, s.line, s.col, s.id
|
|
4720
5908
|
LIMIT ?`
|
|
4721
5909
|
).all(...values, query.trim(), `${escapeLike(query.trim())}%`, safeLimit);
|
|
5910
|
+
if (this.vectorsAvailable && bm25Rows.length > 0) {
|
|
5911
|
+
const queryVec = embedText(query);
|
|
5912
|
+
const candidateIds = bm25Rows.map((r) => r.id);
|
|
5913
|
+
const placeholders = candidateIds.map(() => "?").join(",");
|
|
5914
|
+
const vecRows = this.stmt(
|
|
5915
|
+
`SELECT sv.symbol_id, sv.vector FROM symbol_vectors sv WHERE sv.symbol_id IN (${placeholders})`
|
|
5916
|
+
).all(...candidateIds);
|
|
5917
|
+
const vecScores = vecRows.map((r) => ({
|
|
5918
|
+
id: r.symbol_id,
|
|
5919
|
+
sim: cosineSimilarity(queryVec, decodeVector(r.vector))
|
|
5920
|
+
})).sort((a, b) => b.sim - a.sim);
|
|
5921
|
+
const bm25Rank = /* @__PURE__ */ new Map();
|
|
5922
|
+
bm25Rows.forEach((r, i) => bm25Rank.set(r.id, i));
|
|
5923
|
+
const vecRank = /* @__PURE__ */ new Map();
|
|
5924
|
+
vecScores.forEach((r, i) => vecRank.set(r.id, i));
|
|
5925
|
+
const fused = reciprocalRankFusion(bm25Rank, vecRank, 60);
|
|
5926
|
+
const fusedScore = new Map(fused);
|
|
5927
|
+
const sorted = [...bm25Rows].sort(
|
|
5928
|
+
(a, b) => (fusedScore.get(b.id) ?? 0) - (fusedScore.get(a.id) ?? 0)
|
|
5929
|
+
);
|
|
5930
|
+
return {
|
|
5931
|
+
results: sorted.map(
|
|
5932
|
+
(row) => mapWriterSearchRow(row, filter?.lspKind, Math.max(1e-4, row.score), row.snippet)
|
|
5933
|
+
),
|
|
5934
|
+
total
|
|
5935
|
+
};
|
|
5936
|
+
}
|
|
4722
5937
|
return {
|
|
4723
|
-
results:
|
|
5938
|
+
results: bm25Rows.map(
|
|
4724
5939
|
(row) => mapWriterSearchRow(row, filter?.lspKind, Math.max(1e-4, row.score), row.snippet)
|
|
4725
5940
|
),
|
|
4726
5941
|
total
|
|
@@ -4848,6 +6063,7 @@ var IndexStore = class _IndexStore {
|
|
|
4848
6063
|
this.db.exec("DROP TABLE IF EXISTS files");
|
|
4849
6064
|
this.db.exec("DROP TABLE IF EXISTS metadata");
|
|
4850
6065
|
if (this.ftsAvailable) this.db.exec("DROP TABLE IF EXISTS symbols_fts");
|
|
6066
|
+
this.db.exec("DROP TABLE IF EXISTS symbol_vectors");
|
|
4851
6067
|
this.db.exec("COMMIT");
|
|
4852
6068
|
this.stmtCache.clear();
|
|
4853
6069
|
this.initSchema();
|
|
@@ -4935,6 +6151,11 @@ var IndexStore = class _IndexStore {
|
|
|
4935
6151
|
`DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
4936
6152
|
).run(...options.deleteForFiles);
|
|
4937
6153
|
}
|
|
6154
|
+
if (this.vectorsAvailable) {
|
|
6155
|
+
this.stmt(
|
|
6156
|
+
`DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
6157
|
+
).run(...options.deleteForFiles);
|
|
6158
|
+
}
|
|
4938
6159
|
this.stmt(
|
|
4939
6160
|
`DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file IN (${placeholders}))`
|
|
4940
6161
|
).run(...options.deleteForFiles);
|
|
@@ -4948,6 +6169,7 @@ var IndexStore = class _IndexStore {
|
|
|
4948
6169
|
const refsToInsert = [];
|
|
4949
6170
|
const bulkSyms = [];
|
|
4950
6171
|
const ftsRows = [];
|
|
6172
|
+
const vectorRows = [];
|
|
4951
6173
|
for (const entry of entries) {
|
|
4952
6174
|
const insertedForEntry = [];
|
|
4953
6175
|
for (const s of entry.symbols) {
|
|
@@ -4971,6 +6193,10 @@ var IndexStore = class _IndexStore {
|
|
|
4971
6193
|
text: buildIndexableText(s.name, s.signature, s.docComment)
|
|
4972
6194
|
});
|
|
4973
6195
|
}
|
|
6196
|
+
vectorRows.push({
|
|
6197
|
+
id,
|
|
6198
|
+
vector: encodeVector(embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment)))
|
|
6199
|
+
});
|
|
4974
6200
|
const inserted = { ...s, id };
|
|
4975
6201
|
allInserted.push(inserted);
|
|
4976
6202
|
insertedForEntry.push(inserted);
|
|
@@ -4984,19 +6210,34 @@ var IndexStore = class _IndexStore {
|
|
|
4984
6210
|
this.ftsAvailable,
|
|
4985
6211
|
ftsRows
|
|
4986
6212
|
);
|
|
6213
|
+
if (this.vectorsAvailable) {
|
|
6214
|
+
bulkInsertVectorsWithStatement(
|
|
6215
|
+
(sql) => this.stmt(sql),
|
|
6216
|
+
_IndexStore.MAX_SQL_VARS,
|
|
6217
|
+
vectorRows
|
|
6218
|
+
);
|
|
6219
|
+
}
|
|
4987
6220
|
bulkInsertRefsWithStatement((sql) => this.stmt(sql), _IndexStore.MAX_SQL_VARS, refsToInsert);
|
|
4988
6221
|
const upsertStmt = this.stmt(
|
|
4989
|
-
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
4990
|
-
VALUES (?, ?, ?, ?, ?)
|
|
6222
|
+
`INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
|
|
6223
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
4991
6224
|
ON CONFLICT(file) DO UPDATE SET
|
|
4992
6225
|
lang = excluded.lang,
|
|
4993
6226
|
mtime_ms = excluded.mtime_ms,
|
|
6227
|
+
content_hash = excluded.content_hash,
|
|
4994
6228
|
symbol_count = excluded.symbol_count,
|
|
4995
6229
|
last_indexed = excluded.last_indexed`
|
|
4996
6230
|
);
|
|
4997
6231
|
const now = Date.now();
|
|
4998
6232
|
for (const entry of entries) {
|
|
4999
|
-
upsertStmt.run(
|
|
6233
|
+
upsertStmt.run(
|
|
6234
|
+
entry.file,
|
|
6235
|
+
entry.lang,
|
|
6236
|
+
entry.mtimeMs,
|
|
6237
|
+
entry.contentHash ?? "",
|
|
6238
|
+
entry.symbolCount,
|
|
6239
|
+
now
|
|
6240
|
+
);
|
|
5000
6241
|
}
|
|
5001
6242
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
5002
6243
|
this.db.exec("COMMIT");
|
|
@@ -5087,19 +6328,32 @@ var IndexStore = class _IndexStore {
|
|
|
5087
6328
|
"DELETE FROM symbols_fts WHERE rowid IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
5088
6329
|
).run(meta.file);
|
|
5089
6330
|
}
|
|
6331
|
+
if (this.vectorsAvailable) {
|
|
6332
|
+
this.stmt(
|
|
6333
|
+
"DELETE FROM symbol_vectors WHERE symbol_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
6334
|
+
).run(meta.file);
|
|
6335
|
+
}
|
|
5090
6336
|
this.stmt(
|
|
5091
6337
|
"DELETE FROM refs WHERE from_id IN (SELECT id FROM symbols WHERE file_fk = ?)"
|
|
5092
6338
|
).run(meta.file);
|
|
5093
6339
|
this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(meta.file);
|
|
5094
6340
|
this.stmt(
|
|
5095
|
-
`INSERT INTO files(file, lang, mtime_ms, symbol_count, last_indexed)
|
|
5096
|
-
VALUES (?, ?, ?, ?, ?)
|
|
6341
|
+
`INSERT INTO files(file, lang, mtime_ms, content_hash, symbol_count, last_indexed)
|
|
6342
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
5097
6343
|
ON CONFLICT(file) DO UPDATE SET
|
|
5098
6344
|
lang = excluded.lang,
|
|
5099
6345
|
mtime_ms = excluded.mtime_ms,
|
|
6346
|
+
content_hash = excluded.content_hash,
|
|
5100
6347
|
symbol_count = excluded.symbol_count,
|
|
5101
6348
|
last_indexed = excluded.last_indexed`
|
|
5102
|
-
).run(
|
|
6349
|
+
).run(
|
|
6350
|
+
meta.file,
|
|
6351
|
+
meta.lang,
|
|
6352
|
+
meta.mtimeMs,
|
|
6353
|
+
meta.contentHash ?? "",
|
|
6354
|
+
meta.symbolCount,
|
|
6355
|
+
meta.lastIndexed
|
|
6356
|
+
);
|
|
5103
6357
|
this.resolveRefsForNamesUnsafe(affectedNames);
|
|
5104
6358
|
this.db.exec("COMMIT");
|
|
5105
6359
|
} catch (err) {
|
|
@@ -5162,6 +6416,31 @@ var IndexStore = class _IndexStore {
|
|
|
5162
6416
|
findOutgoingCallsByName(symbolName, file, limit = 100) {
|
|
5163
6417
|
return findOutgoingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
5164
6418
|
}
|
|
6419
|
+
/**
|
|
6420
|
+
* Transitive incoming-call tree: all symbols that transitively call the
|
|
6421
|
+
* target, to an unbounded depth (cycle-safe via SQL UNION deduplication).
|
|
6422
|
+
* Used by `codebase-incoming-calls` when the caller wants the full call
|
|
6423
|
+
* chain rather than just direct callers.
|
|
6424
|
+
*/
|
|
6425
|
+
findTransitiveIncomingCallsByName(symbolName, file, limit = 200) {
|
|
6426
|
+
return findTransitiveIncomingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
6427
|
+
}
|
|
6428
|
+
/**
|
|
6429
|
+
* Transitive outgoing-call tree: all symbols the target transitively calls.
|
|
6430
|
+
* Used by `codebase-outgoing-calls` when the caller wants the full
|
|
6431
|
+
* dependency chain rather than just direct callees.
|
|
6432
|
+
*/
|
|
6433
|
+
findTransitiveOutgoingCallsByName(symbolName, file, limit = 200) {
|
|
6434
|
+
return findTransitiveOutgoingCallsByName((sql) => this.stmt(sql), symbolName, file, limit);
|
|
6435
|
+
}
|
|
6436
|
+
/**
|
|
6437
|
+
* Compute the set of symbol IDs reachable from the given seed IDs using a
|
|
6438
|
+
* native SQLite recursive CTE. Used by dead-code detection to replace the
|
|
6439
|
+
* in-memory BFS.
|
|
6440
|
+
*/
|
|
6441
|
+
findReachableSymbolIds(seedIds) {
|
|
6442
|
+
return findReachableSymbolIds((sql) => this.stmt(sql), seedIds);
|
|
6443
|
+
}
|
|
5165
6444
|
/**
|
|
5166
6445
|
* Find all references TO a given symbol (who calls / uses this symbol?).
|
|
5167
6446
|
*/
|
|
@@ -5269,15 +6548,15 @@ var INDEXABLE_EXTENSION_SET = new Set(INDEXABLE_EXTENSIONS);
|
|
|
5269
6548
|
var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
|
|
5270
6549
|
var MAX_GIT_FILE_LIST_BYTES = 64 * 1024 * 1024;
|
|
5271
6550
|
function isWithinProject(projectRoot2, file) {
|
|
5272
|
-
const rel =
|
|
5273
|
-
return rel !== "" && !rel.startsWith(`..${
|
|
6551
|
+
const rel = path12.relative(projectRoot2, file);
|
|
6552
|
+
return rel !== "" && !rel.startsWith(`..${path12.sep}`) && rel !== ".." && !path12.isAbsolute(rel);
|
|
5274
6553
|
}
|
|
5275
6554
|
function isMissingPathError(err) {
|
|
5276
6555
|
const code = err?.code;
|
|
5277
6556
|
return code === "ENOENT" || code === "ENOTDIR";
|
|
5278
6557
|
}
|
|
5279
6558
|
function normalizeComparablePath(value) {
|
|
5280
|
-
const resolved =
|
|
6559
|
+
const resolved = path12.resolve(value);
|
|
5281
6560
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
5282
6561
|
}
|
|
5283
6562
|
function gitOutput(projectRoot2, args) {
|
|
@@ -5322,24 +6601,24 @@ async function findGitSourceFiles(projectRoot2, ignore, signal) {
|
|
|
5322
6601
|
const record = statusRecords[i];
|
|
5323
6602
|
if (!record) continue;
|
|
5324
6603
|
const status = record.slice(0, 2);
|
|
5325
|
-
const changedPath =
|
|
6604
|
+
const changedPath = path12.resolve(projectRoot2, record.slice(3));
|
|
5326
6605
|
dirty.add(changedPath);
|
|
5327
6606
|
if (status.includes("D")) deleted.add(changedPath);
|
|
5328
6607
|
if (status.includes("R") || status.includes("C")) {
|
|
5329
6608
|
const source = statusRecords[++i];
|
|
5330
|
-
if (source) dirty.add(
|
|
6609
|
+
if (source) dirty.add(path12.resolve(projectRoot2, source));
|
|
5331
6610
|
}
|
|
5332
6611
|
}
|
|
5333
6612
|
const files = [];
|
|
5334
6613
|
for (const relative3 of output.toString("utf8").split("\0")) {
|
|
5335
6614
|
if (!relative3) continue;
|
|
5336
6615
|
const portable = relative3.replace(/\\/g, "/");
|
|
5337
|
-
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(
|
|
6616
|
+
if (portable.split("/").some((segment) => ignoreSet.has(segment)) || DEFAULT_IGNORE_FILES.has(path12.posix.basename(portable))) {
|
|
5338
6617
|
continue;
|
|
5339
6618
|
}
|
|
5340
|
-
const full =
|
|
6619
|
+
const full = path12.resolve(projectRoot2, relative3);
|
|
5341
6620
|
if (deleted.has(full)) continue;
|
|
5342
|
-
const ext =
|
|
6621
|
+
const ext = path12.extname(relative3).toLowerCase();
|
|
5343
6622
|
if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
|
|
5344
6623
|
}
|
|
5345
6624
|
return {
|
|
@@ -5374,7 +6653,7 @@ async function findSourceFiles(projectRoot2, ignore, isGitIgnored, signal) {
|
|
|
5374
6653
|
}
|
|
5375
6654
|
let entries;
|
|
5376
6655
|
try {
|
|
5377
|
-
entries = await
|
|
6656
|
+
entries = await fs9.readdir(dir, { withFileTypes: true });
|
|
5378
6657
|
} catch (err) {
|
|
5379
6658
|
complete = false;
|
|
5380
6659
|
errors.push(`scan error: ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -5383,14 +6662,14 @@ async function findSourceFiles(projectRoot2, ignore, isGitIgnored, signal) {
|
|
|
5383
6662
|
dirCount++;
|
|
5384
6663
|
for (const e of entries) {
|
|
5385
6664
|
if (ignoreSet.has(e.name)) continue;
|
|
5386
|
-
const full =
|
|
5387
|
-
const rel =
|
|
6665
|
+
const full = path12.join(dir, e.name);
|
|
6666
|
+
const rel = path12.relative(projectRoot2, full).replace(/\\/g, "/");
|
|
5388
6667
|
if (e.isDirectory()) {
|
|
5389
6668
|
if (isGitIgnored(rel, true)) continue;
|
|
5390
6669
|
await walk(full);
|
|
5391
6670
|
} else if (e.isFile()) {
|
|
5392
6671
|
if (DEFAULT_IGNORE_FILES.has(e.name) || isGitIgnored(rel, false)) continue;
|
|
5393
|
-
const ext =
|
|
6672
|
+
const ext = path12.extname(e.name).toLowerCase();
|
|
5394
6673
|
if (indexableExts.has(ext) || detectLang(full) !== null) {
|
|
5395
6674
|
results.push(full);
|
|
5396
6675
|
}
|
|
@@ -5464,10 +6743,10 @@ async function runIndexerWithStore(store, opts) {
|
|
|
5464
6743
|
let discoveryComplete = true;
|
|
5465
6744
|
let trustedUnchanged;
|
|
5466
6745
|
if (opts.files && opts.files.length > 0) {
|
|
5467
|
-
files = opts.files.map((f) =>
|
|
6746
|
+
files = opts.files.map((f) => path12.resolve(projectRoot2, f)).filter((f) => {
|
|
5468
6747
|
if (!isWithinProject(projectRoot2, f)) return false;
|
|
5469
|
-
const rel =
|
|
5470
|
-
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(
|
|
6748
|
+
const rel = path12.relative(projectRoot2, f).replace(/\\/g, "/");
|
|
6749
|
+
return !rel.split("/").some((seg) => DEFAULT_IGNORE.includes(seg)) && !DEFAULT_IGNORE_FILES.has(path12.basename(f)) && !isGitIgnored(rel, false);
|
|
5471
6750
|
});
|
|
5472
6751
|
} else {
|
|
5473
6752
|
const discovery = await findSourceFiles(projectRoot2, ignore, isGitIgnored, signal);
|
|
@@ -5524,7 +6803,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
5524
6803
|
async (file) => {
|
|
5525
6804
|
let stat2;
|
|
5526
6805
|
try {
|
|
5527
|
-
stat2 = await
|
|
6806
|
+
stat2 = await fs9.stat(file, statOpts);
|
|
5528
6807
|
} catch (e) {
|
|
5529
6808
|
if (isAbortError(e)) throw e;
|
|
5530
6809
|
return {
|
|
@@ -5554,7 +6833,7 @@ async function runIndexerWithStore(store, opts) {
|
|
|
5554
6833
|
}
|
|
5555
6834
|
let content;
|
|
5556
6835
|
try {
|
|
5557
|
-
content = await
|
|
6836
|
+
content = await fs9.readFile(file, { encoding: "utf8", signal });
|
|
5558
6837
|
} catch (e) {
|
|
5559
6838
|
if (isAbortError(e)) throw e;
|
|
5560
6839
|
return {
|
|
@@ -5565,22 +6844,78 @@ async function runIndexerWithStore(store, opts) {
|
|
|
5565
6844
|
error: `read error: ${e instanceof Error ? e.message : String(e)}`
|
|
5566
6845
|
};
|
|
5567
6846
|
}
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
parsed2 = await parseFileContent(file, content, lang);
|
|
5571
|
-
} catch (e) {
|
|
6847
|
+
const contentHash = xxhash64String(content);
|
|
6848
|
+
if (!force && meta && meta.contentHash && contentHash === meta.contentHash) {
|
|
5572
6849
|
return {
|
|
5573
6850
|
file,
|
|
5574
6851
|
stat: stat2,
|
|
5575
6852
|
lang,
|
|
5576
6853
|
parsed: null,
|
|
5577
|
-
|
|
6854
|
+
content,
|
|
6855
|
+
contentHash,
|
|
6856
|
+
skippedMeta: { ...meta, mtimeMs: Math.floor(stat2.mtimeMs) }
|
|
5578
6857
|
};
|
|
5579
6858
|
}
|
|
5580
|
-
return { file, stat: stat2, lang, parsed:
|
|
6859
|
+
return { file, stat: stat2, lang, parsed: null, content, contentHash };
|
|
5581
6860
|
}
|
|
5582
6861
|
)
|
|
5583
6862
|
);
|
|
6863
|
+
const toParse = [];
|
|
6864
|
+
for (let pi = 0; pi < statReadParse.length; pi++) {
|
|
6865
|
+
const s = statReadParse[pi];
|
|
6866
|
+
if (s.status !== "fulfilled") continue;
|
|
6867
|
+
const r = s.value;
|
|
6868
|
+
if (r.error || r.skippedMeta || !r.lang || r.parsed) continue;
|
|
6869
|
+
if (r.content === void 0) continue;
|
|
6870
|
+
toParse.push({
|
|
6871
|
+
index: pi,
|
|
6872
|
+
file: batchFiles[pi],
|
|
6873
|
+
content: r.content,
|
|
6874
|
+
lang: r.lang
|
|
6875
|
+
});
|
|
6876
|
+
}
|
|
6877
|
+
if (toParse.length > 0) {
|
|
6878
|
+
let pool = toParse.length >= WORKER_POOL_THRESHOLD ? getParserPool() : null;
|
|
6879
|
+
if (pool) {
|
|
6880
|
+
try {
|
|
6881
|
+
await pool.ensureReady();
|
|
6882
|
+
const parsedResults = await pool.parseFiles(
|
|
6883
|
+
toParse.map((p) => ({ file: p.file, content: p.content, lang: p.lang }))
|
|
6884
|
+
);
|
|
6885
|
+
const byFile = new Map(parsedResults.map((r) => [r.file, r]));
|
|
6886
|
+
for (const item of toParse) {
|
|
6887
|
+
const parsed2 = byFile.get(item.file);
|
|
6888
|
+
const settled = statReadParse[item.index];
|
|
6889
|
+
if (settled.status !== "fulfilled") continue;
|
|
6890
|
+
if (parsed2) {
|
|
6891
|
+
settled.value.parsed = parsed2;
|
|
6892
|
+
} else {
|
|
6893
|
+
settled.value.error = `parse error: worker returned no result for ${item.file}`;
|
|
6894
|
+
}
|
|
6895
|
+
}
|
|
6896
|
+
} catch {
|
|
6897
|
+
pool = null;
|
|
6898
|
+
}
|
|
6899
|
+
}
|
|
6900
|
+
if (!pool) {
|
|
6901
|
+
await Promise.all(
|
|
6902
|
+
toParse.map(async (item) => {
|
|
6903
|
+
try {
|
|
6904
|
+
const parsed2 = await parseFileContent(item.file, item.content, item.lang);
|
|
6905
|
+
const settled = statReadParse[item.index];
|
|
6906
|
+
if (settled.status === "fulfilled") {
|
|
6907
|
+
settled.value.parsed = parsed2;
|
|
6908
|
+
}
|
|
6909
|
+
} catch (e) {
|
|
6910
|
+
const settled = statReadParse[item.index];
|
|
6911
|
+
if (settled.status === "fulfilled") {
|
|
6912
|
+
settled.value.error = `parse error: ${e instanceof Error ? e.message : String(e)}`;
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
})
|
|
6916
|
+
);
|
|
6917
|
+
}
|
|
6918
|
+
}
|
|
5584
6919
|
const batchEntries = [];
|
|
5585
6920
|
const deleteForFiles = [];
|
|
5586
6921
|
for (let fi = 0; fi < statReadParse.length; fi++) {
|
|
@@ -5603,6 +6938,17 @@ async function runIndexerWithStore(store, opts) {
|
|
|
5603
6938
|
langStats[lang] = (langStats[lang] ?? 0) + result.skippedMeta.symbolCount;
|
|
5604
6939
|
symbolsIndexed += result.skippedMeta.symbolCount;
|
|
5605
6940
|
filesIndexed++;
|
|
6941
|
+
const stored = existingMeta.get(file);
|
|
6942
|
+
if (stored && stored.mtimeMs !== result.skippedMeta.mtimeMs) {
|
|
6943
|
+
store.upsertFile({
|
|
6944
|
+
file,
|
|
6945
|
+
lang,
|
|
6946
|
+
mtimeMs: result.skippedMeta.mtimeMs,
|
|
6947
|
+
symbolCount: result.skippedMeta.symbolCount,
|
|
6948
|
+
lastIndexed: Date.now(),
|
|
6949
|
+
contentHash: result.skippedMeta.contentHash
|
|
6950
|
+
});
|
|
6951
|
+
}
|
|
5606
6952
|
continue;
|
|
5607
6953
|
}
|
|
5608
6954
|
if (!lang || !parsed2) {
|
|
@@ -5612,7 +6958,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
5612
6958
|
lang,
|
|
5613
6959
|
mtimeMs: Math.floor(stat2.mtimeMs),
|
|
5614
6960
|
symbolCount: 0,
|
|
5615
|
-
lastIndexed: Date.now()
|
|
6961
|
+
lastIndexed: Date.now(),
|
|
6962
|
+
contentHash: result.contentHash ?? ""
|
|
5616
6963
|
});
|
|
5617
6964
|
filesIndexed++;
|
|
5618
6965
|
}
|
|
@@ -5624,7 +6971,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
5624
6971
|
lang,
|
|
5625
6972
|
mtimeMs: Math.floor(stat2.mtimeMs),
|
|
5626
6973
|
symbolCount: 0,
|
|
5627
|
-
lastIndexed: Date.now()
|
|
6974
|
+
lastIndexed: Date.now(),
|
|
6975
|
+
contentHash: result.contentHash ?? ""
|
|
5628
6976
|
});
|
|
5629
6977
|
filesIndexed++;
|
|
5630
6978
|
continue;
|
|
@@ -5635,7 +6983,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
5635
6983
|
symbols: parsed2.symbols,
|
|
5636
6984
|
refs: parsed2.refs ?? [],
|
|
5637
6985
|
mtimeMs: Math.floor(stat2.mtimeMs),
|
|
5638
|
-
symbolCount: parsed2.symbols.length
|
|
6986
|
+
symbolCount: parsed2.symbols.length,
|
|
6987
|
+
contentHash: result.contentHash ?? ""
|
|
5639
6988
|
});
|
|
5640
6989
|
deleteForFiles.push(file);
|
|
5641
6990
|
}
|
|
@@ -5672,7 +7021,8 @@ async function runIndexerWithStore(store, opts) {
|
|
|
5672
7021
|
lang: entry.lang,
|
|
5673
7022
|
mtimeMs: entry.mtimeMs,
|
|
5674
7023
|
symbolCount: entry.symbolCount,
|
|
5675
|
-
lastIndexed: Date.now()
|
|
7024
|
+
lastIndexed: Date.now(),
|
|
7025
|
+
contentHash: entry.contentHash
|
|
5676
7026
|
});
|
|
5677
7027
|
} catch (innerErr) {
|
|
5678
7028
|
errors.push(
|
|
@@ -5784,6 +7134,9 @@ function symbolGraphService(args) {
|
|
|
5784
7134
|
function incomingCallsService(args) {
|
|
5785
7135
|
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
5786
7136
|
try {
|
|
7137
|
+
if (args.transitive) {
|
|
7138
|
+
return store.findTransitiveIncomingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
7139
|
+
}
|
|
5787
7140
|
return store.findIncomingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
5788
7141
|
} finally {
|
|
5789
7142
|
indexStorePool.release(store);
|
|
@@ -5792,6 +7145,9 @@ function incomingCallsService(args) {
|
|
|
5792
7145
|
function outgoingCallsService(args) {
|
|
5793
7146
|
const store = indexStorePool.acquire(args.projectRoot, { indexDir: args.indexDir });
|
|
5794
7147
|
try {
|
|
7148
|
+
if (args.transitive) {
|
|
7149
|
+
return store.findTransitiveOutgoingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
7150
|
+
}
|
|
5795
7151
|
return store.findOutgoingCallsByName(args.symbol, args.file, args.limit ?? 100);
|
|
5796
7152
|
} finally {
|
|
5797
7153
|
indexStorePool.release(store);
|
|
@@ -5835,10 +7191,10 @@ var GenerationLruCache = class {
|
|
|
5835
7191
|
|
|
5836
7192
|
// src/codebase-index/project-server-endpoint.ts
|
|
5837
7193
|
import { createHash } from "node:crypto";
|
|
5838
|
-
import * as
|
|
7194
|
+
import * as fs10 from "node:fs";
|
|
5839
7195
|
import * as os3 from "node:os";
|
|
5840
|
-
import * as
|
|
5841
|
-
import { fileURLToPath } from "node:url";
|
|
7196
|
+
import * as path13 from "node:path";
|
|
7197
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
5842
7198
|
import { assertUnixSocketPathWithinLimit } from "@wrongstack/core/utils";
|
|
5843
7199
|
var PROJECT_INDEX_SERVER_PROTOCOL_VERSION = 1;
|
|
5844
7200
|
var PROJECT_INDEX_SERVER_METADATA_FILE = "server.json";
|
|
@@ -5847,21 +7203,21 @@ var buildIdCache;
|
|
|
5847
7203
|
function projectIndexServerBuildId(entrypoint) {
|
|
5848
7204
|
const href = entrypoint instanceof URL ? entrypoint.href : entrypoint;
|
|
5849
7205
|
const cleanHref = href.split(/[?#]/, 1)[0] ?? href;
|
|
5850
|
-
const file = cleanHref.startsWith("file:") ?
|
|
7206
|
+
const file = cleanHref.startsWith("file:") ? fileURLToPath3(cleanHref) : path13.resolve(cleanHref);
|
|
5851
7207
|
try {
|
|
5852
|
-
const stat2 =
|
|
7208
|
+
const stat2 = fs10.statSync(file);
|
|
5853
7209
|
if (buildIdCache?.file === file && buildIdCache.mtimeMs === stat2.mtimeMs && buildIdCache.size === stat2.size) {
|
|
5854
7210
|
return buildIdCache.buildId;
|
|
5855
7211
|
}
|
|
5856
|
-
const buildId = createHash("sha256").update(
|
|
7212
|
+
const buildId = createHash("sha256").update(fs10.readFileSync(file)).digest("hex").slice(0, 24);
|
|
5857
7213
|
buildIdCache = { file, mtimeMs: stat2.mtimeMs, size: stat2.size, buildId };
|
|
5858
7214
|
return buildId;
|
|
5859
7215
|
} catch {
|
|
5860
|
-
return `unreadable:${
|
|
7216
|
+
return `unreadable:${path13.basename(file)}`;
|
|
5861
7217
|
}
|
|
5862
7218
|
}
|
|
5863
7219
|
function normalizeLocalPath(value) {
|
|
5864
|
-
const resolved =
|
|
7220
|
+
const resolved = path13.resolve(value);
|
|
5865
7221
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
5866
7222
|
}
|
|
5867
7223
|
function projectIndexServerKey(projectRoot2, indexDir2) {
|
|
@@ -5873,18 +7229,18 @@ function projectIndexServerEndpoint(projectRoot2, indexDir2) {
|
|
|
5873
7229
|
if (process.platform === "win32") {
|
|
5874
7230
|
return `\\\\.\\pipe\\wrongstack-codebase-index-v${PROJECT_INDEX_SERVER_PROTOCOL_VERSION}-${key}`;
|
|
5875
7231
|
}
|
|
5876
|
-
return
|
|
7232
|
+
return path13.join(os3.tmpdir(), PROJECT_INDEX_SERVER_SOCKET_DIR, `${key}.sock`);
|
|
5877
7233
|
}
|
|
5878
7234
|
function projectIndexServerMetadataPath(projectRoot2, indexDir2) {
|
|
5879
|
-
return
|
|
5880
|
-
|
|
7235
|
+
return path13.join(
|
|
7236
|
+
path13.resolve(resolveIndexDir(projectRoot2, indexDir2)),
|
|
5881
7237
|
PROJECT_INDEX_SERVER_METADATA_FILE
|
|
5882
7238
|
);
|
|
5883
7239
|
}
|
|
5884
7240
|
function ensureProjectIndexSocketDirectory(endpoint2) {
|
|
5885
7241
|
if (process.platform !== "win32") {
|
|
5886
7242
|
assertUnixSocketPathWithinLimit(endpoint2, "codebase-index");
|
|
5887
|
-
|
|
7243
|
+
fs10.mkdirSync(path13.dirname(endpoint2), { recursive: true, mode: 448 });
|
|
5888
7244
|
}
|
|
5889
7245
|
}
|
|
5890
7246
|
|
|
@@ -5908,8 +7264,8 @@ function parseArgs(argv) {
|
|
|
5908
7264
|
}
|
|
5909
7265
|
if (!projectRoot2) throw new Error("codebase-index project server requires --project-root");
|
|
5910
7266
|
return {
|
|
5911
|
-
projectRoot:
|
|
5912
|
-
...indexDir2 ? { indexDir:
|
|
7267
|
+
projectRoot: path14.resolve(projectRoot2),
|
|
7268
|
+
...indexDir2 ? { indexDir: path14.resolve(indexDir2) } : {}
|
|
5913
7269
|
};
|
|
5914
7270
|
}
|
|
5915
7271
|
useDaemonPerfDefaults();
|
|
@@ -5938,7 +7294,7 @@ var serverInfo = {
|
|
|
5938
7294
|
endpoint,
|
|
5939
7295
|
startedAt
|
|
5940
7296
|
};
|
|
5941
|
-
process.title = `wrongstack-codebase-index:${
|
|
7297
|
+
process.title = `wrongstack-codebase-index:${path14.basename(projectRoot)}`;
|
|
5942
7298
|
var clients = /* @__PURE__ */ new Set();
|
|
5943
7299
|
var idleTimer;
|
|
5944
7300
|
var stopping = false;
|
|
@@ -6202,12 +7558,12 @@ async function dispatchOperation(state, message) {
|
|
|
6202
7558
|
);
|
|
6203
7559
|
case "incomingCalls": {
|
|
6204
7560
|
const callArgs = fixedArgs(message.args);
|
|
6205
|
-
const cacheKey = JSON.stringify([callArgs.symbol, callArgs.file ?? "", callArgs.limit ?? 100]);
|
|
7561
|
+
const cacheKey = JSON.stringify([callArgs.symbol, callArgs.file ?? "", callArgs.limit ?? 100, callArgs.transitive ?? false]);
|
|
6206
7562
|
return cachedRead(incomingCallsCache, cacheKey, () => incomingCallsService(callArgs));
|
|
6207
7563
|
}
|
|
6208
7564
|
case "outgoingCalls": {
|
|
6209
7565
|
const callArgs = fixedArgs(message.args);
|
|
6210
|
-
const cacheKey = JSON.stringify([callArgs.symbol, callArgs.file ?? "", callArgs.limit ?? 100]);
|
|
7566
|
+
const cacheKey = JSON.stringify([callArgs.symbol, callArgs.file ?? "", callArgs.limit ?? 100, callArgs.transitive ?? false]);
|
|
6211
7567
|
return cachedRead(outgoingCallsCache, cacheKey, () => outgoingCallsService(callArgs));
|
|
6212
7568
|
}
|
|
6213
7569
|
default:
|
|
@@ -6336,9 +7692,9 @@ function ensureExternalWatcher() {
|
|
|
6336
7692
|
projectRoot,
|
|
6337
7693
|
({ filename }) => {
|
|
6338
7694
|
if (!filename || isIgnoredRelativePath(filename)) return;
|
|
6339
|
-
const absolute =
|
|
6340
|
-
const relative3 =
|
|
6341
|
-
if (relative3 === ".." || relative3.startsWith(`..${
|
|
7695
|
+
const absolute = path14.resolve(projectRoot, filename);
|
|
7696
|
+
const relative3 = path14.relative(projectRoot, absolute);
|
|
7697
|
+
if (relative3 === ".." || relative3.startsWith(`..${path14.sep}`) || path14.isAbsolute(relative3) || !isIndexablePath(absolute)) {
|
|
6342
7698
|
return;
|
|
6343
7699
|
}
|
|
6344
7700
|
enqueueExternalFile(absolute);
|
|
@@ -6407,22 +7763,22 @@ function scheduleIdleStop() {
|
|
|
6407
7763
|
}
|
|
6408
7764
|
function removeMetadataIfOwned() {
|
|
6409
7765
|
try {
|
|
6410
|
-
const current = JSON.parse(
|
|
6411
|
-
if (current.pid === process.pid)
|
|
7766
|
+
const current = JSON.parse(fs11.readFileSync(metadataPath, "utf8"));
|
|
7767
|
+
if (current.pid === process.pid) fs11.rmSync(metadataPath, { force: true });
|
|
6412
7768
|
} catch {
|
|
6413
7769
|
}
|
|
6414
7770
|
}
|
|
6415
7771
|
function writeMetadata() {
|
|
6416
|
-
|
|
7772
|
+
fs11.mkdirSync(path14.dirname(metadataPath), { recursive: true });
|
|
6417
7773
|
const temporary = `${metadataPath}.${process.pid}.tmp`;
|
|
6418
7774
|
const metadata = { ...serverInfo, authToken };
|
|
6419
|
-
|
|
7775
|
+
fs11.writeFileSync(temporary, `${JSON.stringify(metadata, null, 2)}
|
|
6420
7776
|
`, { mode: 384 });
|
|
6421
7777
|
try {
|
|
6422
|
-
|
|
7778
|
+
fs11.renameSync(temporary, metadataPath);
|
|
6423
7779
|
} catch {
|
|
6424
|
-
|
|
6425
|
-
|
|
7780
|
+
fs11.rmSync(metadataPath, { force: true });
|
|
7781
|
+
fs11.renameSync(temporary, metadataPath);
|
|
6426
7782
|
}
|
|
6427
7783
|
}
|
|
6428
7784
|
var server = net.createServer((socket) => {
|
|
@@ -6492,7 +7848,7 @@ async function stop(_reason) {
|
|
|
6492
7848
|
});
|
|
6493
7849
|
if (process.platform !== "win32") {
|
|
6494
7850
|
try {
|
|
6495
|
-
|
|
7851
|
+
fs11.rmSync(endpoint, { force: true });
|
|
6496
7852
|
} catch {
|
|
6497
7853
|
}
|
|
6498
7854
|
}
|
|
@@ -6511,7 +7867,7 @@ server.once("error", (error) => {
|
|
|
6511
7867
|
server.listen(endpoint, () => {
|
|
6512
7868
|
if (process.platform !== "win32") {
|
|
6513
7869
|
try {
|
|
6514
|
-
|
|
7870
|
+
fs11.chmodSync(endpoint, 384);
|
|
6515
7871
|
} catch {
|
|
6516
7872
|
}
|
|
6517
7873
|
}
|