sweet-search 2.5.2 → 2.5.4
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/core/cli.js +24 -3
- package/core/graph/graph-expansion.js +215 -36
- package/core/graph/graph-extractor.js +196 -11
- package/core/graph/graph-search.js +395 -92
- package/core/graph/hcgs-generator.js +2 -1
- package/core/graph/index.js +2 -0
- package/core/graph/repo-map.js +28 -6
- package/core/graph/structural-answer-cues.js +168 -0
- package/core/graph/structural-callsite-hints.js +40 -0
- package/core/graph/structural-context-format.js +40 -0
- package/core/graph/structural-context.js +450 -0
- package/core/graph/structural-forward-push.js +156 -0
- package/core/graph/structural-header-context.js +19 -0
- package/core/graph/structural-importance.js +148 -0
- package/core/graph/structural-pagerank.js +197 -0
- package/core/graph/summary-manager.js +13 -9
- package/core/incremental-indexing/application/dirty-scan.mjs +236 -0
- package/core/incremental-indexing/application/file-watcher.mjs +197 -0
- package/core/incremental-indexing/application/maintenance-handlers.mjs +519 -0
- package/core/incremental-indexing/application/maintenance-worker.mjs +380 -0
- package/core/incremental-indexing/application/operator-cli.mjs +554 -0
- package/core/incremental-indexing/application/production-li-delta.mjs +192 -0
- package/core/incremental-indexing/application/production-reconciler-helpers.mjs +107 -0
- package/core/incremental-indexing/application/production-reconciler.mjs +583 -0
- package/core/incremental-indexing/application/reconciler.mjs +477 -0
- package/core/incremental-indexing/application/tombstone-injector.mjs +148 -0
- package/core/incremental-indexing/domain/chunk-identity.mjs +260 -0
- package/core/incremental-indexing/domain/encoder-deps.mjs +193 -0
- package/core/incremental-indexing/domain/encoder-input.mjs +225 -0
- package/core/incremental-indexing/domain/interval-autotune.mjs +255 -0
- package/core/incremental-indexing/domain/reconcile-counters.mjs +149 -0
- package/core/incremental-indexing/domain/watermark-scheduler.mjs +239 -0
- package/core/incremental-indexing/infrastructure/artifact-temp-sweep.mjs +163 -0
- package/core/incremental-indexing/infrastructure/baseline-readiness.mjs +121 -0
- package/core/incremental-indexing/infrastructure/dirty-set.mjs +233 -0
- package/core/incremental-indexing/infrastructure/graph-gc.mjs +314 -0
- package/core/incremental-indexing/infrastructure/hashing.mjs +298 -0
- package/core/incremental-indexing/infrastructure/hcgs-invalidation.mjs +182 -0
- package/core/incremental-indexing/infrastructure/li-segment-merge.mjs +278 -0
- package/core/incremental-indexing/infrastructure/li-segment-state.mjs +173 -0
- package/core/incremental-indexing/infrastructure/lockfile.mjs +119 -0
- package/core/incremental-indexing/infrastructure/maintenance-state-reader.mjs +283 -0
- package/core/incremental-indexing/infrastructure/manifest.mjs +194 -0
- package/core/incremental-indexing/infrastructure/path-filter.mjs +190 -0
- package/core/incremental-indexing/infrastructure/reader-heartbeat.mjs +201 -0
- package/core/incremental-indexing/infrastructure/schema-migrations.mjs +257 -0
- package/core/incremental-indexing/infrastructure/sparse-gram-delta.mjs +335 -0
- package/core/incremental-indexing/infrastructure/sqlite-fts5.mjs +176 -0
- package/core/incremental-indexing/infrastructure/staleness-display.mjs +105 -0
- package/core/incremental-indexing/infrastructure/tombstone-bitmap.mjs +234 -0
- package/core/incremental-indexing/infrastructure/vector-delta-writer.mjs +359 -0
- package/core/incremental-indexing/infrastructure/vector-gc.mjs +133 -0
- package/core/incremental-indexing/infrastructure/worktree-stamp.mjs +155 -0
- package/core/incremental-indexing/infrastructure/wsl2-detect.mjs +115 -0
- package/core/indexing/admission-policy.js +139 -0
- package/core/indexing/artifact-builder.js +29 -12
- package/core/indexing/ast-chunker.js +107 -30
- package/core/indexing/dedup/exemplar-selector.js +19 -1
- package/core/indexing/gitignore-filter.js +223 -0
- package/core/indexing/incremental-tracker.js +99 -30
- package/core/indexing/index-codebase-v21.js +6 -5
- package/core/indexing/index-maintainer.mjs +698 -6
- package/core/indexing/indexer-ann.js +99 -15
- package/core/indexing/indexer-build.js +158 -45
- package/core/indexing/indexer-empty-baseline.js +80 -0
- package/core/indexing/indexer-manifest.js +66 -0
- package/core/indexing/indexer-phases.js +56 -23
- package/core/indexing/indexer-sparse-gram.js +54 -13
- package/core/indexing/indexer-utils.js +26 -208
- package/core/indexing/indexing-file-policy.js +32 -7
- package/core/indexing/maintainer-launcher.mjs +137 -0
- package/core/indexing/merkle-tracker.js +251 -244
- package/core/indexing/model-pool.js +46 -5
- package/core/infrastructure/code-graph-repository.js +758 -6
- package/core/infrastructure/code-graph-visibility.js +157 -0
- package/core/infrastructure/codebase-repository.js +100 -13
- package/core/infrastructure/config/search.js +1 -1
- package/core/infrastructure/db-utils.js +118 -0
- package/core/infrastructure/dedup-hashing.js +10 -13
- package/core/infrastructure/hardware-capability.js +17 -7
- package/core/infrastructure/index.js +8 -2
- package/core/infrastructure/language-patterns/maps.js +4 -1
- package/core/infrastructure/language-patterns/registry-core.js +56 -17
- package/core/infrastructure/language-patterns/registry-object-oriented.js +12 -5
- package/core/infrastructure/language-patterns.js +69 -0
- package/core/infrastructure/model-registry.js +20 -0
- package/core/infrastructure/native-inference.js +7 -12
- package/core/infrastructure/native-resolver.js +52 -37
- package/core/infrastructure/native-sparse-gram.js +261 -20
- package/core/infrastructure/native-tokenizer.js +6 -15
- package/core/infrastructure/simd-distance.js +10 -16
- package/core/infrastructure/sparse-gram-delta-reader.js +76 -0
- package/core/infrastructure/structural-alias-resolver.js +122 -0
- package/core/infrastructure/structural-candidate-ranker.js +34 -0
- package/core/infrastructure/structural-context-repository.js +472 -0
- package/core/infrastructure/structural-context-utils.js +51 -0
- package/core/infrastructure/structural-graph-signals.js +121 -0
- package/core/infrastructure/structural-qualified-resolution.js +15 -0
- package/core/infrastructure/structural-source-definitions.js +100 -0
- package/core/infrastructure/tombstone-bitmap-reader.js +139 -0
- package/core/infrastructure/tree-sitter-provider.js +811 -37
- package/core/prompt-optimization/data/p7-final/sweet-search-system-prompt.md +50 -0
- package/core/query/query-router.js +55 -5
- package/core/ranking/file-kind-ranking.js +2192 -15
- package/core/ranking/late-interaction-index.js +87 -12
- package/core/search/cli-decoration.js +290 -0
- package/core/search/context-expander.js +988 -78
- package/core/search/index.js +1 -0
- package/core/search/output-policy.js +275 -0
- package/core/search/search-anchor.js +499 -0
- package/core/search/search-boost.js +93 -1
- package/core/search/search-cli.js +61 -204
- package/core/search/search-hybrid.js +250 -10
- package/core/search/search-pattern-chunks.js +57 -8
- package/core/search/search-pattern-planner.js +68 -9
- package/core/search/search-pattern-prefilter.js +30 -10
- package/core/search/search-pattern-ripgrep.js +40 -4
- package/core/search/search-pattern-sparse-overlay.js +256 -0
- package/core/search/search-pattern.js +117 -29
- package/core/search/search-postprocess.js +479 -5
- package/core/search/search-read-semantic.js +260 -23
- package/core/search/search-read.js +82 -64
- package/core/search/search-reader-pin.js +71 -0
- package/core/search/search-rrf.js +279 -0
- package/core/search/search-semantic.js +110 -5
- package/core/search/search-server.js +130 -57
- package/core/search/search-trace.js +107 -0
- package/core/search/server-identity.js +93 -0
- package/core/search/session-daemon-prewarm.mjs +33 -10
- package/core/search/sweet-search.js +399 -7
- package/core/skills/sweet-index/SKILL.md +8 -6
- package/core/vector-store/binary-hnsw-index.js +194 -30
- package/core/vector-store/float-vector-store.js +96 -6
- package/core/vector-store/hnsw-index.js +220 -49
- package/eval/agent-read-workflows/bin/_ss-helpers.mjs +471 -0
- package/eval/agent-read-workflows/bin/ss-find +15 -0
- package/eval/agent-read-workflows/bin/ss-grep +12 -0
- package/eval/agent-read-workflows/bin/ss-read +14 -0
- package/eval/agent-read-workflows/bin/ss-search +18 -0
- package/eval/agent-read-workflows/bin/ss-semantic +12 -0
- package/eval/agent-read-workflows/bin/ss-trace +11 -0
- package/mcp/read-tool.js +109 -0
- package/mcp/server.js +55 -15
- package/mcp/tool-handlers.js +14 -124
- package/mcp/trace-tool.js +81 -0
- package/package.json +25 -10
- package/scripts/hooks/intercept-read.mjs +55 -0
- package/scripts/hooks/remind-tools.mjs +40 -0
- package/scripts/init.js +698 -54
- package/scripts/inject-agent-instructions.js +431 -0
- package/scripts/install-prompt-reminders.js +188 -0
- package/scripts/install-tool-enforcement.js +220 -0
- package/scripts/smoke-test.js +12 -9
- package/scripts/uninstall.js +276 -18
- package/scripts/write-claude-rules.js +110 -0
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
|
|
16
16
|
import { existsSync } from 'fs';
|
|
17
17
|
import { DB_PATHS, GRAPH_CONFIG } from '../infrastructure/config/index.js';
|
|
18
|
-
import { applyReadPragmas } from '../infrastructure/db-utils.js';
|
|
18
|
+
import { applyReadPragmas, assertInClauseSize } from '../infrastructure/db-utils.js';
|
|
19
19
|
import { detectIntent, getIntentBoost } from '../query/intent-detector.js';
|
|
20
20
|
import { applyMMR, shouldApplyMMR } from '../ranking/mmr.js';
|
|
21
21
|
import { SYMBOL_KIND_WEIGHTS, DEFINITION_TYPES } from '../infrastructure/constants.js';
|
|
22
|
+
import { readAdjacentManifest, resolveManifestCodeGraphPath, sqlAliasPrefix } from '../infrastructure/code-graph-visibility.js';
|
|
22
23
|
|
|
23
24
|
// Fix 9: Abbreviation expansion dictionary for common software abbreviations
|
|
24
25
|
const ABBREVIATION_EXPANSIONS = {
|
|
@@ -44,17 +45,54 @@ const ABBREVIATION_EXPANSIONS = {
|
|
|
44
45
|
// =============================================================================
|
|
45
46
|
|
|
46
47
|
export class GraphSearch {
|
|
47
|
-
constructor(dbPath = DB_PATHS.codeGraph) {
|
|
48
|
-
this.
|
|
48
|
+
constructor(dbPath = DB_PATHS.codeGraph, options = {}) {
|
|
49
|
+
this._baseDbPath = dbPath;
|
|
50
|
+
this._explicitManifestEpoch = Number.isInteger(options.manifestEpoch);
|
|
51
|
+
this._manifestEpoch = this._explicitManifestEpoch ? options.manifestEpoch : null;
|
|
52
|
+
const manifest = this._explicitManifestEpoch ? readAdjacentManifest(this._baseDbPath) : null;
|
|
53
|
+
this.dbPath = this._explicitManifestEpoch
|
|
54
|
+
? resolveManifestCodeGraphPath(this._baseDbPath, manifest)
|
|
55
|
+
: dbPath;
|
|
49
56
|
this.db = null;
|
|
50
57
|
this.hasFts5 = false;
|
|
51
58
|
this.hasTrigram = false;
|
|
59
|
+
this._hasEntityEpochVisibility = false;
|
|
60
|
+
this._hasRelationshipEpochVisibility = false;
|
|
61
|
+
this._hasHcgsSummaryMetadata = false;
|
|
62
|
+
this._summaryVisibilityCache = new Map();
|
|
63
|
+
if (!this._explicitManifestEpoch) {
|
|
64
|
+
this._syncAdjacentManifest();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_syncAdjacentManifest() {
|
|
69
|
+
if (this._explicitManifestEpoch) return false;
|
|
70
|
+
const manifest = readAdjacentManifest(this._baseDbPath);
|
|
71
|
+
const nextEpoch = Number.isInteger(manifest?.epoch) ? manifest.epoch : null;
|
|
72
|
+
const nextDbPath = resolveManifestCodeGraphPath(this._baseDbPath, manifest);
|
|
73
|
+
const changed = nextEpoch !== this._manifestEpoch || nextDbPath !== this.dbPath;
|
|
74
|
+
this._manifestEpoch = nextEpoch;
|
|
75
|
+
this.dbPath = nextDbPath;
|
|
76
|
+
if (changed) {
|
|
77
|
+
this.close();
|
|
78
|
+
}
|
|
79
|
+
return changed;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
refreshManifestEpoch() {
|
|
83
|
+
this._syncAdjacentManifest();
|
|
84
|
+
return this._manifestEpoch;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getManifestEpoch() {
|
|
88
|
+
return this._manifestEpoch;
|
|
52
89
|
}
|
|
53
90
|
|
|
54
91
|
/**
|
|
55
92
|
* Initialize database connection (synchronous with better-sqlite3)
|
|
56
93
|
*/
|
|
57
94
|
async init() {
|
|
95
|
+
this._syncAdjacentManifest();
|
|
58
96
|
if (this.db) return;
|
|
59
97
|
|
|
60
98
|
if (!existsSync(this.dbPath)) {
|
|
@@ -80,6 +118,16 @@ export class GraphSearch {
|
|
|
80
118
|
this.hasTrigram = false;
|
|
81
119
|
}
|
|
82
120
|
|
|
121
|
+
const entityCols = db.prepare('PRAGMA table_info(entities)').all().map((c) => c.name);
|
|
122
|
+
this._hasEntityEpochVisibility = entityCols.includes('epoch_written')
|
|
123
|
+
&& entityCols.includes('epoch_retired');
|
|
124
|
+
const relCols = db.prepare('PRAGMA table_info(relationships)').all().map((c) => c.name);
|
|
125
|
+
this._hasRelationshipEpochVisibility = relCols.includes('epoch_written')
|
|
126
|
+
&& relCols.includes('epoch_retired');
|
|
127
|
+
this._hasHcgsSummaryMetadata = !!db.prepare(
|
|
128
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='hcgs_summary_metadata'",
|
|
129
|
+
).get();
|
|
130
|
+
|
|
83
131
|
// Detect FTS5 column count to warn about schema mismatches with old databases.
|
|
84
132
|
// The bm25() weights are positional — wrong column count produces wrong ranking.
|
|
85
133
|
if (this.hasFts5) {
|
|
@@ -102,6 +150,7 @@ export class GraphSearch {
|
|
|
102
150
|
// Prepare failures on optional FTS paths should degrade to existing fallbacks.
|
|
103
151
|
if (this.hasFts5) {
|
|
104
152
|
try {
|
|
153
|
+
const entityVis = this._entityVisibilitySql('e');
|
|
105
154
|
this._stmtFts5 = db.prepare(`
|
|
106
155
|
SELECT
|
|
107
156
|
e.id, e.file_path, e.type, e.name, e.signature,
|
|
@@ -110,7 +159,7 @@ export class GraphSearch {
|
|
|
110
159
|
FROM entities_fts
|
|
111
160
|
JOIN entities e ON entities_fts.rowid = e.rowid
|
|
112
161
|
WHERE entities_fts MATCH ?
|
|
113
|
-
AND
|
|
162
|
+
AND ${entityVis}
|
|
114
163
|
ORDER BY score
|
|
115
164
|
LIMIT ?
|
|
116
165
|
`);
|
|
@@ -122,6 +171,7 @@ export class GraphSearch {
|
|
|
122
171
|
|
|
123
172
|
if (this.hasTrigram) {
|
|
124
173
|
try {
|
|
174
|
+
const entityVis = this._entityVisibilitySql('e');
|
|
125
175
|
this._stmtTrigram = db.prepare(`
|
|
126
176
|
SELECT
|
|
127
177
|
e.id, e.file_path, e.type, e.name, e.signature,
|
|
@@ -130,7 +180,7 @@ export class GraphSearch {
|
|
|
130
180
|
FROM entities_trigram
|
|
131
181
|
JOIN entities e ON entities_trigram.rowid = e.rowid
|
|
132
182
|
WHERE entities_trigram MATCH ?
|
|
133
|
-
AND
|
|
183
|
+
AND ${entityVis}
|
|
134
184
|
ORDER BY score
|
|
135
185
|
LIMIT ?
|
|
136
186
|
`);
|
|
@@ -141,15 +191,19 @@ export class GraphSearch {
|
|
|
141
191
|
}
|
|
142
192
|
|
|
143
193
|
this._stmtEntityById = db.prepare(
|
|
144
|
-
|
|
194
|
+
`SELECT * FROM entities WHERE id = ? AND ${this._entityVisibilitySql('')}`
|
|
145
195
|
);
|
|
146
196
|
|
|
147
197
|
this._stmtOutRels = db.prepare(`
|
|
148
198
|
SELECT e.*, r.type as rel_type, r.weight as rel_weight
|
|
149
199
|
FROM relationships r
|
|
150
|
-
JOIN entities e ON
|
|
200
|
+
JOIN entities e ON (
|
|
201
|
+
(r.target_id IS NOT NULL AND e.id = r.target_id)
|
|
202
|
+
OR (r.target_id IS NULL AND e.name = r.target_name)
|
|
203
|
+
)
|
|
151
204
|
WHERE r.source_id = ?
|
|
152
|
-
AND e
|
|
205
|
+
AND ${this._entityVisibilitySql('e')}
|
|
206
|
+
AND ${this._relationshipVisibilitySql('r')}
|
|
153
207
|
LIMIT 20
|
|
154
208
|
`);
|
|
155
209
|
|
|
@@ -158,7 +212,8 @@ export class GraphSearch {
|
|
|
158
212
|
FROM relationships r
|
|
159
213
|
JOIN entities e ON e.id = r.source_id
|
|
160
214
|
WHERE (r.target_id = ? OR r.target_name = (SELECT name FROM entities WHERE id = ?))
|
|
161
|
-
AND e
|
|
215
|
+
AND ${this._entityVisibilitySql('e')}
|
|
216
|
+
AND ${this._relationshipVisibilitySql('r')}
|
|
162
217
|
LIMIT 20
|
|
163
218
|
`);
|
|
164
219
|
|
|
@@ -176,6 +231,107 @@ export class GraphSearch {
|
|
|
176
231
|
}
|
|
177
232
|
}
|
|
178
233
|
|
|
234
|
+
_entityVisibilitySql(alias = 'e', options = {}) {
|
|
235
|
+
const prefix = sqlAliasPrefix(alias);
|
|
236
|
+
let sql;
|
|
237
|
+
if (!this._hasEntityEpochVisibility) {
|
|
238
|
+
sql = `${prefix}stale_since IS NULL`;
|
|
239
|
+
} else if (this._manifestEpoch !== null) {
|
|
240
|
+
// For a pinned reader, a row retired after the pinned epoch remains
|
|
241
|
+
// visible even if the compatibility stale_since flag has been set.
|
|
242
|
+
sql = `(${prefix}epoch_written IS NULL OR ${prefix}epoch_written <= ?)
|
|
243
|
+
AND (${prefix}epoch_retired IS NULL OR ${prefix}epoch_retired > ?)
|
|
244
|
+
AND (${prefix}stale_since IS NULL OR (${prefix}epoch_retired IS NOT NULL AND ${prefix}epoch_retired > ?))`;
|
|
245
|
+
} else {
|
|
246
|
+
sql = `${prefix}stale_since IS NULL AND ${prefix}epoch_retired IS NULL`;
|
|
247
|
+
}
|
|
248
|
+
if (options.allowNullJoined) {
|
|
249
|
+
return `(${sql} OR ${prefix}id IS NULL)`;
|
|
250
|
+
}
|
|
251
|
+
return sql;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
_entityVisibilityParams() {
|
|
255
|
+
if (!this._hasEntityEpochVisibility || this._manifestEpoch === null) return [];
|
|
256
|
+
return [this._manifestEpoch, this._manifestEpoch, this._manifestEpoch];
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
_relationshipVisibilitySql(alias = 'r') {
|
|
260
|
+
if (!this._hasRelationshipEpochVisibility) return '1=1';
|
|
261
|
+
const prefix = sqlAliasPrefix(alias);
|
|
262
|
+
if (this._manifestEpoch !== null) {
|
|
263
|
+
return `(${prefix}epoch_written IS NULL OR ${prefix}epoch_written <= ?)
|
|
264
|
+
AND (${prefix}epoch_retired IS NULL OR ${prefix}epoch_retired > ?)`;
|
|
265
|
+
}
|
|
266
|
+
return `${prefix}epoch_retired IS NULL`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
_relationshipVisibilityParams() {
|
|
270
|
+
if (!this._hasRelationshipEpochVisibility || this._manifestEpoch === null) return [];
|
|
271
|
+
return [this._manifestEpoch, this._manifestEpoch];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
_summaryJoinSql(entityAlias = '') {
|
|
275
|
+
if (!this._hasHcgsSummaryMetadata) return '';
|
|
276
|
+
const prefix = sqlAliasPrefix(entityAlias);
|
|
277
|
+
return `LEFT JOIN hcgs_summary_metadata hm ON hm.entity_id = ${prefix}id`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
_summarySelectSql(entityAlias = '') {
|
|
281
|
+
const prefix = sqlAliasPrefix(entityAlias);
|
|
282
|
+
if (!this._hasHcgsSummaryMetadata) return `${prefix}summary`;
|
|
283
|
+
if (this._manifestEpoch !== null) {
|
|
284
|
+
return `CASE WHEN hm.entity_id IS NOT NULL
|
|
285
|
+
AND (hm.epoch_written IS NULL OR hm.epoch_written <= ?)
|
|
286
|
+
AND (hm.epoch_retired IS NULL OR hm.epoch_retired > ?)
|
|
287
|
+
THEN ${prefix}summary ELSE NULL END AS summary`;
|
|
288
|
+
}
|
|
289
|
+
return `CASE WHEN hm.entity_id IS NOT NULL AND hm.epoch_retired IS NULL
|
|
290
|
+
THEN ${prefix}summary ELSE NULL END AS summary`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
_summaryVisibilityParams() {
|
|
294
|
+
if (!this._hasHcgsSummaryMetadata || this._manifestEpoch === null) return [];
|
|
295
|
+
return [this._manifestEpoch, this._manifestEpoch];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
_summaryVisibleForEntity(entityId) {
|
|
299
|
+
if (!this._hasHcgsSummaryMetadata && this.db) {
|
|
300
|
+
this._hasHcgsSummaryMetadata = !!this.db.prepare(
|
|
301
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name='hcgs_summary_metadata'",
|
|
302
|
+
).get();
|
|
303
|
+
}
|
|
304
|
+
if (!this._hasHcgsSummaryMetadata || entityId == null) return true;
|
|
305
|
+
if (this._manifestEpoch === null) {
|
|
306
|
+
const row = this.db.prepare(`
|
|
307
|
+
SELECT epoch_retired
|
|
308
|
+
FROM hcgs_summary_metadata
|
|
309
|
+
WHERE entity_id = ?
|
|
310
|
+
`).get(String(entityId));
|
|
311
|
+
return row ? row.epoch_retired == null : false;
|
|
312
|
+
}
|
|
313
|
+
const key = `${entityId}:${this._manifestEpoch ?? 'live'}`;
|
|
314
|
+
if (this._summaryVisibilityCache.has(key)) return this._summaryVisibilityCache.get(key);
|
|
315
|
+
const row = this.db.prepare(`
|
|
316
|
+
SELECT epoch_written, epoch_retired
|
|
317
|
+
FROM hcgs_summary_metadata
|
|
318
|
+
WHERE entity_id = ?
|
|
319
|
+
`).get(String(entityId));
|
|
320
|
+
const visible = row
|
|
321
|
+
? (this._manifestEpoch !== null
|
|
322
|
+
? (row.epoch_written == null || row.epoch_written <= this._manifestEpoch)
|
|
323
|
+
&& (row.epoch_retired == null || row.epoch_retired > this._manifestEpoch)
|
|
324
|
+
: row.epoch_retired == null)
|
|
325
|
+
: false;
|
|
326
|
+
this._summaryVisibilityCache.set(key, visible);
|
|
327
|
+
return visible;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
_rowWithVisibleSummary(row) {
|
|
331
|
+
if (!row || row.summary == null || this._summaryVisibleForEntity(row.id)) return row;
|
|
332
|
+
return { ...row, summary: null };
|
|
333
|
+
}
|
|
334
|
+
|
|
179
335
|
/**
|
|
180
336
|
* Map a raw FTS5/trigram/LIKE row to a search result object.
|
|
181
337
|
*/
|
|
@@ -223,14 +379,14 @@ export class GraphSearch {
|
|
|
223
379
|
start_line, end_line, package, parent_class, search_text
|
|
224
380
|
FROM entities
|
|
225
381
|
WHERE ${likeConditions || '1=1'}
|
|
226
|
-
AND
|
|
382
|
+
AND ${this._entityVisibilitySql('')}
|
|
227
383
|
ORDER BY
|
|
228
384
|
CASE WHEN name LIKE ? THEN 0 ELSE 1 END,
|
|
229
385
|
length(name)
|
|
230
386
|
LIMIT ?
|
|
231
387
|
`);
|
|
232
388
|
|
|
233
|
-
const rows = stmt.all(...likeParams, `%${query}%`, limit);
|
|
389
|
+
const rows = stmt.all(...likeParams, ...this._entityVisibilityParams(), `%${query}%`, limit);
|
|
234
390
|
|
|
235
391
|
let rank = 0;
|
|
236
392
|
for (const row of rows) {
|
|
@@ -287,13 +443,18 @@ export class GraphSearch {
|
|
|
287
443
|
await this.init();
|
|
288
444
|
if (!this._stmtEntityByName) {
|
|
289
445
|
this._stmtEntityByName = this.db.prepare(`
|
|
290
|
-
SELECT id, name, type, signature,
|
|
446
|
+
SELECT id, name, type, signature, ${this._summarySelectSql('entities')}, file_path, start_line
|
|
291
447
|
FROM entities
|
|
292
|
-
|
|
448
|
+
${this._summaryJoinSql('entities')}
|
|
449
|
+
WHERE name = ? AND ${this._entityVisibilitySql('entities')}
|
|
293
450
|
LIMIT 1
|
|
294
451
|
`);
|
|
295
452
|
}
|
|
296
|
-
return this._stmtEntityByName.get(
|
|
453
|
+
return this._rowWithVisibleSummary(this._stmtEntityByName.get(
|
|
454
|
+
...this._summaryVisibilityParams(),
|
|
455
|
+
name,
|
|
456
|
+
...this._entityVisibilityParams(),
|
|
457
|
+
)) || null;
|
|
297
458
|
}
|
|
298
459
|
|
|
299
460
|
/**
|
|
@@ -304,14 +465,21 @@ export class GraphSearch {
|
|
|
304
465
|
await this.init();
|
|
305
466
|
if (!this._stmtEntityByLocation) {
|
|
306
467
|
this._stmtEntityByLocation = this.db.prepare(`
|
|
307
|
-
SELECT id, name, type, signature,
|
|
468
|
+
SELECT id, name, type, signature, ${this._summarySelectSql('entities')}, file_path, start_line
|
|
308
469
|
FROM entities
|
|
470
|
+
${this._summaryJoinSql('entities')}
|
|
309
471
|
WHERE file_path LIKE ? AND start_line <= ? AND end_line >= ?
|
|
310
|
-
AND
|
|
472
|
+
AND ${this._entityVisibilitySql('entities')}
|
|
311
473
|
LIMIT 1
|
|
312
474
|
`);
|
|
313
475
|
}
|
|
314
|
-
return this._stmtEntityByLocation.get(
|
|
476
|
+
return this._rowWithVisibleSummary(this._stmtEntityByLocation.get(
|
|
477
|
+
...this._summaryVisibilityParams(),
|
|
478
|
+
`%${filePath}%`,
|
|
479
|
+
line,
|
|
480
|
+
line,
|
|
481
|
+
...this._entityVisibilityParams(),
|
|
482
|
+
)) || null;
|
|
315
483
|
}
|
|
316
484
|
|
|
317
485
|
/**
|
|
@@ -340,14 +508,19 @@ export class GraphSearch {
|
|
|
340
508
|
if (names.size > 0) {
|
|
341
509
|
if (!this._stmtEntityByName) {
|
|
342
510
|
this._stmtEntityByName = this.db.prepare(`
|
|
343
|
-
SELECT id, name, type, signature,
|
|
511
|
+
SELECT id, name, type, signature, ${this._summarySelectSql('entities')}, file_path, start_line
|
|
344
512
|
FROM entities
|
|
345
|
-
|
|
513
|
+
${this._summaryJoinSql('entities')}
|
|
514
|
+
WHERE name = ? AND ${this._entityVisibilitySql('entities')}
|
|
346
515
|
LIMIT 1
|
|
347
516
|
`);
|
|
348
517
|
}
|
|
349
518
|
for (const name of names) {
|
|
350
|
-
const entity = this._stmtEntityByName.get(
|
|
519
|
+
const entity = this._rowWithVisibleSummary(this._stmtEntityByName.get(
|
|
520
|
+
...this._summaryVisibilityParams(),
|
|
521
|
+
name,
|
|
522
|
+
...this._entityVisibilityParams(),
|
|
523
|
+
));
|
|
351
524
|
if (entity) byName.set(name, entity);
|
|
352
525
|
}
|
|
353
526
|
}
|
|
@@ -356,17 +529,24 @@ export class GraphSearch {
|
|
|
356
529
|
if (locations.length > 0) {
|
|
357
530
|
if (!this._stmtEntityByLocation) {
|
|
358
531
|
this._stmtEntityByLocation = this.db.prepare(`
|
|
359
|
-
SELECT id, name, type, signature,
|
|
532
|
+
SELECT id, name, type, signature, ${this._summarySelectSql('entities')}, file_path, start_line
|
|
360
533
|
FROM entities
|
|
534
|
+
${this._summaryJoinSql('entities')}
|
|
361
535
|
WHERE file_path LIKE ? AND start_line <= ? AND end_line >= ?
|
|
362
|
-
AND
|
|
536
|
+
AND ${this._entityVisibilitySql('entities')}
|
|
363
537
|
LIMIT 1
|
|
364
538
|
`);
|
|
365
539
|
}
|
|
366
540
|
for (const { file, line } of locations) {
|
|
367
541
|
const key = `${file}:${line}`;
|
|
368
542
|
if (byLocation.has(key)) continue;
|
|
369
|
-
const entity = this._stmtEntityByLocation.get(
|
|
543
|
+
const entity = this._rowWithVisibleSummary(this._stmtEntityByLocation.get(
|
|
544
|
+
...this._summaryVisibilityParams(),
|
|
545
|
+
`%${file}%`,
|
|
546
|
+
line,
|
|
547
|
+
line,
|
|
548
|
+
...this._entityVisibilityParams(),
|
|
549
|
+
));
|
|
370
550
|
if (entity) byLocation.set(key, entity);
|
|
371
551
|
}
|
|
372
552
|
}
|
|
@@ -382,7 +562,14 @@ export class GraphSearch {
|
|
|
382
562
|
await this.init();
|
|
383
563
|
const result = new Set();
|
|
384
564
|
try {
|
|
385
|
-
const rows = this.
|
|
565
|
+
const rows = this._hasEntityEpochVisibility && this._manifestEpoch !== null
|
|
566
|
+
? this.db.prepare(`
|
|
567
|
+
SELECT id FROM entities
|
|
568
|
+
WHERE stale_since IS NOT NULL
|
|
569
|
+
AND (epoch_written IS NULL OR epoch_written <= ?)
|
|
570
|
+
AND (epoch_retired IS NULL OR epoch_retired <= ?)
|
|
571
|
+
`).all(this._manifestEpoch, this._manifestEpoch)
|
|
572
|
+
: this.db.prepare('SELECT id FROM entities WHERE stale_since IS NOT NULL').all();
|
|
386
573
|
for (const row of rows) result.add(row.id);
|
|
387
574
|
} catch {
|
|
388
575
|
// Column may not exist in older indexes
|
|
@@ -418,6 +605,7 @@ export class GraphSearch {
|
|
|
418
605
|
this._stmtInRels = null;
|
|
419
606
|
this._stmtEntityByName = null;
|
|
420
607
|
this._stmtEntityByLocation = null;
|
|
608
|
+
this._summaryVisibilityCache = new Map();
|
|
421
609
|
if (this.db) {
|
|
422
610
|
this.db.close();
|
|
423
611
|
this.db = null;
|
|
@@ -452,7 +640,11 @@ export class GraphSearch {
|
|
|
452
640
|
if (useNameRestriction) {
|
|
453
641
|
restrictedAttempted = true;
|
|
454
642
|
try {
|
|
455
|
-
rows = this._stmtFts5.all(
|
|
643
|
+
rows = this._stmtFts5.all(
|
|
644
|
+
`name : ${this.sanitizeFtsQuery(query)}`,
|
|
645
|
+
...this._entityVisibilityParams(),
|
|
646
|
+
limit,
|
|
647
|
+
);
|
|
456
648
|
} catch (err) {
|
|
457
649
|
this.log(`[bm25Search] Name-restricted FTS5 query failed: ${err.message}`);
|
|
458
650
|
rows = [];
|
|
@@ -463,7 +655,11 @@ export class GraphSearch {
|
|
|
463
655
|
if (rows.length === 0) {
|
|
464
656
|
restrictedFallback = restrictedAttempted;
|
|
465
657
|
try {
|
|
466
|
-
rows = this._stmtFts5.all(
|
|
658
|
+
rows = this._stmtFts5.all(
|
|
659
|
+
this.sanitizeFtsQuery(query),
|
|
660
|
+
...this._entityVisibilityParams(),
|
|
661
|
+
limit,
|
|
662
|
+
);
|
|
467
663
|
} catch (err) {
|
|
468
664
|
this.log(`[bm25Search] FTS5 query failed: ${err.message}`);
|
|
469
665
|
rows = [];
|
|
@@ -481,7 +677,11 @@ export class GraphSearch {
|
|
|
481
677
|
try {
|
|
482
678
|
// Trigram search - just use the raw query (trigram handles substrings)
|
|
483
679
|
const escaped = query.replace(/"/g, '""');
|
|
484
|
-
const trigramRows = this._stmtTrigram.all(
|
|
680
|
+
const trigramRows = this._stmtTrigram.all(
|
|
681
|
+
`"${escaped}"`,
|
|
682
|
+
...this._entityVisibilityParams(),
|
|
683
|
+
limit,
|
|
684
|
+
);
|
|
485
685
|
|
|
486
686
|
this._mergeRows(results, trigramRows, 'trigram', 0.9);
|
|
487
687
|
} catch (err) {
|
|
@@ -494,7 +694,11 @@ export class GraphSearch {
|
|
|
494
694
|
try {
|
|
495
695
|
const expandedForm = this.expandIdentifierQuery(query);
|
|
496
696
|
if (expandedForm && expandedForm !== this.sanitizeFtsQuery(query)) {
|
|
497
|
-
const expandedRows = this._stmtFts5.all(
|
|
697
|
+
const expandedRows = this._stmtFts5.all(
|
|
698
|
+
expandedForm,
|
|
699
|
+
...this._entityVisibilityParams(),
|
|
700
|
+
limit,
|
|
701
|
+
);
|
|
498
702
|
this._mergeRows(results, expandedRows, 'fts5_expanded', 0.85);
|
|
499
703
|
}
|
|
500
704
|
} catch (err) {
|
|
@@ -507,7 +711,11 @@ export class GraphSearch {
|
|
|
507
711
|
try {
|
|
508
712
|
const abbrQuery = this.expandAbbreviations(query);
|
|
509
713
|
if (abbrQuery) {
|
|
510
|
-
const abbrRows = this._stmtFts5.all(
|
|
714
|
+
const abbrRows = this._stmtFts5.all(
|
|
715
|
+
abbrQuery,
|
|
716
|
+
...this._entityVisibilityParams(),
|
|
717
|
+
limit,
|
|
718
|
+
);
|
|
511
719
|
this._mergeRows(results, abbrRows, 'fts5_abbr', 0.8);
|
|
512
720
|
}
|
|
513
721
|
} catch (err) {
|
|
@@ -559,7 +767,11 @@ export class GraphSearch {
|
|
|
559
767
|
|
|
560
768
|
if (useNameRestriction) {
|
|
561
769
|
try {
|
|
562
|
-
rows = this._stmtFts5.all(
|
|
770
|
+
rows = this._stmtFts5.all(
|
|
771
|
+
`name : ${this.sanitizeFtsQuery(query)}`,
|
|
772
|
+
...this._entityVisibilityParams(),
|
|
773
|
+
limit,
|
|
774
|
+
);
|
|
563
775
|
} catch (err) {
|
|
564
776
|
this.log(`[bm25SearchRaw] Name-restricted FTS5 query failed: ${err.message}`);
|
|
565
777
|
rows = [];
|
|
@@ -568,7 +780,11 @@ export class GraphSearch {
|
|
|
568
780
|
|
|
569
781
|
if (rows.length === 0) {
|
|
570
782
|
try {
|
|
571
|
-
rows = this._stmtFts5.all(
|
|
783
|
+
rows = this._stmtFts5.all(
|
|
784
|
+
this.sanitizeFtsQuery(query),
|
|
785
|
+
...this._entityVisibilityParams(),
|
|
786
|
+
limit,
|
|
787
|
+
);
|
|
572
788
|
} catch (err) {
|
|
573
789
|
this.log(`[bm25SearchRaw] FTS5 query failed: ${err.message}`);
|
|
574
790
|
rows = [];
|
|
@@ -584,7 +800,11 @@ export class GraphSearch {
|
|
|
584
800
|
if (this.hasTrigram && results.length < 3 && query.length >= 3) {
|
|
585
801
|
try {
|
|
586
802
|
const escapedRaw = query.replace(/"/g, '""');
|
|
587
|
-
const trigramRows = this._stmtTrigram.all(
|
|
803
|
+
const trigramRows = this._stmtTrigram.all(
|
|
804
|
+
`"${escapedRaw}"`,
|
|
805
|
+
...this._entityVisibilityParams(),
|
|
806
|
+
limit,
|
|
807
|
+
);
|
|
588
808
|
this._mergeRows(results, trigramRows, 'trigram_raw', 0.9);
|
|
589
809
|
} catch (err) {
|
|
590
810
|
this.log(`[bm25SearchRaw] Trigram query failed: ${err.message}`);
|
|
@@ -972,7 +1192,7 @@ export class GraphSearch {
|
|
|
972
1192
|
async searchByName(name, type = null) {
|
|
973
1193
|
await this.init();
|
|
974
1194
|
|
|
975
|
-
let query =
|
|
1195
|
+
let query = 'SELECT * FROM entities WHERE name = ?';
|
|
976
1196
|
const params = [name];
|
|
977
1197
|
|
|
978
1198
|
if (type) {
|
|
@@ -980,10 +1200,12 @@ export class GraphSearch {
|
|
|
980
1200
|
params.push(type);
|
|
981
1201
|
}
|
|
982
1202
|
|
|
1203
|
+
query += ` AND ${this._entityVisibilitySql('')}`;
|
|
983
1204
|
query += ' LIMIT 50';
|
|
984
1205
|
|
|
985
1206
|
const stmt = this.db.prepare(query);
|
|
986
|
-
return stmt.all(...params)
|
|
1207
|
+
return stmt.all(...params, ...this._entityVisibilityParams())
|
|
1208
|
+
.map(row => this._rowWithVisibleSummary(row));
|
|
987
1209
|
}
|
|
988
1210
|
|
|
989
1211
|
/**
|
|
@@ -995,12 +1217,13 @@ export class GraphSearch {
|
|
|
995
1217
|
const stmt = this.db.prepare(`
|
|
996
1218
|
SELECT * FROM entities
|
|
997
1219
|
WHERE file_path LIKE ?
|
|
998
|
-
AND
|
|
1220
|
+
AND ${this._entityVisibilitySql('')}
|
|
999
1221
|
ORDER BY start_line
|
|
1000
1222
|
LIMIT 100
|
|
1001
1223
|
`);
|
|
1002
1224
|
|
|
1003
|
-
return stmt.all(`%${pathPattern}
|
|
1225
|
+
return stmt.all(`%${pathPattern}%`, ...this._entityVisibilityParams())
|
|
1226
|
+
.map(row => this._rowWithVisibleSummary(row));
|
|
1004
1227
|
}
|
|
1005
1228
|
|
|
1006
1229
|
/**
|
|
@@ -1024,30 +1247,44 @@ export class GraphSearch {
|
|
|
1024
1247
|
if (frontierIds.length === 0) break;
|
|
1025
1248
|
|
|
1026
1249
|
// Single query for all entities in this hop
|
|
1250
|
+
assertInClauseSize(frontierIds.length, 'graph-search.frontier-fetch');
|
|
1027
1251
|
const placeholders = frontierIds.map(() => '?').join(',');
|
|
1028
1252
|
const entities = this.db.prepare(`
|
|
1029
1253
|
SELECT * FROM entities
|
|
1030
1254
|
WHERE id IN (${placeholders})
|
|
1031
|
-
AND
|
|
1032
|
-
`).all(...frontierIds);
|
|
1255
|
+
AND ${this._entityVisibilitySql('')}
|
|
1256
|
+
`).all(...frontierIds, ...this._entityVisibilityParams());
|
|
1033
1257
|
|
|
1034
1258
|
// Create lookup map for fast access
|
|
1035
1259
|
const entityMap = new Map(entities.map(e => [e.id, e]));
|
|
1036
1260
|
|
|
1037
|
-
// P2 FIX: Batch fetch all relationships (outgoing + incoming) in single query
|
|
1261
|
+
// P2 FIX: Batch fetch all relationships (outgoing + incoming) in single query.
|
|
1262
|
+
// The UNION ALL binds `frontierIds` twice in one prepared statement, so the
|
|
1263
|
+
// SQLite-parameter ceiling is reached at half the array length — guard the
|
|
1264
|
+
// actual bind count, not the array length.
|
|
1265
|
+
assertInClauseSize(2 * frontierIds.length, 'graph-search.frontier-relationships (double-bind UNION)');
|
|
1038
1266
|
const relationships = this.db.prepare(`
|
|
1039
1267
|
SELECT r.*, 'out' as direction, r.source_id as origin_id
|
|
1040
1268
|
FROM relationships r
|
|
1041
1269
|
JOIN entities e ON r.target_id = e.id
|
|
1042
1270
|
WHERE r.source_id IN (${placeholders})
|
|
1043
|
-
AND e
|
|
1271
|
+
AND ${this._entityVisibilitySql('e')}
|
|
1272
|
+
AND ${this._relationshipVisibilitySql('r')}
|
|
1044
1273
|
UNION ALL
|
|
1045
1274
|
SELECT r.*, 'in' as direction, r.target_id as origin_id
|
|
1046
1275
|
FROM relationships r
|
|
1047
1276
|
JOIN entities e ON r.source_id = e.id
|
|
1048
1277
|
WHERE r.target_id IN (${placeholders})
|
|
1049
|
-
AND e
|
|
1050
|
-
|
|
1278
|
+
AND ${this._entityVisibilitySql('e')}
|
|
1279
|
+
AND ${this._relationshipVisibilitySql('r')}
|
|
1280
|
+
`).all(
|
|
1281
|
+
...frontierIds,
|
|
1282
|
+
...this._entityVisibilityParams(),
|
|
1283
|
+
...this._relationshipVisibilityParams(),
|
|
1284
|
+
...frontierIds,
|
|
1285
|
+
...this._entityVisibilityParams(),
|
|
1286
|
+
...this._relationshipVisibilityParams(),
|
|
1287
|
+
);
|
|
1051
1288
|
|
|
1052
1289
|
// Group relationships by their origin entity
|
|
1053
1290
|
const relMap = new Map();
|
|
@@ -1059,7 +1296,7 @@ export class GraphSearch {
|
|
|
1059
1296
|
|
|
1060
1297
|
// Process all entities without additional queries
|
|
1061
1298
|
for (const entityId of frontierIds) {
|
|
1062
|
-
const entity = entityMap.get(entityId);
|
|
1299
|
+
const entity = this._rowWithVisibleSummary(entityMap.get(entityId));
|
|
1063
1300
|
if (entity) {
|
|
1064
1301
|
expanded.set(entityId, {
|
|
1065
1302
|
...entity,
|
|
@@ -1090,7 +1327,9 @@ export class GraphSearch {
|
|
|
1090
1327
|
*/
|
|
1091
1328
|
async getEntity(entityId) {
|
|
1092
1329
|
await this.init();
|
|
1093
|
-
return this.
|
|
1330
|
+
return this._rowWithVisibleSummary(
|
|
1331
|
+
this._stmtEntityById.get(entityId, ...this._entityVisibilityParams()),
|
|
1332
|
+
) || null;
|
|
1094
1333
|
}
|
|
1095
1334
|
|
|
1096
1335
|
/**
|
|
@@ -1102,8 +1341,13 @@ export class GraphSearch {
|
|
|
1102
1341
|
const results = [];
|
|
1103
1342
|
|
|
1104
1343
|
// Find outgoing relationships (cached prepared statement)
|
|
1105
|
-
const outRows = this._stmtOutRels.all(
|
|
1106
|
-
|
|
1344
|
+
const outRows = this._stmtOutRels.all(
|
|
1345
|
+
entityId,
|
|
1346
|
+
...this._entityVisibilityParams(),
|
|
1347
|
+
...this._relationshipVisibilityParams(),
|
|
1348
|
+
);
|
|
1349
|
+
for (const rawRow of outRows) {
|
|
1350
|
+
const row = this._rowWithVisibleSummary(rawRow);
|
|
1107
1351
|
results.push({
|
|
1108
1352
|
...row,
|
|
1109
1353
|
direction: 'outgoing',
|
|
@@ -1111,8 +1355,14 @@ export class GraphSearch {
|
|
|
1111
1355
|
}
|
|
1112
1356
|
|
|
1113
1357
|
// Find incoming relationships (cached prepared statement)
|
|
1114
|
-
const inRows = this._stmtInRels.all(
|
|
1115
|
-
|
|
1358
|
+
const inRows = this._stmtInRels.all(
|
|
1359
|
+
entityId,
|
|
1360
|
+
entityId,
|
|
1361
|
+
...this._entityVisibilityParams(),
|
|
1362
|
+
...this._relationshipVisibilityParams(),
|
|
1363
|
+
);
|
|
1364
|
+
for (const rawRow of inRows) {
|
|
1365
|
+
const row = this._rowWithVisibleSummary(rawRow);
|
|
1116
1366
|
results.push({
|
|
1117
1367
|
...row,
|
|
1118
1368
|
direction: 'incoming',
|
|
@@ -1690,7 +1940,7 @@ export class GraphSearch {
|
|
|
1690
1940
|
)
|
|
1691
1941
|
-- Only match top-level definition types, not methods
|
|
1692
1942
|
AND e.type IN ('class', 'interface', 'struct', 'enum', 'function', 'type', 'component')
|
|
1693
|
-
AND e
|
|
1943
|
+
AND ${this._entityVisibilitySql('e')}
|
|
1694
1944
|
ORDER BY
|
|
1695
1945
|
match_priority,
|
|
1696
1946
|
CASE e.type
|
|
@@ -1703,7 +1953,13 @@ export class GraphSearch {
|
|
|
1703
1953
|
length(e.name)
|
|
1704
1954
|
LIMIT 5
|
|
1705
1955
|
`);
|
|
1706
|
-
const rows = stmt.all(
|
|
1956
|
+
const rows = stmt.all(
|
|
1957
|
+
queryLower,
|
|
1958
|
+
queryLower,
|
|
1959
|
+
queryLower,
|
|
1960
|
+
queryLower,
|
|
1961
|
+
...this._entityVisibilityParams(),
|
|
1962
|
+
);
|
|
1707
1963
|
resolve(rows);
|
|
1708
1964
|
} catch (err) {
|
|
1709
1965
|
this.log(`Definition search error: ${err.message}`);
|
|
@@ -1786,6 +2042,34 @@ export class GraphSearch {
|
|
|
1786
2042
|
return merged.slice(0, k);
|
|
1787
2043
|
}
|
|
1788
2044
|
|
|
2045
|
+
/**
|
|
2046
|
+
* Resolve `entityName` to a single target entity row.
|
|
2047
|
+
*
|
|
2048
|
+
* Substring fallback (`name LIKE '%X%'`) is preserved so callers like
|
|
2049
|
+
* `findCallers("HttpClient")` still resolve when only `MyHttpClient`
|
|
2050
|
+
* exists in the index. But exact matches now sort first; otherwise short
|
|
2051
|
+
* names like `Vec` silently resolved to substring siblings like
|
|
2052
|
+
* `AlignedVector` because the prior `LIMIT 1` had no preference order.
|
|
2053
|
+
*/
|
|
2054
|
+
_resolveStructuralTarget(entityName) {
|
|
2055
|
+
return this.db.prepare(`
|
|
2056
|
+
SELECT id, name, type, file_path FROM entities
|
|
2057
|
+
WHERE (name = ? OR name LIKE ?)
|
|
2058
|
+
AND ${this._entityVisibilitySql('')}
|
|
2059
|
+
ORDER BY
|
|
2060
|
+
CASE WHEN name = ? THEN 0 ELSE 1 END,
|
|
2061
|
+
CASE WHEN file_path LIKE '%/test/%' OR file_path LIKE 'test/%' OR file_path LIKE 'tests/%' THEN 1 ELSE 0 END,
|
|
2062
|
+
CASE type
|
|
2063
|
+
WHEN 'class' THEN 0 WHEN 'struct' THEN 0 WHEN 'trait' THEN 0
|
|
2064
|
+
WHEN 'interface' THEN 1 WHEN 'enum' THEN 1 WHEN 'type' THEN 1 WHEN 'typeAlias' THEN 1
|
|
2065
|
+
WHEN 'function' THEN 2 WHEN 'method' THEN 2
|
|
2066
|
+
ELSE 3
|
|
2067
|
+
END,
|
|
2068
|
+
length(name) ASC
|
|
2069
|
+
LIMIT 1
|
|
2070
|
+
`).get(entityName, `%${entityName}%`, ...this._entityVisibilityParams(), entityName);
|
|
2071
|
+
}
|
|
2072
|
+
|
|
1789
2073
|
/**
|
|
1790
2074
|
* Find all callers of a given entity
|
|
1791
2075
|
*/
|
|
@@ -1793,13 +2077,7 @@ export class GraphSearch {
|
|
|
1793
2077
|
await this.init();
|
|
1794
2078
|
const { maxDepth = 2, limit = 50 } = options;
|
|
1795
2079
|
|
|
1796
|
-
|
|
1797
|
-
const target = this.db.prepare(`
|
|
1798
|
-
SELECT id, name, type, file_path FROM entities
|
|
1799
|
-
WHERE (name = ? OR name LIKE ?)
|
|
1800
|
-
AND stale_since IS NULL
|
|
1801
|
-
LIMIT 1
|
|
1802
|
-
`).get(entityName, `%${entityName}%`);
|
|
2080
|
+
const target = this._resolveStructuralTarget(entityName);
|
|
1803
2081
|
|
|
1804
2082
|
if (!target) return { results: [], stats: { found: false, query: entityName } };
|
|
1805
2083
|
|
|
@@ -1821,14 +2099,22 @@ export class GraphSearch {
|
|
|
1821
2099
|
OR r.target_name LIKE ?
|
|
1822
2100
|
OR r.target_name LIKE ?
|
|
1823
2101
|
)
|
|
1824
|
-
AND e
|
|
2102
|
+
AND ${this._entityVisibilitySql('e')}
|
|
2103
|
+
AND ${this._relationshipVisibilitySql('r')}
|
|
1825
2104
|
ORDER BY e.file_path, r.context_line
|
|
1826
2105
|
LIMIT ?
|
|
1827
|
-
`).all(
|
|
2106
|
+
`).all(
|
|
2107
|
+
target.id,
|
|
2108
|
+
targetNamePattern,
|
|
2109
|
+
`${target.name}.%`,
|
|
2110
|
+
...this._entityVisibilityParams(),
|
|
2111
|
+
...this._relationshipVisibilityParams(),
|
|
2112
|
+
limit,
|
|
2113
|
+
);
|
|
1828
2114
|
|
|
1829
2115
|
return {
|
|
1830
2116
|
results: callers.map(c => ({
|
|
1831
|
-
...c,
|
|
2117
|
+
...this._rowWithVisibleSummary(c),
|
|
1832
2118
|
relationship: 'calls',
|
|
1833
2119
|
depth: 1,
|
|
1834
2120
|
})),
|
|
@@ -1843,12 +2129,7 @@ export class GraphSearch {
|
|
|
1843
2129
|
await this.init();
|
|
1844
2130
|
const { limit = 50 } = options;
|
|
1845
2131
|
|
|
1846
|
-
const source = this.
|
|
1847
|
-
SELECT id, name, type FROM entities
|
|
1848
|
-
WHERE (name = ? OR name LIKE ?)
|
|
1849
|
-
AND stale_since IS NULL
|
|
1850
|
-
LIMIT 1
|
|
1851
|
-
`).get(entityName, `%${entityName}%`);
|
|
2132
|
+
const source = this._resolveStructuralTarget(entityName);
|
|
1852
2133
|
|
|
1853
2134
|
if (!source) return { results: [], stats: { found: false } };
|
|
1854
2135
|
|
|
@@ -1859,22 +2140,31 @@ export class GraphSearch {
|
|
|
1859
2140
|
FROM relationships r
|
|
1860
2141
|
LEFT JOIN entities e ON e.id = r.target_id OR e.name = r.target_name
|
|
1861
2142
|
WHERE r.source_id = ? AND r.type = 'calls'
|
|
1862
|
-
AND (e
|
|
2143
|
+
AND ${this._entityVisibilitySql('e', { allowNullJoined: true })}
|
|
2144
|
+
AND ${this._relationshipVisibilitySql('r')}
|
|
1863
2145
|
ORDER BY r.context_line
|
|
1864
2146
|
LIMIT ?
|
|
1865
|
-
`).all(
|
|
2147
|
+
`).all(
|
|
2148
|
+
source.id,
|
|
2149
|
+
...this._entityVisibilityParams(),
|
|
2150
|
+
...this._relationshipVisibilityParams(),
|
|
2151
|
+
limit,
|
|
2152
|
+
);
|
|
1866
2153
|
|
|
1867
2154
|
return {
|
|
1868
|
-
results: callees.map(c =>
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
2155
|
+
results: callees.map(c => {
|
|
2156
|
+
const row = this._rowWithVisibleSummary(c);
|
|
2157
|
+
return {
|
|
2158
|
+
id: row.id,
|
|
2159
|
+
name: row.name || row.target_name,
|
|
2160
|
+
type: row.type || 'external',
|
|
2161
|
+
file_path: row.file_path,
|
|
2162
|
+
start_line: row.start_line,
|
|
2163
|
+
signature: row.signature,
|
|
2164
|
+
call_line: row.call_line,
|
|
2165
|
+
relationship: 'calls',
|
|
2166
|
+
};
|
|
2167
|
+
}),
|
|
1878
2168
|
stats: { sourceEntity: source.name, totalCallees: callees.length },
|
|
1879
2169
|
};
|
|
1880
2170
|
}
|
|
@@ -1894,13 +2184,23 @@ export class GraphSearch {
|
|
|
1894
2184
|
JOIN entities e ON e.id = r.source_id
|
|
1895
2185
|
WHERE (r.target_name = ? OR r.target_name LIKE ?)
|
|
1896
2186
|
AND r.type IN ('implements', 'extends')
|
|
1897
|
-
AND e
|
|
2187
|
+
AND ${this._entityVisibilitySql('e')}
|
|
2188
|
+
AND ${this._relationshipVisibilitySql('r')}
|
|
1898
2189
|
ORDER BY e.name
|
|
1899
2190
|
LIMIT ?
|
|
1900
|
-
`).all(
|
|
2191
|
+
`).all(
|
|
2192
|
+
interfaceName,
|
|
2193
|
+
`%${interfaceName}%`,
|
|
2194
|
+
...this._entityVisibilityParams(),
|
|
2195
|
+
...this._relationshipVisibilityParams(),
|
|
2196
|
+
limit,
|
|
2197
|
+
);
|
|
1901
2198
|
|
|
1902
2199
|
return {
|
|
1903
|
-
results: implementations.map(i => ({
|
|
2200
|
+
results: implementations.map(i => ({
|
|
2201
|
+
...this._rowWithVisibleSummary(i),
|
|
2202
|
+
relationship: i.rel_type,
|
|
2203
|
+
})),
|
|
1904
2204
|
stats: { targetInterface: interfaceName, totalImplementations: implementations.length },
|
|
1905
2205
|
};
|
|
1906
2206
|
}
|
|
@@ -1912,12 +2212,7 @@ export class GraphSearch {
|
|
|
1912
2212
|
await this.init();
|
|
1913
2213
|
const { maxDepth = 3, limit = 100 } = options;
|
|
1914
2214
|
|
|
1915
|
-
const target = this.
|
|
1916
|
-
SELECT id, name, type FROM entities
|
|
1917
|
-
WHERE (name = ? OR name LIKE ?)
|
|
1918
|
-
AND stale_since IS NULL
|
|
1919
|
-
LIMIT 1
|
|
1920
|
-
`).get(entityName, `%${entityName}%`);
|
|
2215
|
+
const target = this._resolveStructuralTarget(entityName);
|
|
1921
2216
|
|
|
1922
2217
|
if (!target) return { results: [], stats: { found: false } };
|
|
1923
2218
|
|
|
@@ -1930,6 +2225,7 @@ export class GraphSearch {
|
|
|
1930
2225
|
for (let depth = 1; depth <= maxDepth && impacted.size < limit; depth++) {
|
|
1931
2226
|
if (frontier.length === 0) break;
|
|
1932
2227
|
|
|
2228
|
+
assertInClauseSize(frontier.length, 'graph-search.impact-frontier');
|
|
1933
2229
|
const placeholders = frontier.map(() => '?').join(',');
|
|
1934
2230
|
let dependents;
|
|
1935
2231
|
|
|
@@ -1942,9 +2238,15 @@ export class GraphSearch {
|
|
|
1942
2238
|
FROM relationships r
|
|
1943
2239
|
JOIN entities e ON e.id = r.source_id
|
|
1944
2240
|
WHERE (r.target_id IN (${placeholders}) OR r.target_name LIKE ?)
|
|
1945
|
-
AND e
|
|
2241
|
+
AND ${this._entityVisibilitySql('e')}
|
|
2242
|
+
AND ${this._relationshipVisibilitySql('r')}
|
|
1946
2243
|
LIMIT 50
|
|
1947
|
-
`).all(
|
|
2244
|
+
`).all(
|
|
2245
|
+
...frontier,
|
|
2246
|
+
targetNamePattern,
|
|
2247
|
+
...this._entityVisibilityParams(),
|
|
2248
|
+
...this._relationshipVisibilityParams(),
|
|
2249
|
+
);
|
|
1948
2250
|
} else {
|
|
1949
2251
|
// Subsequent depths: only match by target_id
|
|
1950
2252
|
dependents = this.db.prepare(`
|
|
@@ -1954,16 +2256,17 @@ export class GraphSearch {
|
|
|
1954
2256
|
FROM relationships r
|
|
1955
2257
|
JOIN entities e ON e.id = r.source_id
|
|
1956
2258
|
WHERE r.target_id IN (${placeholders})
|
|
1957
|
-
AND e
|
|
2259
|
+
AND ${this._entityVisibilitySql('e')}
|
|
2260
|
+
AND ${this._relationshipVisibilitySql('r')}
|
|
1958
2261
|
LIMIT 50
|
|
1959
|
-
`).all(...frontier);
|
|
2262
|
+
`).all(...frontier, ...this._entityVisibilityParams(), ...this._relationshipVisibilityParams());
|
|
1960
2263
|
}
|
|
1961
2264
|
|
|
1962
2265
|
const nextFrontier = [];
|
|
1963
2266
|
for (const dep of dependents) {
|
|
1964
2267
|
if (!impacted.has(dep.id) && dep.id !== target.id) {
|
|
1965
2268
|
impacted.set(dep.id, {
|
|
1966
|
-
...dep,
|
|
2269
|
+
...this._rowWithVisibleSummary(dep),
|
|
1967
2270
|
relationship: dep.rel_type,
|
|
1968
2271
|
depth,
|
|
1969
2272
|
riskScore: (4 - depth) / 3, // Higher for closer dependencies
|