homegraph 1.2.0 → 1.4.1
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/bin/homegraph.js +3 -0
- package/dist/bin/homegraph.js.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +85 -0
- package/dist/context/index.js.map +1 -1
- package/dist/db/index.d.ts +64 -5
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +178 -14
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +19 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/queries.d.ts +117 -2
- package/dist/db/queries.d.ts.map +1 -1
- package/dist/db/queries.js +299 -6
- package/dist/db/queries.js.map +1 -1
- package/dist/db/schema.sql +32 -1
- package/dist/db/wal-valve.d.ts +106 -0
- package/dist/db/wal-valve.d.ts.map +1 -0
- package/dist/db/wal-valve.js +208 -0
- package/dist/db/wal-valve.js.map +1 -0
- package/dist/directory.d.ts +9 -5
- package/dist/directory.d.ts.map +1 -1
- package/dist/directory.js +210 -19
- package/dist/directory.js.map +1 -1
- package/dist/extraction/cfml-extractor.d.ts +107 -0
- package/dist/extraction/cfml-extractor.d.ts.map +1 -0
- package/dist/extraction/cfml-extractor.js +494 -0
- package/dist/extraction/cfml-extractor.js.map +1 -0
- package/dist/extraction/grammars.d.ts +25 -1
- package/dist/extraction/grammars.d.ts.map +1 -1
- package/dist/extraction/grammars.js +177 -24
- package/dist/extraction/grammars.js.map +1 -1
- package/dist/extraction/index.d.ts +43 -4
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +308 -5
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/languages/c-cpp.d.ts +42 -0
- package/dist/extraction/languages/c-cpp.d.ts.map +1 -1
- package/dist/extraction/languages/c-cpp.js +369 -4
- package/dist/extraction/languages/c-cpp.js.map +1 -1
- package/dist/extraction/languages/cfquery.d.ts +12 -0
- package/dist/extraction/languages/cfquery.d.ts.map +1 -0
- package/dist/extraction/languages/cfquery.js +28 -0
- package/dist/extraction/languages/cfquery.js.map +1 -0
- package/dist/extraction/languages/cfscript.d.ts +3 -0
- package/dist/extraction/languages/cfscript.d.ts.map +1 -0
- package/dist/extraction/languages/cfscript.js +73 -0
- package/dist/extraction/languages/cfscript.js.map +1 -0
- package/dist/extraction/languages/cobol.d.ts +33 -0
- package/dist/extraction/languages/cobol.d.ts.map +1 -0
- package/dist/extraction/languages/cobol.js +499 -0
- package/dist/extraction/languages/cobol.js.map +1 -0
- package/dist/extraction/languages/erlang.d.ts +3 -0
- package/dist/extraction/languages/erlang.d.ts.map +1 -0
- package/dist/extraction/languages/erlang.js +350 -0
- package/dist/extraction/languages/erlang.js.map +1 -0
- package/dist/extraction/languages/index.d.ts.map +1 -1
- package/dist/extraction/languages/index.js +16 -0
- package/dist/extraction/languages/index.js.map +1 -1
- package/dist/extraction/languages/nix.d.ts +3 -0
- package/dist/extraction/languages/nix.d.ts.map +1 -0
- package/dist/extraction/languages/nix.js +294 -0
- package/dist/extraction/languages/nix.js.map +1 -0
- package/dist/extraction/languages/solidity.d.ts +3 -0
- package/dist/extraction/languages/solidity.d.ts.map +1 -0
- package/dist/extraction/languages/solidity.js +293 -0
- package/dist/extraction/languages/solidity.js.map +1 -0
- package/dist/extraction/languages/terraform.d.ts +3 -0
- package/dist/extraction/languages/terraform.d.ts.map +1 -0
- package/dist/extraction/languages/terraform.js +641 -0
- package/dist/extraction/languages/terraform.js.map +1 -0
- package/dist/extraction/languages/vbnet.d.ts +11 -0
- package/dist/extraction/languages/vbnet.d.ts.map +1 -0
- package/dist/extraction/languages/vbnet.js +141 -0
- package/dist/extraction/languages/vbnet.js.map +1 -0
- package/dist/extraction/mybatis-extractor.d.ts +30 -10
- package/dist/extraction/mybatis-extractor.d.ts.map +1 -1
- package/dist/extraction/mybatis-extractor.js +140 -38
- package/dist/extraction/mybatis-extractor.js.map +1 -1
- package/dist/extraction/parse-pool.d.ts +27 -1
- package/dist/extraction/parse-pool.d.ts.map +1 -1
- package/dist/extraction/parse-pool.js +75 -7
- package/dist/extraction/parse-pool.js.map +1 -1
- package/dist/extraction/parse-worker.js +9 -2
- package/dist/extraction/parse-worker.js.map +1 -1
- package/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
- package/dist/extraction/tree-sitter-helpers.js +1 -0
- package/dist/extraction/tree-sitter-helpers.js.map +1 -1
- package/dist/extraction/tree-sitter-types.d.ts +3 -1
- package/dist/extraction/tree-sitter-types.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.d.ts +38 -0
- package/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/dist/extraction/tree-sitter.js +754 -10
- package/dist/extraction/tree-sitter.js.map +1 -1
- package/dist/extraction/wasm/tree-sitter-arkts.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-terraform.wasm +0 -0
- package/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/dist/graph/traversal.d.ts.map +1 -1
- package/dist/graph/traversal.js +1 -1
- package/dist/graph/traversal.js.map +1 -1
- package/dist/index.d.ts +60 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +348 -5
- package/dist/index.js.map +1 -1
- package/dist/installer/index.d.ts +42 -0
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +97 -2
- package/dist/installer/index.js.map +1 -1
- package/dist/mcp/daemon.d.ts +25 -3
- package/dist/mcp/daemon.d.ts.map +1 -1
- package/dist/mcp/daemon.js +62 -8
- package/dist/mcp/daemon.js.map +1 -1
- package/dist/mcp/dynamic-boundaries.d.ts.map +1 -1
- package/dist/mcp/dynamic-boundaries.js +2 -1
- package/dist/mcp/dynamic-boundaries.js.map +1 -1
- package/dist/mcp/early-ppid.d.ts +26 -0
- package/dist/mcp/early-ppid.d.ts.map +1 -0
- package/dist/mcp/early-ppid.js +29 -0
- package/dist/mcp/early-ppid.js.map +1 -0
- package/dist/mcp/engine.d.ts +4 -5
- package/dist/mcp/engine.d.ts.map +1 -1
- package/dist/mcp/engine.js +27 -1
- package/dist/mcp/engine.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +41 -6
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/liveness-watchdog.d.ts +18 -1
- package/dist/mcp/liveness-watchdog.d.ts.map +1 -1
- package/dist/mcp/liveness-watchdog.js +73 -13
- package/dist/mcp/liveness-watchdog.js.map +1 -1
- package/dist/mcp/memory-budget.d.ts +30 -0
- package/dist/mcp/memory-budget.d.ts.map +1 -0
- package/dist/mcp/memory-budget.js +123 -0
- package/dist/mcp/memory-budget.js.map +1 -0
- package/dist/mcp/proxy.d.ts.map +1 -1
- package/dist/mcp/proxy.js +88 -4
- package/dist/mcp/proxy.js.map +1 -1
- package/dist/mcp/query-cache.d.ts +35 -4
- package/dist/mcp/query-cache.d.ts.map +1 -1
- package/dist/mcp/query-cache.js +89 -19
- package/dist/mcp/query-cache.js.map +1 -1
- package/dist/mcp/query-pool.d.ts +46 -12
- package/dist/mcp/query-pool.d.ts.map +1 -1
- package/dist/mcp/query-pool.js +100 -23
- package/dist/mcp/query-pool.js.map +1 -1
- package/dist/mcp/server-instructions.d.ts +4 -27
- package/dist/mcp/server-instructions.d.ts.map +1 -1
- package/dist/mcp/server-instructions.js +39 -84
- package/dist/mcp/server-instructions.js.map +1 -1
- package/dist/mcp/session.d.ts +14 -0
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +28 -1
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/startup-handshake.d.ts +44 -0
- package/dist/mcp/startup-handshake.d.ts.map +1 -0
- package/dist/mcp/startup-handshake.js +73 -0
- package/dist/mcp/startup-handshake.js.map +1 -0
- package/dist/mcp/tools.d.ts +104 -4
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +2390 -191
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/transport.d.ts.map +1 -1
- package/dist/mcp/transport.js +18 -0
- package/dist/mcp/transport.js.map +1 -1
- package/dist/project-config.d.ts +38 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +101 -2
- package/dist/project-config.js.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
- package/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
- package/dist/resolution/c-fnptr-synthesizer.js +178 -146
- package/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
- package/dist/resolution/callback-synthesizer.d.ts +9 -1
- package/dist/resolution/callback-synthesizer.d.ts.map +1 -1
- package/dist/resolution/callback-synthesizer.js +1164 -176
- package/dist/resolution/callback-synthesizer.js.map +1 -1
- package/dist/resolution/frameworks/cics.d.ts +20 -0
- package/dist/resolution/frameworks/cics.d.ts.map +1 -0
- package/dist/resolution/frameworks/cics.js +90 -0
- package/dist/resolution/frameworks/cics.js.map +1 -0
- package/dist/resolution/frameworks/index.d.ts.map +1 -1
- package/dist/resolution/frameworks/index.js +3 -0
- package/dist/resolution/frameworks/index.js.map +1 -1
- package/dist/resolution/frameworks/java.d.ts.map +1 -1
- package/dist/resolution/frameworks/java.js +14 -6
- package/dist/resolution/frameworks/java.js.map +1 -1
- package/dist/resolution/frameworks/terraform.d.ts +38 -0
- package/dist/resolution/frameworks/terraform.d.ts.map +1 -0
- package/dist/resolution/frameworks/terraform.js +277 -0
- package/dist/resolution/frameworks/terraform.js.map +1 -0
- package/dist/resolution/goframe-synthesizer.d.ts +2 -1
- package/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
- package/dist/resolution/goframe-synthesizer.js +8 -3
- package/dist/resolution/goframe-synthesizer.js.map +1 -1
- package/dist/resolution/import-resolver.d.ts +7 -0
- package/dist/resolution/import-resolver.d.ts.map +1 -1
- package/dist/resolution/import-resolver.js +129 -4
- package/dist/resolution/import-resolver.js.map +1 -1
- package/dist/resolution/index.d.ts +60 -7
- package/dist/resolution/index.d.ts.map +1 -1
- package/dist/resolution/index.js +430 -80
- package/dist/resolution/index.js.map +1 -1
- package/dist/resolution/name-matcher.d.ts +0 -3
- package/dist/resolution/name-matcher.d.ts.map +1 -1
- package/dist/resolution/name-matcher.js +354 -33
- package/dist/resolution/name-matcher.js.map +1 -1
- package/dist/resolution/strip-comments.d.ts +1 -1
- package/dist/resolution/strip-comments.d.ts.map +1 -1
- package/dist/resolution/strip-comments.js +49 -0
- package/dist/resolution/strip-comments.js.map +1 -1
- package/dist/resolution/types.d.ts +29 -0
- package/dist/resolution/types.d.ts.map +1 -1
- package/dist/resolution/workspace-packages.d.ts +10 -0
- package/dist/resolution/workspace-packages.d.ts.map +1 -1
- package/dist/resolution/workspace-packages.js +142 -4
- package/dist/resolution/workspace-packages.js.map +1 -1
- package/dist/search/identifier-segments.d.ts +60 -0
- package/dist/search/identifier-segments.d.ts.map +1 -0
- package/dist/search/identifier-segments.js +176 -0
- package/dist/search/identifier-segments.js.map +1 -0
- package/dist/search/query-utils.d.ts +201 -0
- package/dist/search/query-utils.d.ts.map +1 -1
- package/dist/search/query-utils.js +919 -2
- package/dist/search/query-utils.js.map +1 -1
- package/dist/sync/git-hooks.d.ts.map +1 -1
- package/dist/sync/git-hooks.js +2 -0
- package/dist/sync/git-hooks.js.map +1 -1
- package/dist/sync/watcher.d.ts +10 -5
- package/dist/sync/watcher.d.ts.map +1 -1
- package/dist/sync/watcher.js +51 -14
- package/dist/sync/watcher.js.map +1 -1
- package/dist/sync/worktree.d.ts.map +1 -1
- package/dist/sync/worktree.js +5 -0
- package/dist/sync/worktree.js.map +1 -1
- package/dist/types.d.ts +19 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -1
- package/dist/upgrade/index.d.ts +32 -0
- package/dist/upgrade/index.d.ts.map +1 -1
- package/dist/upgrade/index.js +162 -14
- package/dist/upgrade/index.js.map +1 -1
- package/dist/upgrade/remove-binary.d.ts +87 -0
- package/dist/upgrade/remove-binary.d.ts.map +1 -0
- package/dist/upgrade/remove-binary.js +289 -0
- package/dist/upgrade/remove-binary.js.map +1 -0
- package/dist/upgrade/update-check.d.ts +92 -0
- package/dist/upgrade/update-check.d.ts.map +1 -0
- package/dist/upgrade/update-check.js +258 -0
- package/dist/upgrade/update-check.js.map +1 -0
- package/package.json +2 -2
- package/scripts/agent-eval/run-all.sh +6 -0
- package/scripts/build-bundle.sh +5 -0
- package/scripts/npm-shim.js +8 -1
- package/scripts/exp_boundary_eval/__pycache__/_utils.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/analyze.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/deveco_arm.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_one.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/run_session.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/__pycache__/setup.cpython-310.pyc +0 -0
- package/scripts/exp_boundary_eval/data/agents.json +0 -109
- package/scripts/exp_boundary_eval/data/experiments.json +0 -140
- package/scripts/qa_eval/README.md +0 -407
- package/scripts/qa_eval/_test_deveco_probe.py +0 -41
- package/scripts/qa_eval/agent_runner.py +0 -526
- package/scripts/qa_eval/data/.gitignore +0 -4
- package/scripts/qa_eval/data/test-set.jsonl +0 -2
- package/scripts/qa_eval/eval_metrics.py +0 -274
- package/scripts/qa_eval/external_agent.py +0 -976
- package/scripts/qa_eval/llm_config.py +0 -92
- package/scripts/qa_eval/memory_monitor.py +0 -132
- package/scripts/qa_eval/my_answer_accuracy.py +0 -187
- package/scripts/qa_eval/requirements.txt +0 -2
- package/scripts/qa_eval/run_pipeline.py +0 -804
- package/scripts/qa_eval/stats_efficiency.py +0 -279
- package/scripts/qa_eval/stats_scores.py +0 -207
package/dist/db/queries.js
CHANGED
|
@@ -46,6 +46,7 @@ const query_utils_1 = require("../search/query-utils");
|
|
|
46
46
|
const query_parser_1 = require("../search/query-parser");
|
|
47
47
|
const generated_detection_1 = require("../extraction/generated-detection");
|
|
48
48
|
const arkts_1 = require("../extraction/languages/arkts");
|
|
49
|
+
const identifier_segments_1 = require("../search/identifier-segments");
|
|
49
50
|
/**
|
|
50
51
|
* Path-only heuristic for files that should not be candidates for
|
|
51
52
|
* "dominant file" detection: test/spec files and tool-generated files.
|
|
@@ -69,6 +70,14 @@ function isLowValueFile(filePath) {
|
|
|
69
70
|
(0, generated_detection_1.isGeneratedFile)(filePath));
|
|
70
71
|
}
|
|
71
72
|
const SQLITE_PARAM_CHUNK_SIZE = 500;
|
|
73
|
+
/**
|
|
74
|
+
* Last segment of a (possibly dotted/qualified) reference name — the part a
|
|
75
|
+
* new symbol's plain node name could match: 'util.greet' → 'greet'.
|
|
76
|
+
*/
|
|
77
|
+
function referenceNameTail(referenceName) {
|
|
78
|
+
const idx = Math.max(referenceName.lastIndexOf('.'), referenceName.lastIndexOf(':'));
|
|
79
|
+
return idx >= 0 ? referenceName.slice(idx + 1) : referenceName;
|
|
80
|
+
}
|
|
72
81
|
/**
|
|
73
82
|
* Convert database row to Node object
|
|
74
83
|
*/
|
|
@@ -136,6 +145,8 @@ class QueryBuilder {
|
|
|
136
145
|
// whole project, not a symbol, so it carries no discriminative signal (#720).
|
|
137
146
|
// Set once by the HomeGraph instance; empty by default (no down-weighting).
|
|
138
147
|
projectNameTokens = new Set();
|
|
148
|
+
segmentedNames = new Set();
|
|
149
|
+
static MAX_SEGMENTED_NAMES = 65536;
|
|
139
150
|
// Node cache for frequently accessed nodes (LRU-style, max 1000 entries)
|
|
140
151
|
nodeCache = new Map();
|
|
141
152
|
maxCacheSize = 1000;
|
|
@@ -254,6 +265,10 @@ class QueryBuilder {
|
|
|
254
265
|
returnType: node.returnType ?? null,
|
|
255
266
|
updatedAt: node.updatedAt ?? Date.now(),
|
|
256
267
|
});
|
|
268
|
+
// Segment vocabulary rides the same write path so it never drifts ahead of
|
|
269
|
+
// the nodes it describes. File/import nodes are excluded (#1144).
|
|
270
|
+
if (this.isSegmentableKind(node.kind))
|
|
271
|
+
this.insertNameSegments(node.name);
|
|
257
272
|
}
|
|
258
273
|
/**
|
|
259
274
|
* Insert multiple nodes in a transaction
|
|
@@ -325,6 +340,9 @@ class QueryBuilder {
|
|
|
325
340
|
returnType: node.returnType ?? null,
|
|
326
341
|
updatedAt: node.updatedAt ?? Date.now(),
|
|
327
342
|
});
|
|
343
|
+
// Framework post-extract renames must reach the segment vocabulary (#1141).
|
|
344
|
+
if (this.isSegmentableKind(node.kind))
|
|
345
|
+
this.insertNameSegments(node.name);
|
|
328
346
|
}
|
|
329
347
|
/**
|
|
330
348
|
* Delete a node by ID
|
|
@@ -352,6 +370,104 @@ class QueryBuilder {
|
|
|
352
370
|
}
|
|
353
371
|
this.stmts.deleteNodesByFile.run(filePath);
|
|
354
372
|
}
|
|
373
|
+
/** Which node kinds contribute their name to the segment vocabulary. */
|
|
374
|
+
isSegmentableKind(kind) {
|
|
375
|
+
return kind !== 'file' && kind !== 'import';
|
|
376
|
+
}
|
|
377
|
+
/** Write `name`'s segments into name_segment_vocab (idempotent). */
|
|
378
|
+
insertNameSegments(name) {
|
|
379
|
+
if (this.segmentedNames.has(name))
|
|
380
|
+
return;
|
|
381
|
+
if (this.segmentedNames.size >= QueryBuilder.MAX_SEGMENTED_NAMES)
|
|
382
|
+
this.segmentedNames.clear();
|
|
383
|
+
this.segmentedNames.add(name);
|
|
384
|
+
if (!this.stmts.insertNameSegment) {
|
|
385
|
+
this.stmts.insertNameSegment = this.db.prepare('INSERT OR IGNORE INTO name_segment_vocab (segment, name) VALUES (?, ?)');
|
|
386
|
+
}
|
|
387
|
+
for (const segment of (0, identifier_segments_1.splitIdentifierSegments)(name)) {
|
|
388
|
+
this.stmts.insertNameSegment.run(segment, name);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// ===========================================================================
|
|
392
|
+
// Name-segment vocabulary (prompt-hook graph-derived gate)
|
|
393
|
+
// ===========================================================================
|
|
394
|
+
/** Wipe the segment vocabulary. A full index calls this at its start; the
|
|
395
|
+
* node write path repopulates it as files (re-)index, so the end state is
|
|
396
|
+
* exactly the current names with no orphan rows. */
|
|
397
|
+
clearNameSegmentVocab() {
|
|
398
|
+
this.db.exec('DELETE FROM name_segment_vocab');
|
|
399
|
+
this.segmentedNames.clear();
|
|
400
|
+
}
|
|
401
|
+
/** True when the vocab has no rows — an index built before the table existed.
|
|
402
|
+
* `sync` uses this to heal such databases (see rebuildNameSegmentVocabFrom). */
|
|
403
|
+
isNameSegmentVocabEmpty() {
|
|
404
|
+
const row = this.db.prepare('SELECT 1 FROM name_segment_vocab LIMIT 1').get();
|
|
405
|
+
return row === undefined;
|
|
406
|
+
}
|
|
407
|
+
/** One page of distinct segmentable node names, for batched vocab rebuilds
|
|
408
|
+
* (file basenames and import specifiers are excluded from the vocab — see
|
|
409
|
+
* insertNode). */
|
|
410
|
+
getDistinctNodeNames(limit, offset) {
|
|
411
|
+
const rows = this.db
|
|
412
|
+
.prepare("SELECT DISTINCT name FROM nodes WHERE kind NOT IN ('file', 'import') ORDER BY name LIMIT ? OFFSET ?")
|
|
413
|
+
.all(limit, offset);
|
|
414
|
+
return rows.map((r) => r.name);
|
|
415
|
+
}
|
|
416
|
+
/** Insert segments for a batch of names in one transaction (vocab heal path). */
|
|
417
|
+
insertNameSegmentsBatch(names) {
|
|
418
|
+
this.db.transaction(() => {
|
|
419
|
+
for (const name of names)
|
|
420
|
+
this.insertNameSegments(name);
|
|
421
|
+
})();
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Names whose segments cover at least `minWords` distinct PROMPT WORDS —
|
|
425
|
+
* the co-occurrence probe behind the prompt hook's medium tier: the words
|
|
426
|
+
* "state" and "machine" both being segments of `OrderStateMachine` is strong
|
|
427
|
+
* evidence the prompt names that symbol in prose. Ordered by coverage.
|
|
428
|
+
*
|
|
429
|
+
* Takes (segment variant → original word) pairs and folds variants back to
|
|
430
|
+
* their word INSIDE the SQL: a name matching both `service` and `services`
|
|
431
|
+
* counts ONE word, not two. Counting raw variants let plural-variant pairs
|
|
432
|
+
* of a single word tie with genuine two-word matches and — because ORDER
|
|
433
|
+
* BY/LIMIT run here, before any JS-side re-check — crowd a real match past
|
|
434
|
+
* the LIMIT on vocab-heavy repos (#1146).
|
|
435
|
+
*/
|
|
436
|
+
getSegmentCoOccurrence(variants, minWords, limit) {
|
|
437
|
+
if (variants.length === 0)
|
|
438
|
+
return [];
|
|
439
|
+
const placeholders = variants.map(() => '?').join(', ');
|
|
440
|
+
const whens = variants.map(() => 'WHEN ? THEN ?').join(' ');
|
|
441
|
+
const rows = this.db
|
|
442
|
+
.prepare(`SELECT name, COUNT(DISTINCT CASE segment ${whens} END) AS matches
|
|
443
|
+
FROM name_segment_vocab
|
|
444
|
+
WHERE segment IN (${placeholders})
|
|
445
|
+
GROUP BY name
|
|
446
|
+
HAVING matches >= ?
|
|
447
|
+
ORDER BY matches DESC, length(name) ASC
|
|
448
|
+
LIMIT ?`)
|
|
449
|
+
.all(...variants.flatMap((v) => [v.segment, v.word]), ...variants.map((v) => v.segment), minWords, limit);
|
|
450
|
+
return rows;
|
|
451
|
+
}
|
|
452
|
+
/** How many distinct names each segment appears in — the rarity signal that
|
|
453
|
+
* separates a discriminative word ("checkout") from a ubiquitous one ("state"). */
|
|
454
|
+
getSegmentNameCounts(segments) {
|
|
455
|
+
if (segments.length === 0)
|
|
456
|
+
return new Map();
|
|
457
|
+
const placeholders = segments.map(() => '?').join(', ');
|
|
458
|
+
const rows = this.db
|
|
459
|
+
.prepare(`SELECT segment, COUNT(*) AS n FROM name_segment_vocab
|
|
460
|
+
WHERE segment IN (${placeholders}) GROUP BY segment`)
|
|
461
|
+
.all(...segments);
|
|
462
|
+
return new Map(rows.map((r) => [r.segment, r.n]));
|
|
463
|
+
}
|
|
464
|
+
/** Names containing the given segment (rare-single-word tier). */
|
|
465
|
+
getNamesForSegment(segment, limit) {
|
|
466
|
+
const rows = this.db
|
|
467
|
+
.prepare('SELECT name FROM name_segment_vocab WHERE segment = ? ORDER BY length(name) ASC LIMIT ?')
|
|
468
|
+
.all(segment, limit);
|
|
469
|
+
return rows.map((r) => r.name);
|
|
470
|
+
}
|
|
355
471
|
/**
|
|
356
472
|
* Get a node by ID
|
|
357
473
|
*/
|
|
@@ -645,6 +761,33 @@ class QueryBuilder {
|
|
|
645
761
|
yield rowToNode(row);
|
|
646
762
|
}
|
|
647
763
|
}
|
|
764
|
+
/**
|
|
765
|
+
* Stream nodes of a language whose `decorators` JSON array contains a
|
|
766
|
+
* quoted decorator name. LIKE is a substring filter (a decorator name can
|
|
767
|
+
* appear as a substring of another), so callers must still exact-check
|
|
768
|
+
* `node.decorators.includes(decorator)`. Exists so the kotlin expect/actual
|
|
769
|
+
* synthesizer never materializes the whole node table the way
|
|
770
|
+
* `getAllNodes().filter(...)` did — that array alone OOM'd Node's default
|
|
771
|
+
* heap on a 2M-node graph (#1212).
|
|
772
|
+
*/
|
|
773
|
+
*iterateNodesByLanguageWithDecorator(language, decorator) {
|
|
774
|
+
// Fresh statement per call — an iterator holds an open cursor (see
|
|
775
|
+
// iterateNodesByKind).
|
|
776
|
+
const stmt = this.db.prepare("SELECT * FROM nodes WHERE language = ? AND decorators LIKE '%' || ? || '%'");
|
|
777
|
+
for (const row of stmt.iterate(language, `"${decorator}"`)) {
|
|
778
|
+
yield rowToNode(row);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Distinct languages present in the files table. One indexed aggregate —
|
|
783
|
+
* lets the dynamic-edge synthesizers skip passes for languages the project
|
|
784
|
+
* doesn't contain at all (a Kotlin pass has no work on a pure-C repo), so
|
|
785
|
+
* their cost is zero rather than a full-graph scan that finds nothing (#1212).
|
|
786
|
+
*/
|
|
787
|
+
getDistinctFileLanguages() {
|
|
788
|
+
const rows = this.db.prepare('SELECT DISTINCT language FROM files').all();
|
|
789
|
+
return new Set(rows.map((r) => r.language));
|
|
790
|
+
}
|
|
648
791
|
/**
|
|
649
792
|
* Get all nodes in the database
|
|
650
793
|
*/
|
|
@@ -1259,12 +1402,23 @@ class QueryBuilder {
|
|
|
1259
1402
|
/**
|
|
1260
1403
|
* Get incoming edges to a node
|
|
1261
1404
|
*/
|
|
1262
|
-
getIncomingEdges(targetId, kinds) {
|
|
1405
|
+
getIncomingEdges(targetId, kinds, limit) {
|
|
1406
|
+
const cap = typeof limit === 'number' && Number.isFinite(limit)
|
|
1407
|
+
? Math.max(1, Math.min(Math.floor(limit), 2000))
|
|
1408
|
+
: undefined;
|
|
1263
1409
|
if (kinds && kinds.length > 0) {
|
|
1264
|
-
|
|
1410
|
+
let sql = `SELECT * FROM edges WHERE target = ? AND kind IN (${kinds.map(() => '?').join(',')})`;
|
|
1411
|
+
if (cap !== undefined)
|
|
1412
|
+
sql += ` LIMIT ${cap}`;
|
|
1265
1413
|
const rows = this.db.prepare(sql).all(targetId, ...kinds);
|
|
1266
1414
|
return rows.map(rowToEdge);
|
|
1267
1415
|
}
|
|
1416
|
+
if (cap !== undefined) {
|
|
1417
|
+
const rows = this.db
|
|
1418
|
+
.prepare('SELECT * FROM edges WHERE target = ? LIMIT ?')
|
|
1419
|
+
.all(targetId, cap);
|
|
1420
|
+
return rows.map(rowToEdge);
|
|
1421
|
+
}
|
|
1268
1422
|
if (!this.stmts.getEdgesByTarget) {
|
|
1269
1423
|
this.stmts.getEdgesByTarget = this.db.prepare('SELECT * FROM edges WHERE target = ?');
|
|
1270
1424
|
}
|
|
@@ -1343,7 +1497,8 @@ class QueryBuilder {
|
|
|
1343
1497
|
* {@link getDependentFilePaths}: all kinds except `contains`.
|
|
1344
1498
|
*/
|
|
1345
1499
|
getCrossFileIncomingEdgesWithTarget(filePath) {
|
|
1346
|
-
const sql = `SELECT e.*, tgt.name AS target_name, tgt.kind AS target_kind
|
|
1500
|
+
const sql = `SELECT e.*, tgt.name AS target_name, tgt.kind AS target_kind,
|
|
1501
|
+
src.file_path AS source_file_path, src.language AS source_language
|
|
1347
1502
|
FROM edges e
|
|
1348
1503
|
JOIN nodes tgt ON tgt.id = e.target
|
|
1349
1504
|
JOIN nodes src ON src.id = e.source
|
|
@@ -1355,6 +1510,8 @@ class QueryBuilder {
|
|
|
1355
1510
|
...rowToEdge(row),
|
|
1356
1511
|
targetName: row.target_name,
|
|
1357
1512
|
targetKind: row.target_kind,
|
|
1513
|
+
sourceFilePath: row.source_file_path,
|
|
1514
|
+
sourceLanguage: row.source_language,
|
|
1358
1515
|
}));
|
|
1359
1516
|
}
|
|
1360
1517
|
/**
|
|
@@ -1541,7 +1698,7 @@ class QueryBuilder {
|
|
|
1541
1698
|
*/
|
|
1542
1699
|
getUnresolvedReferencesCount() {
|
|
1543
1700
|
if (!this.stmts.getUnresolvedCount) {
|
|
1544
|
-
this.stmts.getUnresolvedCount = this.db.prepare(
|
|
1701
|
+
this.stmts.getUnresolvedCount = this.db.prepare("SELECT COUNT(*) as count FROM unresolved_refs WHERE status = 'pending'");
|
|
1545
1702
|
}
|
|
1546
1703
|
const row = this.stmts.getUnresolvedCount.get();
|
|
1547
1704
|
return row.count;
|
|
@@ -1552,7 +1709,7 @@ class QueryBuilder {
|
|
|
1552
1709
|
*/
|
|
1553
1710
|
getUnresolvedReferencesBatch(offset, limit) {
|
|
1554
1711
|
if (!this.stmts.getUnresolvedBatch) {
|
|
1555
|
-
this.stmts.getUnresolvedBatch = this.db.prepare(
|
|
1712
|
+
this.stmts.getUnresolvedBatch = this.db.prepare("SELECT * FROM unresolved_refs WHERE status = 'pending' LIMIT ? OFFSET ?");
|
|
1556
1713
|
}
|
|
1557
1714
|
const rows = this.stmts.getUnresolvedBatch.all(limit, offset);
|
|
1558
1715
|
return rows.map((row) => ({
|
|
@@ -1586,6 +1743,135 @@ class QueryBuilder {
|
|
|
1586
1743
|
const rows = this.stmts.getAllNodeNames.all();
|
|
1587
1744
|
return rows.map((r) => r.name);
|
|
1588
1745
|
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Stream distinct node names one row at a time — for resolver cache warm-up
|
|
1748
|
+
* that needs to yield to the event loop mid-scan.
|
|
1749
|
+
*/
|
|
1750
|
+
*iterateNodeNames() {
|
|
1751
|
+
const stmt = this.db.prepare('SELECT DISTINCT name FROM nodes');
|
|
1752
|
+
for (const row of stmt.iterate()) {
|
|
1753
|
+
yield row.name;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
/**
|
|
1757
|
+
* Park refs as status='failed' so drain loops skip them while sync can retry
|
|
1758
|
+
* when a later file change introduces a matching symbol.
|
|
1759
|
+
*/
|
|
1760
|
+
markReferencesFailed(refs) {
|
|
1761
|
+
if (refs.length === 0)
|
|
1762
|
+
return;
|
|
1763
|
+
const stmt = this.db.prepare("UPDATE unresolved_refs SET status = 'failed', name_tail = ? WHERE from_node_id = ? AND reference_name = ? AND reference_kind = ?");
|
|
1764
|
+
const markMany = this.db.transaction((items) => {
|
|
1765
|
+
for (const ref of items) {
|
|
1766
|
+
stmt.run(referenceNameTail(ref.referenceName), ref.fromNodeId, ref.referenceName, ref.referenceKind);
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
markMany(refs);
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* Delete unresolved-ref rows by row id — the precise cleanup for refs a
|
|
1773
|
+
* resolution pass actually processed. The key-tuple variant above also
|
|
1774
|
+
* deletes SIBLING rows (same caller calling the same callee at other lines)
|
|
1775
|
+
* that a later batch hasn't attempted yet, so when a batch boundary split a
|
|
1776
|
+
* caller's same-named call sites, the later sites' edges were silently never
|
|
1777
|
+
* created (#1269).
|
|
1778
|
+
*/
|
|
1779
|
+
deleteReferencesByRowIds(rowIds) {
|
|
1780
|
+
if (rowIds.length === 0)
|
|
1781
|
+
return;
|
|
1782
|
+
for (let i = 0; i < rowIds.length; i += SQLITE_PARAM_CHUNK_SIZE) {
|
|
1783
|
+
const chunk = rowIds.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
|
|
1784
|
+
const placeholders = chunk.map(() => '?').join(',');
|
|
1785
|
+
this.db.prepare(`DELETE FROM unresolved_refs WHERE id IN (${placeholders})`).run(...chunk);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Park refs as status='failed' by row id — the precise counterpart of
|
|
1790
|
+
* markReferencesFailed, for the same reason as deleteReferencesByRowIds:
|
|
1791
|
+
* the key-tuple variant also flips same-key sibling rows in later batches
|
|
1792
|
+
* to 'failed' before they were ever attempted (#1269). Resolution outcome
|
|
1793
|
+
* can differ per call site (receiver-type inference reads the ref's line),
|
|
1794
|
+
* so a sibling must not inherit this row's failure.
|
|
1795
|
+
*/
|
|
1796
|
+
markReferencesFailedByRowIds(refs) {
|
|
1797
|
+
if (refs.length === 0)
|
|
1798
|
+
return;
|
|
1799
|
+
const stmt = this.db.prepare("UPDATE unresolved_refs SET status = 'failed', name_tail = ? WHERE id = ?");
|
|
1800
|
+
const markMany = this.db.transaction((items) => {
|
|
1801
|
+
for (const ref of items) {
|
|
1802
|
+
stmt.run(referenceNameTail(ref.referenceName), ref.rowId);
|
|
1803
|
+
}
|
|
1804
|
+
});
|
|
1805
|
+
markMany(refs);
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Failed refs whose name tail matches one of the given symbol names — the
|
|
1809
|
+
* candidates a sync should retry after files carrying those names changed
|
|
1810
|
+
* (#1240). Names matching more than `perNameCeiling` failed refs are
|
|
1811
|
+
* skipped entirely: at that population a name is external/builtin noise
|
|
1812
|
+
* (`get`, `map`, …) that one new definition won't resolve — the same
|
|
1813
|
+
* rationale as resolution's AMBIGUOUS_NAME_CEILING (#999) — and retrying an
|
|
1814
|
+
* arbitrary subset would be both wasted work and incoherent coverage.
|
|
1815
|
+
*/
|
|
1816
|
+
getRetryableFailedReferences(names, perNameCeiling = 500) {
|
|
1817
|
+
if (names.length === 0)
|
|
1818
|
+
return [];
|
|
1819
|
+
// Pass 1: per-tail counts, chunked under the SQLite parameter limit.
|
|
1820
|
+
const retryNames = [];
|
|
1821
|
+
for (let i = 0; i < names.length; i += SQLITE_PARAM_CHUNK_SIZE) {
|
|
1822
|
+
const chunk = names.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
|
|
1823
|
+
const placeholders = chunk.map(() => '?').join(',');
|
|
1824
|
+
const counts = this.db
|
|
1825
|
+
.prepare(`SELECT name_tail, COUNT(*) as count FROM unresolved_refs WHERE status = 'failed' AND name_tail IN (${placeholders}) GROUP BY name_tail`)
|
|
1826
|
+
.all(...chunk);
|
|
1827
|
+
for (const row of counts) {
|
|
1828
|
+
if (row.count <= perNameCeiling)
|
|
1829
|
+
retryNames.push(row.name_tail);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
if (retryNames.length === 0)
|
|
1833
|
+
return [];
|
|
1834
|
+
// Pass 2: load the surviving rows.
|
|
1835
|
+
const rows = [];
|
|
1836
|
+
for (let i = 0; i < retryNames.length; i += SQLITE_PARAM_CHUNK_SIZE) {
|
|
1837
|
+
const chunk = retryNames.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
|
|
1838
|
+
const placeholders = chunk.map(() => '?').join(',');
|
|
1839
|
+
const chunkRows = this.db
|
|
1840
|
+
.prepare(`SELECT * FROM unresolved_refs WHERE status = 'failed' AND name_tail IN (${placeholders})`)
|
|
1841
|
+
.all(...chunk);
|
|
1842
|
+
rows.push(...chunkRows);
|
|
1843
|
+
}
|
|
1844
|
+
return rows.map((row) => ({
|
|
1845
|
+
fromNodeId: row.from_node_id,
|
|
1846
|
+
referenceName: row.reference_name,
|
|
1847
|
+
referenceKind: row.reference_kind,
|
|
1848
|
+
line: row.line,
|
|
1849
|
+
column: row.col,
|
|
1850
|
+
candidates: row.candidates ? (0, utils_1.safeJsonParse)(row.candidates, undefined) : undefined,
|
|
1851
|
+
filePath: row.file_path,
|
|
1852
|
+
language: row.language,
|
|
1853
|
+
rowId: row.id,
|
|
1854
|
+
}));
|
|
1855
|
+
}
|
|
1856
|
+
/**
|
|
1857
|
+
* Distinct node names present in the given files — the symbol names a sync
|
|
1858
|
+
* pass uses to look up retryable failed refs after those files changed.
|
|
1859
|
+
*/
|
|
1860
|
+
getNodeNamesByFiles(filePaths) {
|
|
1861
|
+
if (filePaths.length === 0)
|
|
1862
|
+
return [];
|
|
1863
|
+
const names = new Set();
|
|
1864
|
+
for (let i = 0; i < filePaths.length; i += SQLITE_PARAM_CHUNK_SIZE) {
|
|
1865
|
+
const chunk = filePaths.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
|
|
1866
|
+
const placeholders = chunk.map(() => '?').join(',');
|
|
1867
|
+
const rows = this.db
|
|
1868
|
+
.prepare(`SELECT DISTINCT name FROM nodes WHERE file_path IN (${placeholders})`)
|
|
1869
|
+
.all(...chunk);
|
|
1870
|
+
for (const row of rows)
|
|
1871
|
+
names.add(row.name);
|
|
1872
|
+
}
|
|
1873
|
+
return [...names];
|
|
1874
|
+
}
|
|
1589
1875
|
/**
|
|
1590
1876
|
* Get unresolved references scoped to specific file paths.
|
|
1591
1877
|
* Uses the idx_unresolved_file_path index for efficient lookup.
|
|
@@ -1602,7 +1888,7 @@ class QueryBuilder {
|
|
|
1602
1888
|
const chunk = filePaths.slice(i, i + SQLITE_PARAM_CHUNK_SIZE);
|
|
1603
1889
|
const placeholders = chunk.map(() => '?').join(',');
|
|
1604
1890
|
const chunkRows = this.db
|
|
1605
|
-
.prepare(`SELECT * FROM unresolved_refs WHERE file_path IN (${placeholders})`)
|
|
1891
|
+
.prepare(`SELECT * FROM unresolved_refs WHERE status = 'pending' AND file_path IN (${placeholders})`)
|
|
1606
1892
|
.all(...chunk);
|
|
1607
1893
|
rows.push(...chunkRows);
|
|
1608
1894
|
}
|
|
@@ -1752,6 +2038,13 @@ class QueryBuilder {
|
|
|
1752
2038
|
.get(cacheKey);
|
|
1753
2039
|
return row ?? null;
|
|
1754
2040
|
}
|
|
2041
|
+
/** All cache keys — used to warm the in-memory key index after daemon restart. */
|
|
2042
|
+
listMcpQueryCacheKeys() {
|
|
2043
|
+
const rows = this.db
|
|
2044
|
+
.prepare('SELECT cache_key FROM mcp_query_cache')
|
|
2045
|
+
.all();
|
|
2046
|
+
return rows.map((r) => r.cache_key);
|
|
2047
|
+
}
|
|
1755
2048
|
setMcpQueryCache(cacheKey, tool, response, createdAt) {
|
|
1756
2049
|
this.db
|
|
1757
2050
|
.prepare('INSERT INTO mcp_query_cache (cache_key, tool, response, created_at) VALUES (?, ?, ?, ?) ' +
|