@neuralsea/workspace-indexer 0.3.6 → 0.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.
@@ -1,3 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // src/progress.ts
2
9
  function toHandler(progress) {
3
10
  if (!progress) return null;
@@ -2548,63 +2555,6 @@ var RepoIndexer = class {
2548
2555
  }
2549
2556
  };
2550
2557
 
2551
- // src/profiles.ts
2552
- var DEFAULT_PROFILES = {
2553
- search: {
2554
- name: "search",
2555
- k: 10,
2556
- weights: { vector: 0.65, lexical: 0.35, recency: 0 },
2557
- expand: { adjacentChunks: 0, followImports: 0, includeFileSynopsis: false },
2558
- candidates: { vectorK: 25, lexicalK: 25, maxMergedCandidates: 60 }
2559
- },
2560
- refactor: {
2561
- name: "refactor",
2562
- k: 15,
2563
- weights: { vector: 0.55, lexical: 0.35, recency: 0.1 },
2564
- expand: { adjacentChunks: 1, followImports: 2, includeFileSynopsis: true },
2565
- candidates: { vectorK: 60, lexicalK: 40, maxMergedCandidates: 140 }
2566
- },
2567
- review: {
2568
- name: "review",
2569
- k: 20,
2570
- weights: { vector: 0.45, lexical: 0.35, recency: 0.2 },
2571
- expand: { adjacentChunks: 1, followImports: 1, includeFileSynopsis: true },
2572
- candidates: { vectorK: 80, lexicalK: 60, maxMergedCandidates: 180 }
2573
- },
2574
- architecture: {
2575
- name: "architecture",
2576
- k: 20,
2577
- weights: { vector: 0.7, lexical: 0.2, recency: 0.1 },
2578
- expand: { adjacentChunks: 0, followImports: 3, includeFileSynopsis: true },
2579
- candidates: { vectorK: 120, lexicalK: 40, maxMergedCandidates: 220 }
2580
- },
2581
- rca: {
2582
- name: "rca",
2583
- k: 25,
2584
- weights: { vector: 0.5, lexical: 0.25, recency: 0.25 },
2585
- expand: { adjacentChunks: 2, followImports: 1, includeFileSynopsis: true },
2586
- candidates: { vectorK: 140, lexicalK: 80, maxMergedCandidates: 260 }
2587
- },
2588
- custom: {
2589
- name: "custom",
2590
- k: 10,
2591
- weights: { vector: 0.65, lexical: 0.35, recency: 0 },
2592
- expand: { adjacentChunks: 0, followImports: 0, includeFileSynopsis: false },
2593
- candidates: { vectorK: 25, lexicalK: 25, maxMergedCandidates: 60 }
2594
- }
2595
- };
2596
- function deepMergeProfile(base, patch) {
2597
- if (!patch) return base;
2598
- const merged = {
2599
- ...base,
2600
- ...patch,
2601
- weights: { ...base.weights, ...patch.weights ?? {} },
2602
- expand: { ...base.expand, ...patch.expand ?? {} },
2603
- candidates: { ...base.candidates, ...patch.candidates ?? {} }
2604
- };
2605
- return merged;
2606
- }
2607
-
2608
2558
  // src/indexer/repoDiscovery.ts
2609
2559
  import fs10 from "fs";
2610
2560
  import path13 from "path";
@@ -2767,9 +2717,181 @@ function mergeIndexerConfig(target, patch) {
2767
2717
  }
2768
2718
  }
2769
2719
 
2770
- // src/store/workspaceStore.ts
2720
+ // src/store/workspace/db.ts
2721
+ import Database3 from "better-sqlite3";
2722
+ import fs11 from "fs";
2723
+ import path14 from "path";
2724
+ function detectFts5Support(db) {
2725
+ try {
2726
+ const rows = db.prepare(`PRAGMA compile_options`).all();
2727
+ if (rows.some((r) => String(r.compile_options ?? "").includes("ENABLE_FTS5"))) return true;
2728
+ } catch {
2729
+ }
2730
+ try {
2731
+ db.exec(`
2732
+ CREATE VIRTUAL TABLE IF NOT EXISTS __fts5_probe USING fts5(x);
2733
+ DROP TABLE __fts5_probe;
2734
+ `);
2735
+ return true;
2736
+ } catch {
2737
+ return false;
2738
+ }
2739
+ }
2740
+ var BetterSqlite3Adapter = class {
2741
+ db;
2742
+ capabilities;
2743
+ constructor(dbPath) {
2744
+ this.db = new Database3(dbPath);
2745
+ this.capabilities = { supportsFts5: detectFts5Support(this.db) };
2746
+ }
2747
+ pragma(sql) {
2748
+ this.db.pragma(sql);
2749
+ }
2750
+ exec(sql) {
2751
+ this.db.exec(sql);
2752
+ }
2753
+ prepare(sql) {
2754
+ return this.db.prepare(sql);
2755
+ }
2756
+ transaction(fn) {
2757
+ return this.db.transaction(fn);
2758
+ }
2759
+ close() {
2760
+ this.db.close();
2761
+ }
2762
+ };
2763
+ var betterSqlite3Adapter = {
2764
+ open(dbPath) {
2765
+ fs11.mkdirSync(path14.dirname(dbPath), { recursive: true });
2766
+ const db = new BetterSqlite3Adapter(dbPath);
2767
+ db.pragma("journal_mode = WAL");
2768
+ return db;
2769
+ }
2770
+ };
2771
+
2772
+ // src/store/workspace/sqlJsAdapter.ts
2771
2773
  import fs12 from "fs";
2774
+ import { createRequire as createRequire2 } from "module";
2772
2775
  import path15 from "path";
2776
+ function detectFts5Support2(db) {
2777
+ try {
2778
+ db.exec(`
2779
+ CREATE VIRTUAL TABLE IF NOT EXISTS __fts5_probe USING fts5(x);
2780
+ DROP TABLE __fts5_probe;
2781
+ `);
2782
+ return true;
2783
+ } catch {
2784
+ return false;
2785
+ }
2786
+ }
2787
+ var SqlJsStatement = class {
2788
+ constructor(stmt) {
2789
+ this.stmt = stmt;
2790
+ }
2791
+ run(...args) {
2792
+ this.stmt.run(args);
2793
+ return void 0;
2794
+ }
2795
+ get(...args) {
2796
+ this.stmt.bind(args);
2797
+ const hasRow = this.stmt.step();
2798
+ if (!hasRow) {
2799
+ this.stmt.reset();
2800
+ return void 0;
2801
+ }
2802
+ const row = this.stmt.getAsObject();
2803
+ this.stmt.reset();
2804
+ return row;
2805
+ }
2806
+ all(...args) {
2807
+ this.stmt.bind(args);
2808
+ const rows = [];
2809
+ while (this.stmt.step()) rows.push(this.stmt.getAsObject());
2810
+ this.stmt.reset();
2811
+ return rows;
2812
+ }
2813
+ };
2814
+ var SqlJsDbAdapter = class {
2815
+ constructor(db, dbPath) {
2816
+ this.db = db;
2817
+ this.dbPath = dbPath;
2818
+ this.capabilities = { supportsFts5: detectFts5Support2(db) };
2819
+ }
2820
+ capabilities;
2821
+ pragma(sql) {
2822
+ this.exec(`PRAGMA ${sql}`);
2823
+ }
2824
+ exec(sql) {
2825
+ this.db.exec(sql);
2826
+ }
2827
+ prepare(sql) {
2828
+ return new SqlJsStatement(this.db.prepare(sql));
2829
+ }
2830
+ transaction(fn) {
2831
+ return () => {
2832
+ this.db.exec("BEGIN");
2833
+ try {
2834
+ const out = fn();
2835
+ this.db.exec("COMMIT");
2836
+ return out;
2837
+ } catch (e) {
2838
+ try {
2839
+ this.db.exec("ROLLBACK");
2840
+ } catch {
2841
+ }
2842
+ throw e;
2843
+ }
2844
+ };
2845
+ }
2846
+ close() {
2847
+ if (this.dbPath && this.dbPath !== ":memory:") {
2848
+ fs12.mkdirSync(path15.dirname(this.dbPath), { recursive: true });
2849
+ const bytes = this.db.export();
2850
+ fs12.writeFileSync(this.dbPath, Buffer.from(bytes));
2851
+ }
2852
+ this.db.close();
2853
+ }
2854
+ };
2855
+ function defaultLocateFile(file) {
2856
+ const spec = `sql.js/dist/${file}`;
2857
+ try {
2858
+ if (typeof __require === "function" && typeof __require.resolve === "function") {
2859
+ return __require.resolve(spec);
2860
+ }
2861
+ } catch {
2862
+ }
2863
+ try {
2864
+ const req = createRequire2(import.meta.url);
2865
+ return req.resolve(spec);
2866
+ } catch {
2867
+ return file;
2868
+ }
2869
+ }
2870
+ async function sqlJsAdapter(opts = {}) {
2871
+ let init;
2872
+ try {
2873
+ const mod = await import("sql.js");
2874
+ init = mod?.default ?? mod;
2875
+ } catch (e) {
2876
+ throw new Error(`sqlJsAdapter requires optional dependency 'sql.js' (install it to use this adapter): ${String(e?.message ?? e)}`);
2877
+ }
2878
+ const SQL = await init({
2879
+ locateFile: opts.locateFile ?? defaultLocateFile,
2880
+ wasmBinary: opts.wasmBinary
2881
+ });
2882
+ return {
2883
+ open(dbPath) {
2884
+ const abs = dbPath === ":memory:" ? ":memory:" : path15.resolve(dbPath);
2885
+ const bytes = abs !== ":memory:" && fs12.existsSync(abs) ? new Uint8Array(fs12.readFileSync(abs)) : void 0;
2886
+ const db = bytes ? new SQL.Database(bytes) : new SQL.Database();
2887
+ return new SqlJsDbAdapter(db, abs);
2888
+ }
2889
+ };
2890
+ }
2891
+
2892
+ // src/store/workspaceStore.ts
2893
+ import fs14 from "fs";
2894
+ import path17 from "path";
2773
2895
 
