@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.
- package/dist/{chunk-FUUQXFJQ.js → chunk-TQTWTPPG.js} +563 -421
- package/dist/cli.cjs +506 -377
- package/dist/cli.js +1 -1
- package/dist/index.cjs +517 -379
- package/dist/index.d.cts +57 -3
- package/dist/index.d.ts +57 -3
- package/dist/index.js +9 -1
- package/package.json +6 -3
|
@@ -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/
|
|
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
|
|
2993
|
-
import
|
|
3114
|
+
import fs13 from "fs";
|
|
3115
|
+
import path16 from "path";
|
|
2994
3116
|
|
|
2995
|
-
// src/store/workspace/
|
|
2996
|
-
|
|
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
|
-
|
|
3139
|
-
|
|
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 =
|
|
3391
|
+
const abs = path17.join(row.repo_root, row.path.split("/").join(path17.sep));
|
|
3366
3392
|
try {
|
|
3367
|
-
const raw =
|
|
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
|
|
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 ${
|
|
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 ${
|
|
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:${
|
|
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:${
|
|
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:${
|
|
3563
|
-
MERGE (b:${
|
|
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:${
|
|
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:${
|
|
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:${
|
|
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:${
|
|
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:${
|
|
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:${
|
|
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:${
|
|
3688
|
-
MERGE (b:${
|
|
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:${
|
|
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:${
|
|
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:${
|
|
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 =
|
|
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
|
|
3838
|
-
import
|
|
3863
|
+
import fs15 from "fs";
|
|
3864
|
+
import path18 from "path";
|
|
3839
3865
|
function readText(absPath) {
|
|
3840
3866
|
try {
|
|
3841
|
-
return
|
|
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 +
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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:
|
|
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(
|
|
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
|
|
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(
|
|
4151
|
-
|
|
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.
|
|
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
|
|
4259
|
-
const
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
4585
|
-
import
|
|
4722
|
+
import fs16 from "fs";
|
|
4723
|
+
import path21 from "path";
|
|
4586
4724
|
function loadConfigFile(filePath) {
|
|
4587
|
-
const abs =
|
|
4588
|
-
const raw =
|
|
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,
|