@vohongtho.infotech/code-intel 1.0.1 → 1.0.3
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/README.md +34 -4
- package/dist/cli/hook.js +451 -0
- package/dist/cli/hook.js.map +1 -0
- package/dist/cli/main.js +3254 -904
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/router.js +35 -0
- package/dist/cli/router.js.map +1 -0
- package/dist/cli/search.js +244 -0
- package/dist/cli/search.js.map +1 -0
- package/dist/index.d.ts +29 -2
- package/dist/index.js +675 -232
- package/dist/index.js.map +1 -1
- package/dist/web/assets/es-DOPp2DTx.js +10 -0
- package/dist/web/assets/index-BPmJG_ti.css +2 -0
- package/dist/web/assets/index-gqp6A4LM.js +354 -0
- package/dist/web/index.html +2 -2
- package/package.json +8 -6
- package/dist/web/assets/es-Bu8iwdFw.js +0 -10
- package/dist/web/assets/index-C9M6YLlS.css +0 -2
- package/dist/web/assets/index-CKc3HEpe.js +0 -354
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { fileURLToPath } from 'url';
|
|
|
14
14
|
import { Parser, Language, Query } from 'web-tree-sitter';
|
|
15
15
|
import { Database, Connection } from '@ladybugdb/core';
|
|
16
16
|
import { execSync } from 'child_process';
|
|
17
|
-
import
|
|
17
|
+
import Database3 from 'better-sqlite3';
|
|
18
18
|
import bcrypt from 'bcrypt';
|
|
19
19
|
import crypto5 from 'crypto';
|
|
20
20
|
import { v4 } from 'uuid';
|
|
@@ -198,6 +198,13 @@ var init_id_generator = __esm({
|
|
|
198
198
|
});
|
|
199
199
|
|
|
200
200
|
// src/storage/schema.ts
|
|
201
|
+
var schema_exports = {};
|
|
202
|
+
__export(schema_exports, {
|
|
203
|
+
ALL_NODE_TABLES: () => ALL_NODE_TABLES,
|
|
204
|
+
NODE_TABLE_MAP: () => NODE_TABLE_MAP,
|
|
205
|
+
getCreateEdgeTableDDL: () => getCreateEdgeTableDDL,
|
|
206
|
+
getCreateNodeTableDDL: () => getCreateNodeTableDDL
|
|
207
|
+
});
|
|
201
208
|
function getCreateNodeTableDDL(tableName) {
|
|
202
209
|
return `CREATE NODE TABLE IF NOT EXISTS ${tableName} (
|
|
203
210
|
id STRING,
|
|
@@ -528,7 +535,7 @@ var init_logger = __esm({
|
|
|
528
535
|
const isProduction = process.env.NODE_ENV === "production";
|
|
529
536
|
const logLevel = process.env.LOG_LEVEL ?? "info";
|
|
530
537
|
const transports = [];
|
|
531
|
-
transports.push(new winston.transports.Console());
|
|
538
|
+
transports.push(new winston.transports.Console({ stderrLevels: ["error", "warn", "info", "http", "verbose", "debug", "silly"] }));
|
|
532
539
|
if (!isProduction) {
|
|
533
540
|
try {
|
|
534
541
|
if (!fs26.existsSync(_Logger.LOG_DIR)) {
|
|
@@ -2580,6 +2587,65 @@ var init_resolve_phase = __esm({
|
|
|
2580
2587
|
}
|
|
2581
2588
|
});
|
|
2582
2589
|
|
|
2590
|
+
// src/storage/db-manager.ts
|
|
2591
|
+
var db_manager_exports = {};
|
|
2592
|
+
__export(db_manager_exports, {
|
|
2593
|
+
DbManager: () => DbManager
|
|
2594
|
+
});
|
|
2595
|
+
var DbManager;
|
|
2596
|
+
var init_db_manager = __esm({
|
|
2597
|
+
"src/storage/db-manager.ts"() {
|
|
2598
|
+
DbManager = class {
|
|
2599
|
+
db = null;
|
|
2600
|
+
conn = null;
|
|
2601
|
+
dbPath;
|
|
2602
|
+
readOnly;
|
|
2603
|
+
constructor(dbPath, readOnly = false) {
|
|
2604
|
+
this.dbPath = dbPath;
|
|
2605
|
+
this.readOnly = readOnly;
|
|
2606
|
+
}
|
|
2607
|
+
async init() {
|
|
2608
|
+
if (!this.readOnly) {
|
|
2609
|
+
fs26.mkdirSync(path32.dirname(this.dbPath), { recursive: true });
|
|
2610
|
+
}
|
|
2611
|
+
this.db = new Database(this.dbPath, 0, true, this.readOnly);
|
|
2612
|
+
await this.db.init();
|
|
2613
|
+
this.conn = new Connection(this.db);
|
|
2614
|
+
await this.conn.init();
|
|
2615
|
+
}
|
|
2616
|
+
async query(cypher) {
|
|
2617
|
+
if (!this.conn) throw new Error("Database not initialized");
|
|
2618
|
+
const result = await this.conn.query(cypher);
|
|
2619
|
+
const qr = Array.isArray(result) ? result[0] : result;
|
|
2620
|
+
const rows = await qr.getAll();
|
|
2621
|
+
qr.close();
|
|
2622
|
+
return rows;
|
|
2623
|
+
}
|
|
2624
|
+
async execute(cypher) {
|
|
2625
|
+
if (!this.conn) throw new Error("Database not initialized");
|
|
2626
|
+
const result = await this.conn.query(cypher);
|
|
2627
|
+
const qr = Array.isArray(result) ? result[0] : result;
|
|
2628
|
+
qr.close();
|
|
2629
|
+
}
|
|
2630
|
+
close() {
|
|
2631
|
+
try {
|
|
2632
|
+
this.conn?.closeSync();
|
|
2633
|
+
} catch {
|
|
2634
|
+
}
|
|
2635
|
+
try {
|
|
2636
|
+
this.db?.closeSync();
|
|
2637
|
+
} catch {
|
|
2638
|
+
}
|
|
2639
|
+
this.conn = null;
|
|
2640
|
+
this.db = null;
|
|
2641
|
+
}
|
|
2642
|
+
get isOpen() {
|
|
2643
|
+
return this.conn !== null;
|
|
2644
|
+
}
|
|
2645
|
+
};
|
|
2646
|
+
}
|
|
2647
|
+
});
|
|
2648
|
+
|
|
2583
2649
|
// src/llm/providers/openai.ts
|
|
2584
2650
|
var openai_exports = {};
|
|
2585
2651
|
__export(openai_exports, {
|
|
@@ -2590,18 +2656,42 @@ var init_openai = __esm({
|
|
|
2590
2656
|
"src/llm/providers/openai.ts"() {
|
|
2591
2657
|
OpenAIProvider = class {
|
|
2592
2658
|
modelName;
|
|
2593
|
-
|
|
2659
|
+
endpoint;
|
|
2660
|
+
apiKey;
|
|
2661
|
+
constructor(model, baseUrl, apiKey) {
|
|
2594
2662
|
this.modelName = model ?? "gpt-4o-mini";
|
|
2663
|
+
this.endpoint = (baseUrl ?? "https://api.openai.com/v1").replace(/\/$/, "");
|
|
2664
|
+
this.apiKey = apiKey ?? "";
|
|
2665
|
+
}
|
|
2666
|
+
resolvedKey() {
|
|
2667
|
+
return this.apiKey || process.env["OPENAI_API_KEY"] || "";
|
|
2668
|
+
}
|
|
2669
|
+
async getContextWindow() {
|
|
2670
|
+
try {
|
|
2671
|
+
const res = await fetch(`${this.endpoint}/models/${encodeURIComponent(this.modelName)}`, {
|
|
2672
|
+
headers: { "Authorization": `Bearer ${this.resolvedKey()}` },
|
|
2673
|
+
signal: AbortSignal.timeout(5e3)
|
|
2674
|
+
});
|
|
2675
|
+
if (!res.ok) return void 0;
|
|
2676
|
+
const json = await res.json();
|
|
2677
|
+
return json.context_window;
|
|
2678
|
+
} catch {
|
|
2679
|
+
return void 0;
|
|
2680
|
+
}
|
|
2595
2681
|
}
|
|
2596
2682
|
async summarize(prompt) {
|
|
2597
2683
|
const { default: OpenAI } = await import('openai');
|
|
2598
|
-
const client = new OpenAI({ apiKey:
|
|
2684
|
+
const client = new OpenAI({ apiKey: this.resolvedKey(), baseURL: this.endpoint });
|
|
2599
2685
|
const res = await client.chat.completions.create({
|
|
2600
2686
|
model: this.modelName,
|
|
2601
2687
|
messages: [{ role: "user", content: prompt }],
|
|
2602
|
-
max_tokens:
|
|
2688
|
+
max_tokens: 500
|
|
2603
2689
|
});
|
|
2604
|
-
return
|
|
2690
|
+
return {
|
|
2691
|
+
text: res.choices?.[0]?.message?.content?.trim() ?? "",
|
|
2692
|
+
promptTokens: res.usage?.prompt_tokens ?? 0,
|
|
2693
|
+
completionTokens: res.usage?.completion_tokens ?? 0
|
|
2694
|
+
};
|
|
2605
2695
|
}
|
|
2606
2696
|
};
|
|
2607
2697
|
}
|
|
@@ -2612,24 +2702,138 @@ var anthropic_exports = {};
|
|
|
2612
2702
|
__export(anthropic_exports, {
|
|
2613
2703
|
AnthropicProvider: () => AnthropicProvider
|
|
2614
2704
|
});
|
|
2615
|
-
var AnthropicProvider;
|
|
2705
|
+
var ANTHROPIC_CONTEXT, AnthropicProvider;
|
|
2616
2706
|
var init_anthropic = __esm({
|
|
2617
2707
|
"src/llm/providers/anthropic.ts"() {
|
|
2708
|
+
ANTHROPIC_CONTEXT = {
|
|
2709
|
+
"claude-3-5-sonnet-20241022": 2e5,
|
|
2710
|
+
"claude-3-5-haiku-20241022": 2e5,
|
|
2711
|
+
"claude-3-opus-20240229": 2e5,
|
|
2712
|
+
"claude-3-sonnet-20240229": 2e5,
|
|
2713
|
+
"claude-3-haiku-20240307": 2e5,
|
|
2714
|
+
"claude-haiku-4-5": 2e5,
|
|
2715
|
+
"claude-sonnet-4-5": 2e5,
|
|
2716
|
+
"claude-opus-4-5": 2e5
|
|
2717
|
+
};
|
|
2618
2718
|
AnthropicProvider = class {
|
|
2619
2719
|
modelName;
|
|
2620
|
-
|
|
2720
|
+
endpoint;
|
|
2721
|
+
apiKey;
|
|
2722
|
+
constructor(model, baseUrl, apiKey) {
|
|
2621
2723
|
this.modelName = model ?? "claude-haiku-4-5";
|
|
2724
|
+
this.endpoint = (baseUrl ?? "https://api.anthropic.com/v1").replace(/\/$/, "");
|
|
2725
|
+
this.apiKey = apiKey ?? "";
|
|
2726
|
+
}
|
|
2727
|
+
resolvedKey() {
|
|
2728
|
+
return this.apiKey || process.env["ANTHROPIC_API_KEY"] || "";
|
|
2729
|
+
}
|
|
2730
|
+
async getContextWindow() {
|
|
2731
|
+
return ANTHROPIC_CONTEXT[this.modelName] ?? 2e5;
|
|
2622
2732
|
}
|
|
2623
2733
|
async summarize(prompt) {
|
|
2624
2734
|
const Anthropic = (await import('@anthropic-ai/sdk')).default;
|
|
2625
|
-
const client = new Anthropic({
|
|
2735
|
+
const client = new Anthropic({
|
|
2736
|
+
apiKey: this.resolvedKey(),
|
|
2737
|
+
baseURL: this.endpoint
|
|
2738
|
+
});
|
|
2626
2739
|
const res = await client.messages.create({
|
|
2627
2740
|
model: this.modelName,
|
|
2628
|
-
max_tokens:
|
|
2741
|
+
max_tokens: 500,
|
|
2629
2742
|
messages: [{ role: "user", content: prompt }]
|
|
2630
2743
|
});
|
|
2631
2744
|
const block = res.content?.[0];
|
|
2632
|
-
return
|
|
2745
|
+
return {
|
|
2746
|
+
text: block && block.type === "text" ? block.text.trim() : "",
|
|
2747
|
+
promptTokens: res.usage?.input_tokens ?? 0,
|
|
2748
|
+
completionTokens: res.usage?.output_tokens ?? 0
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
});
|
|
2754
|
+
|
|
2755
|
+
// src/llm/providers/custom.ts
|
|
2756
|
+
var custom_exports = {};
|
|
2757
|
+
__export(custom_exports, {
|
|
2758
|
+
CustomProvider: () => CustomProvider
|
|
2759
|
+
});
|
|
2760
|
+
var KNOWN_CONTEXT_WINDOWS, CustomProvider;
|
|
2761
|
+
var init_custom = __esm({
|
|
2762
|
+
"src/llm/providers/custom.ts"() {
|
|
2763
|
+
KNOWN_CONTEXT_WINDOWS = {
|
|
2764
|
+
// DeepSeek
|
|
2765
|
+
"deepseek-v4-flash": 64e3,
|
|
2766
|
+
"deepseek-v4-pro": 64e3,
|
|
2767
|
+
"deepseek-chat": 64e3,
|
|
2768
|
+
"deepseek-coder": 16384,
|
|
2769
|
+
"deepseek-v2": 128e3,
|
|
2770
|
+
"deepseek-v3": 64e3,
|
|
2771
|
+
// Groq
|
|
2772
|
+
"llama3-8b-8192": 8192,
|
|
2773
|
+
"llama3-70b-8192": 8192,
|
|
2774
|
+
"mixtral-8x7b-32768": 32768,
|
|
2775
|
+
"gemma-7b-it": 8192,
|
|
2776
|
+
// Together AI
|
|
2777
|
+
"mistralai/Mistral-7B-v0.1": 8192,
|
|
2778
|
+
"meta-llama/Llama-3-8b-chat": 8192,
|
|
2779
|
+
// LM Studio / vLLM defaults
|
|
2780
|
+
"default": 4096
|
|
2781
|
+
};
|
|
2782
|
+
CustomProvider = class {
|
|
2783
|
+
modelName;
|
|
2784
|
+
endpoint;
|
|
2785
|
+
baseUrl;
|
|
2786
|
+
apiKey;
|
|
2787
|
+
constructor(baseUrl, model, apiKey = "") {
|
|
2788
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
2789
|
+
this.modelName = model;
|
|
2790
|
+
this.apiKey = apiKey;
|
|
2791
|
+
this.endpoint = this.baseUrl;
|
|
2792
|
+
}
|
|
2793
|
+
async getContextWindow() {
|
|
2794
|
+
const modelsUrl = this.baseUrl.endsWith("/v1") ? `${this.baseUrl}/models` : `${this.baseUrl}/v1/models`;
|
|
2795
|
+
try {
|
|
2796
|
+
const res = await fetch(modelsUrl, {
|
|
2797
|
+
headers: this.apiKey ? { "Authorization": `Bearer ${this.apiKey}` } : {},
|
|
2798
|
+
signal: AbortSignal.timeout(5e3)
|
|
2799
|
+
});
|
|
2800
|
+
if (res.ok) {
|
|
2801
|
+
const json = await res.json();
|
|
2802
|
+
const model = json.data?.find((m) => m.id === this.modelName);
|
|
2803
|
+
if (model?.context_window) return model.context_window;
|
|
2804
|
+
}
|
|
2805
|
+
} catch {
|
|
2806
|
+
}
|
|
2807
|
+
return KNOWN_CONTEXT_WINDOWS[this.modelName];
|
|
2808
|
+
}
|
|
2809
|
+
async summarize(prompt) {
|
|
2810
|
+
const base = this.baseUrl.endsWith("/v1") ? this.baseUrl : `${this.baseUrl}/v1`;
|
|
2811
|
+
const url = `${base}/chat/completions`;
|
|
2812
|
+
const headers = {
|
|
2813
|
+
"Content-Type": "application/json"
|
|
2814
|
+
};
|
|
2815
|
+
if (this.apiKey) {
|
|
2816
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
2817
|
+
}
|
|
2818
|
+
const body = JSON.stringify({
|
|
2819
|
+
model: this.modelName,
|
|
2820
|
+
messages: [{ role: "user", content: prompt }],
|
|
2821
|
+
max_tokens: 500,
|
|
2822
|
+
temperature: 0.3
|
|
2823
|
+
});
|
|
2824
|
+
const res = await fetch(url, { method: "POST", headers, body });
|
|
2825
|
+
if (!res.ok) {
|
|
2826
|
+
const text2 = await res.text().catch(() => res.statusText);
|
|
2827
|
+
throw new Error(`Custom LLM API error ${res.status}: ${text2}`);
|
|
2828
|
+
}
|
|
2829
|
+
const data = await res.json();
|
|
2830
|
+
const msg = data.choices?.[0]?.message;
|
|
2831
|
+
const text = (msg?.content?.trim() || msg?.reasoning_content?.trim()) ?? "";
|
|
2832
|
+
return {
|
|
2833
|
+
text,
|
|
2834
|
+
promptTokens: data.usage?.prompt_tokens ?? 0,
|
|
2835
|
+
completionTokens: data.usage?.completion_tokens ?? 0
|
|
2836
|
+
};
|
|
2633
2837
|
}
|
|
2634
2838
|
};
|
|
2635
2839
|
}
|
|
@@ -2645,10 +2849,31 @@ var init_ollama = __esm({
|
|
|
2645
2849
|
"src/llm/providers/ollama.ts"() {
|
|
2646
2850
|
OllamaProvider = class {
|
|
2647
2851
|
modelName;
|
|
2852
|
+
endpoint;
|
|
2648
2853
|
baseUrl;
|
|
2649
2854
|
constructor(model, baseUrl) {
|
|
2650
2855
|
this.modelName = model ?? "llama3";
|
|
2651
2856
|
this.baseUrl = baseUrl ?? "http://localhost:11434";
|
|
2857
|
+
this.endpoint = this.baseUrl;
|
|
2858
|
+
}
|
|
2859
|
+
async getContextWindow() {
|
|
2860
|
+
try {
|
|
2861
|
+
const res = await fetch(`${this.baseUrl}/api/show`, {
|
|
2862
|
+
method: "POST",
|
|
2863
|
+
headers: { "Content-Type": "application/json" },
|
|
2864
|
+
body: JSON.stringify({ model: this.modelName }),
|
|
2865
|
+
signal: AbortSignal.timeout(5e3)
|
|
2866
|
+
});
|
|
2867
|
+
if (!res.ok) return void 0;
|
|
2868
|
+
const json = await res.json();
|
|
2869
|
+
const info = json.model_info ?? {};
|
|
2870
|
+
for (const [k, v] of Object.entries(info)) {
|
|
2871
|
+
if (k.endsWith(".context_length") && typeof v === "number") return v;
|
|
2872
|
+
}
|
|
2873
|
+
return void 0;
|
|
2874
|
+
} catch {
|
|
2875
|
+
return void 0;
|
|
2876
|
+
}
|
|
2652
2877
|
}
|
|
2653
2878
|
async summarize(prompt) {
|
|
2654
2879
|
const url = `${this.baseUrl}/api/generate`;
|
|
@@ -2666,7 +2891,11 @@ var init_ollama = __esm({
|
|
|
2666
2891
|
throw new Error(`Ollama request failed: ${res.status} ${res.statusText}`);
|
|
2667
2892
|
}
|
|
2668
2893
|
const json = await res.json();
|
|
2669
|
-
return
|
|
2894
|
+
return {
|
|
2895
|
+
text: (json.response ?? "").trim(),
|
|
2896
|
+
promptTokens: json.prompt_eval_count ?? 0,
|
|
2897
|
+
completionTokens: json.eval_count ?? 0
|
|
2898
|
+
};
|
|
2670
2899
|
}
|
|
2671
2900
|
};
|
|
2672
2901
|
}
|
|
@@ -2678,20 +2907,27 @@ __export(factory_exports, {
|
|
|
2678
2907
|
createLLMProvider: () => createLLMProvider
|
|
2679
2908
|
});
|
|
2680
2909
|
async function createLLMProvider(config = {}) {
|
|
2681
|
-
const { provider = "ollama", model } = config;
|
|
2910
|
+
const { provider = "ollama", model, baseUrl, apiKey } = config;
|
|
2682
2911
|
switch (provider) {
|
|
2683
2912
|
case "openai": {
|
|
2684
2913
|
const { OpenAIProvider: OpenAIProvider2 } = await Promise.resolve().then(() => (init_openai(), openai_exports));
|
|
2685
|
-
return new OpenAIProvider2(model);
|
|
2914
|
+
return new OpenAIProvider2(model, baseUrl, apiKey);
|
|
2686
2915
|
}
|
|
2687
2916
|
case "anthropic": {
|
|
2688
2917
|
const { AnthropicProvider: AnthropicProvider2 } = await Promise.resolve().then(() => (init_anthropic(), anthropic_exports));
|
|
2689
|
-
return new AnthropicProvider2(model);
|
|
2918
|
+
return new AnthropicProvider2(model, baseUrl, apiKey);
|
|
2919
|
+
}
|
|
2920
|
+
case "custom": {
|
|
2921
|
+
const { CustomProvider: CustomProvider2 } = await Promise.resolve().then(() => (init_custom(), custom_exports));
|
|
2922
|
+
const url = baseUrl ?? "http://localhost:1234/v1";
|
|
2923
|
+
const mdl = model ?? "default";
|
|
2924
|
+
const key = apiKey ?? "";
|
|
2925
|
+
return new CustomProvider2(url, mdl, key);
|
|
2690
2926
|
}
|
|
2691
2927
|
case "ollama":
|
|
2692
2928
|
default: {
|
|
2693
2929
|
const { OllamaProvider: OllamaProvider2 } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
|
|
2694
|
-
return new OllamaProvider2(model);
|
|
2930
|
+
return new OllamaProvider2(model, baseUrl);
|
|
2695
2931
|
}
|
|
2696
2932
|
}
|
|
2697
2933
|
}
|
|
@@ -2762,168 +2998,6 @@ var init_embedder = __esm({
|
|
|
2762
2998
|
}
|
|
2763
2999
|
});
|
|
2764
3000
|
|
|
2765
|
-
// src/storage/db-manager.ts
|
|
2766
|
-
var db_manager_exports = {};
|
|
2767
|
-
__export(db_manager_exports, {
|
|
2768
|
-
DbManager: () => DbManager
|
|
2769
|
-
});
|
|
2770
|
-
var DbManager;
|
|
2771
|
-
var init_db_manager = __esm({
|
|
2772
|
-
"src/storage/db-manager.ts"() {
|
|
2773
|
-
DbManager = class {
|
|
2774
|
-
db = null;
|
|
2775
|
-
conn = null;
|
|
2776
|
-
dbPath;
|
|
2777
|
-
readOnly;
|
|
2778
|
-
constructor(dbPath, readOnly = false) {
|
|
2779
|
-
this.dbPath = dbPath;
|
|
2780
|
-
this.readOnly = readOnly;
|
|
2781
|
-
}
|
|
2782
|
-
async init() {
|
|
2783
|
-
if (!this.readOnly) {
|
|
2784
|
-
fs26.mkdirSync(path32.dirname(this.dbPath), { recursive: true });
|
|
2785
|
-
}
|
|
2786
|
-
this.db = new Database(this.dbPath, 0, true, this.readOnly);
|
|
2787
|
-
await this.db.init();
|
|
2788
|
-
this.conn = new Connection(this.db);
|
|
2789
|
-
await this.conn.init();
|
|
2790
|
-
}
|
|
2791
|
-
async query(cypher) {
|
|
2792
|
-
if (!this.conn) throw new Error("Database not initialized");
|
|
2793
|
-
const result = await this.conn.query(cypher);
|
|
2794
|
-
const qr = Array.isArray(result) ? result[0] : result;
|
|
2795
|
-
const rows = await qr.getAll();
|
|
2796
|
-
qr.close();
|
|
2797
|
-
return rows;
|
|
2798
|
-
}
|
|
2799
|
-
async execute(cypher) {
|
|
2800
|
-
if (!this.conn) throw new Error("Database not initialized");
|
|
2801
|
-
const result = await this.conn.query(cypher);
|
|
2802
|
-
const qr = Array.isArray(result) ? result[0] : result;
|
|
2803
|
-
qr.close();
|
|
2804
|
-
}
|
|
2805
|
-
close() {
|
|
2806
|
-
try {
|
|
2807
|
-
this.conn?.closeSync();
|
|
2808
|
-
} catch {
|
|
2809
|
-
}
|
|
2810
|
-
try {
|
|
2811
|
-
this.db?.closeSync();
|
|
2812
|
-
} catch {
|
|
2813
|
-
}
|
|
2814
|
-
this.conn = null;
|
|
2815
|
-
this.db = null;
|
|
2816
|
-
}
|
|
2817
|
-
get isOpen() {
|
|
2818
|
-
return this.conn !== null;
|
|
2819
|
-
}
|
|
2820
|
-
};
|
|
2821
|
-
}
|
|
2822
|
-
});
|
|
2823
|
-
|
|
2824
|
-
// src/multi-repo/group-registry.ts
|
|
2825
|
-
var group_registry_exports = {};
|
|
2826
|
-
__export(group_registry_exports, {
|
|
2827
|
-
addMember: () => addMember,
|
|
2828
|
-
deleteGroup: () => deleteGroup,
|
|
2829
|
-
groupExists: () => groupExists,
|
|
2830
|
-
listGroups: () => listGroups,
|
|
2831
|
-
loadGroup: () => loadGroup,
|
|
2832
|
-
loadSyncResult: () => loadSyncResult,
|
|
2833
|
-
removeMember: () => removeMember,
|
|
2834
|
-
saveGroup: () => saveGroup,
|
|
2835
|
-
saveSyncResult: () => saveSyncResult
|
|
2836
|
-
});
|
|
2837
|
-
function groupFile(name) {
|
|
2838
|
-
return path32.join(GROUPS_DIR, `${name}.json`);
|
|
2839
|
-
}
|
|
2840
|
-
function loadGroup(name) {
|
|
2841
|
-
try {
|
|
2842
|
-
return JSON.parse(fs26.readFileSync(groupFile(name), "utf-8"));
|
|
2843
|
-
} catch {
|
|
2844
|
-
return null;
|
|
2845
|
-
}
|
|
2846
|
-
}
|
|
2847
|
-
function saveGroup(group) {
|
|
2848
|
-
fs26.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
2849
|
-
fs26.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
|
|
2850
|
-
}
|
|
2851
|
-
function listGroups() {
|
|
2852
|
-
const groups = [];
|
|
2853
|
-
try {
|
|
2854
|
-
for (const file of fs26.readdirSync(GROUPS_DIR)) {
|
|
2855
|
-
if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
|
|
2856
|
-
try {
|
|
2857
|
-
const g = JSON.parse(
|
|
2858
|
-
fs26.readFileSync(path32.join(GROUPS_DIR, file), "utf-8")
|
|
2859
|
-
);
|
|
2860
|
-
groups.push(g);
|
|
2861
|
-
} catch {
|
|
2862
|
-
}
|
|
2863
|
-
}
|
|
2864
|
-
} catch {
|
|
2865
|
-
}
|
|
2866
|
-
return groups;
|
|
2867
|
-
}
|
|
2868
|
-
function deleteGroup(name) {
|
|
2869
|
-
try {
|
|
2870
|
-
fs26.unlinkSync(groupFile(name));
|
|
2871
|
-
} catch {
|
|
2872
|
-
}
|
|
2873
|
-
try {
|
|
2874
|
-
fs26.unlinkSync(path32.join(GROUPS_DIR, `${name}.sync.json`));
|
|
2875
|
-
} catch {
|
|
2876
|
-
}
|
|
2877
|
-
}
|
|
2878
|
-
function groupExists(name) {
|
|
2879
|
-
return fs26.existsSync(groupFile(name));
|
|
2880
|
-
}
|
|
2881
|
-
function addMember(groupName, member) {
|
|
2882
|
-
const group = loadGroup(groupName);
|
|
2883
|
-
if (!group) throw new Error(`Group "${groupName}" not found.`);
|
|
2884
|
-
const idx = group.members.findIndex((m) => m.groupPath === member.groupPath);
|
|
2885
|
-
if (idx >= 0) {
|
|
2886
|
-
group.members[idx] = member;
|
|
2887
|
-
} else {
|
|
2888
|
-
group.members.push(member);
|
|
2889
|
-
}
|
|
2890
|
-
saveGroup(group);
|
|
2891
|
-
return group;
|
|
2892
|
-
}
|
|
2893
|
-
function removeMember(groupName, groupPath) {
|
|
2894
|
-
const group = loadGroup(groupName);
|
|
2895
|
-
if (!group) throw new Error(`Group "${groupName}" not found.`);
|
|
2896
|
-
const before = group.members.length;
|
|
2897
|
-
group.members = group.members.filter((m) => m.groupPath !== groupPath);
|
|
2898
|
-
if (group.members.length === before) {
|
|
2899
|
-
throw new Error(`No member at path "${groupPath}" in group "${groupName}".`);
|
|
2900
|
-
}
|
|
2901
|
-
saveGroup(group);
|
|
2902
|
-
return group;
|
|
2903
|
-
}
|
|
2904
|
-
function saveSyncResult(result) {
|
|
2905
|
-
fs26.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
2906
|
-
fs26.writeFileSync(
|
|
2907
|
-
path32.join(GROUPS_DIR, `${result.groupName}.sync.json`),
|
|
2908
|
-
JSON.stringify(result, null, 2) + "\n"
|
|
2909
|
-
);
|
|
2910
|
-
}
|
|
2911
|
-
function loadSyncResult(groupName) {
|
|
2912
|
-
try {
|
|
2913
|
-
return JSON.parse(
|
|
2914
|
-
fs26.readFileSync(path32.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
|
|
2915
|
-
);
|
|
2916
|
-
} catch {
|
|
2917
|
-
return null;
|
|
2918
|
-
}
|
|
2919
|
-
}
|
|
2920
|
-
var GROUPS_DIR;
|
|
2921
|
-
var init_group_registry = __esm({
|
|
2922
|
-
"src/multi-repo/group-registry.ts"() {
|
|
2923
|
-
GROUPS_DIR = path32.join(os13.homedir(), ".code-intel", "groups");
|
|
2924
|
-
}
|
|
2925
|
-
});
|
|
2926
|
-
|
|
2927
3001
|
// src/multi-repo/graph-from-db.ts
|
|
2928
3002
|
var graph_from_db_exports = {};
|
|
2929
3003
|
__export(graph_from_db_exports, {
|
|
@@ -3813,19 +3887,26 @@ function executeFIND(stmt, graph) {
|
|
|
3813
3887
|
totalCount
|
|
3814
3888
|
};
|
|
3815
3889
|
}
|
|
3890
|
+
function findBestNode(graph, name) {
|
|
3891
|
+
const matches = [];
|
|
3892
|
+
for (const node of graph.allNodes()) {
|
|
3893
|
+
if (node.name === name) matches.push(node);
|
|
3894
|
+
}
|
|
3895
|
+
if (matches.length === 0) return void 0;
|
|
3896
|
+
if (matches.length === 1) return matches[0];
|
|
3897
|
+
return matches.sort((a, b) => {
|
|
3898
|
+
const aEdges = [...graph.findEdgesFrom(a.id)].length + [...graph.findEdgesTo(a.id)].length;
|
|
3899
|
+
const bEdges = [...graph.findEdgesFrom(b.id)].length + [...graph.findEdgesTo(b.id)].length;
|
|
3900
|
+
return bEdges - aEdges;
|
|
3901
|
+
})[0];
|
|
3902
|
+
}
|
|
3816
3903
|
function executeTRAVERSE(stmt, graph) {
|
|
3817
3904
|
const start = Date.now();
|
|
3818
3905
|
const maxDepth = stmt.depth ?? 5;
|
|
3819
3906
|
const edgeKind = stmt.edgeKind;
|
|
3820
3907
|
const direction = stmt.direction ?? "OUTGOING";
|
|
3821
3908
|
const deadline = start + EXECUTION_TIMEOUT_MS;
|
|
3822
|
-
|
|
3823
|
-
for (const node of graph.allNodes()) {
|
|
3824
|
-
if (node.name === stmt.from) {
|
|
3825
|
-
startNode = node;
|
|
3826
|
-
break;
|
|
3827
|
-
}
|
|
3828
|
-
}
|
|
3909
|
+
const startNode = findBestNode(graph, stmt.from);
|
|
3829
3910
|
if (!startNode) {
|
|
3830
3911
|
return {
|
|
3831
3912
|
nodes: [],
|
|
@@ -3889,13 +3970,8 @@ function executeTRAVERSE(stmt, graph) {
|
|
|
3889
3970
|
function executePATH(stmt, graph) {
|
|
3890
3971
|
const start = Date.now();
|
|
3891
3972
|
const deadline = start + EXECUTION_TIMEOUT_MS;
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
for (const node of graph.allNodes()) {
|
|
3895
|
-
if (node.name === stmt.from) startNode = node;
|
|
3896
|
-
if (node.name === stmt.to) endNode = node;
|
|
3897
|
-
if (startNode && endNode) break;
|
|
3898
|
-
}
|
|
3973
|
+
const startNode = findBestNode(graph, stmt.from);
|
|
3974
|
+
const endNode = findBestNode(graph, stmt.to);
|
|
3899
3975
|
if (!startNode || !endNode) {
|
|
3900
3976
|
return {
|
|
3901
3977
|
path: null,
|
|
@@ -4585,6 +4661,7 @@ var init_codes = __esm({
|
|
|
4585
4661
|
RATE_LIMIT_EXCEEDED: "CI-1100",
|
|
4586
4662
|
PAYLOAD_TOO_LARGE: "CI-1101",
|
|
4587
4663
|
INVALID_REQUEST: "CI-1200",
|
|
4664
|
+
CONFLICT: "CI-1409",
|
|
4588
4665
|
// Config (CI-2xxx)
|
|
4589
4666
|
CONFIG_INVALID: "CI-2000",
|
|
4590
4667
|
CONFIG_NOT_FOUND: "CI-2001",
|
|
@@ -4667,7 +4744,7 @@ var init_users_db = __esm({
|
|
|
4667
4744
|
constructor(dbPath) {
|
|
4668
4745
|
const dir = path32.dirname(dbPath);
|
|
4669
4746
|
secureMkdir(dir);
|
|
4670
|
-
this.db = new
|
|
4747
|
+
this.db = new Database3(dbPath);
|
|
4671
4748
|
this.db.pragma("journal_mode = WAL");
|
|
4672
4749
|
this.db.pragma("foreign_keys = ON");
|
|
4673
4750
|
this.createTables();
|
|
@@ -6822,12 +6899,16 @@ function textSearch(graph, query, limit = 20) {
|
|
|
6822
6899
|
let score = 0;
|
|
6823
6900
|
const nameLC = node.name.toLowerCase();
|
|
6824
6901
|
const pathLC = node.filePath.toLowerCase();
|
|
6902
|
+
const fileBaseName = (node.filePath.split(/[/\\]/).pop()?.replace(/\.[^.]+$/, "") ?? "").toLowerCase();
|
|
6903
|
+
const contentLC = node.content?.toLowerCase() ?? "";
|
|
6825
6904
|
for (const term of terms) {
|
|
6826
6905
|
if (nameLC === term) score += 10;
|
|
6827
6906
|
else if (nameLC.startsWith(term)) score += 7;
|
|
6828
6907
|
else if (nameLC.includes(term)) score += 5;
|
|
6829
|
-
if (
|
|
6830
|
-
if (
|
|
6908
|
+
if (fileBaseName === term) score += 8;
|
|
6909
|
+
else if (fileBaseName.startsWith(term)) score += 5;
|
|
6910
|
+
else if (pathLC.includes(term)) score += 2;
|
|
6911
|
+
if (contentLC.includes(term)) score += 3;
|
|
6831
6912
|
}
|
|
6832
6913
|
if (score > 0) {
|
|
6833
6914
|
if (isDistPath(node.filePath)) score -= 8;
|
|
@@ -6882,7 +6963,7 @@ var VectorIndex = class {
|
|
|
6882
6963
|
this.sqlitePath = sqlitePath;
|
|
6883
6964
|
}
|
|
6884
6965
|
async init() {
|
|
6885
|
-
this.db = new
|
|
6966
|
+
this.db = new Database3(this.sqlitePath);
|
|
6886
6967
|
this.db.pragma("journal_mode = WAL");
|
|
6887
6968
|
this.db.exec(`
|
|
6888
6969
|
CREATE TABLE IF NOT EXISTS ${EMBED_TABLE} (
|
|
@@ -7012,24 +7093,92 @@ function siftDown(arr, i, score) {
|
|
|
7012
7093
|
}
|
|
7013
7094
|
}
|
|
7014
7095
|
init_embedder();
|
|
7096
|
+
|
|
7097
|
+
// src/search/reranker.ts
|
|
7098
|
+
function tokenizeForRerank(text) {
|
|
7099
|
+
return text.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[\s\-_./:(){}[\]<>,"'`~!@#$%^&*+=|;?\\]+/).filter((t) => t.length >= 2);
|
|
7100
|
+
}
|
|
7101
|
+
var DEFAULT_KIND_WEIGHTS = {
|
|
7102
|
+
class: 1.2,
|
|
7103
|
+
interface: 1.15,
|
|
7104
|
+
function: 1.1,
|
|
7105
|
+
method: 1.08,
|
|
7106
|
+
type_alias: 1.03,
|
|
7107
|
+
enum: 1.02,
|
|
7108
|
+
constant: 0.98,
|
|
7109
|
+
variable: 0.9,
|
|
7110
|
+
file: 0.85
|
|
7111
|
+
};
|
|
7112
|
+
var PATH_MULTIPLIER_TEST = 0.4;
|
|
7113
|
+
var PATH_MULTIPLIER_DIST = 0.25;
|
|
7114
|
+
function rerank(query, results, options = {}) {
|
|
7115
|
+
if (results.length === 0) return results;
|
|
7116
|
+
const {
|
|
7117
|
+
nameWeight = 0.4,
|
|
7118
|
+
snippetWeight = 0.25,
|
|
7119
|
+
kindWeights = {}
|
|
7120
|
+
} = options;
|
|
7121
|
+
const effectiveKindWeights = {
|
|
7122
|
+
...DEFAULT_KIND_WEIGHTS,
|
|
7123
|
+
...kindWeights
|
|
7124
|
+
};
|
|
7125
|
+
const queryTerms = [...new Set(tokenizeForRerank(query))];
|
|
7126
|
+
if (queryTerms.length === 0) return results.slice();
|
|
7127
|
+
const queryLower = query.toLowerCase();
|
|
7128
|
+
const scored = results.map((r) => {
|
|
7129
|
+
let bonus = 0;
|
|
7130
|
+
const nameLower = r.name.toLowerCase();
|
|
7131
|
+
const nameTerms = tokenizeForRerank(r.name);
|
|
7132
|
+
if (nameLower === queryLower) {
|
|
7133
|
+
bonus += nameWeight;
|
|
7134
|
+
} else if (nameLower.startsWith(queryLower)) {
|
|
7135
|
+
bonus += nameWeight * 0.75;
|
|
7136
|
+
} else if (queryLower.includes(nameLower) && nameLower.length >= 3) {
|
|
7137
|
+
bonus += nameWeight * 0.45;
|
|
7138
|
+
} else {
|
|
7139
|
+
const matchCount = queryTerms.filter((t) => nameTerms.includes(t)).length;
|
|
7140
|
+
if (matchCount > 0) {
|
|
7141
|
+
const overlap = matchCount / queryTerms.length;
|
|
7142
|
+
bonus += nameWeight * overlap * 0.6;
|
|
7143
|
+
}
|
|
7144
|
+
}
|
|
7145
|
+
if (r.snippet && r.snippet.length > 0) {
|
|
7146
|
+
const snippetLower = r.snippet.toLowerCase();
|
|
7147
|
+
const hitCount = queryTerms.filter((t) => snippetLower.includes(t)).length;
|
|
7148
|
+
bonus += snippetWeight * (hitCount / queryTerms.length);
|
|
7149
|
+
}
|
|
7150
|
+
const kw = effectiveKindWeights[r.kind] ?? 1;
|
|
7151
|
+
const fp = r.filePath;
|
|
7152
|
+
const fpNorm = "/" + fp;
|
|
7153
|
+
const isTestPath = fpNorm.includes("/test/") || fpNorm.includes("/tests/") || fpNorm.includes("/spec/") || fpNorm.includes("/__tests__/") || fp.includes(".test.") || fp.includes(".spec.");
|
|
7154
|
+
const isDistPath = fpNorm.includes("/dist/") || fpNorm.includes("/build/") || fp.endsWith(".d.ts") || fpNorm.includes("/node_modules/");
|
|
7155
|
+
const pathMul = isDistPath ? PATH_MULTIPLIER_DIST : isTestPath ? PATH_MULTIPLIER_TEST : 1;
|
|
7156
|
+
const clampedBonus = Math.max(0, Math.min(nameWeight + snippetWeight, bonus));
|
|
7157
|
+
const finalScore = r.score * (1 + clampedBonus) * kw * pathMul;
|
|
7158
|
+
return { result: r, finalScore };
|
|
7159
|
+
});
|
|
7160
|
+
return scored.sort((a, b) => b.finalScore - a.finalScore).map(({ result, finalScore }) => ({ ...result, score: finalScore }));
|
|
7161
|
+
}
|
|
7162
|
+
|
|
7163
|
+
// src/search/hybrid-search.ts
|
|
7015
7164
|
async function hybridSearch(graph, query, limit, options = {}) {
|
|
7016
7165
|
const { vectorDbPath, bm25Limit = 50, vectorLimit = 50, bm25Results: precomputedBm25 } = options;
|
|
7166
|
+
const rerankEnabled = options.rerank?.enabled !== false;
|
|
7167
|
+
const rerankOptions = rerankEnabled && options.rerank && options.rerank.enabled !== false ? options.rerank : {};
|
|
7017
7168
|
const bm25Promise = precomputedBm25 ? Promise.resolve(precomputedBm25) : Promise.resolve(textSearch(graph, query, bm25Limit));
|
|
7018
7169
|
const hasVectorDb = Boolean(vectorDbPath && fs26.existsSync(vectorDbPath));
|
|
7019
7170
|
if (!hasVectorDb) {
|
|
7020
7171
|
const bm25Results2 = await bm25Promise;
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
};
|
|
7172
|
+
const candidates2 = bm25Results2.slice(0, limit).map((r) => ({ ...r, searchMode: "bm25" }));
|
|
7173
|
+
const final2 = rerankEnabled ? rerank(query, candidates2, rerankOptions) : candidates2;
|
|
7174
|
+
return { results: final2, searchMode: "bm25" };
|
|
7025
7175
|
}
|
|
7026
7176
|
const vectorPromise = runVectorSearch(vectorDbPath, query, vectorLimit);
|
|
7027
7177
|
const [bm25Results, vectorResults] = await Promise.all([bm25Promise, vectorPromise]);
|
|
7028
7178
|
if (vectorResults === null || vectorResults.length === 0) {
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
};
|
|
7179
|
+
const candidates2 = bm25Results.slice(0, limit).map((r) => ({ ...r, searchMode: "bm25" }));
|
|
7180
|
+
const final2 = rerankEnabled ? rerank(query, candidates2, rerankOptions) : candidates2;
|
|
7181
|
+
return { results: final2, searchMode: "bm25" };
|
|
7033
7182
|
}
|
|
7034
7183
|
const vectorAsSearchResults = vectorResults.map((h) => ({
|
|
7035
7184
|
nodeId: h.nodeId,
|
|
@@ -7040,10 +7189,9 @@ async function hybridSearch(graph, query, limit, options = {}) {
|
|
|
7040
7189
|
snippet: graph.getNode(h.nodeId)?.content?.slice(0, 200)
|
|
7041
7190
|
}));
|
|
7042
7191
|
const merged = reciprocalRankFusion(bm25Results, vectorAsSearchResults);
|
|
7043
|
-
|
|
7044
|
-
|
|
7045
|
-
|
|
7046
|
-
};
|
|
7192
|
+
const candidates = merged.slice(0, limit).map((r) => ({ ...r, searchMode: "hybrid" }));
|
|
7193
|
+
const final = rerankEnabled ? rerank(query, candidates, rerankOptions) : candidates;
|
|
7194
|
+
return { results: final, searchMode: "hybrid" };
|
|
7047
7195
|
}
|
|
7048
7196
|
async function runVectorSearch(vectorDbPath, query, topK) {
|
|
7049
7197
|
try {
|
|
@@ -7073,11 +7221,19 @@ function tokenize(text) {
|
|
|
7073
7221
|
return text.toLowerCase().split(/[\s\-_./\\:(){}[\]<>,"'`~!@#$%^&*+=|;?]+/).filter((t) => t.length >= 2 && t.length <= 64);
|
|
7074
7222
|
}
|
|
7075
7223
|
function nodeToDoc(node) {
|
|
7224
|
+
const fileBaseName = node.filePath.split(/[/\\]/).pop()?.replace(/\.[^.]+$/, "") ?? "";
|
|
7076
7225
|
return [
|
|
7077
7226
|
node.name,
|
|
7227
|
+
node.name,
|
|
7228
|
+
// repeat name to boost exact-name matches
|
|
7078
7229
|
node.kind,
|
|
7079
7230
|
node.filePath,
|
|
7080
|
-
|
|
7231
|
+
fileBaseName,
|
|
7232
|
+
// filename stem (class name) — extra weight
|
|
7233
|
+
fileBaseName,
|
|
7234
|
+
// repeat again for stronger class-name boosting
|
|
7235
|
+
(node.content ?? "").slice(0, 1500)
|
|
7236
|
+
// increased from 1000
|
|
7081
7237
|
].join(" ");
|
|
7082
7238
|
}
|
|
7083
7239
|
function heapTopK(scores, k) {
|
|
@@ -7174,7 +7330,7 @@ var Bm25Index = class {
|
|
|
7174
7330
|
} catch {
|
|
7175
7331
|
}
|
|
7176
7332
|
}
|
|
7177
|
-
const db = new
|
|
7333
|
+
const db = new Database3(this.dbPath);
|
|
7178
7334
|
db.pragma("journal_mode = WAL");
|
|
7179
7335
|
db.exec(`
|
|
7180
7336
|
CREATE TABLE bm25_index (term TEXT PRIMARY KEY, postings TEXT NOT NULL);
|
|
@@ -7209,7 +7365,7 @@ var Bm25Index = class {
|
|
|
7209
7365
|
*/
|
|
7210
7366
|
load() {
|
|
7211
7367
|
if (!fs26.existsSync(this.dbPath)) return;
|
|
7212
|
-
const db = new
|
|
7368
|
+
const db = new Database3(this.dbPath, { readonly: true });
|
|
7213
7369
|
try {
|
|
7214
7370
|
const getMeta = db.prepare("SELECT value FROM bm25_meta WHERE key = ?");
|
|
7215
7371
|
this.avgdl = parseFloat(getMeta.get("avgdl")?.value ?? "1");
|
|
@@ -7302,7 +7458,7 @@ var Bm25Index = class {
|
|
|
7302
7458
|
newTermFreqs.set(node.id, tf);
|
|
7303
7459
|
}
|
|
7304
7460
|
const newTermSet = new Set([...newTermFreqs.values()].flatMap((m) => [...m.keys()]));
|
|
7305
|
-
const db = new
|
|
7461
|
+
const db = new Database3(this.dbPath);
|
|
7306
7462
|
db.pragma("journal_mode = WAL");
|
|
7307
7463
|
const termsToRewrite = /* @__PURE__ */ new Map();
|
|
7308
7464
|
for (const term of newTermSet) {
|
|
@@ -7564,19 +7720,24 @@ function buildNodeProps(node) {
|
|
|
7564
7720
|
function escCypher(s) {
|
|
7565
7721
|
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "");
|
|
7566
7722
|
}
|
|
7567
|
-
|
|
7568
|
-
|
|
7723
|
+
function getGlobalDir() {
|
|
7724
|
+
return path32.join(process.env["CODE_INTEL_HOME"] ?? os13.homedir(), ".code-intel");
|
|
7725
|
+
}
|
|
7726
|
+
function getReposFile() {
|
|
7727
|
+
return path32.join(getGlobalDir(), "repos.json");
|
|
7728
|
+
}
|
|
7569
7729
|
function loadRegistry() {
|
|
7570
7730
|
try {
|
|
7571
|
-
const data = fs26.readFileSync(
|
|
7731
|
+
const data = fs26.readFileSync(getReposFile(), "utf-8");
|
|
7572
7732
|
return JSON.parse(data);
|
|
7573
7733
|
} catch {
|
|
7574
7734
|
return [];
|
|
7575
7735
|
}
|
|
7576
7736
|
}
|
|
7577
7737
|
function saveRegistry(entries) {
|
|
7578
|
-
|
|
7579
|
-
fs26.
|
|
7738
|
+
const globalDir = getGlobalDir();
|
|
7739
|
+
fs26.mkdirSync(globalDir, { recursive: true });
|
|
7740
|
+
fs26.writeFileSync(getReposFile(), JSON.stringify(entries, null, 2));
|
|
7580
7741
|
}
|
|
7581
7742
|
function upsertRepo(entry) {
|
|
7582
7743
|
const entries = loadRegistry();
|
|
@@ -7611,9 +7772,90 @@ function getDbPath(repoDir) {
|
|
|
7611
7772
|
function getVectorDbPath(repoDir) {
|
|
7612
7773
|
return path32.join(repoDir, ".code-intel", "vector.db");
|
|
7613
7774
|
}
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7775
|
+
var GROUPS_DIR = path32.join(os13.homedir(), ".code-intel", "groups");
|
|
7776
|
+
function groupFile(name) {
|
|
7777
|
+
return path32.join(GROUPS_DIR, `${name}.json`);
|
|
7778
|
+
}
|
|
7779
|
+
function loadGroup(name) {
|
|
7780
|
+
try {
|
|
7781
|
+
return JSON.parse(fs26.readFileSync(groupFile(name), "utf-8"));
|
|
7782
|
+
} catch {
|
|
7783
|
+
return null;
|
|
7784
|
+
}
|
|
7785
|
+
}
|
|
7786
|
+
function saveGroup(group) {
|
|
7787
|
+
fs26.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
7788
|
+
fs26.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
|
|
7789
|
+
}
|
|
7790
|
+
function listGroups() {
|
|
7791
|
+
const groups = [];
|
|
7792
|
+
try {
|
|
7793
|
+
for (const file of fs26.readdirSync(GROUPS_DIR)) {
|
|
7794
|
+
if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
|
|
7795
|
+
try {
|
|
7796
|
+
const g = JSON.parse(
|
|
7797
|
+
fs26.readFileSync(path32.join(GROUPS_DIR, file), "utf-8")
|
|
7798
|
+
);
|
|
7799
|
+
groups.push(g);
|
|
7800
|
+
} catch {
|
|
7801
|
+
}
|
|
7802
|
+
}
|
|
7803
|
+
} catch {
|
|
7804
|
+
}
|
|
7805
|
+
return groups;
|
|
7806
|
+
}
|
|
7807
|
+
function deleteGroup(name) {
|
|
7808
|
+
try {
|
|
7809
|
+
fs26.unlinkSync(groupFile(name));
|
|
7810
|
+
} catch {
|
|
7811
|
+
}
|
|
7812
|
+
try {
|
|
7813
|
+
fs26.unlinkSync(path32.join(GROUPS_DIR, `${name}.sync.json`));
|
|
7814
|
+
} catch {
|
|
7815
|
+
}
|
|
7816
|
+
}
|
|
7817
|
+
function groupExists(name) {
|
|
7818
|
+
return fs26.existsSync(groupFile(name));
|
|
7819
|
+
}
|
|
7820
|
+
function addMember(groupName, member) {
|
|
7821
|
+
const group = loadGroup(groupName);
|
|
7822
|
+
if (!group) throw new Error(`Group "${groupName}" not found.`);
|
|
7823
|
+
const idx = group.members.findIndex((m) => m.groupPath === member.groupPath);
|
|
7824
|
+
if (idx >= 0) {
|
|
7825
|
+
group.members[idx] = member;
|
|
7826
|
+
} else {
|
|
7827
|
+
group.members.push(member);
|
|
7828
|
+
}
|
|
7829
|
+
saveGroup(group);
|
|
7830
|
+
return group;
|
|
7831
|
+
}
|
|
7832
|
+
function removeMember(groupName, groupPath) {
|
|
7833
|
+
const group = loadGroup(groupName);
|
|
7834
|
+
if (!group) throw new Error(`Group "${groupName}" not found.`);
|
|
7835
|
+
const before = group.members.length;
|
|
7836
|
+
group.members = group.members.filter((m) => m.groupPath !== groupPath);
|
|
7837
|
+
if (group.members.length === before) {
|
|
7838
|
+
throw new Error(`No member at path "${groupPath}" in group "${groupName}".`);
|
|
7839
|
+
}
|
|
7840
|
+
saveGroup(group);
|
|
7841
|
+
return group;
|
|
7842
|
+
}
|
|
7843
|
+
function saveSyncResult(result) {
|
|
7844
|
+
fs26.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
7845
|
+
fs26.writeFileSync(
|
|
7846
|
+
path32.join(GROUPS_DIR, `${result.groupName}.sync.json`),
|
|
7847
|
+
JSON.stringify(result, null, 2) + "\n"
|
|
7848
|
+
);
|
|
7849
|
+
}
|
|
7850
|
+
function loadSyncResult(groupName) {
|
|
7851
|
+
try {
|
|
7852
|
+
return JSON.parse(
|
|
7853
|
+
fs26.readFileSync(path32.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
|
|
7854
|
+
);
|
|
7855
|
+
} catch {
|
|
7856
|
+
return null;
|
|
7857
|
+
}
|
|
7858
|
+
}
|
|
7617
7859
|
init_db_manager();
|
|
7618
7860
|
init_knowledge_graph();
|
|
7619
7861
|
init_graph_from_db();
|
|
@@ -8712,7 +8954,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8712
8954
|
// ── Search & inspect ─────────────────────────────────────────────────
|
|
8713
8955
|
{
|
|
8714
8956
|
name: "search",
|
|
8715
|
-
description:
|
|
8957
|
+
description: `BM25 keyword search across all indexed symbols \u2014 functions, classes, files, routes, etc. Optionally scope to a specific repo or group. TIP: To find a method in a specific class when multiple classes have the same method name, include the class/file name in the query (e.g. search("Token requestAccessToken redis save") to find Token.php's version vs JWT.php's). This is the preferred alternative to bare inspect() for ambiguous method names.`,
|
|
8716
8958
|
inputSchema: {
|
|
8717
8959
|
type: "object",
|
|
8718
8960
|
properties: {
|
|
@@ -8728,16 +8970,31 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
8728
8970
|
},
|
|
8729
8971
|
{
|
|
8730
8972
|
name: "inspect",
|
|
8731
|
-
description:
|
|
8973
|
+
description: '360\xB0 view of a symbol: definition location, callers, callees, heritage (extends/implements), members, cluster, and source preview. When multiple files define the same symbol name, returns a disambiguation list \u2014 re-call with file_path to select the correct implementation. IMPORTANT: When you expect a symbol to be in a specific class (e.g. Token vs JWT, or CMS vs API module), always pass file_path to avoid silently resolving the wrong class. For methods that exist in multiple classes (requestAccessToken, verify, revoke, login, logout), prefer search("ClassName methodName unique-context") over bare inspect to guarantee the right class.',
|
|
8732
8974
|
inputSchema: {
|
|
8733
8975
|
type: "object",
|
|
8734
8976
|
properties: {
|
|
8735
8977
|
symbol_name: { type: "string", description: "Exact symbol name to inspect" },
|
|
8978
|
+
file_path: { type: "string", description: 'Optional partial file path to disambiguate when multiple symbols share the same name (e.g. "Token.php" or "src/Modules/CMS"). Required when inspect returns a disambiguation list or when you know the target class.' },
|
|
8736
8979
|
..._tokenProp
|
|
8737
8980
|
},
|
|
8738
8981
|
required: ["symbol_name"]
|
|
8739
8982
|
}
|
|
8740
8983
|
},
|
|
8984
|
+
{
|
|
8985
|
+
name: "get_source",
|
|
8986
|
+
description: `Read raw source lines from any file in the workspace. Use this to verify exact implementation details that inspect's content preview may not fully show. Supports partial file path matching (e.g. "Token.php"). Returns numbered lines.`,
|
|
8987
|
+
inputSchema: {
|
|
8988
|
+
type: "object",
|
|
8989
|
+
properties: {
|
|
8990
|
+
file_path: { type: "string", description: 'File path to read (partial match supported, e.g. "Token.php" or "src/Modules/CMS/AuthController.php")' },
|
|
8991
|
+
start_line: { type: "number", description: "First line to return (1-indexed, default: 1)" },
|
|
8992
|
+
end_line: { type: "number", description: "Last line to return inclusive (default: start_line + 99). Max 300 lines per call." },
|
|
8993
|
+
..._tokenProp
|
|
8994
|
+
},
|
|
8995
|
+
required: ["file_path"]
|
|
8996
|
+
}
|
|
8997
|
+
},
|
|
8741
8998
|
{
|
|
8742
8999
|
name: "blast_radius",
|
|
8743
9000
|
description: "Impact analysis: traverse the call/import graph to find all symbols that depend on or are affected by a given symbol. Returns risk level (LOW / MEDIUM / HIGH).",
|
|
@@ -9290,8 +9547,41 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot, bm25Resolve
|
|
|
9290
9547
|
// ── inspect ────────────────────────────────────────────────────────────
|
|
9291
9548
|
case "inspect": {
|
|
9292
9549
|
const symbolName = a.symbol_name;
|
|
9293
|
-
const
|
|
9294
|
-
|
|
9550
|
+
const filePathHint = a.file_path;
|
|
9551
|
+
const allMatchingNodes = findNodesByName(graph, symbolName);
|
|
9552
|
+
if (allMatchingNodes.length === 0) {
|
|
9553
|
+
return { content: [{ type: "text", text: `Symbol "${symbolName}" not found. Try search first.` }] };
|
|
9554
|
+
}
|
|
9555
|
+
let node = allMatchingNodes[0];
|
|
9556
|
+
if (allMatchingNodes.length > 1) {
|
|
9557
|
+
if (filePathHint) {
|
|
9558
|
+
const filtered = allMatchingNodes.filter((n) => n.filePath.includes(filePathHint));
|
|
9559
|
+
if (filtered.length === 0) {
|
|
9560
|
+
return {
|
|
9561
|
+
content: [{
|
|
9562
|
+
type: "text",
|
|
9563
|
+
text: compact({
|
|
9564
|
+
disambiguation: true,
|
|
9565
|
+
message: `No match for file_path "${filePathHint}". Available definitions of "${symbolName}":`,
|
|
9566
|
+
candidates: allMatchingNodes.map((n) => ({ filePath: n.filePath, kind: n.kind, startLine: n.startLine }))
|
|
9567
|
+
})
|
|
9568
|
+
}]
|
|
9569
|
+
};
|
|
9570
|
+
}
|
|
9571
|
+
node = filtered[0];
|
|
9572
|
+
} else {
|
|
9573
|
+
return {
|
|
9574
|
+
content: [{
|
|
9575
|
+
type: "text",
|
|
9576
|
+
text: compact({
|
|
9577
|
+
disambiguation: true,
|
|
9578
|
+
message: `Multiple definitions of "${symbolName}" found. Re-call inspect with file_path to select one.`,
|
|
9579
|
+
candidates: allMatchingNodes.map((n) => ({ filePath: n.filePath, kind: n.kind, startLine: n.startLine, content: n.content?.slice(0, 120) }))
|
|
9580
|
+
})
|
|
9581
|
+
}]
|
|
9582
|
+
};
|
|
9583
|
+
}
|
|
9584
|
+
}
|
|
9295
9585
|
const incoming = [...graph.findEdgesTo(node.id)];
|
|
9296
9586
|
const outgoing = [...graph.findEdgesFrom(node.id)];
|
|
9297
9587
|
const callers = incoming.filter((e) => e.kind === "calls").map((e) => ({
|
|
@@ -9338,12 +9628,70 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot, bm25Resolve
|
|
|
9338
9628
|
kind: graph.getNode(e.target)?.kind
|
|
9339
9629
|
})),
|
|
9340
9630
|
cluster,
|
|
9341
|
-
content: node.content?.slice(0,
|
|
9631
|
+
content: node.content?.slice(0, 1500),
|
|
9632
|
+
contentNote: node.content && node.content.length > 1500 ? `(truncated \u2014 use get_source with file_path="${node.filePath}" start_line=${node.startLine} end_line=${node.endLine} for full source)` : void 0,
|
|
9342
9633
|
...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
|
|
9343
9634
|
})
|
|
9344
9635
|
}]
|
|
9345
9636
|
};
|
|
9346
9637
|
}
|
|
9638
|
+
// ── get_source ─────────────────────────────────────────────────────────
|
|
9639
|
+
case "get_source": {
|
|
9640
|
+
const filePathQuery = a.file_path;
|
|
9641
|
+
const startLine = Math.max(1, a.start_line ?? 1);
|
|
9642
|
+
const maxLines = 300;
|
|
9643
|
+
const endLine = Math.min(a.end_line ?? startLine + 99, startLine + maxLines - 1);
|
|
9644
|
+
if (!workspaceRoot) {
|
|
9645
|
+
return { content: [{ type: "text", text: "workspaceRoot not set \u2014 cannot read files." }] };
|
|
9646
|
+
}
|
|
9647
|
+
const matchedFiles = /* @__PURE__ */ new Set();
|
|
9648
|
+
for (const node of graph.allNodes()) {
|
|
9649
|
+
if (node.filePath && node.filePath.includes(filePathQuery)) {
|
|
9650
|
+
matchedFiles.add(node.filePath);
|
|
9651
|
+
}
|
|
9652
|
+
}
|
|
9653
|
+
if (matchedFiles.size === 0) {
|
|
9654
|
+
return { content: [{ type: "text", text: `No indexed file matching "${filePathQuery}". Try a different partial path or use file_symbols to browse.` }] };
|
|
9655
|
+
}
|
|
9656
|
+
if (matchedFiles.size > 1) {
|
|
9657
|
+
return {
|
|
9658
|
+
content: [{
|
|
9659
|
+
type: "text",
|
|
9660
|
+
text: compact({
|
|
9661
|
+
disambiguation: true,
|
|
9662
|
+
message: `Multiple files match "${filePathQuery}". Re-call with a more specific file_path.`,
|
|
9663
|
+
candidates: [...matchedFiles]
|
|
9664
|
+
})
|
|
9665
|
+
}]
|
|
9666
|
+
};
|
|
9667
|
+
}
|
|
9668
|
+
const [resolvedPath] = [...matchedFiles];
|
|
9669
|
+
let fileContent;
|
|
9670
|
+
try {
|
|
9671
|
+
fileContent = fs26.readFileSync(resolvedPath, "utf-8");
|
|
9672
|
+
} catch {
|
|
9673
|
+
return { content: [{ type: "text", text: `Could not read file: ${resolvedPath}` }] };
|
|
9674
|
+
}
|
|
9675
|
+
const lines = fileContent.split("\n");
|
|
9676
|
+
const totalLines = lines.length;
|
|
9677
|
+
const sliceStart = startLine - 1;
|
|
9678
|
+
const sliceEnd = Math.min(endLine, totalLines);
|
|
9679
|
+
const selectedLines = lines.slice(sliceStart, sliceEnd);
|
|
9680
|
+
const numbered = selectedLines.map((l, i) => `${sliceStart + i + 1}: ${l}`).join("\n");
|
|
9681
|
+
return {
|
|
9682
|
+
content: [{
|
|
9683
|
+
type: "text",
|
|
9684
|
+
text: compact({
|
|
9685
|
+
filePath: resolvedPath,
|
|
9686
|
+
startLine,
|
|
9687
|
+
endLine: sliceEnd,
|
|
9688
|
+
totalLines,
|
|
9689
|
+
hasMore: sliceEnd < totalLines,
|
|
9690
|
+
source: numbered
|
|
9691
|
+
})
|
|
9692
|
+
}]
|
|
9693
|
+
};
|
|
9694
|
+
}
|
|
9347
9695
|
// ── blast_radius ───────────────────────────────────────────────────────
|
|
9348
9696
|
case "blast_radius": {
|
|
9349
9697
|
const target = a.target;
|
|
@@ -9476,12 +9824,12 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot, bm25Resolve
|
|
|
9476
9824
|
allExports.push({ kind: node.kind, name: node.name, filePath: node.filePath, startLine: node.startLine });
|
|
9477
9825
|
}
|
|
9478
9826
|
const total = allExports.length;
|
|
9479
|
-
const exports
|
|
9827
|
+
const exports = allExports.slice(offset, offset + effectiveLimit);
|
|
9480
9828
|
const hasMore = offset + effectiveLimit < total;
|
|
9481
9829
|
return {
|
|
9482
9830
|
content: [{
|
|
9483
9831
|
type: "text",
|
|
9484
|
-
text: compact({ exports
|
|
9832
|
+
text: compact({ exports, total, offset, limit: effectiveLimit, hasMore })
|
|
9485
9833
|
}]
|
|
9486
9834
|
};
|
|
9487
9835
|
}
|
|
@@ -9962,6 +10310,13 @@ function findNodeByName(graph, name) {
|
|
|
9962
10310
|
}
|
|
9963
10311
|
return void 0;
|
|
9964
10312
|
}
|
|
10313
|
+
function findNodesByName(graph, name) {
|
|
10314
|
+
const results = [];
|
|
10315
|
+
for (const node of graph.allNodes()) {
|
|
10316
|
+
if (node.name === name) results.push(node);
|
|
10317
|
+
}
|
|
10318
|
+
return results;
|
|
10319
|
+
}
|
|
9965
10320
|
function parseDiff(diffText) {
|
|
9966
10321
|
const result = [];
|
|
9967
10322
|
let currentFile = null;
|
|
@@ -9992,7 +10347,6 @@ function parseDiff(diffText) {
|
|
|
9992
10347
|
}
|
|
9993
10348
|
return result;
|
|
9994
10349
|
}
|
|
9995
|
-
init_group_registry();
|
|
9996
10350
|
init_knowledge_graph();
|
|
9997
10351
|
init_graph_from_db();
|
|
9998
10352
|
init_logger();
|
|
@@ -10006,7 +10360,7 @@ var JobsDB = class {
|
|
|
10006
10360
|
db;
|
|
10007
10361
|
constructor(dbPath) {
|
|
10008
10362
|
fs26.mkdirSync(path32.dirname(dbPath), { recursive: true });
|
|
10009
|
-
this.db = new
|
|
10363
|
+
this.db = new Database3(dbPath);
|
|
10010
10364
|
this.db.pragma("journal_mode = WAL");
|
|
10011
10365
|
this.db.pragma("foreign_keys = ON");
|
|
10012
10366
|
this.createTables();
|
|
@@ -12289,6 +12643,97 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12289
12643
|
}
|
|
12290
12644
|
res.json(result);
|
|
12291
12645
|
});
|
|
12646
|
+
app.post("/api/v1/groups", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12647
|
+
const { name } = req.body;
|
|
12648
|
+
if (!name || !name.trim()) {
|
|
12649
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "Group name is required" } });
|
|
12650
|
+
return;
|
|
12651
|
+
}
|
|
12652
|
+
const trimmed = name.trim();
|
|
12653
|
+
if (groupExists(trimmed)) {
|
|
12654
|
+
res.status(409).json({ error: { code: ErrorCodes.CONFLICT, message: `Group "${trimmed}" already exists` } });
|
|
12655
|
+
return;
|
|
12656
|
+
}
|
|
12657
|
+
const group = { name: trimmed, createdAt: (/* @__PURE__ */ new Date()).toISOString(), members: [] };
|
|
12658
|
+
saveGroup(group);
|
|
12659
|
+
res.status(201).json(group);
|
|
12660
|
+
});
|
|
12661
|
+
app.delete("/api/v1/groups/:name", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12662
|
+
const groupName = req.params["name"];
|
|
12663
|
+
if (!groupExists(groupName)) {
|
|
12664
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
12665
|
+
return;
|
|
12666
|
+
}
|
|
12667
|
+
deleteGroup(groupName);
|
|
12668
|
+
res.status(204).end();
|
|
12669
|
+
});
|
|
12670
|
+
app.patch("/api/v1/groups/:name", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12671
|
+
const groupName = req.params["name"];
|
|
12672
|
+
const group = loadGroup(groupName);
|
|
12673
|
+
if (!group) {
|
|
12674
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
12675
|
+
return;
|
|
12676
|
+
}
|
|
12677
|
+
const { name } = req.body;
|
|
12678
|
+
if (!name || !name.trim()) {
|
|
12679
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "New name is required" } });
|
|
12680
|
+
return;
|
|
12681
|
+
}
|
|
12682
|
+
const newName = name.trim();
|
|
12683
|
+
if (newName !== group.name && groupExists(newName)) {
|
|
12684
|
+
res.status(409).json({ error: { code: ErrorCodes.CONFLICT, message: `Group "${newName}" already exists` } });
|
|
12685
|
+
return;
|
|
12686
|
+
}
|
|
12687
|
+
if (newName !== group.name) {
|
|
12688
|
+
deleteGroup(group.name);
|
|
12689
|
+
group.name = newName;
|
|
12690
|
+
}
|
|
12691
|
+
saveGroup(group);
|
|
12692
|
+
res.json(group);
|
|
12693
|
+
});
|
|
12694
|
+
app.post("/api/v1/groups/:name/members", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12695
|
+
const groupName = req.params["name"];
|
|
12696
|
+
const group = loadGroup(groupName);
|
|
12697
|
+
if (!group) {
|
|
12698
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
12699
|
+
return;
|
|
12700
|
+
}
|
|
12701
|
+
const { groupPath, registryName } = req.body;
|
|
12702
|
+
if (!groupPath || !registryName) {
|
|
12703
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "groupPath and registryName are required" } });
|
|
12704
|
+
return;
|
|
12705
|
+
}
|
|
12706
|
+
const registry = loadRegistry();
|
|
12707
|
+
if (!registry.find((r) => r.name === registryName)) {
|
|
12708
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: `Repo "${registryName}" not found in registry. Run code-intel analyze first.` } });
|
|
12709
|
+
return;
|
|
12710
|
+
}
|
|
12711
|
+
try {
|
|
12712
|
+
const updated = addMember(groupName, { groupPath, registryName });
|
|
12713
|
+
res.json(updated);
|
|
12714
|
+
} catch (err) {
|
|
12715
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: err instanceof Error ? err.message : String(err) } });
|
|
12716
|
+
}
|
|
12717
|
+
});
|
|
12718
|
+
app.delete("/api/v1/groups/:name/members", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12719
|
+
const groupName = req.params["name"];
|
|
12720
|
+
const group = loadGroup(groupName);
|
|
12721
|
+
if (!group) {
|
|
12722
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
12723
|
+
return;
|
|
12724
|
+
}
|
|
12725
|
+
const { groupPath } = req.body;
|
|
12726
|
+
if (!groupPath) {
|
|
12727
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "groupPath is required" } });
|
|
12728
|
+
return;
|
|
12729
|
+
}
|
|
12730
|
+
try {
|
|
12731
|
+
const updated = removeMember(groupName, groupPath);
|
|
12732
|
+
res.json(updated);
|
|
12733
|
+
} catch (err) {
|
|
12734
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: err instanceof Error ? err.message : String(err) } });
|
|
12735
|
+
}
|
|
12736
|
+
});
|
|
12292
12737
|
app.post("/api/v1/groups/:name/sync", async (req, res) => {
|
|
12293
12738
|
const group = loadGroup(req.params.name);
|
|
12294
12739
|
if (!group) {
|
|
@@ -12299,8 +12744,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12299
12744
|
const result = await syncGroup(group);
|
|
12300
12745
|
saveSyncResult(result);
|
|
12301
12746
|
group.lastSync = result.syncedAt;
|
|
12302
|
-
|
|
12303
|
-
saveGroup2(group);
|
|
12747
|
+
saveGroup(group);
|
|
12304
12748
|
res.json(result);
|
|
12305
12749
|
} catch (err) {
|
|
12306
12750
|
res.status(500).json({ error: { code: ErrorCodes.INTERNAL_ERROR, message: err instanceof Error ? err.message : String(err) } });
|
|
@@ -12800,7 +13244,6 @@ async function startHttpServer(graph, repoName, port = 4747, workspaceRoot, watc
|
|
|
12800
13244
|
}
|
|
12801
13245
|
|
|
12802
13246
|
// src/multi-repo/index.ts
|
|
12803
|
-
init_group_registry();
|
|
12804
13247
|
init_graph_from_db();
|
|
12805
13248
|
|
|
12806
13249
|
// src/multi-repo/cross-repo-search.ts
|