2774
2896
  // src/store/workspace/unitOfWork.ts
2775
2897
  var UnitOfWork = class {
@@ -2989,32 +3111,11 @@ var RepoLinksRepository = class {
2989
3111
  };
2990
3112
 
2991
3113
  // src/store/workspace/factory.ts
2992
- import fs11 from "fs";
2993
- import path14 from "path";
3114
+ import fs13 from "fs";
3115
+ import path16 from "path";
2994
3116
 
2995
- // src/store/workspace/db.ts
2996
- import Database3 from "better-sqlite3";
2997
- var BetterSqlite3Adapter = class {
2998
- db;
2999
- constructor(dbPath) {
3000
- this.db = new Database3(dbPath);
3001
- }
3002
- pragma(sql) {
3003
- this.db.pragma(sql);
3004
- }
3005
- exec(sql) {
3006
- this.db.exec(sql);
3007
- }
3008
- prepare(sql) {
3009
- return this.db.prepare(sql);
3010
- }
3011
- transaction(fn) {
3012
- return this.db.transaction(fn);
3013
- }
3014
- close() {
3015
- this.db.close();
3016
- }
3017
- };
3117
+ // src/store/workspace/fts5.sql
3118
+ var fts5_default = "CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(\n id UNINDEXED,\n repo_id UNINDEXED,\n repo_root UNINDEXED,\n path,\n language,\n kind,\n text,\n tokenize='unicode61'\n);\n\n";
3018
3119
 
3019
3120
  // src/store/workspace/fts.ts
3020
3121
  var NoopFtsStrategy = class {
@@ -3038,18 +3139,7 @@ var Fts5Strategy = class {
3038
3139
  enabled = true;
3039
3140
  ins = null;
3040
3141
  init(db) {
3041
- db.exec(`
3042
- CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
3043
- id UNINDEXED,
3044
- repo_id UNINDEXED,
3045
- repo_root UNINDEXED,
3046
- path,
3047
- language,
3048
- kind,
3049
- text,
3050
- tokenize='unicode61'
3051
- );
3052
- `);
3142
+ db.exec(fts5_default);
3053
3143
  }
3054
3144
  clearRepo(repoId) {
3055
3145
  this.db.prepare(`DELETE FROM chunks_fts WHERE repo_id = ?`).run(repoId);
@@ -3133,109 +3223,26 @@ var WorkspaceMigrator = class {
3133
3223
  }
3134
3224
  };
3135
3225
 
3226
+ // src/store/workspace/baseSchema.sql
3227
+ var baseSchema_default = "CREATE TABLE IF NOT EXISTS meta (\n k TEXT PRIMARY KEY,\n v TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS repos (\n repo_id TEXT PRIMARY KEY,\n repo_root TEXT NOT NULL,\n head_commit TEXT NOT NULL,\n head_branch TEXT NOT NULL,\n updated_at INTEGER NOT NULL\n);\n\nCREATE UNIQUE INDEX IF NOT EXISTS idx_repos_root ON repos(repo_root);\n\nCREATE TABLE IF NOT EXISTS files (\n repo_id TEXT NOT NULL,\n path TEXT NOT NULL,\n hash TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n language TEXT NOT NULL,\n size INTEGER NOT NULL,\n PRIMARY KEY(repo_id, path)\n);\n\nCREATE INDEX IF NOT EXISTS idx_files_repo ON files(repo_id);\n\nCREATE TABLE IF NOT EXISTS chunks (\n id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL,\n repo_root TEXT NOT NULL,\n path TEXT NOT NULL,\n language TEXT NOT NULL,\n kind TEXT NOT NULL DEFAULT 'chunk',\n start_line INTEGER NOT NULL,\n end_line INTEGER NOT NULL,\n content_hash TEXT NOT NULL,\n tokens INTEGER NOT NULL,\n file_mtime INTEGER NOT NULL,\n text TEXT NOT NULL,\n embedding BLOB NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_chunks_repo_path ON chunks(repo_id, path);\nCREATE INDEX IF NOT EXISTS idx_chunks_kind_repo_path ON chunks(kind, repo_id, path);\n\nCREATE TABLE IF NOT EXISTS edges (\n repo_id TEXT NOT NULL,\n from_path TEXT NOT NULL,\n kind TEXT NOT NULL,\n value TEXT NOT NULL,\n PRIMARY KEY(repo_id, from_path, kind, value)\n);\n\nCREATE INDEX IF NOT EXISTS idx_edges_repo_from ON edges(repo_id, from_path);\n\nCREATE TABLE IF NOT EXISTS symbols (\n id TEXT PRIMARY KEY,\n repo_id TEXT NOT NULL,\n repo_root TEXT NOT NULL,\n path TEXT NOT NULL,\n language TEXT NOT NULL,\n name TEXT NOT NULL,\n kind TEXT NOT NULL,\n start_line INTEGER NOT NULL,\n start_char INTEGER NOT NULL,\n end_line INTEGER NOT NULL,\n end_char INTEGER NOT NULL,\n container_name TEXT NOT NULL DEFAULT '',\n detail TEXT NOT NULL DEFAULT ''\n);\n\nCREATE INDEX IF NOT EXISTS idx_symbols_repo_path ON symbols(repo_id, path);\nCREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);\n\nCREATE TABLE IF NOT EXISTS symbol_edges (\n repo_id TEXT NOT NULL,\n from_id TEXT NOT NULL,\n to_id TEXT NOT NULL,\n kind TEXT NOT NULL,\n from_path TEXT NOT NULL,\n to_path TEXT NOT NULL,\n PRIMARY KEY(repo_id, from_id, to_id, kind)\n);\n\nCREATE INDEX IF NOT EXISTS idx_symbol_edges_from ON symbol_edges(repo_id, from_id);\nCREATE INDEX IF NOT EXISTS idx_symbol_edges_paths ON symbol_edges(repo_id, from_path);\n\n";
3228
+
3136
3229
  // src/store/workspace/factory.ts
3137
- function createWorkspaceDb(dbPath) {
3138
- fs11.mkdirSync(path14.dirname(dbPath), { recursive: true });
3139
- const db = new BetterSqlite3Adapter(dbPath);
3140
- db.pragma("journal_mode = WAL");
3141
- return db;
3230
+ function createWorkspaceDb(dbPath, opts = {}) {
3231
+ fs13.mkdirSync(path16.dirname(dbPath), { recursive: true });
3232
+ return (opts.db ?? betterSqlite3Adapter).open(dbPath);
3142
3233
  }
3143
3234
  function createWorkspaceBaseSchema(db) {
3144
- db.exec(`
3145
- CREATE TABLE IF NOT EXISTS meta (
3146
- k TEXT PRIMARY KEY,
3147
- v TEXT NOT NULL
3148
- );
3149
-
3150
- CREATE TABLE IF NOT EXISTS repos (
3151
- repo_id TEXT PRIMARY KEY,
3152
- repo_root TEXT NOT NULL,
3153
- head_commit TEXT NOT NULL,
3154
- head_branch TEXT NOT NULL,
3155
- updated_at INTEGER NOT NULL
3156
- );
3157
-
3158
- CREATE UNIQUE INDEX IF NOT EXISTS idx_repos_root ON repos(repo_root);
3159
-
3160
- CREATE TABLE IF NOT EXISTS files (
3161
- repo_id TEXT NOT NULL,
3162
- path TEXT NOT NULL,
3163
- hash TEXT NOT NULL,
3164
- mtime INTEGER NOT NULL,
3165
- language TEXT NOT NULL,
3166
- size INTEGER NOT NULL,
3167
- PRIMARY KEY(repo_id, path)
3168
- );
3169
-
3170
- CREATE INDEX IF NOT EXISTS idx_files_repo ON files(repo_id);
3171
-
3172
- CREATE TABLE IF NOT EXISTS chunks (
3173
- id TEXT PRIMARY KEY,
3174
- repo_id TEXT NOT NULL,
3175
- repo_root TEXT NOT NULL,
3176
- path TEXT NOT NULL,
3177
- language TEXT NOT NULL,
3178
- kind TEXT NOT NULL DEFAULT 'chunk',
3179
- start_line INTEGER NOT NULL,
3180
- end_line INTEGER NOT NULL,
3181
- content_hash TEXT NOT NULL,
3182
- tokens INTEGER NOT NULL,
3183
- file_mtime INTEGER NOT NULL,
3184
- text TEXT NOT NULL,
3185
- embedding BLOB NOT NULL
3186
- );
3187
-
3188
- CREATE INDEX IF NOT EXISTS idx_chunks_repo_path ON chunks(repo_id, path);
3189
- CREATE INDEX IF NOT EXISTS idx_chunks_kind_repo_path ON chunks(kind, repo_id, path);
3190
-
3191
- CREATE TABLE IF NOT EXISTS edges (
3192
- repo_id TEXT NOT NULL,
3193
- from_path TEXT NOT NULL,
3194
- kind TEXT NOT NULL,
3195
- value TEXT NOT NULL,
3196
- PRIMARY KEY(repo_id, from_path, kind, value)
3197
- );
3198
-
3199
- CREATE INDEX IF NOT EXISTS idx_edges_repo_from ON edges(repo_id, from_path);
3200
-
3201
- CREATE TABLE IF NOT EXISTS symbols (
3202
- id TEXT PRIMARY KEY,
3203
- repo_id TEXT NOT NULL,
3204
- repo_root TEXT NOT NULL,
3205
- path TEXT NOT NULL,
3206
- language TEXT NOT NULL,
3207
- name TEXT NOT NULL,
3208
- kind TEXT NOT NULL,
3209
- start_line INTEGER NOT NULL,
3210
- start_char INTEGER NOT NULL,
3211
- end_line INTEGER NOT NULL,
3212
- end_char INTEGER NOT NULL,
3213
- container_name TEXT NOT NULL DEFAULT '',
3214
- detail TEXT NOT NULL DEFAULT ''
3215
- );
3216
-
3217
- CREATE INDEX IF NOT EXISTS idx_symbols_repo_path ON symbols(repo_id, path);
3218
- CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
3219
-
3220
- CREATE TABLE IF NOT EXISTS symbol_edges (
3221
- repo_id TEXT NOT NULL,
3222
- from_id TEXT NOT NULL,
3223
- to_id TEXT NOT NULL,
3224
- kind TEXT NOT NULL,
3225
- from_path TEXT NOT NULL,
3226
- to_path TEXT NOT NULL,
3227
- PRIMARY KEY(repo_id, from_id, to_id, kind)
3228
- );
3229
-
3230
- CREATE INDEX IF NOT EXISTS idx_symbol_edges_from ON symbol_edges(repo_id, from_id);
3231
- CREATE INDEX IF NOT EXISTS idx_symbol_edges_paths ON symbol_edges(repo_id, from_path);
3232
- `);
3235
+ db.exec(baseSchema_default);
3233
3236
  }
3234
3237
  function createWorkspaceFts(db, meta, opts = {}) {
3235
3238
  if (opts.fts === "off") {
3236
3239
  meta.set("fts", "0");
3237
3240
  return new NoopFtsStrategy();
3238
3241
  }
3242
+ if (!db.capabilities.supportsFts5) {
3243
+ meta.set("fts", "0");
3244
+ return new NoopFtsStrategy();
3245
+ }
3239
3246
  try {
3240
3247
  const fts = new Fts5Strategy(db);
3241
3248
  fts.init(db);
@@ -3252,16 +3259,31 @@ function migrateWorkspaceDb(db, meta) {
3252
3259
  }
3253
3260
 
3254
3261
  // src/store/workspaceStore.ts
3262
+ function createWorkspaceStore(dbPath, opts = {}) {
3263
+ return new WorkspaceStore(dbPath, opts);
3264
+ }
3265
+ async function defaultWorkspaceDbFactory() {
3266
+ try {
3267
+ return await sqlJsAdapter();
3268
+ } catch {
3269
+ return betterSqlite3Adapter;
3270
+ }
3271
+ }
3272
+ async function createWorkspaceStoreAsync(dbPath, opts = {}) {
3273
+ const dbFactory = opts.db ? await Promise.resolve(opts.db) : await defaultWorkspaceDbFactory();
3274
+ return new WorkspaceStore(dbPath, { ...opts, db: dbFactory });
3275
+ }
3255
3276
  var WorkspaceStore = class {
3256
3277
  constructor(dbPath, opts = {}) {
3257
3278
  this.dbPath = dbPath;
3258
3279
  this.opts = opts;
3259
- this.db = createWorkspaceDb(dbPath);
3280
+ this.db = createWorkspaceDb(dbPath, { db: opts.db });
3260
3281
  this.uow = new UnitOfWork(this.db);
3261
3282
  createWorkspaceBaseSchema(this.db);
3262
3283
  this.meta = new MetaRepository(this.db);
3263
3284
  migrateWorkspaceDb(this.db, this.meta);
3264
3285
  const fts = createWorkspaceFts(this.db, this.meta, opts);
3286
+ this.ftsEnabledInternal = fts.enabled;
3265
3287
  this.repoHeads = new RepoHeadsRepository(this.db);
3266
3288
  this.files = new FilesRepository(this.db);
3267
3289
  this.edges = new EdgesRepository(this.db);
@@ -3271,6 +3293,7 @@ var WorkspaceStore = class {
3271
3293
  }
3272
3294
  db;
3273
3295
  uow;
3296
+ ftsEnabledInternal;
3274
3297
  meta;
3275
3298
  repoHeads;
3276
3299
  files;
@@ -3279,6 +3302,9 @@ var WorkspaceStore = class {
3279
3302
  symbols;
3280
3303
  chunks;
3281
3304
  opts;
3305
+ get ftsEnabled() {
3306
+ return this.ftsEnabledInternal;
3307
+ }
3282
3308
  setMeta(k, v) {
3283
3309
  this.meta.set(k, v);
3284
3310
  }
@@ -3362,9 +3388,9 @@ var WorkspaceStore = class {
3362
3388
  * The chunk boundaries are approximate; the stored row includes start/end line.
3363
3389
  */
3364
3390
  getChunkTextFallback(row) {
3365
- const abs = path15.join(row.repo_root, row.path.split("/").join(path15.sep));
3391
+ const abs = path17.join(row.repo_root, row.path.split("/").join(path17.sep));
3366
3392
  try {
3367
- const raw = fs12.readFileSync(abs, "utf8");
3393
+ const raw = fs14.readFileSync(abs, "utf8");
3368
3394
  const lines = raw.split(/\r?\n/);
3369
3395
  const start = Math.max(1, row.start_line);
3370
3396
  const end = Math.max(start, row.end_line);
@@ -3379,7 +3405,7 @@ var WorkspaceStore = class {
3379
3405
  };
3380
3406
 
3381
3407
  // src/graph/neo4j.ts
3382
- import { createRequire as createRequire2 } from "module";
3408
+ import { createRequire as createRequire3 } from "module";
3383
3409
  async function runSession(driver, database, fn) {
3384
3410
  const session = driver.session(database ? { database } : void 0);
3385
3411
  try {
@@ -3463,13 +3489,13 @@ var Neo4jGraphStore = class {
3463
3489
  async runMigrations() {
3464
3490
  const startedAt = Date.now();
3465
3491
  this.opStart("migrations");
3466
- const { Repo, File, Symbol, External } = this.labels();
3492
+ const { Repo, File, Symbol: Symbol2, External } = this.labels();
3467
3493
  let v = await this.getSchemaVersion();
3468
3494
  if (v < 1) {
3469
3495
  await runSession(this.driver, this.cfg.database, async (s) => {
3470
3496
  const stmts = [
3471
3497
  `CREATE CONSTRAINT ${Repo}_repo_id IF NOT EXISTS FOR (r:${Repo}) REQUIRE r.repo_id IS UNIQUE`,
3472
- `CREATE CONSTRAINT ${Symbol}_id IF NOT EXISTS FOR (s:${Symbol}) REQUIRE s.id IS UNIQUE`,
3498
+ `CREATE CONSTRAINT ${Symbol2}_id IF NOT EXISTS FOR (s:${Symbol2}) REQUIRE s.id IS UNIQUE`,
3473
3499
  `CREATE CONSTRAINT ${External}_key IF NOT EXISTS FOR (e:${External}) REQUIRE e.key IS UNIQUE`,
3474
3500
  `CREATE CONSTRAINT ${File}_key IF NOT EXISTS FOR (f:${File}) REQUIRE f.key IS UNIQUE`
3475
3501
  ];
@@ -3486,7 +3512,7 @@ var Neo4jGraphStore = class {
3486
3512
  if (v < 2) {
3487
3513
  await runSession(this.driver, this.cfg.database, async (s) => {
3488
3514
  const stmts = [
3489
- `CREATE INDEX ${Symbol}_repo_path IF NOT EXISTS FOR (s:${Symbol}) ON (s.repo_id, s.path)`,
3515
+ `CREATE INDEX ${Symbol2}_repo_path IF NOT EXISTS FOR (s:${Symbol2}) ON (s.repo_id, s.path)`,
3490
3516
  `CREATE INDEX ${File}_repo IF NOT EXISTS FOR (f:${File}) ON (f.repo_id)`,
3491
3517
  `CREATE INDEX ${External}_repo IF NOT EXISTS FOR (e:${External}) ON (e.repo_id)`
3492
3518
  ];
@@ -3508,7 +3534,7 @@ var Neo4jGraphStore = class {
3508
3534
  async setRepoHead(args) {
3509
3535
  const startedAt = Date.now();
3510
3536
  this.opStart("setRepoHead", { repoId: args.repoId });
3511
- const { Repo, File, Symbol, External } = this.labels();
3537
+ const { Repo, File, Symbol: Symbol2, External } = this.labels();
3512
3538
  const { repoId, repoRoot, commit, branch } = args;
3513
3539
  await runSession(this.driver, this.cfg.database, async (s) => {
3514
3540
  const prev = await s.run(
@@ -3521,7 +3547,7 @@ var Neo4jGraphStore = class {
3521
3547
  const prevCommit = prev?.records?.[0]?.get?.("commit");
3522
3548
  const prevCommitStr = typeof prevCommit === "string" ? prevCommit : `${prevCommit ?? ""}`;
3523
3549
  if (prevCommitStr && prevCommitStr !== commit) {
3524
- await s.run(`MATCH (s:${Symbol} {repo_id:$repoId}) DETACH DELETE s`, { repoId });
3550
+ await s.run(`MATCH (s:${Symbol2} {repo_id:$repoId}) DETACH DELETE s`, { repoId });
3525
3551
  await s.run(
3526
3552
  `MATCH (f:${File} {repo_id:$repoId})
3527
3553
  DETACH DELETE f`,
@@ -3543,9 +3569,9 @@ var Neo4jGraphStore = class {
3543
3569
  async deleteFile(args) {
3544
3570
  const startedAt = Date.now();
3545
3571
  this.opStart("deleteFile", { repoId: args.repoId, path: args.path });
3546
- const { File, Symbol } = this.labels();
3572
+ const { File, Symbol: Symbol2 } = this.labels();
3547
3573
  await runSession(this.driver, this.cfg.database, async (s) => {
3548
- await s.run(`MATCH (s:${Symbol} {repo_id:$repoId, path:$path}) DETACH DELETE s`, args);
3574
+ await s.run(`MATCH (s:${Symbol2} {repo_id:$repoId, path:$path}) DETACH DELETE s`, args);
3549
3575
  await s.run(`MATCH (f:${File} {repo_id:$repoId, path:$path}) DETACH DELETE f`, args);
3550
3576
  });
3551
3577
  this.opDone("deleteFile", startedAt, { repoId: args.repoId, path: args.path });
@@ -3553,14 +3579,14 @@ var Neo4jGraphStore = class {
3553
3579
  async replaceOutgoingSymbolEdgesFromFile(args) {
3554
3580
  const startedAt = Date.now();
3555
3581
  this.opStart("replaceOutgoingSymbolEdgesFromFile", { repoId: args.repoId, path: args.fromPath });
3556
- const { Symbol } = this.labels();
3582
+ const { Symbol: Symbol2 } = this.labels();
3557
3583
  const kinds = args.edges.length > 0 ? Array.from(new Set(args.edges.map((e) => e.kind))) : ["definition", "reference", "implementation", "typeDefinition"];
3558
3584
  const keep = args.edges.map((e) => `${e.fromId}|${e.kind}|${e.toId}`);
3559
3585
  await runSession(this.driver, this.cfg.database, async (s) => {
3560
3586
  await s.run(
3561
3587
  `UNWIND $edges AS e
3562
- MERGE (a:${Symbol} {id:e.fromId})
3563
- MERGE (b:${Symbol} {id:e.toId})
3588
+ MERGE (a:${Symbol2} {id:e.fromId})
3589
+ MERGE (b:${Symbol2} {id:e.toId})
3564
3590
  ON CREATE SET b.repo_id=$repoId, b.path=coalesce(e.toPath, '')
3565
3591
  SET b.repo_id = coalesce(b.repo_id, $repoId),
3566
3592
  b.path = CASE WHEN coalesce(b.path, '') = '' AND e.toPath IS NOT NULL THEN e.toPath ELSE b.path END
@@ -3569,7 +3595,7 @@ var Neo4jGraphStore = class {
3569
3595
  ).catch(() => void 0);
3570
3596
  if (keep.length === 0) {
3571
3597
  await s.run(
3572
- `MATCH (a:${Symbol} {repo_id:$repoId, path:$fromPath})-[r:SYMBOL_EDGE]->()
3598
+ `MATCH (a:${Symbol2} {repo_id:$repoId, path:$fromPath})-[r:SYMBOL_EDGE]->()
3573
3599
  WHERE r.kind IN $kinds
3574
3600
  DELETE r`,
3575
3601
  { repoId: args.repoId, fromPath: args.fromPath, kinds }
@@ -3577,7 +3603,7 @@ var Neo4jGraphStore = class {
3577
3603
  return;
3578
3604
  }
3579
3605
  await s.run(
3580
- `MATCH (a:${Symbol} {repo_id:$repoId, path:$fromPath})-[r:SYMBOL_EDGE]->(b:${Symbol})
3606
+ `MATCH (a:${Symbol2} {repo_id:$repoId, path:$fromPath})-[r:SYMBOL_EDGE]->(b:${Symbol2})
3581
3607
  WHERE r.kind IN $kinds
3582
3608
  AND NOT (a.id + '|' + r.kind + '|' + b.id) IN $keep
3583
3609
  DELETE r`,
@@ -3589,7 +3615,7 @@ var Neo4jGraphStore = class {
3589
3615
  async replaceFileGraph(update) {
3590
3616
  const startedAt = Date.now();
3591
3617
  this.opStart("replaceFileGraph", { repoId: update.repoId, path: update.path });
3592
- const { Repo, File, Symbol, External } = this.labels();
3618
+ const { Repo, File, Symbol: Symbol2, External } = this.labels();
3593
3619
  const symbols = update.symbols.map((s) => ({
3594
3620
  id: s.id,
3595
3621
  name: s.name,
@@ -3649,7 +3675,7 @@ var Neo4jGraphStore = class {
3649
3675
  await s.run(
3650
3676
  `MATCH (f:${File} {repo_id:$repoId, path:$path})
3651
3677
  UNWIND $symbols AS s
3652
- MERGE (n:${Symbol} {id:s.id})
3678
+ MERGE (n:${Symbol2} {id:s.id})
3653
3679
  ON CREATE SET n.repo_id=$repoId
3654
3680
  SET n.repo_root=$repoRoot, n.path=$path,
3655
3681
  n.language=s.language, n.name=s.name, n.kind=s.kind,
@@ -3660,13 +3686,13 @@ var Neo4jGraphStore = class {
3660
3686
  );
3661
3687
  const ids = symbols.map((s2) => s2.id);
3662
3688
  await s.run(
3663
- `MATCH (f:${File} {repo_id:$repoId, path:$path})-[r:CONTAINS]->(n:${Symbol})
3689
+ `MATCH (f:${File} {repo_id:$repoId, path:$path})-[r:CONTAINS]->(n:${Symbol2})
3664
3690
  WHERE NOT n.id IN $ids
3665
3691
  DELETE r`,
3666
3692
  { repoId: update.repoId, path: update.path, ids }
3667
3693
  ).catch(() => void 0);
3668
3694
  await s.run(
3669
- `MATCH (n:${Symbol} {repo_id:$repoId, path:$path})
3695
+ `MATCH (n:${Symbol2} {repo_id:$repoId, path:$path})
3670
3696
  WHERE NOT n.id IN $ids
3671
3697
  DETACH DELETE n`,
3672
3698
  { repoId: update.repoId, path: update.path, ids }
@@ -3677,21 +3703,21 @@ var Neo4jGraphStore = class {
3677
3703
  { repoId: update.repoId, path: update.path }
3678
3704
  ).catch(() => void 0);
3679
3705
  await s.run(
3680
- `MATCH (n:${Symbol} {repo_id:$repoId, path:$path}) DETACH DELETE n`,
3706
+ `MATCH (n:${Symbol2} {repo_id:$repoId, path:$path}) DETACH DELETE n`,
3681
3707
  { repoId: update.repoId, path: update.path }
3682
3708
  ).catch(() => void 0);
3683
3709
  }
3684
3710
  if (symbolEdges.length) {
3685
3711
  await s.run(
3686
3712
  `UNWIND $edges AS e
3687
- MERGE (a:${Symbol} {id:e.fromId})
3688
- MERGE (b:${Symbol} {id:e.toId})
3713
+ MERGE (a:${Symbol2} {id:e.fromId})
3714
+ MERGE (b:${Symbol2} {id:e.toId})
3689
3715
  MERGE (a)-[:SYMBOL_EDGE {kind:e.kind}]->(b)`,
3690
3716
  { edges: symbolEdges }
3691
3717
  ).catch(() => void 0);
3692
3718
  if (managedSymbolEdgeKinds.length > 0) {
3693
3719
  await s.run(
3694
- `MATCH (a:${Symbol} {repo_id:$repoId, path:$path})-[r:SYMBOL_EDGE]->(b:${Symbol})
3720
+ `MATCH (a:${Symbol2} {repo_id:$repoId, path:$path})-[r:SYMBOL_EDGE]->(b:${Symbol2})
3695
3721
  WHERE r.kind IN $kinds
3696
3722
  AND NOT (a.id + '|' + r.kind + '|' + b.id) IN $keep
3697
3723
  DELETE r`,
@@ -3740,14 +3766,14 @@ var Neo4jGraphStore = class {
3740
3766
  async neighborFiles(args) {
3741
3767
  const startedAt = Date.now();
3742
3768
  this.opStart("neighborFiles");
3743
- const { File, Symbol } = this.labels();
3769
+ const { File, Symbol: Symbol2 } = this.labels();
3744
3770
  const limit = args.limit ?? 20;
3745
3771
  const kinds = args.kinds && args.kinds.length > 0 ? args.kinds : null;
3746
3772
  const rows = [];
3747
3773
  await runSession(this.driver, this.cfg.database, async (s) => {
3748
3774
  const res = await s.run(
3749
3775
  `UNWIND $seeds AS seed
3750
- MATCH (f:${File} {repo_id:seed.repoId, path:seed.path})-[:CONTAINS]->(a:${Symbol})-[e:SYMBOL_EDGE]->(b:${Symbol})<-[:CONTAINS]-(g:${File})
3776
+ MATCH (f:${File} {repo_id:seed.repoId, path:seed.path})-[:CONTAINS]->(a:${Symbol2})-[e:SYMBOL_EDGE]->(b:${Symbol2})<-[:CONTAINS]-(g:${File})
3751
3777
  WHERE (g.repo_id <> seed.repoId OR g.path <> seed.path)
3752
3778
  AND ($kinds IS NULL OR e.kind IN $kinds)
3753
3779
  RETURN g.repo_id AS repoId, g.path AS path, count(*) AS weight
@@ -3790,14 +3816,14 @@ var Neo4jGraphStore = class {
3790
3816
  async extractFileSubgraph(args) {
3791
3817
  const startedAt = Date.now();
3792
3818
  this.opStart("extractFileSubgraph");
3793
- const { File, Symbol } = this.labels();
3819
+ const { File, Symbol: Symbol2 } = this.labels();
3794
3820
  const limitEdges = args.limitEdges ?? 200;
3795
3821
  const nodes = /* @__PURE__ */ new Map();
3796
3822
  const edges = [];
3797
3823
  await runSession(this.driver, this.cfg.database, async (s) => {
3798
3824
  const res = await s.run(
3799
3825
  `UNWIND $seeds AS seed
3800
- MATCH (f:${File} {repo_id:seed.repoId, path:seed.path})-[:CONTAINS]->(a:${Symbol})-[e:SYMBOL_EDGE]->(b:${Symbol})<-[:CONTAINS]-(g:${File})
3826
+ MATCH (f:${File} {repo_id:seed.repoId, path:seed.path})-[:CONTAINS]->(a:${Symbol2})-[e:SYMBOL_EDGE]->(b:${Symbol2})<-[:CONTAINS]-(g:${File})
3801
3827
  RETURN f.repo_id AS fromRepoId, f.path AS fromPath, g.repo_id AS toRepoId, g.path AS toPath, e.kind AS kind
3802
3828
  LIMIT $limit`,
3803
3829
  { seeds: args.seeds, limit: limitEdges }
@@ -3820,7 +3846,7 @@ var Neo4jGraphStore = class {
3820
3846
  };
3821
3847
  async function createNeo4jGraphStore(cfg) {
3822
3848
  try {
3823
- const require2 = createRequire2(import.meta.url);
3849
+ const require2 = createRequire3(import.meta.url);
3824
3850
  const neo4j = require2("neo4j-driver");
3825
3851
  const driver = neo4j.driver(cfg.uri, neo4j.auth.basic(cfg.user, cfg.password));
3826
3852
  const store = new Neo4jGraphStore(driver, cfg);
@@ -3834,11 +3860,11 @@ ${hint}`);
3834
3860
  }
3835
3861
 
3836
3862
  // src/indexer/workspaceLinker.ts
3837
- import fs13 from "fs";
3838
- import path16 from "path";
3863
+ import fs15 from "fs";
3864
+ import path18 from "path";
3839
3865
  function readText(absPath) {
3840
3866
  try {
3841
- return fs13.readFileSync(absPath, "utf8");
3867
+ return fs15.readFileSync(absPath, "utf8");
3842
3868
  } catch {
3843
3869
  return null;
3844
3870
  }
@@ -3868,7 +3894,7 @@ var NestedRepoLinkStrategy = class {
3868
3894
  for (const child of sorted) {
3869
3895
  for (const parent of sorted) {
3870
3896
  if (child.repoId === parent.repoId) continue;
3871
- if (child.absRoot.startsWith(parent.absRoot + path16.sep)) {
3897
+ if (child.absRoot.startsWith(parent.absRoot + path18.sep)) {
3872
3898
  out.push({
3873
3899
  fromRepoId: child.repoId,
3874
3900
  toRepoId: parent.repoId,
@@ -3888,7 +3914,7 @@ var NpmDependencyLinkStrategy = class {
3888
3914
  const out = [];
3889
3915
  const depSections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
3890
3916
  for (const r of ctx.repos) {
3891
- const pkg = readJson(path16.join(r.absRoot, "package.json"));
3917
+ const pkg = readJson(path18.join(r.absRoot, "package.json"));
3892
3918
  if (!pkg) continue;
3893
3919
  for (const sec of depSections) {
3894
3920
  const deps = pkg?.[sec];
@@ -3906,13 +3932,13 @@ var NpmDependencyLinkStrategy = class {
3906
3932
  }
3907
3933
  };
3908
3934
  function parseGoModule(absRepoRoot) {
3909
- const raw = readText(path16.join(absRepoRoot, "go.mod"));
3935
+ const raw = readText(path18.join(absRepoRoot, "go.mod"));
3910
3936
  if (!raw) return null;
3911
3937
  const m = raw.match(/^\s*module\s+(.+)\s*$/m);
3912
3938
  return m ? String(m[1]).trim() : null;
3913
3939
  }
3914
3940
  function parseGoRequires(absRepoRoot) {
3915
- const raw = readText(path16.join(absRepoRoot, "go.mod"));
3941
+ const raw = readText(path18.join(absRepoRoot, "go.mod"));
3916
3942
  if (!raw) return [];
3917
3943
  const out = [];
3918
3944
  for (const line of raw.split(/\r?\n/)) {
@@ -3952,13 +3978,13 @@ function walkFiles(root, opts, onFile) {
3952
3978
  if (depth > maxDepth) return;
3953
3979
  let ents = [];
3954
3980
  try {
3955
- ents = fs13.readdirSync(dir, { withFileTypes: true });
3981
+ ents = fs15.readdirSync(dir, { withFileTypes: true });
3956
3982
  } catch {
3957
3983
  return;
3958
3984
  }
3959
3985
  for (const e of ents) {
3960
3986
  if (seen >= maxFiles) return;
3961
- const abs = path16.join(dir, e.name);
3987
+ const abs = path18.join(dir, e.name);
3962
3988
  if (opts.shouldVisit && !opts.shouldVisit(abs, e)) continue;
3963
3989
  if (e.isDirectory()) {
3964
3990
  if (isSkippableDir(e.name)) continue;
@@ -3982,7 +4008,7 @@ function collectVsCodeLanguagesForRepo(absRepoRoot) {
3982
4008
  shouldVisit: (_abs, dirent) => !(dirent.isDirectory() && isSkippableDir(dirent.name))
3983
4009
  },
3984
4010
  (absPath) => {
3985
- if (path16.basename(absPath) !== "package.json") return;
4011
+ if (path18.basename(absPath) !== "package.json") return;
3986
4012
  const pkg = readJson(absPath);
3987
4013
  const langs = pkg?.contributes?.languages;
3988
4014
  if (!Array.isArray(langs)) return;
@@ -4011,7 +4037,7 @@ function repoUsedExtensions(absRepoRoot, exts) {
4011
4037
  shouldVisit: (_abs, dirent) => !(dirent.isDirectory() && isSkippableDir(dirent.name))
4012
4038
  },
4013
4039
  (absPath) => {
4014
- const ext = path16.extname(absPath).toLowerCase();
4040
+ const ext = path18.extname(absPath).toLowerCase();
4015
4041
  if (!ext) return;
4016
4042
  if (exts.has(ext)) used.add(ext);
4017
4043
  }
@@ -4083,12 +4109,12 @@ var WorkspaceLinker = class _WorkspaceLinker {
4083
4109
  const repos = repoRoots.map((repoRoot) => ({
4084
4110
  repoRoot,
4085
4111
  repoId: repoIdFromRoot(repoRoot),
4086
- absRoot: path16.resolve(repoRoot)
4112
+ absRoot: path18.resolve(repoRoot)
4087
4113
  }));
4088
4114
  const npmNameToRepoId = /* @__PURE__ */ new Map();
4089
4115
  const goModuleToRepoId = /* @__PURE__ */ new Map();
4090
4116
  for (const r of repos) {
4091
- const pkg = readJson(path16.join(r.absRoot, "package.json"));
4117
+ const pkg = readJson(path18.join(r.absRoot, "package.json"));
4092
4118
  const name = typeof pkg?.name === "string" ? pkg.name : null;
4093
4119
  if (name) npmNameToRepoId.set(name, r.repoId);
4094
4120
  const mod = parseGoModule(r.absRoot);
@@ -4132,14 +4158,265 @@ async function linkWorkspaceRepos(args) {
4132
4158
  return { repos: ctx.repos, links };
4133
4159
  }
4134
4160
 
4161
+ // src/profiles.ts
4162
+ var DEFAULT_PROFILES = {
4163
+ search: {
4164
+ name: "search",
4165
+ k: 10,
4166
+ weights: { vector: 0.65, lexical: 0.35, recency: 0 },
4167
+ expand: { adjacentChunks: 0, followImports: 0, includeFileSynopsis: false },
4168
+ candidates: { vectorK: 25, lexicalK: 25, maxMergedCandidates: 60 }
4169
+ },
4170
+ refactor: {
4171
+ name: "refactor",
4172
+ k: 15,
4173
+ weights: { vector: 0.55, lexical: 0.35, recency: 0.1 },
4174
+ expand: { adjacentChunks: 1, followImports: 2, includeFileSynopsis: true },
4175
+ candidates: { vectorK: 60, lexicalK: 40, maxMergedCandidates: 140 }
4176
+ },
4177
+ review: {
4178
+ name: "review",
4179
+ k: 20,
4180
+ weights: { vector: 0.45, lexical: 0.35, recency: 0.2 },
4181
+ expand: { adjacentChunks: 1, followImports: 1, includeFileSynopsis: true },
4182
+ candidates: { vectorK: 80, lexicalK: 60, maxMergedCandidates: 180 }
4183
+ },
4184
+ architecture: {
4185
+ name: "architecture",
4186
+ k: 20,
4187
+ weights: { vector: 0.7, lexical: 0.2, recency: 0.1 },
4188
+ expand: { adjacentChunks: 0, followImports: 3, includeFileSynopsis: true },
4189
+ candidates: { vectorK: 120, lexicalK: 40, maxMergedCandidates: 220 }
4190
+ },
4191
+ rca: {
4192
+ name: "rca",
4193
+ k: 25,
4194
+ weights: { vector: 0.5, lexical: 0.25, recency: 0.25 },
4195
+ expand: { adjacentChunks: 2, followImports: 1, includeFileSynopsis: true },
4196
+ candidates: { vectorK: 140, lexicalK: 80, maxMergedCandidates: 260 }
4197
+ },
4198
+ custom: {
4199
+ name: "custom",
4200
+ k: 10,
4201
+ weights: { vector: 0.65, lexical: 0.35, recency: 0 },
4202
+ expand: { adjacentChunks: 0, followImports: 0, includeFileSynopsis: false },
4203
+ candidates: { vectorK: 25, lexicalK: 25, maxMergedCandidates: 60 }
4204
+ }
4205
+ };
4206
+ function deepMergeProfile(base, patch) {
4207
+ if (!patch) return base;
4208
+ const merged = {
4209
+ ...base,
4210
+ ...patch,
4211
+ weights: { ...base.weights, ...patch.weights ?? {} },
4212
+ expand: { ...base.expand, ...patch.expand ?? {} },
4213
+ candidates: { ...base.candidates, ...patch.candidates ?? {} }
4214
+ };
4215
+ return merged;
4216
+ }
4217
+
4135
4218
  // src/indexer/workspaceIndexer.ts
4136
- import path17 from "path";
4219
+ import path20 from "path";
4220
+
4221
+ // src/indexer/workspaceRetrieveCandidates.ts
4222
+ import path19 from "path";
4223
+ function resolveWorkspaceProfile(config, opts) {
4224
+ const name = opts?.profile ?? "search";
4225
+ const base = DEFAULT_PROFILES[name] ?? DEFAULT_PROFILES.search;
4226
+ const configPatch = config.profiles?.[name] ?? {};
4227
+ const merged1 = deepMergeProfile(base, configPatch);
4228
+ const merged2 = deepMergeProfile(merged1, opts?.profileOverrides);
4229
+ const w = merged2.weights;
4230
+ const sum = Math.max(1e-6, w.vector + w.lexical + w.recency);
4231
+ merged2.weights = { vector: w.vector / sum, lexical: w.lexical / sum, recency: w.recency / sum };
4232
+ return merged2;
4233
+ }
4137
4234
  function halfLifeDaysForProfile(profileName) {
4138
4235
  if (profileName === "rca") return 7;
4139
4236
  if (profileName === "review") return 14;
4140
4237
  if (profileName === "refactor") return 21;
4141
4238
  return 30;
4142
4239
  }
4240
+ function buildWorkspaceLexByRepoRoot(args) {
4241
+ const { workspaceStore, repos, query, lexicalK, repoFilters } = args;
4242
+ const ftq = ftsQueryFromText(query);
4243
+ if (!ftq) return { lexByRepoRoot: /* @__PURE__ */ new Map(), count: 0 };
4244
+ const allowRoots = repoFilters ? new Set(repoFilters.map((r) => path19.resolve(r))) : null;
4245
+ const repoIds = allowRoots ? repos.filter((r) => allowRoots.has(path19.resolve(r.repoRoot))).map((r) => r.repoId) : void 0;
4246
+ const rows = workspaceStore.searchFts(ftq, lexicalK, repoIds);
4247
+ const lexByRepoRoot = /* @__PURE__ */ new Map();
4248
+ for (const r of rows) {
4249
+ const row = workspaceStore.getChunkById(r.id);
4250
+ if (!row) continue;
4251
+ const rootKey = path19.resolve(row.repo_root);
4252
+ const arr = lexByRepoRoot.get(rootKey) ?? [];
4253
+ arr.push({ id: r.id, score: bm25ToScore01(r.bm25) });
4254
+ lexByRepoRoot.set(rootKey, arr);
4255
+ }
4256
+ return { lexByRepoRoot, count: rows.length };
4257
+ }
4258
+ async function collectWorkspaceCandidates(args) {
4259
+ const { repos, qVec, query, vectorK, lexicalK, profile, opts, lexByRepoRoot, canUseWorkspaceLex } = args;
4260
+ const repoFilters = opts.filters?.repoRoots;
4261
+ const langFilter = opts.filters?.language;
4262
+ const pathPrefix = opts.filters?.pathPrefix;
4263
+ const candidates = [];
4264
+ let vecCount = 0;
4265
+ let lexCount = 0;
4266
+ for (const repo of repos) {
4267
+ if (repoFilters && !repoFilters.includes(repo.repoRoot)) continue;
4268
+ let includePaths = opts.scope?.includePaths?.slice();
4269
+ if (opts.scope?.changedOnly) {
4270
+ try {
4271
+ const changed = await listChangedFiles(repo.repoRoot, opts.scope.baseRef ?? "HEAD~1");
4272
+ includePaths = includePaths ? includePaths.filter((p) => changed.includes(p)) : changed;
4273
+ } catch {
4274
+ }
4275
+ }
4276
+ const [vHits, lHits] = await Promise.all([
4277
+ repo.vectorCandidates(qVec, vectorK, includePaths),
4278
+ canUseWorkspaceLex ? Promise.resolve(lexByRepoRoot?.get(path19.resolve(repo.repoRoot)) ?? []) : repo.lexicalCandidates(query, lexicalK, includePaths)
4279
+ ]);
4280
+ vecCount += vHits.length;
4281
+ if (!canUseWorkspaceLex) lexCount += lHits.length;
4282
+ const m = /* @__PURE__ */ new Map();
4283
+ for (const vh of vHits) {
4284
+ const id = vh.id;
4285
+ const vector01 = vectorCosineToScore01(vh.score);
4286
+ m.set(id, { repo, id, vector01, combined: 0 });
4287
+ }
4288
+ for (const lh of lHits) {
4289
+ const id = lh.id;
4290
+ const prev = m.get(id);
4291
+ if (prev) prev.lexical01 = lh.score;
4292
+ else m.set(id, { repo, id, lexical01: lh.score, combined: 0 });
4293
+ }
4294
+ const halfLife = halfLifeDaysForProfile(profile.name);
4295
+ for (const c of m.values()) {
4296
+ const meta = repo.getChunkMeta(c.id);
4297
+ if (!meta) continue;
4298
+ if (langFilter && meta.language !== langFilter) continue;
4299
+ if (pathPrefix && !meta.path.startsWith(pathPrefix)) continue;
4300
+ c.recency01 = profile.weights.recency > 0 ? recencyScore(meta.fileMtimeMs, halfLife) : 0;
4301
+ let kindFactor = 1;
4302
+ if (meta.kind === "synopsis" && profile.name === "search") kindFactor = 0.85;
4303
+ if (meta.kind === "synopsis" && profile.name === "architecture") kindFactor = 1.05;
4304
+ const v = c.vector01 ?? 0;
4305
+ const l = c.lexical01 ?? 0;
4306
+ const r = c.recency01 ?? 0;
4307
+ c.combined = clamp(kindFactor * (profile.weights.vector * v + profile.weights.lexical * l + profile.weights.recency * r), 0, 1);
4308
+ candidates.push(c);
4309
+ }
4310
+ }
4311
+ return { candidates, vecCount, lexCount };
4312
+ }
4313
+ function rankWorkspaceCandidates(args) {
4314
+ const { candidates, maxMerged, k } = args;
4315
+ candidates.sort((a, b) => b.combined - a.combined);
4316
+ const merged = candidates.slice(0, maxMerged);
4317
+ const top = merged.slice(0, k);
4318
+ const hits = top.map((c) => {
4319
+ const meta = c.repo.getChunkMeta(c.id);
4320
+ const preview = makePreview(c.repo.getChunkText(c.id));
4321
+ return {
4322
+ score: c.combined,
4323
+ scoreBreakdown: { vector: c.vector01, lexical: c.lexical01, recency: c.recency01 },
4324
+ chunk: { ...meta, preview }
4325
+ };
4326
+ });
4327
+ return { merged, hits };
4328
+ }
4329
+
4330
+ // src/indexer/workspaceRetrieveContext.ts
4331
+ async function warmSymbolGraphForHits(repos, hits) {
4332
+ const byRepo = /* @__PURE__ */ new Map();
4333
+ for (const h of hits) {
4334
+ const s = byRepo.get(h.chunk.repoRoot) ?? /* @__PURE__ */ new Set();
4335
+ s.add(h.chunk.path);
4336
+ byRepo.set(h.chunk.repoRoot, s);
4337
+ }
4338
+ for (const [repoRoot, paths] of byRepo) {
4339
+ const repo = repos.find((r) => r.repoRoot === repoRoot);
4340
+ if (!repo) continue;
4341
+ await repo.warmSymbolGraphEdges(Array.from(paths), { maxFiles: 6 });
4342
+ }
4343
+ }
4344
+ async function fetchGraphNeighborFiles(args) {
4345
+ const { graphStore, repos, hits, profile, workspaceRoot, emitProgress } = args;
4346
+ if (!graphStore?.neighborFiles) return [];
4347
+ const seeds = [];
4348
+ const seen = /* @__PURE__ */ new Set();
4349
+ for (const h of hits) {
4350
+ const repo = repos.find((r) => r.repoRoot === h.chunk.repoRoot);
4351
+ if (!repo) continue;
4352
+ const key = `${repo.repoId}:${h.chunk.path}`;
4353
+ if (seen.has(key)) continue;
4354
+ seen.add(key);
4355
+ seeds.push({ repoId: repo.repoId, path: h.chunk.path });
4356
+ if (seeds.length >= 4) break;
4357
+ }
4358
+ if (seeds.length === 0) return [];
4359
+ const startedAt = Date.now();
4360
+ emitProgress({ type: "workspace/retrieve/graph/start", workspaceRoot, seeds: seeds.length });
4361
+ try {
4362
+ const neighbors = await graphStore.neighborFiles({
4363
+ seeds,
4364
+ limit: profile.name === "architecture" ? 16 : 10,
4365
+ kinds: ["definition", "reference", "implementation", "typeDefinition"]
4366
+ });
4367
+ emitProgress({ type: "workspace/retrieve/graph/done", workspaceRoot, neighbors: neighbors.length, ms: Date.now() - startedAt });
4368
+ return neighbors;
4369
+ } catch {
4370
+ return [];
4371
+ }
4372
+ }
4373
+ async function buildContextBlocks(args) {
4374
+ const { repos, hits, graphNeighborFiles, profile } = args;
4375
+ const contextBlocks = [];
4376
+ const seenKey = /* @__PURE__ */ new Set();
4377
+ const addBlock = (repoRoot, filePath, startLine, endLine, text, reason) => {
4378
+ const key = `${repoRoot}:${filePath}:${startLine}:${endLine}:${text.length}:${reason}`;
4379
+ if (seenKey.has(key)) return;
4380
+ seenKey.add(key);
4381
+ if (!text.trim()) return;
4382
+ contextBlocks.push({ repoRoot, path: filePath, startLine, endLine, text, reason });
4383
+ };
4384
+ try {
4385
+ const byRepoId = /* @__PURE__ */ new Map();
4386
+ for (const r of repos) byRepoId.set(r.repoId, r);
4387
+ for (const n of graphNeighborFiles.slice(0, 10)) {
4388
+ const repo = byRepoId.get(n.repoId);
4389
+ if (!repo) continue;
4390
+ const chunkId = await repo.getRepresentativeChunkIdForFile(n.path, true);
4391
+ if (!chunkId) continue;
4392
+ const meta = repo.getChunkMeta(chunkId);
4393
+ if (!meta) continue;
4394
+ const text = repo.getChunkText(chunkId);
4395
+ addBlock(meta.repoRoot, meta.path, meta.startLine, meta.endLine, text, `graph neighbor (${n.weight})`);
4396
+ }
4397
+ } catch {
4398
+ }
4399
+ for (const h of hits) {
4400
+ const repo = repos.find((r) => r.repoRoot === h.chunk.repoRoot);
4401
+ if (!repo) continue;
4402
+ const hitText = repo.getChunkText(h.chunk.id);
4403
+ addBlock(h.chunk.repoRoot, h.chunk.path, h.chunk.startLine, h.chunk.endLine, hitText, "primary hit");
4404
+ const expanded = await repo.expandContext(h.chunk.id, {
4405
+ adjacentChunks: profile.expand.adjacentChunks ?? 0,
4406
+ followImports: profile.expand.followImports ?? 0,
4407
+ includeFileSynopsis: profile.expand.includeFileSynopsis ?? false
4408
+ });
4409
+ for (const ex of expanded) {
4410
+ const meta = repo.getChunkMeta(ex.id);
4411
+ if (!meta) continue;
4412
+ const text = repo.getChunkText(ex.id);
4413
+ addBlock(meta.repoRoot, meta.path, meta.startLine, meta.endLine, text, ex.reason);
4414
+ }
4415
+ }
4416
+ return contextBlocks;
4417
+ }
4418
+
4419
+ // src/indexer/workspaceIndexer.ts
4143
4420
  var WorkspaceIndexer = class {
4144
4421
  constructor(workspaceRoot, embedder, config = {}) {
4145
4422
  this.workspaceRoot = workspaceRoot;
@@ -4147,23 +4424,30 @@ var WorkspaceIndexer = class {
4147
4424
  this.config = { ...config };
4148
4425
  if (!this.config.cacheDir) this.config.cacheDir = defaultCacheDir();
4149
4426
  this.progress = asProgressSink(this.config.progress);
4150
- const wsId = sha256Hex(path17.resolve(this.workspaceRoot)).slice(0, 16);
4151
- const dbPath = path17.join(this.config.cacheDir, "workspace", wsId, "workspace.sqlite");
4152
- this.workspaceStore = new WorkspaceStore(dbPath);
4153
- this.workspaceStore.setMeta("workspaceRoot", path17.resolve(this.workspaceRoot));
4427
+ const wsId = sha256Hex(path20.resolve(this.workspaceRoot)).slice(0, 16);
4428
+ this.workspaceDbPath = path20.join(this.config.cacheDir, "workspace", wsId, "workspace.sqlite");
4154
4429
  }
4155
4430
  repos = [];
4156
4431
  config;
4157
4432
  progress = asProgressSink();
4158
4433
  workspaceStore = null;
4159
4434
  graphStore = null;
4435
+ workspaceDbPath;
4160
4436
  emitProgress(event) {
4161
4437
  try {
4162
4438
  this.progress?.emit(event);
4163
4439
  } catch {
4164
4440
  }
4165
4441
  }
4442
+ async ensureWorkspaceStore() {
4443
+ if (this.workspaceStore) return this.workspaceStore;
4444
+ const ws = await createWorkspaceStoreAsync(this.workspaceDbPath, { db: this.config.workspace?.db });
4445
+ ws.setMeta("workspaceRoot", path20.resolve(this.workspaceRoot));
4446
+ this.workspaceStore = ws;
4447
+ return ws;
4448
+ }
4166
4449
  async open() {
4450
+ await this.ensureWorkspaceStore();
4167
4451
  if (!this.graphStore && this.config.workspace?.graph?.provider === "neo4j") {
4168
4452
  try {
4169
4453
  const n = this.config.workspace.graph.neo4j;
@@ -4235,195 +4519,49 @@ var WorkspaceIndexer = class {
4235
4519
  getRepoIndexers() {
4236
4520
  return this.repos.slice();
4237
4521
  }
4238
- resolveProfile(opts) {
4239
- const name = opts?.profile ?? "search";
4240
- const base = DEFAULT_PROFILES[name] ?? DEFAULT_PROFILES.search;
4241
- const configPatch = this.config.profiles?.[name] ?? {};
4242
- const merged1 = deepMergeProfile(base, configPatch);
4243
- const merged2 = deepMergeProfile(merged1, opts?.profileOverrides);
4244
- const w = merged2.weights;
4245
- const sum = Math.max(1e-6, w.vector + w.lexical + w.recency);
4246
- merged2.weights = { vector: w.vector / sum, lexical: w.lexical / sum, recency: w.recency / sum };
4247
- return merged2;
4248
- }
4249
4522
  async retrieve(query, opts = {}) {
4250
4523
  if (this.repos.length === 0) await this.open();
4251
- const profile = this.resolveProfile(opts);
4524
+ const profile = resolveWorkspaceProfile(this.config, opts);
4252
4525
  const startedAt = Date.now();
4253
4526
  this.emitProgress({ type: "workspace/retrieve/start", workspaceRoot: this.workspaceRoot, profile: profile.name, query });
4254
4527
  const qVec = (await this.embedder.embed([query]))[0];
4255
4528
  const vectorK = profile.candidates?.vectorK ?? Math.max(profile.k * 3, 30);
4256
4529
  const lexicalK = profile.candidates?.lexicalK ?? Math.max(profile.k * 3, 30);
4257
4530
  const maxMerged = profile.candidates?.maxMergedCandidates ?? Math.max(profile.k * 8, 120);
4258
- const repoFilters = opts.filters?.repoRoots;
4259
- const langFilter = opts.filters?.language;
4260
- const pathPrefix = opts.filters?.pathPrefix;
4261
- const candidates = [];
4262
- let vecCount = 0;
4263
- let lexCount = 0;
4264
- const canUseWorkspaceLex = !!this.workspaceStore && this.config.storage?.ftsMode !== "off" && !opts.scope?.includePaths && !opts.scope?.changedOnly;
4265
- const workspaceLexByRepoRoot = /* @__PURE__ */ new Map();
4266
- if (canUseWorkspaceLex && profile.weights.lexical > 0) {
4267
- const ftq = ftsQueryFromText(query);
4268
- const allowRoots = repoFilters ? new Set(repoFilters.map((r) => path17.resolve(r))) : null;
4269
- const repoIds = allowRoots ? this.repos.filter((r) => allowRoots.has(path17.resolve(r.repoRoot))).map((r) => r.repoId) : void 0;
4270
- if (ftq) {
4271
- const rows = this.workspaceStore.searchFts(ftq, lexicalK, repoIds);
4272
- lexCount += rows.length;
4273
- for (const r of rows) {
4274
- const row = this.workspaceStore.getChunkById(r.id);
4275
- if (!row) continue;
4276
- const rootKey = path17.resolve(row.repo_root);
4277
- const arr = workspaceLexByRepoRoot.get(rootKey) ?? [];
4278
- arr.push({ id: r.id, score: bm25ToScore01(r.bm25) });
4279
- workspaceLexByRepoRoot.set(rootKey, arr);
4280
- }
4281
- }
4282
- }
4283
- for (const repo of this.repos) {
4284
- if (repoFilters && !repoFilters.includes(repo.repoRoot)) continue;
4285
- let includePaths = opts.scope?.includePaths?.slice();
4286
- if (opts.scope?.changedOnly) {
4287
- try {
4288
- const changed = await listChangedFiles(repo.repoRoot, opts.scope.baseRef ?? "HEAD~1");
4289
- includePaths = includePaths ? includePaths.filter((p) => changed.includes(p)) : changed;
4290
- } catch {
4291
- }
4292
- }
4293
- const [vHits, lHits] = await Promise.all([
4294
- repo.vectorCandidates(qVec, vectorK, includePaths),
4295
- canUseWorkspaceLex ? Promise.resolve(workspaceLexByRepoRoot.get(path17.resolve(repo.repoRoot)) ?? []) : repo.lexicalCandidates(query, lexicalK, includePaths)
4296
- ]);
4297
- vecCount += vHits.length;
4298
- if (!canUseWorkspaceLex) lexCount += lHits.length;
4299
- const m = /* @__PURE__ */ new Map();
4300
- for (const vh of vHits) {
4301
- const id = vh.id;
4302
- const vector01 = vectorCosineToScore01(vh.score);
4303
- m.set(id, { repo, id, vector01, combined: 0 });
4304
- }
4305
- for (const lh of lHits) {
4306
- const id = lh.id;
4307
- const prev = m.get(id);
4308
- if (prev) prev.lexical01 = lh.score;
4309
- else m.set(id, { repo, id, lexical01: lh.score, combined: 0 });
4310
- }
4311
- const halfLife = halfLifeDaysForProfile(profile.name);
4312
- for (const c of m.values()) {
4313
- const meta = repo.getChunkMeta(c.id);
4314
- if (!meta) continue;
4315
- if (langFilter && meta.language !== langFilter) continue;
4316
- if (pathPrefix && !meta.path.startsWith(pathPrefix)) continue;
4317
- c.recency01 = profile.weights.recency > 0 ? recencyScore(meta.fileMtimeMs, halfLife) : 0;
4318
- let kindFactor = 1;
4319
- if (meta.kind === "synopsis" && profile.name === "search") kindFactor = 0.85;
4320
- if (meta.kind === "synopsis" && profile.name === "architecture") kindFactor = 1.05;
4321
- const v = c.vector01 ?? 0;
4322
- const l = c.lexical01 ?? 0;
4323
- const r = c.recency01 ?? 0;
4324
- c.combined = clamp(
4325
- kindFactor * (profile.weights.vector * v + profile.weights.lexical * l + profile.weights.recency * r),
4326
- 0,
4327
- 1
4328
- );
4329
- candidates.push(c);
4330
- }
4331
- }
4332
- candidates.sort((a, b) => b.combined - a.combined);
4333
- const merged = candidates.slice(0, maxMerged);
4334
- const top = merged.slice(0, profile.k);
4335
- const hits = top.map((c) => {
4336
- const meta = c.repo.getChunkMeta(c.id);
4337
- const preview = makePreview(c.repo.getChunkText(c.id));
4338
- return {
4339
- score: c.combined,
4340
- scoreBreakdown: { vector: c.vector01, lexical: c.lexical01, recency: c.recency01 },
4341
- chunk: { ...meta, preview }
4342
- };
4531
+ const canUseWorkspaceLex = !!this.workspaceStore && this.workspaceStore.ftsEnabled && this.config.storage?.ftsMode !== "off" && !opts.scope?.includePaths && !opts.scope?.changedOnly;
4532
+ const { lexByRepoRoot, count: workspaceLexCount } = canUseWorkspaceLex && profile.weights.lexical > 0 && this.workspaceStore ? buildWorkspaceLexByRepoRoot({
4533
+ workspaceStore: this.workspaceStore,
4534
+ repos: this.repos,
4535
+ query,
4536
+ lexicalK,
4537
+ repoFilters: opts.filters?.repoRoots
4538
+ }) : { lexByRepoRoot: void 0, count: 0 };
4539
+ const { candidates, vecCount, lexCount } = await collectWorkspaceCandidates({
4540
+ repos: this.repos,
4541
+ qVec,
4542
+ query,
4543
+ vectorK,
4544
+ lexicalK,
4545
+ profile,
4546
+ opts,
4547
+ lexByRepoRoot,
4548
+ canUseWorkspaceLex
4343
4549
  });
4550
+ const { merged, hits } = rankWorkspaceCandidates({ candidates, maxMerged, k: profile.k });
4551
+ const totalLexCount = (canUseWorkspaceLex ? workspaceLexCount : 0) + lexCount;
4344
4552
  try {
4345
- const byRepo = /* @__PURE__ */ new Map();
4346
- for (const h of hits) {
4347
- const s = byRepo.get(h.chunk.repoRoot) ?? /* @__PURE__ */ new Set();
4348
- s.add(h.chunk.path);
4349
- byRepo.set(h.chunk.repoRoot, s);
4350
- }
4351
- for (const [repoRoot, paths] of byRepo) {
4352
- const repo = this.repos.find((r) => r.repoRoot === repoRoot);
4353
- if (!repo) continue;
4354
- await repo.warmSymbolGraphEdges(Array.from(paths), { maxFiles: 6 });
4355
- }
4356
- } catch {
4357
- }
4358
- let graphNeighborFiles = [];
4359
- try {
4360
- if (this.graphStore?.neighborFiles) {
4361
- const seeds = [];
4362
- const seen = /* @__PURE__ */ new Set();
4363
- for (const h of hits) {
4364
- const repo = this.repos.find((r) => r.repoRoot === h.chunk.repoRoot);
4365
- if (!repo) continue;
4366
- const key = `${repo.repoId}:${h.chunk.path}`;
4367
- if (seen.has(key)) continue;
4368
- seen.add(key);
4369
- seeds.push({ repoId: repo.repoId, path: h.chunk.path });
4370
- if (seeds.length >= 4) break;
4371
- }
4372
- if (seeds.length > 0) {
4373
- const gs = Date.now();
4374
- this.emitProgress({ type: "workspace/retrieve/graph/start", workspaceRoot: this.workspaceRoot, seeds: seeds.length });
4375
- graphNeighborFiles = await this.graphStore.neighborFiles({
4376
- seeds,
4377
- limit: profile.name === "architecture" ? 16 : 10,
4378
- kinds: ["definition", "reference", "implementation", "typeDefinition"]
4379
- });
4380
- this.emitProgress({ type: "workspace/retrieve/graph/done", workspaceRoot: this.workspaceRoot, neighbors: graphNeighborFiles.length, ms: Date.now() - gs });
4381
- }
4382
- }
4553
+ await warmSymbolGraphForHits(this.repos, hits);
4383
4554
  } catch {
4384
- graphNeighborFiles = [];
4385
- }
4386
- const contextBlocks = [];
4387
- const seenKey = /* @__PURE__ */ new Set();
4388
- const addBlock = (repoRoot, path19, startLine, endLine, text, reason) => {
4389
- const key = `${repoRoot}:${path19}:${startLine}:${endLine}:${text.length}:${reason}`;
4390
- if (seenKey.has(key)) return;
4391
- seenKey.add(key);
4392
- if (!text.trim()) return;
4393
- contextBlocks.push({ repoRoot, path: path19, startLine, endLine, text, reason });
4394
- };
4395
- try {
4396
- const byRepoId = /* @__PURE__ */ new Map();
4397
- for (const r of this.repos) byRepoId.set(r.repoId, r);
4398
- for (const n of graphNeighborFiles.slice(0, 10)) {
4399
- const repo = byRepoId.get(n.repoId);
4400
- if (!repo) continue;
4401
- const chunkId = await repo.getRepresentativeChunkIdForFile(n.path, true);
4402
- if (!chunkId) continue;
4403
- const meta = repo.getChunkMeta(chunkId);
4404
- if (!meta) continue;
4405
- const text = repo.getChunkText(chunkId);
4406
- addBlock(meta.repoRoot, meta.path, meta.startLine, meta.endLine, text, `graph neighbor (${n.weight})`);
4407
- }
4408
- } catch {
4409
- }
4410
- for (const h of hits) {
4411
- const repo = this.repos.find((r) => r.repoRoot === h.chunk.repoRoot);
4412
- if (!repo) continue;
4413
- const text = repo.getChunkText(h.chunk.id);
4414
- addBlock(h.chunk.repoRoot, h.chunk.path, h.chunk.startLine, h.chunk.endLine, text, "primary hit");
4415
- const expanded = await repo.expandContext(h.chunk.id, {
4416
- adjacentChunks: profile.expand.adjacentChunks ?? 0,
4417
- followImports: profile.expand.followImports ?? 0,
4418
- includeFileSynopsis: profile.expand.includeFileSynopsis ?? false
4419
- });
4420
- for (const ex of expanded) {
4421
- const meta = repo.getChunkMeta(ex.id);
4422
- if (!meta) continue;
4423
- const t = repo.getChunkText(ex.id);
4424
- addBlock(meta.repoRoot, meta.path, meta.startLine, meta.endLine, t, ex.reason);
4425
- }
4426
4555
  }
4556
+ const graphNeighborFiles = await fetchGraphNeighborFiles({
4557
+ graphStore: this.graphStore,
4558
+ repos: this.repos,
4559
+ hits,
4560
+ profile,
4561
+ workspaceRoot: this.workspaceRoot,
4562
+ emitProgress: (e) => this.emitProgress(e)
4563
+ });
4564
+ const contextBlocks = await buildContextBlocks({ repos: this.repos, hits, graphNeighborFiles, profile });
4427
4565
  const bundle = {
4428
4566
  hits,
4429
4567
  context: contextBlocks,
@@ -4432,7 +4570,7 @@ var WorkspaceIndexer = class {
4432
4570
  reposSearched: this.repos.length,
4433
4571
  candidates: {
4434
4572
  vector: vecCount,
4435
- lexical: lexCount,
4573
+ lexical: totalLexCount,
4436
4574
  merged: merged.length,
4437
4575
  returned: hits.length
4438
4576
  }
@@ -4444,7 +4582,7 @@ var WorkspaceIndexer = class {
4444
4582
  profile: profile.name,
4445
4583
  ms: Date.now() - startedAt,
4446
4584
  hits: hits.length,
4447
- candidates: { vector: vecCount, lexical: lexCount, merged: merged.length }
4585
+ candidates: { vector: vecCount, lexical: totalLexCount, merged: merged.length }
4448
4586
  });
4449
4587
  return bundle;
4450
4588
  }
@@ -4581,11 +4719,11 @@ var HashEmbeddingsProvider = class {
4581
4719
  };
4582
4720
 
4583
4721
  // src/config.ts
4584
- import fs14 from "fs";
4585
- import path18 from "path";
4722
+ import fs16 from "fs";
4723
+ import path21 from "path";
4586
4724
  function loadConfigFile(filePath) {
4587
- const abs = path18.resolve(filePath);
4588
- const raw = fs14.readFileSync(abs, "utf8");
4725
+ const abs = path21.resolve(filePath);
4726
+ const raw = fs16.readFileSync(abs, "utf8");
4589
4727
  const json = JSON.parse(raw);
4590
4728
  const cfg = { ...json };
4591
4729
  if (json.redact?.patterns && Array.isArray(json.redact.patterns)) {
@@ -4615,11 +4753,13 @@ export {
4615
4753
  NoopAnnIndex,
4616
4754
  createAnnIndex,
4617
4755
  RepoIndexer,
4618
- DEFAULT_PROFILES,
4619
- deepMergeProfile,
4620
4756
  discoverGitRepos,
4621
4757
  pickRepoOverride,
4622
4758
  mergeIndexerConfig,
4759
+ betterSqlite3Adapter,
4760
+ sqlJsAdapter,
4761
+ createWorkspaceStore,
4762
+ createWorkspaceStoreAsync,
4623
4763
  WorkspaceStore,
4624
4764
  Neo4jGraphStore,
4625
4765
  createNeo4jGraphStore,
@@ -4629,6 +4769,8 @@ export {
4629
4769
  VsCodeContributesLanguageLinkStrategy,
4630
4770
  WorkspaceLinker,
4631
4771
  linkWorkspaceRepos,
4772
+ DEFAULT_PROFILES,
4773
+ deepMergeProfile,
4632
4774
  WorkspaceIndexer,
4633
4775
  OllamaEmbeddingsProvider,
4634
4776
  OpenAIEmbeddingsProvider,