@vohongtho.infotech/code-intel 1.0.0 → 1.0.2
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 +28 -4
- package/dist/cli/hook.js +348 -0
- package/dist/cli/hook.js.map +1 -0
- package/dist/cli/main.js +1921 -369
- package/dist/cli/main.js.map +1 -1
- package/dist/index.d.ts +19 -3
- package/dist/index.js +862 -569
- package/dist/index.js.map +1 -1
- package/dist/web/assets/{es-DIfCC5I3.js → es-DiINqj58.js} +1 -1
- package/dist/web/assets/index-CzWucUxe.js +354 -0
- package/dist/web/assets/index-D3zJQH9-.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -2
- package/dist/web/assets/index-QSOOiRQm.js +0 -352
- package/dist/web/assets/index-XjZQJMiV.css +0 -2
package/dist/cli/main.js
CHANGED
|
@@ -7,9 +7,9 @@ import { SEMRESATTRS_DEPLOYMENT_ENVIRONMENT, SEMRESATTRS_SERVICE_NAME } from '@o
|
|
|
7
7
|
import { SpanStatusCode, trace, context } from '@opentelemetry/api';
|
|
8
8
|
import winston from 'winston';
|
|
9
9
|
import DailyRotateFile from 'winston-daily-rotate-file';
|
|
10
|
-
import
|
|
10
|
+
import fs39, { readFileSync, existsSync } from 'fs';
|
|
11
11
|
import path39, { dirname, join } from 'path';
|
|
12
|
-
import
|
|
12
|
+
import os18 from 'os';
|
|
13
13
|
import { Registry, collectDefaultMetrics, Counter, Histogram, Gauge } from 'prom-client';
|
|
14
14
|
import { createRequire } from 'module';
|
|
15
15
|
import { fileURLToPath } from 'url';
|
|
@@ -38,6 +38,7 @@ import https from 'https';
|
|
|
38
38
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
39
39
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
40
40
|
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
41
|
+
import process2 from 'process';
|
|
41
42
|
|
|
42
43
|
var __defProp = Object.defineProperty;
|
|
43
44
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -327,17 +328,17 @@ var init_logger = __esm({
|
|
|
327
328
|
};
|
|
328
329
|
}
|
|
329
330
|
/** Global log directory: ~/.code-intel/logs */
|
|
330
|
-
static LOG_DIR = path39.join(
|
|
331
|
+
static LOG_DIR = path39.join(os18.homedir(), ".code-intel", "logs");
|
|
331
332
|
static getLogger() {
|
|
332
333
|
if (!_Logger.instance) {
|
|
333
334
|
const isProduction = process.env.NODE_ENV === "production";
|
|
334
335
|
const logLevel = process.env.LOG_LEVEL ?? "info";
|
|
335
336
|
const transports = [];
|
|
336
|
-
transports.push(new winston.transports.Console());
|
|
337
|
+
transports.push(new winston.transports.Console({ stderrLevels: ["error", "warn", "info", "http", "verbose", "debug", "silly"] }));
|
|
337
338
|
if (!isProduction) {
|
|
338
339
|
try {
|
|
339
|
-
if (!
|
|
340
|
-
|
|
340
|
+
if (!fs39.existsSync(_Logger.LOG_DIR)) {
|
|
341
|
+
fs39.mkdirSync(_Logger.LOG_DIR, { recursive: true });
|
|
341
342
|
}
|
|
342
343
|
transports.push(
|
|
343
344
|
new DailyRotateFile({
|
|
@@ -394,6 +395,10 @@ var init_logger = __esm({
|
|
|
394
395
|
});
|
|
395
396
|
|
|
396
397
|
// src/graph/knowledge-graph.ts
|
|
398
|
+
var knowledge_graph_exports = {};
|
|
399
|
+
__export(knowledge_graph_exports, {
|
|
400
|
+
createKnowledgeGraph: () => createKnowledgeGraph
|
|
401
|
+
});
|
|
397
402
|
function createKnowledgeGraph() {
|
|
398
403
|
const nodes = /* @__PURE__ */ new Map();
|
|
399
404
|
const edges = /* @__PURE__ */ new Map();
|
|
@@ -889,7 +894,7 @@ var init_id_generator = __esm({
|
|
|
889
894
|
});
|
|
890
895
|
function loadIgnorePatterns(workspaceRoot) {
|
|
891
896
|
try {
|
|
892
|
-
const raw =
|
|
897
|
+
const raw = fs39.readFileSync(path39.join(workspaceRoot, ".codeintelignore"), "utf-8");
|
|
893
898
|
const extras = /* @__PURE__ */ new Set();
|
|
894
899
|
for (const line of raw.split("\n")) {
|
|
895
900
|
const trimmed = line.trim();
|
|
@@ -949,7 +954,7 @@ var init_scan_phase = __esm({
|
|
|
949
954
|
function walk2(dir) {
|
|
950
955
|
let entries;
|
|
951
956
|
try {
|
|
952
|
-
entries =
|
|
957
|
+
entries = fs39.readdirSync(dir, { withFileTypes: true });
|
|
953
958
|
} catch {
|
|
954
959
|
return;
|
|
955
960
|
}
|
|
@@ -966,7 +971,7 @@ var init_scan_phase = __esm({
|
|
|
966
971
|
if (!extensions.has(ext)) continue;
|
|
967
972
|
const fullPath = path39.join(dir, name);
|
|
968
973
|
try {
|
|
969
|
-
const stat =
|
|
974
|
+
const stat = fs39.statSync(fullPath);
|
|
970
975
|
if (stat.size > MAX_FILE_SIZE_BYTES) continue;
|
|
971
976
|
} catch {
|
|
972
977
|
continue;
|
|
@@ -2519,7 +2524,7 @@ var init_parse_phase = __esm({
|
|
|
2519
2524
|
const batch = filePaths.slice(i, i + CONCURRENCY);
|
|
2520
2525
|
await Promise.all(batch.map(async (filePath) => {
|
|
2521
2526
|
try {
|
|
2522
|
-
const source = await
|
|
2527
|
+
const source = await fs39.promises.readFile(filePath, "utf-8");
|
|
2523
2528
|
context2.fileCache.set(filePath, source);
|
|
2524
2529
|
} catch {
|
|
2525
2530
|
}
|
|
@@ -3111,7 +3116,7 @@ var init_llm_governance = __esm({
|
|
|
3111
3116
|
}
|
|
3112
3117
|
/** Path to the JSONL log file. */
|
|
3113
3118
|
getLogPath() {
|
|
3114
|
-
return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path39.join(
|
|
3119
|
+
return process.env["CODE_INTEL_GOVERNANCE_LOG_PATH"] ?? path39.join(os18.homedir(), ".code-intel", "llm-governance.jsonl");
|
|
3115
3120
|
}
|
|
3116
3121
|
/**
|
|
3117
3122
|
* Append an entry to the governance log.
|
|
@@ -3127,8 +3132,8 @@ var init_llm_governance = __esm({
|
|
|
3127
3132
|
...entry
|
|
3128
3133
|
};
|
|
3129
3134
|
const logPath = this.getLogPath();
|
|
3130
|
-
|
|
3131
|
-
|
|
3135
|
+
fs39.mkdirSync(path39.dirname(logPath), { recursive: true });
|
|
3136
|
+
fs39.appendFileSync(logPath, JSON.stringify(full) + "\n", "utf-8");
|
|
3132
3137
|
} catch {
|
|
3133
3138
|
}
|
|
3134
3139
|
}
|
|
@@ -3138,7 +3143,7 @@ var init_llm_governance = __esm({
|
|
|
3138
3143
|
*/
|
|
3139
3144
|
readLog(limit = 100) {
|
|
3140
3145
|
try {
|
|
3141
|
-
const raw =
|
|
3146
|
+
const raw = fs39.readFileSync(this.getLogPath(), "utf-8");
|
|
3142
3147
|
const lines = raw.split("\n").filter((l) => l.trim().length > 0).slice(-limit);
|
|
3143
3148
|
return lines.map((l) => JSON.parse(l));
|
|
3144
3149
|
} catch {
|
|
@@ -3272,6 +3277,49 @@ var init_anthropic = __esm({
|
|
|
3272
3277
|
}
|
|
3273
3278
|
});
|
|
3274
3279
|
|
|
3280
|
+
// src/llm/providers/custom.ts
|
|
3281
|
+
var custom_exports = {};
|
|
3282
|
+
__export(custom_exports, {
|
|
3283
|
+
CustomProvider: () => CustomProvider
|
|
3284
|
+
});
|
|
3285
|
+
var CustomProvider;
|
|
3286
|
+
var init_custom = __esm({
|
|
3287
|
+
"src/llm/providers/custom.ts"() {
|
|
3288
|
+
CustomProvider = class {
|
|
3289
|
+
modelName;
|
|
3290
|
+
baseUrl;
|
|
3291
|
+
apiKey;
|
|
3292
|
+
constructor(baseUrl, model, apiKey = "") {
|
|
3293
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
3294
|
+
this.modelName = model;
|
|
3295
|
+
this.apiKey = apiKey;
|
|
3296
|
+
}
|
|
3297
|
+
async summarize(prompt2) {
|
|
3298
|
+
const url = `${this.baseUrl}/chat/completions`;
|
|
3299
|
+
const headers = {
|
|
3300
|
+
"Content-Type": "application/json"
|
|
3301
|
+
};
|
|
3302
|
+
if (this.apiKey) {
|
|
3303
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
3304
|
+
}
|
|
3305
|
+
const body = JSON.stringify({
|
|
3306
|
+
model: this.modelName,
|
|
3307
|
+
messages: [{ role: "user", content: prompt2 }],
|
|
3308
|
+
max_tokens: 200,
|
|
3309
|
+
temperature: 0.3
|
|
3310
|
+
});
|
|
3311
|
+
const res = await fetch(url, { method: "POST", headers, body });
|
|
3312
|
+
if (!res.ok) {
|
|
3313
|
+
const text = await res.text().catch(() => res.statusText);
|
|
3314
|
+
throw new Error(`Custom LLM API error ${res.status}: ${text}`);
|
|
3315
|
+
}
|
|
3316
|
+
const data = await res.json();
|
|
3317
|
+
return data.choices?.[0]?.message?.content?.trim() ?? "";
|
|
3318
|
+
}
|
|
3319
|
+
};
|
|
3320
|
+
}
|
|
3321
|
+
});
|
|
3322
|
+
|
|
3275
3323
|
// src/llm/providers/ollama.ts
|
|
3276
3324
|
var ollama_exports = {};
|
|
3277
3325
|
__export(ollama_exports, {
|
|
@@ -3315,7 +3363,7 @@ __export(factory_exports, {
|
|
|
3315
3363
|
createLLMProvider: () => createLLMProvider
|
|
3316
3364
|
});
|
|
3317
3365
|
async function createLLMProvider(config = {}) {
|
|
3318
|
-
const { provider = "ollama", model } = config;
|
|
3366
|
+
const { provider = "ollama", model, baseUrl, apiKey } = config;
|
|
3319
3367
|
switch (provider) {
|
|
3320
3368
|
case "openai": {
|
|
3321
3369
|
const { OpenAIProvider: OpenAIProvider2 } = await Promise.resolve().then(() => (init_openai(), openai_exports));
|
|
@@ -3325,6 +3373,13 @@ async function createLLMProvider(config = {}) {
|
|
|
3325
3373
|
const { AnthropicProvider: AnthropicProvider2 } = await Promise.resolve().then(() => (init_anthropic(), anthropic_exports));
|
|
3326
3374
|
return new AnthropicProvider2(model);
|
|
3327
3375
|
}
|
|
3376
|
+
case "custom": {
|
|
3377
|
+
const { CustomProvider: CustomProvider2 } = await Promise.resolve().then(() => (init_custom(), custom_exports));
|
|
3378
|
+
const url = baseUrl ?? "http://localhost:1234/v1";
|
|
3379
|
+
const mdl = model ?? "default";
|
|
3380
|
+
const key = apiKey ?? "";
|
|
3381
|
+
return new CustomProvider2(url, mdl, key);
|
|
3382
|
+
}
|
|
3328
3383
|
case "ollama":
|
|
3329
3384
|
default: {
|
|
3330
3385
|
const { OllamaProvider: OllamaProvider2 } = await Promise.resolve().then(() => (init_ollama(), ollama_exports));
|
|
@@ -3478,7 +3533,7 @@ var init_worker_pool = __esm({
|
|
|
3478
3533
|
constructor(opts) {
|
|
3479
3534
|
super();
|
|
3480
3535
|
this.workerScript = opts.workerScript;
|
|
3481
|
-
this.workerCount = opts.workerCount ?? Math.max(1,
|
|
3536
|
+
this.workerCount = opts.workerCount ?? Math.max(1, os18.cpus().length - 1);
|
|
3482
3537
|
this.maxQueueSize = opts.maxQueueSize ?? 200;
|
|
3483
3538
|
this.maxTaskRetries = opts.maxTaskRetries ?? 2;
|
|
3484
3539
|
}
|
|
@@ -3606,20 +3661,20 @@ var init_parse_phase_parallel = __esm({
|
|
|
3606
3661
|
if (!context2.fileCache) context2.fileCache = /* @__PURE__ */ new Map();
|
|
3607
3662
|
if (!context2.fileFunctionIndex) context2.fileFunctionIndex = /* @__PURE__ */ new Map();
|
|
3608
3663
|
const filePaths = context2.filePaths;
|
|
3609
|
-
const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1,
|
|
3664
|
+
const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1, os18.cpus().length - 1);
|
|
3610
3665
|
const CONCURRENCY = 64;
|
|
3611
3666
|
for (let i = 0; i < filePaths.length; i += CONCURRENCY) {
|
|
3612
3667
|
const batch = filePaths.slice(i, i + CONCURRENCY);
|
|
3613
3668
|
await Promise.all(batch.map(async (filePath) => {
|
|
3614
3669
|
try {
|
|
3615
|
-
const source = await
|
|
3670
|
+
const source = await fs39.promises.readFile(filePath, "utf-8");
|
|
3616
3671
|
context2.fileCache.set(filePath, source);
|
|
3617
3672
|
} catch {
|
|
3618
3673
|
}
|
|
3619
3674
|
}));
|
|
3620
3675
|
}
|
|
3621
3676
|
const workerScript = workerScriptPath();
|
|
3622
|
-
const workerScriptExists =
|
|
3677
|
+
const workerScriptExists = fs39.existsSync(workerScript);
|
|
3623
3678
|
if (!workerScriptExists || workerCount === 1) {
|
|
3624
3679
|
logger_default.info(`[parse-parallel] falling back to sequential (workerCount=${workerCount}, scriptExists=${workerScriptExists})`);
|
|
3625
3680
|
const { parsePhase: parsePhase2 } = await Promise.resolve().then(() => (init_parse_phase(), parse_phase_exports));
|
|
@@ -3734,8 +3789,8 @@ var init_resolve_phase_parallel = __esm({
|
|
|
3734
3789
|
}
|
|
3735
3790
|
const snapshot = { symbolIndex, fileSymbolIndex, fileIndex, workspaceRoot };
|
|
3736
3791
|
const workerScript = workerScriptPath2();
|
|
3737
|
-
const workerScriptExists =
|
|
3738
|
-
const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1,
|
|
3792
|
+
const workerScriptExists = fs39.existsSync(workerScript);
|
|
3793
|
+
const workerCount = parseInt(process.env["PARSE_WORKERS"] ?? "", 10) || Math.max(1, os18.cpus().length - 1);
|
|
3739
3794
|
if (!workerScriptExists || workerCount === 1) {
|
|
3740
3795
|
logger_default.info(`[resolve-parallel] falling back to sequential`);
|
|
3741
3796
|
const { resolvePhase: resolvePhase2 } = await Promise.resolve().then(() => (init_resolve_phase(), resolve_phase_exports));
|
|
@@ -4071,6 +4126,41 @@ function nodeToDoc(node) {
|
|
|
4071
4126
|
(node.content ?? "").slice(0, 1e3)
|
|
4072
4127
|
].join(" ");
|
|
4073
4128
|
}
|
|
4129
|
+
function heapTopK(scores, k) {
|
|
4130
|
+
if (k <= 0) return [];
|
|
4131
|
+
const heap = [];
|
|
4132
|
+
function heapifyUp(i) {
|
|
4133
|
+
while (i > 0) {
|
|
4134
|
+
const parent = i - 1 >> 1;
|
|
4135
|
+
if (heap[parent][1] > heap[i][1]) {
|
|
4136
|
+
[heap[parent], heap[i]] = [heap[i], heap[parent]];
|
|
4137
|
+
i = parent;
|
|
4138
|
+
} else break;
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
function heapifyDown(i) {
|
|
4142
|
+
const n = heap.length;
|
|
4143
|
+
while (true) {
|
|
4144
|
+
let smallest = i;
|
|
4145
|
+
const l = 2 * i + 1, r = 2 * i + 2;
|
|
4146
|
+
if (l < n && heap[l][1] < heap[smallest][1]) smallest = l;
|
|
4147
|
+
if (r < n && heap[r][1] < heap[smallest][1]) smallest = r;
|
|
4148
|
+
if (smallest === i) break;
|
|
4149
|
+
[heap[smallest], heap[i]] = [heap[i], heap[smallest]];
|
|
4150
|
+
i = smallest;
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
for (const [nodeId, score] of scores) {
|
|
4154
|
+
if (heap.length < k) {
|
|
4155
|
+
heap.push([nodeId, score]);
|
|
4156
|
+
heapifyUp(heap.length - 1);
|
|
4157
|
+
} else if (score > heap[0][1]) {
|
|
4158
|
+
heap[0] = [nodeId, score];
|
|
4159
|
+
heapifyDown(0);
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
return heap.sort((a, b) => b[1] - a[1]);
|
|
4163
|
+
}
|
|
4074
4164
|
function getBm25DbPath(workspaceRoot) {
|
|
4075
4165
|
return path39.join(workspaceRoot, ".code-intel", "bm25.db");
|
|
4076
4166
|
}
|
|
@@ -4132,10 +4222,10 @@ var init_bm25_index = __esm({
|
|
|
4132
4222
|
postings.push({ nodeId, tf: count });
|
|
4133
4223
|
}
|
|
4134
4224
|
}
|
|
4135
|
-
|
|
4225
|
+
fs39.mkdirSync(path39.dirname(this.dbPath), { recursive: true });
|
|
4136
4226
|
for (const f of [this.dbPath, `${this.dbPath}-shm`, `${this.dbPath}-wal`]) {
|
|
4137
4227
|
try {
|
|
4138
|
-
if (
|
|
4228
|
+
if (fs39.existsSync(f)) fs39.unlinkSync(f);
|
|
4139
4229
|
} catch {
|
|
4140
4230
|
}
|
|
4141
4231
|
}
|
|
@@ -4173,7 +4263,7 @@ var init_bm25_index = __esm({
|
|
|
4173
4263
|
* Called once on `serve` startup.
|
|
4174
4264
|
*/
|
|
4175
4265
|
load() {
|
|
4176
|
-
if (!
|
|
4266
|
+
if (!fs39.existsSync(this.dbPath)) return;
|
|
4177
4267
|
const db = new Database2(this.dbPath, { readonly: true });
|
|
4178
4268
|
try {
|
|
4179
4269
|
const getMeta = db.prepare("SELECT value FROM bm25_meta WHERE key = ?");
|
|
@@ -4207,8 +4297,13 @@ var init_bm25_index = __esm({
|
|
|
4207
4297
|
}
|
|
4208
4298
|
// ── Search ──────────────────────────────────────────────────────────────────
|
|
4209
4299
|
/**
|
|
4210
|
-
* BM25 search.
|
|
4211
|
-
*
|
|
4300
|
+
* BM25 search.
|
|
4301
|
+
*
|
|
4302
|
+
* Performance strategy:
|
|
4303
|
+
* 1. Skip ultra-high-df terms (df/N > 0.6) — near-zero IDF, dominate posting
|
|
4304
|
+
* lists for common words like "function", "return", "export" in large repos.
|
|
4305
|
+
* 2. Min-heap top-K selection — O(n log k) instead of full O(n log n) sort.
|
|
4306
|
+
* For k=10 and n=30,000 candidates this is ~10× faster than Array.sort.
|
|
4212
4307
|
*/
|
|
4213
4308
|
search(query, limit) {
|
|
4214
4309
|
if (!this._loaded || this.invertedIndex.size === 0) return [];
|
|
@@ -4221,6 +4316,7 @@ var init_bm25_index = __esm({
|
|
|
4221
4316
|
const postings = this.invertedIndex.get(term);
|
|
4222
4317
|
if (!postings) continue;
|
|
4223
4318
|
const df = postings.length;
|
|
4319
|
+
if (N > 100 && df / N > 0.6) continue;
|
|
4224
4320
|
const idf = Math.log((N - df + 0.5) / (df + 0.5) + 1);
|
|
4225
4321
|
for (const { nodeId, tf } of postings) {
|
|
4226
4322
|
const dl = this.docLengths.get(nodeId) ?? avgdl;
|
|
@@ -4228,10 +4324,9 @@ var init_bm25_index = __esm({
|
|
|
4228
4324
|
scores.set(nodeId, (scores.get(nodeId) ?? 0) + score);
|
|
4229
4325
|
}
|
|
4230
4326
|
}
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
return topK.map(([nodeId, score]) => {
|
|
4327
|
+
if (scores.size === 0) return [];
|
|
4328
|
+
const topEntries = heapTopK(scores, limit);
|
|
4329
|
+
return topEntries.map(([nodeId, score]) => {
|
|
4235
4330
|
const meta = this.nodeMeta.get(nodeId);
|
|
4236
4331
|
return {
|
|
4237
4332
|
nodeId,
|
|
@@ -4250,7 +4345,7 @@ var init_bm25_index = __esm({
|
|
|
4250
4345
|
* Works even if `load()` was not called (reads affected terms directly from DB).
|
|
4251
4346
|
*/
|
|
4252
4347
|
updateNodes(nodes) {
|
|
4253
|
-
if (!
|
|
4348
|
+
if (!fs39.existsSync(this.dbPath)) return;
|
|
4254
4349
|
if (nodes.length === 0) return;
|
|
4255
4350
|
const changedIds = new Set(nodes.map((n) => n.id));
|
|
4256
4351
|
const newTermFreqs = /* @__PURE__ */ new Map();
|
|
@@ -4325,6 +4420,12 @@ var init_bm25_index = __esm({
|
|
|
4325
4420
|
};
|
|
4326
4421
|
}
|
|
4327
4422
|
});
|
|
4423
|
+
|
|
4424
|
+
// src/storage/db-manager.ts
|
|
4425
|
+
var db_manager_exports = {};
|
|
4426
|
+
__export(db_manager_exports, {
|
|
4427
|
+
DbManager: () => DbManager
|
|
4428
|
+
});
|
|
4328
4429
|
var DbManager;
|
|
4329
4430
|
var init_db_manager = __esm({
|
|
4330
4431
|
"src/storage/db-manager.ts"() {
|
|
@@ -4339,7 +4440,7 @@ var init_db_manager = __esm({
|
|
|
4339
4440
|
}
|
|
4340
4441
|
async init() {
|
|
4341
4442
|
if (!this.readOnly) {
|
|
4342
|
-
|
|
4443
|
+
fs39.mkdirSync(path39.dirname(this.dbPath), { recursive: true });
|
|
4343
4444
|
}
|
|
4344
4445
|
this.db = new Database(this.dbPath, 0, true, this.readOnly);
|
|
4345
4446
|
await this.db.init();
|
|
@@ -4379,7 +4480,7 @@ var init_db_manager = __esm({
|
|
|
4379
4480
|
}
|
|
4380
4481
|
});
|
|
4381
4482
|
function writeNodeCSVs(graph, outputDir) {
|
|
4382
|
-
|
|
4483
|
+
fs39.mkdirSync(outputDir, { recursive: true });
|
|
4383
4484
|
const header = "id,name,file_path,start_line,end_line,exported,content,metadata\n";
|
|
4384
4485
|
const tableBuffers = /* @__PURE__ */ new Map();
|
|
4385
4486
|
const tableFilePaths = /* @__PURE__ */ new Map();
|
|
@@ -4407,12 +4508,12 @@ function writeNodeCSVs(graph, outputDir) {
|
|
|
4407
4508
|
);
|
|
4408
4509
|
}
|
|
4409
4510
|
for (const [table, lines] of tableBuffers) {
|
|
4410
|
-
|
|
4511
|
+
fs39.writeFileSync(tableFilePaths.get(table), lines.join(""), "utf-8");
|
|
4411
4512
|
}
|
|
4412
4513
|
return tableFilePaths;
|
|
4413
4514
|
}
|
|
4414
4515
|
function writeEdgeCSV(graph, outputDir) {
|
|
4415
|
-
|
|
4516
|
+
fs39.mkdirSync(outputDir, { recursive: true });
|
|
4416
4517
|
const header = "from_id,to_id,kind,weight,label\n";
|
|
4417
4518
|
const groups = /* @__PURE__ */ new Map();
|
|
4418
4519
|
for (const edge of graph.allEdges()) {
|
|
@@ -4438,7 +4539,7 @@ function writeEdgeCSV(graph, outputDir) {
|
|
|
4438
4539
|
}
|
|
4439
4540
|
const result = [];
|
|
4440
4541
|
for (const group of groups.values()) {
|
|
4441
|
-
|
|
4542
|
+
fs39.writeFileSync(group.filePath, group.lines.join(""), "utf-8");
|
|
4442
4543
|
result.push({ fromTable: group.from, toTable: group.to, filePath: group.filePath });
|
|
4443
4544
|
}
|
|
4444
4545
|
return result;
|
|
@@ -4481,7 +4582,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
4481
4582
|
} catch {
|
|
4482
4583
|
}
|
|
4483
4584
|
}
|
|
4484
|
-
const tmpDir =
|
|
4585
|
+
const tmpDir = fs39.mkdtempSync(path39.join(os18.tmpdir(), "code-intel-csv-"));
|
|
4485
4586
|
try {
|
|
4486
4587
|
const nodeTableFiles = writeNodeCSVs(graph, tmpDir);
|
|
4487
4588
|
const edgeGroups = writeEdgeCSV(graph, tmpDir);
|
|
@@ -4500,8 +4601,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
4500
4601
|
}
|
|
4501
4602
|
let nodeCount = 0;
|
|
4502
4603
|
for (const [table, csvPath] of nodeTableFiles) {
|
|
4503
|
-
if (!
|
|
4504
|
-
const stat =
|
|
4604
|
+
if (!fs39.existsSync(csvPath)) continue;
|
|
4605
|
+
const stat = fs39.statSync(csvPath);
|
|
4505
4606
|
if (stat.size < 50) continue;
|
|
4506
4607
|
try {
|
|
4507
4608
|
await dbManager.execute(
|
|
@@ -4514,8 +4615,8 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
4514
4615
|
}
|
|
4515
4616
|
let edgeCount = 0;
|
|
4516
4617
|
for (const group of edgeGroups) {
|
|
4517
|
-
if (!
|
|
4518
|
-
const stat =
|
|
4618
|
+
if (!fs39.existsSync(group.filePath)) continue;
|
|
4619
|
+
const stat = fs39.statSync(group.filePath);
|
|
4519
4620
|
if (stat.size < 50) continue;
|
|
4520
4621
|
try {
|
|
4521
4622
|
await dbManager.execute(
|
|
@@ -4529,7 +4630,7 @@ async function loadGraphToDB(graph, dbManager) {
|
|
|
4529
4630
|
return { nodeCount, edgeCount };
|
|
4530
4631
|
} finally {
|
|
4531
4632
|
try {
|
|
4532
|
-
|
|
4633
|
+
fs39.rmSync(tmpDir, { recursive: true, force: true });
|
|
4533
4634
|
} catch {
|
|
4534
4635
|
}
|
|
4535
4636
|
}
|
|
@@ -4640,15 +4741,15 @@ __export(repo_registry_exports, {
|
|
|
4640
4741
|
});
|
|
4641
4742
|
function loadRegistry() {
|
|
4642
4743
|
try {
|
|
4643
|
-
const data =
|
|
4744
|
+
const data = fs39.readFileSync(REPOS_FILE, "utf-8");
|
|
4644
4745
|
return JSON.parse(data);
|
|
4645
4746
|
} catch {
|
|
4646
4747
|
return [];
|
|
4647
4748
|
}
|
|
4648
4749
|
}
|
|
4649
4750
|
function saveRegistry(entries) {
|
|
4650
|
-
|
|
4651
|
-
|
|
4751
|
+
fs39.mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
4752
|
+
fs39.writeFileSync(REPOS_FILE, JSON.stringify(entries, null, 2));
|
|
4652
4753
|
}
|
|
4653
4754
|
function upsertRepo(entry) {
|
|
4654
4755
|
const entries = loadRegistry();
|
|
@@ -4667,7 +4768,7 @@ function removeRepo(repoPath) {
|
|
|
4667
4768
|
var GLOBAL_DIR, REPOS_FILE;
|
|
4668
4769
|
var init_repo_registry = __esm({
|
|
4669
4770
|
"src/storage/repo-registry.ts"() {
|
|
4670
|
-
GLOBAL_DIR = path39.join(
|
|
4771
|
+
GLOBAL_DIR = path39.join(os18.homedir(), ".code-intel");
|
|
4671
4772
|
REPOS_FILE = path39.join(GLOBAL_DIR, "repos.json");
|
|
4672
4773
|
}
|
|
4673
4774
|
});
|
|
@@ -4682,12 +4783,12 @@ __export(metadata_exports, {
|
|
|
4682
4783
|
});
|
|
4683
4784
|
function saveMetadata(repoDir, metadata) {
|
|
4684
4785
|
const metaDir = path39.join(repoDir, ".code-intel");
|
|
4685
|
-
|
|
4686
|
-
|
|
4786
|
+
fs39.mkdirSync(metaDir, { recursive: true });
|
|
4787
|
+
fs39.writeFileSync(path39.join(metaDir, "meta.json"), JSON.stringify(metadata, null, 2));
|
|
4687
4788
|
}
|
|
4688
4789
|
function loadMetadata(repoDir) {
|
|
4689
4790
|
try {
|
|
4690
|
-
const data =
|
|
4791
|
+
const data = fs39.readFileSync(path39.join(repoDir, ".code-intel", "meta.json"), "utf-8");
|
|
4691
4792
|
return JSON.parse(data);
|
|
4692
4793
|
} catch {
|
|
4693
4794
|
return null;
|
|
@@ -4757,23 +4858,23 @@ function groupFile(name) {
|
|
|
4757
4858
|
}
|
|
4758
4859
|
function loadGroup(name) {
|
|
4759
4860
|
try {
|
|
4760
|
-
return JSON.parse(
|
|
4861
|
+
return JSON.parse(fs39.readFileSync(groupFile(name), "utf-8"));
|
|
4761
4862
|
} catch {
|
|
4762
4863
|
return null;
|
|
4763
4864
|
}
|
|
4764
4865
|
}
|
|
4765
4866
|
function saveGroup(group) {
|
|
4766
|
-
|
|
4767
|
-
|
|
4867
|
+
fs39.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
4868
|
+
fs39.writeFileSync(groupFile(group.name), JSON.stringify(group, null, 2) + "\n");
|
|
4768
4869
|
}
|
|
4769
4870
|
function listGroups() {
|
|
4770
4871
|
const groups = [];
|
|
4771
4872
|
try {
|
|
4772
|
-
for (const file of
|
|
4873
|
+
for (const file of fs39.readdirSync(GROUPS_DIR)) {
|
|
4773
4874
|
if (!file.endsWith(".json") || file.endsWith(".sync.json")) continue;
|
|
4774
4875
|
try {
|
|
4775
4876
|
const g = JSON.parse(
|
|
4776
|
-
|
|
4877
|
+
fs39.readFileSync(path39.join(GROUPS_DIR, file), "utf-8")
|
|
4777
4878
|
);
|
|
4778
4879
|
groups.push(g);
|
|
4779
4880
|
} catch {
|
|
@@ -4785,16 +4886,16 @@ function listGroups() {
|
|
|
4785
4886
|
}
|
|
4786
4887
|
function deleteGroup(name) {
|
|
4787
4888
|
try {
|
|
4788
|
-
|
|
4889
|
+
fs39.unlinkSync(groupFile(name));
|
|
4789
4890
|
} catch {
|
|
4790
4891
|
}
|
|
4791
4892
|
try {
|
|
4792
|
-
|
|
4893
|
+
fs39.unlinkSync(path39.join(GROUPS_DIR, `${name}.sync.json`));
|
|
4793
4894
|
} catch {
|
|
4794
4895
|
}
|
|
4795
4896
|
}
|
|
4796
4897
|
function groupExists(name) {
|
|
4797
|
-
return
|
|
4898
|
+
return fs39.existsSync(groupFile(name));
|
|
4798
4899
|
}
|
|
4799
4900
|
function addMember(groupName, member) {
|
|
4800
4901
|
const group = loadGroup(groupName);
|
|
@@ -4820,8 +4921,8 @@ function removeMember(groupName, groupPath) {
|
|
|
4820
4921
|
return group;
|
|
4821
4922
|
}
|
|
4822
4923
|
function saveSyncResult(result) {
|
|
4823
|
-
|
|
4824
|
-
|
|
4924
|
+
fs39.mkdirSync(GROUPS_DIR, { recursive: true });
|
|
4925
|
+
fs39.writeFileSync(
|
|
4825
4926
|
path39.join(GROUPS_DIR, `${result.groupName}.sync.json`),
|
|
4826
4927
|
JSON.stringify(result, null, 2) + "\n"
|
|
4827
4928
|
);
|
|
@@ -4829,7 +4930,7 @@ function saveSyncResult(result) {
|
|
|
4829
4930
|
function loadSyncResult(groupName) {
|
|
4830
4931
|
try {
|
|
4831
4932
|
return JSON.parse(
|
|
4832
|
-
|
|
4933
|
+
fs39.readFileSync(path39.join(GROUPS_DIR, `${groupName}.sync.json`), "utf-8")
|
|
4833
4934
|
);
|
|
4834
4935
|
} catch {
|
|
4835
4936
|
return null;
|
|
@@ -4838,11 +4939,15 @@ function loadSyncResult(groupName) {
|
|
|
4838
4939
|
var GROUPS_DIR;
|
|
4839
4940
|
var init_group_registry = __esm({
|
|
4840
4941
|
"src/multi-repo/group-registry.ts"() {
|
|
4841
|
-
GROUPS_DIR = path39.join(
|
|
4942
|
+
GROUPS_DIR = path39.join(os18.homedir(), ".code-intel", "groups");
|
|
4842
4943
|
}
|
|
4843
4944
|
});
|
|
4844
4945
|
|
|
4845
4946
|
// src/multi-repo/graph-from-db.ts
|
|
4947
|
+
var graph_from_db_exports = {};
|
|
4948
|
+
__export(graph_from_db_exports, {
|
|
4949
|
+
loadGraphFromDB: () => loadGraphFromDB
|
|
4950
|
+
});
|
|
4846
4951
|
function parseRow(row, kind) {
|
|
4847
4952
|
return {
|
|
4848
4953
|
id: String(row["id"] ?? ""),
|
|
@@ -4923,7 +5028,7 @@ function scanForFiles(root, matcher, maxDepth = 2) {
|
|
|
4923
5028
|
if (depth > maxDepth) return;
|
|
4924
5029
|
let entries;
|
|
4925
5030
|
try {
|
|
4926
|
-
entries =
|
|
5031
|
+
entries = fs39.readdirSync(dir, { withFileTypes: true });
|
|
4927
5032
|
} catch {
|
|
4928
5033
|
return;
|
|
4929
5034
|
}
|
|
@@ -4945,7 +5050,7 @@ var init_file_scanner = __esm({
|
|
|
4945
5050
|
});
|
|
4946
5051
|
function tryParseFile(filePath) {
|
|
4947
5052
|
const ext = path39.extname(filePath).toLowerCase();
|
|
4948
|
-
const content =
|
|
5053
|
+
const content = fs39.readFileSync(filePath, "utf-8");
|
|
4949
5054
|
if (ext === ".json") {
|
|
4950
5055
|
try {
|
|
4951
5056
|
return JSON.parse(content);
|
|
@@ -5014,7 +5119,7 @@ async function parseGraphQLContracts(repoRoot) {
|
|
|
5014
5119
|
const files = scanForFiles(repoRoot, (name) => name.endsWith(".graphql") || name.endsWith(".gql"));
|
|
5015
5120
|
const contracts = [];
|
|
5016
5121
|
for (const filePath of files) {
|
|
5017
|
-
const content =
|
|
5122
|
+
const content = fs39.readFileSync(filePath, "utf-8");
|
|
5018
5123
|
const typeRegex = /type\s+(\w+)\s*\{([^}]+)\}/g;
|
|
5019
5124
|
let match;
|
|
5020
5125
|
while ((match = typeRegex.exec(content)) !== null) {
|
|
@@ -5050,7 +5155,7 @@ async function parseProtoContracts(repoRoot) {
|
|
|
5050
5155
|
const files = scanForFiles(repoRoot, (name) => name.endsWith(".proto"));
|
|
5051
5156
|
const contracts = [];
|
|
5052
5157
|
for (const filePath of files) {
|
|
5053
|
-
const content =
|
|
5158
|
+
const content = fs39.readFileSync(filePath, "utf-8");
|
|
5054
5159
|
const serviceRegex = /service\s+(\w+)\s*\{([^}]+)\}/g;
|
|
5055
5160
|
let serviceMatch;
|
|
5056
5161
|
while ((serviceMatch = serviceRegex.exec(content)) !== null) {
|
|
@@ -5269,7 +5374,7 @@ async function syncGroup(group) {
|
|
|
5269
5374
|
continue;
|
|
5270
5375
|
}
|
|
5271
5376
|
const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
|
|
5272
|
-
if (!
|
|
5377
|
+
if (!fs39.existsSync(dbPath)) {
|
|
5273
5378
|
logger_default.warn(` \u26A0 No index at ${dbPath} \u2014 run \`code-intel analyze ${regEntry.path}\` first`);
|
|
5274
5379
|
continue;
|
|
5275
5380
|
}
|
|
@@ -5393,14 +5498,14 @@ function runPrerequisiteChecks() {
|
|
|
5393
5498
|
});
|
|
5394
5499
|
}
|
|
5395
5500
|
try {
|
|
5396
|
-
const out = execSync(`df -BM "${
|
|
5501
|
+
const out = execSync(`df -BM "${os18.homedir()}" 2>/dev/null | tail -1 | awk '{print $4}'`, { encoding: "utf8" });
|
|
5397
5502
|
const availMB = parseInt(out.trim().replace("M", ""), 10);
|
|
5398
5503
|
if (Number.isFinite(availMB) && availMB < 500) {
|
|
5399
5504
|
results.push({
|
|
5400
5505
|
name: "Disk space",
|
|
5401
5506
|
ok: false,
|
|
5402
5507
|
level: "warn",
|
|
5403
|
-
message: `Low disk space: ${availMB} MB available in ${
|
|
5508
|
+
message: `Low disk space: ${availMB} MB available in ${os18.homedir()} (500 MB recommended)`
|
|
5404
5509
|
});
|
|
5405
5510
|
}
|
|
5406
5511
|
} catch {
|
|
@@ -5421,6 +5526,7 @@ var init_codes = __esm({
|
|
|
5421
5526
|
RATE_LIMIT_EXCEEDED: "CI-1100",
|
|
5422
5527
|
PAYLOAD_TOO_LARGE: "CI-1101",
|
|
5423
5528
|
INVALID_REQUEST: "CI-1200",
|
|
5529
|
+
CONFLICT: "CI-1409",
|
|
5424
5530
|
// Config (CI-2xxx)
|
|
5425
5531
|
CONFIG_INVALID: "CI-2000",
|
|
5426
5532
|
CONFIG_NOT_FOUND: "CI-2001",
|
|
@@ -5450,10 +5556,10 @@ var init_codes = __esm({
|
|
|
5450
5556
|
}
|
|
5451
5557
|
});
|
|
5452
5558
|
function secureMkdir(dir) {
|
|
5453
|
-
|
|
5559
|
+
fs39.mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
5454
5560
|
if (process.platform !== "win32") {
|
|
5455
5561
|
try {
|
|
5456
|
-
|
|
5562
|
+
fs39.chmodSync(dir, SECURE_DIR_MODE);
|
|
5457
5563
|
} catch {
|
|
5458
5564
|
}
|
|
5459
5565
|
}
|
|
@@ -5461,22 +5567,22 @@ function secureMkdir(dir) {
|
|
|
5461
5567
|
function secureChmodFile(file) {
|
|
5462
5568
|
if (process.platform === "win32") return;
|
|
5463
5569
|
try {
|
|
5464
|
-
|
|
5570
|
+
fs39.chmodSync(file, SECURE_FILE_MODE);
|
|
5465
5571
|
} catch {
|
|
5466
5572
|
}
|
|
5467
5573
|
}
|
|
5468
5574
|
function secureWriteFile(file, data) {
|
|
5469
5575
|
secureMkdir(path39.dirname(file));
|
|
5470
|
-
|
|
5576
|
+
fs39.writeFileSync(file, data, { mode: SECURE_FILE_MODE });
|
|
5471
5577
|
secureChmodFile(file);
|
|
5472
5578
|
}
|
|
5473
5579
|
function tightenDbFiles(dir) {
|
|
5474
5580
|
if (process.platform === "win32") return;
|
|
5475
|
-
if (!
|
|
5476
|
-
for (const name of
|
|
5581
|
+
if (!fs39.existsSync(dir)) return;
|
|
5582
|
+
for (const name of fs39.readdirSync(dir)) {
|
|
5477
5583
|
if (name.endsWith(".db") || name.endsWith(".db-wal") || name.endsWith(".db-shm")) {
|
|
5478
5584
|
try {
|
|
5479
|
-
|
|
5585
|
+
fs39.chmodSync(path39.join(dir, name), SECURE_FILE_MODE);
|
|
5480
5586
|
} catch {
|
|
5481
5587
|
}
|
|
5482
5588
|
}
|
|
@@ -5490,7 +5596,7 @@ var init_fs_secure = __esm({
|
|
|
5490
5596
|
}
|
|
5491
5597
|
});
|
|
5492
5598
|
function getUsersDBPath() {
|
|
5493
|
-
return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path39.join(
|
|
5599
|
+
return process.env["CODE_INTEL_USERS_DB_PATH"] ?? path39.join(os18.homedir(), ".code-intel", "users.db");
|
|
5494
5600
|
}
|
|
5495
5601
|
function getOrCreateUsersDB() {
|
|
5496
5602
|
if (!_usersDB) {
|
|
@@ -5783,17 +5889,17 @@ function getScryptN() {
|
|
|
5783
5889
|
return Number.isInteger(v) && v >= 1024 ? v : 1 << 14;
|
|
5784
5890
|
}
|
|
5785
5891
|
function getSecretsPath() {
|
|
5786
|
-
return process.env["CODE_INTEL_SECRETS_PATH"] ?? path39.join(
|
|
5892
|
+
return process.env["CODE_INTEL_SECRETS_PATH"] ?? path39.join(os18.homedir(), ".code-intel", ".secrets");
|
|
5787
5893
|
}
|
|
5788
5894
|
function getMasterPassword() {
|
|
5789
5895
|
const fromEnv = process.env["CODE_INTEL_SECRET_KEY"];
|
|
5790
5896
|
if (fromEnv && fromEnv.length >= 16) return fromEnv;
|
|
5791
5897
|
let username = "unknown";
|
|
5792
5898
|
try {
|
|
5793
|
-
username =
|
|
5899
|
+
username = os18.userInfo().username;
|
|
5794
5900
|
} catch {
|
|
5795
5901
|
}
|
|
5796
|
-
return `code-intel-machine:${
|
|
5902
|
+
return `code-intel-machine:${os18.hostname()}:${username}:${os18.platform()}`;
|
|
5797
5903
|
}
|
|
5798
5904
|
function deriveKey(password, salt) {
|
|
5799
5905
|
return crypto5.scryptSync(password, salt, KEY_LEN, { N: getScryptN(), r: 8, p: 1 });
|
|
@@ -5825,8 +5931,8 @@ function decryptSecrets(encrypted) {
|
|
|
5825
5931
|
return JSON.parse(plaintext.toString("utf8"));
|
|
5826
5932
|
}
|
|
5827
5933
|
function loadSecrets(secretsPath = getSecretsPath()) {
|
|
5828
|
-
if (!
|
|
5829
|
-
const blob =
|
|
5934
|
+
if (!fs39.existsSync(secretsPath)) return {};
|
|
5935
|
+
const blob = fs39.readFileSync(secretsPath);
|
|
5830
5936
|
return decryptSecrets(blob);
|
|
5831
5937
|
}
|
|
5832
5938
|
function saveSecrets(blob, secretsPath = getSecretsPath()) {
|
|
@@ -5866,11 +5972,18 @@ function getSessionTtlMs() {
|
|
|
5866
5972
|
const hours = parseInt(process.env["CODE_INTEL_SESSION_TTL_HOURS"] ?? "8", 10);
|
|
5867
5973
|
return (isNaN(hours) ? 8 : hours) * 60 * 60 * 1e3;
|
|
5868
5974
|
}
|
|
5869
|
-
function createSession(user) {
|
|
5975
|
+
function createSession(user, rememberMe = false) {
|
|
5870
5976
|
const sessionId = v4();
|
|
5871
|
-
const
|
|
5872
|
-
|
|
5873
|
-
|
|
5977
|
+
const ttlMs = rememberMe ? REMEMBER_ME_TTL_MS : getSessionTtlMs();
|
|
5978
|
+
const expiresAt = Date.now() + ttlMs;
|
|
5979
|
+
sessionStore.set(sessionId, {
|
|
5980
|
+
userId: user.id,
|
|
5981
|
+
username: user.username,
|
|
5982
|
+
role: user.role,
|
|
5983
|
+
expiresAt,
|
|
5984
|
+
ttlMs
|
|
5985
|
+
});
|
|
5986
|
+
return { sessionId, ttlMs };
|
|
5874
5987
|
}
|
|
5875
5988
|
function getSession(sessionId) {
|
|
5876
5989
|
const entry = sessionStore.get(sessionId);
|
|
@@ -5879,10 +5992,9 @@ function getSession(sessionId) {
|
|
|
5879
5992
|
sessionStore.delete(sessionId);
|
|
5880
5993
|
return null;
|
|
5881
5994
|
}
|
|
5882
|
-
const ttlMs = getSessionTtlMs();
|
|
5883
5995
|
const remaining = entry.expiresAt - Date.now();
|
|
5884
|
-
if (remaining < ttlMs * 0.75) {
|
|
5885
|
-
entry.expiresAt = Date.now() + ttlMs;
|
|
5996
|
+
if (remaining < entry.ttlMs * 0.75) {
|
|
5997
|
+
entry.expiresAt = Date.now() + entry.ttlMs;
|
|
5886
5998
|
}
|
|
5887
5999
|
return entry;
|
|
5888
6000
|
}
|
|
@@ -5902,7 +6014,7 @@ function authMiddleware(req, res, next) {
|
|
|
5902
6014
|
const session = getSession(sessionId);
|
|
5903
6015
|
if (session) {
|
|
5904
6016
|
req.user = { id: session.userId, username: session.username, role: session.role, authMethod: "session" };
|
|
5905
|
-
res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
|
|
6017
|
+
res.setHeader("Set-Cookie", buildSessionCookie(sessionId, session.ttlMs));
|
|
5906
6018
|
next();
|
|
5907
6019
|
return;
|
|
5908
6020
|
}
|
|
@@ -6072,9 +6184,9 @@ function parseCookies(cookieHeader) {
|
|
|
6072
6184
|
}
|
|
6073
6185
|
return result;
|
|
6074
6186
|
}
|
|
6075
|
-
function buildSessionCookie(sessionId) {
|
|
6187
|
+
function buildSessionCookie(sessionId, ttlMs) {
|
|
6076
6188
|
const isProduction = process.env["NODE_ENV"] === "production";
|
|
6077
|
-
const maxAge = Math.floor(getSessionTtlMs() / 1e3);
|
|
6189
|
+
const maxAge = Math.floor((ttlMs ?? getSessionTtlMs()) / 1e3);
|
|
6078
6190
|
const parts = [
|
|
6079
6191
|
`${SESSION_COOKIE_NAME}=${encodeURIComponent(sessionId)}`,
|
|
6080
6192
|
`HttpOnly`,
|
|
@@ -6091,7 +6203,7 @@ function clearSessionCookie() {
|
|
|
6091
6203
|
async function verifyPassword(plain, hash) {
|
|
6092
6204
|
return bcrypt.compare(plain, hash);
|
|
6093
6205
|
}
|
|
6094
|
-
var sessionStore, SESSION_COOKIE_NAME, ROLE_RANK;
|
|
6206
|
+
var sessionStore, SESSION_COOKIE_NAME, REMEMBER_ME_TTL_MS, ROLE_RANK;
|
|
6095
6207
|
var init_middleware = __esm({
|
|
6096
6208
|
"src/auth/middleware.ts"() {
|
|
6097
6209
|
init_users_db();
|
|
@@ -6099,6 +6211,7 @@ var init_middleware = __esm({
|
|
|
6099
6211
|
init_secret_store();
|
|
6100
6212
|
sessionStore = /* @__PURE__ */ new Map();
|
|
6101
6213
|
SESSION_COOKIE_NAME = "code_intel_session";
|
|
6214
|
+
REMEMBER_ME_TTL_MS = 12 * 60 * 60 * 1e3;
|
|
6102
6215
|
ROLE_RANK = {
|
|
6103
6216
|
viewer: 1,
|
|
6104
6217
|
"repo-owner": 2,
|
|
@@ -7871,7 +7984,7 @@ var init_secret_scanner = __esm({
|
|
|
7871
7984
|
const ignorePatterns = [...options?.ignorePatterns ?? []];
|
|
7872
7985
|
if (options?.workspaceRoot) {
|
|
7873
7986
|
try {
|
|
7874
|
-
const raw =
|
|
7987
|
+
const raw = fs39.readFileSync(path39.join(options.workspaceRoot, ".codeintelignore"), "utf-8");
|
|
7875
7988
|
for (const line of raw.split("\n")) {
|
|
7876
7989
|
const trimmed = line.trim();
|
|
7877
7990
|
if (trimmed && !trimmed.startsWith("#")) ignorePatterns.push(trimmed);
|
|
@@ -8129,22 +8242,22 @@ __export(init_wizard_exports, {
|
|
|
8129
8242
|
wipeConfig: () => wipeConfig
|
|
8130
8243
|
});
|
|
8131
8244
|
function configExists() {
|
|
8132
|
-
return
|
|
8245
|
+
return fs39.existsSync(CONFIG_PATH);
|
|
8133
8246
|
}
|
|
8134
8247
|
function loadConfig() {
|
|
8135
8248
|
if (!configExists()) return null;
|
|
8136
8249
|
try {
|
|
8137
|
-
return JSON.parse(
|
|
8250
|
+
return JSON.parse(fs39.readFileSync(CONFIG_PATH, "utf-8"));
|
|
8138
8251
|
} catch {
|
|
8139
8252
|
return null;
|
|
8140
8253
|
}
|
|
8141
8254
|
}
|
|
8142
8255
|
function saveConfig(cfg) {
|
|
8143
|
-
|
|
8144
|
-
|
|
8256
|
+
fs39.mkdirSync(GLOBAL_DIR2, { recursive: true });
|
|
8257
|
+
fs39.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
|
|
8145
8258
|
}
|
|
8146
8259
|
function wipeConfig() {
|
|
8147
|
-
if (
|
|
8260
|
+
if (fs39.existsSync(CONFIG_PATH)) fs39.unlinkSync(CONFIG_PATH);
|
|
8148
8261
|
}
|
|
8149
8262
|
function commandExists(bin) {
|
|
8150
8263
|
try {
|
|
@@ -8224,8 +8337,8 @@ async function runInitWizard(opts = {}) {
|
|
|
8224
8337
|
const mcpFile = path39.resolve(editor.mcpConfigKey);
|
|
8225
8338
|
try {
|
|
8226
8339
|
let existing = {};
|
|
8227
|
-
if (
|
|
8228
|
-
existing = JSON.parse(
|
|
8340
|
+
if (fs39.existsSync(mcpFile)) {
|
|
8341
|
+
existing = JSON.parse(fs39.readFileSync(mcpFile, "utf-8"));
|
|
8229
8342
|
}
|
|
8230
8343
|
const merged = {
|
|
8231
8344
|
...existing,
|
|
@@ -8234,8 +8347,8 @@ async function runInitWizard(opts = {}) {
|
|
|
8234
8347
|
...mcpConfig.servers
|
|
8235
8348
|
}
|
|
8236
8349
|
};
|
|
8237
|
-
|
|
8238
|
-
|
|
8350
|
+
fs39.mkdirSync(path39.dirname(mcpFile), { recursive: true });
|
|
8351
|
+
fs39.writeFileSync(mcpFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
8239
8352
|
console.log(` \u2705 MCP registered for ${name} \u2192 ${editor.mcpConfigKey}`);
|
|
8240
8353
|
} catch {
|
|
8241
8354
|
console.log(` \u26A0 Could not write MCP config for ${name} \u2014 do it manually.`);
|
|
@@ -8252,6 +8365,7 @@ async function runInitWizard(opts = {}) {
|
|
|
8252
8365
|
{ label: "Ollama (local, free, requires Ollama running)", value: "ollama" },
|
|
8253
8366
|
{ label: "OpenAI (requires OPENAI_API_KEY env var)", value: "openai" },
|
|
8254
8367
|
{ label: "Anthropic (requires ANTHROPIC_API_KEY env var)", value: "anthropic" },
|
|
8368
|
+
{ label: "Custom (OpenAI-compatible API \u2014 enter URL, token & model)", value: "custom" },
|
|
8255
8369
|
{ label: "Skip (configure later)", value: "none" }
|
|
8256
8370
|
], 0);
|
|
8257
8371
|
cfg.llm.provider = llmProvider;
|
|
@@ -8267,6 +8381,19 @@ async function runInitWizard(opts = {}) {
|
|
|
8267
8381
|
cfg.llm.model = "llama3";
|
|
8268
8382
|
cfg.llm.apiKey = "";
|
|
8269
8383
|
console.log(" Make sure Ollama is running: https://ollama.com");
|
|
8384
|
+
} else if (llmProvider === "custom") {
|
|
8385
|
+
console.log(" Configure your OpenAI-compatible provider (e.g. LM Studio, vLLM, Together, Groq, Azure).\n");
|
|
8386
|
+
const baseUrl = (await prompt(rl, " API Base URL (e.g. http://localhost:1234/v1): ")).trim();
|
|
8387
|
+
cfg.llm.baseUrl = baseUrl || "http://localhost:1234/v1";
|
|
8388
|
+
const apiKey = (await prompt(rl, " API Token/Key (leave blank if not required): ")).trim();
|
|
8389
|
+
cfg.llm.apiKey = apiKey || "";
|
|
8390
|
+
const model = (await prompt(rl, " Model name (e.g. mistral-7b-instruct): ")).trim();
|
|
8391
|
+
cfg.llm.model = model || "default";
|
|
8392
|
+
console.log(`
|
|
8393
|
+
\u2705 Custom provider configured:`);
|
|
8394
|
+
console.log(` URL: ${cfg.llm.baseUrl}`);
|
|
8395
|
+
console.log(` Model: ${cfg.llm.model}`);
|
|
8396
|
+
console.log(` Token: ${cfg.llm.apiKey ? "(set)" : "(none)"}`);
|
|
8270
8397
|
} else {
|
|
8271
8398
|
cfg.llm.apiKey = "";
|
|
8272
8399
|
console.log(" Skipped. Run `code-intel config set llm.provider openai` later.");
|
|
@@ -8331,7 +8458,7 @@ function printNextSteps() {
|
|
|
8331
8458
|
var GLOBAL_DIR2, CONFIG_PATH, DEFAULT_CONFIG, EDITORS;
|
|
8332
8459
|
var init_init_wizard = __esm({
|
|
8333
8460
|
"src/cli/init-wizard.ts"() {
|
|
8334
|
-
GLOBAL_DIR2 = path39.join(
|
|
8461
|
+
GLOBAL_DIR2 = path39.join(os18.homedir(), ".code-intel");
|
|
8335
8462
|
CONFIG_PATH = path39.join(GLOBAL_DIR2, "config.json");
|
|
8336
8463
|
DEFAULT_CONFIG = {
|
|
8337
8464
|
$schema: "https://code-intel.dev/config-schema.json",
|
|
@@ -8644,9 +8771,10 @@ var init_config_manager = __esm({
|
|
|
8644
8771
|
/access[_-]?token/i
|
|
8645
8772
|
];
|
|
8646
8773
|
CONFIG_SCHEMA = {
|
|
8647
|
-
"llm.provider": { type: "string", enum: ["openai", "anthropic", "ollama", "none"], default: "ollama", description: "LLM provider for AI summaries" },
|
|
8774
|
+
"llm.provider": { type: "string", enum: ["openai", "anthropic", "ollama", "custom", "none"], default: "ollama", description: "LLM provider for AI summaries" },
|
|
8648
8775
|
"llm.model": { type: "string", default: "llama3", description: "LLM model name" },
|
|
8649
8776
|
"llm.apiKey": { type: "string", default: "", description: "API key \u2014 use $ENV_VAR syntax, e.g. $OPENAI_API_KEY" },
|
|
8777
|
+
"llm.baseUrl": { type: "string", default: "", description: "Base URL for custom OpenAI-compatible API (e.g. http://localhost:1234/v1)" },
|
|
8650
8778
|
"llm.batchSize": { type: "number", minimum: 1, maximum: 100, default: 20, description: "Concurrent LLM calls per batch" },
|
|
8651
8779
|
"llm.maxTokensPerSummary": { type: "number", minimum: 10, maximum: 2e3, default: 100, description: "Max tokens per AI summary" },
|
|
8652
8780
|
"embeddings.model": { type: "string", default: "all-MiniLM-L6-v2", description: "Embedding model name" },
|
|
@@ -9005,8 +9133,8 @@ var init_file_watcher = __esm({
|
|
|
9005
9133
|
readCodeIntelIgnore() {
|
|
9006
9134
|
const ignoreFile = path39.join(this.workspaceRoot, ".codeintelignore");
|
|
9007
9135
|
try {
|
|
9008
|
-
if (!
|
|
9009
|
-
return
|
|
9136
|
+
if (!fs39.existsSync(ignoreFile)) return [];
|
|
9137
|
+
return fs39.readFileSync(ignoreFile, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
9010
9138
|
} catch {
|
|
9011
9139
|
return [];
|
|
9012
9140
|
}
|
|
@@ -9067,7 +9195,7 @@ var init_incremental_indexer = __esm({
|
|
|
9067
9195
|
graph.removeNodeCascade(id);
|
|
9068
9196
|
nodesRemoved++;
|
|
9069
9197
|
}
|
|
9070
|
-
if (
|
|
9198
|
+
if (fs39.existsSync(dbPath)) {
|
|
9071
9199
|
try {
|
|
9072
9200
|
const db = new DbManager(dbPath);
|
|
9073
9201
|
await db.init();
|
|
@@ -9082,7 +9210,7 @@ var init_incremental_indexer = __esm({
|
|
|
9082
9210
|
}
|
|
9083
9211
|
const existingFiles = changedFiles.filter((f) => {
|
|
9084
9212
|
try {
|
|
9085
|
-
return
|
|
9213
|
+
return fs39.statSync(f).isFile();
|
|
9086
9214
|
} catch {
|
|
9087
9215
|
return false;
|
|
9088
9216
|
}
|
|
@@ -9104,7 +9232,7 @@ var init_incremental_indexer = __esm({
|
|
|
9104
9232
|
await runPipeline([noopScan, parsePhase, resolvePhase], context2);
|
|
9105
9233
|
}
|
|
9106
9234
|
const nodesAdded = Math.max(0, graph.size.nodes - (nodesBeforeParse - nodesRemoved));
|
|
9107
|
-
if (
|
|
9235
|
+
if (fs39.existsSync(dbPath) && existingFiles.length > 0) {
|
|
9108
9236
|
try {
|
|
9109
9237
|
const db = new DbManager(dbPath);
|
|
9110
9238
|
await db.init();
|
|
@@ -9116,7 +9244,7 @@ var init_incremental_indexer = __esm({
|
|
|
9116
9244
|
db.close();
|
|
9117
9245
|
try {
|
|
9118
9246
|
const bm25DbPath = getBm25DbPath(workspaceRoot);
|
|
9119
|
-
if (
|
|
9247
|
+
if (fs39.existsSync(bm25DbPath)) {
|
|
9120
9248
|
const bm25 = new Bm25Index(bm25DbPath);
|
|
9121
9249
|
bm25.updateNodes(nodesToUpsert);
|
|
9122
9250
|
logger_default.info(`[incremental] BM25 index updated: ${nodesToUpsert.length} nodes`);
|
|
@@ -9155,31 +9283,31 @@ function getQueriesDir(workspaceRoot) {
|
|
|
9155
9283
|
}
|
|
9156
9284
|
function ensureQueriesDir(workspaceRoot) {
|
|
9157
9285
|
const dir = getQueriesDir(workspaceRoot);
|
|
9158
|
-
if (!
|
|
9159
|
-
|
|
9286
|
+
if (!fs39.existsSync(dir)) {
|
|
9287
|
+
fs39.mkdirSync(dir, { recursive: true });
|
|
9160
9288
|
}
|
|
9161
9289
|
return dir;
|
|
9162
9290
|
}
|
|
9163
9291
|
function saveQuery(workspaceRoot, name, gql) {
|
|
9164
9292
|
const dir = ensureQueriesDir(workspaceRoot);
|
|
9165
9293
|
const filePath = path39.join(dir, `${name}.gql`);
|
|
9166
|
-
|
|
9294
|
+
fs39.writeFileSync(filePath, gql, "utf-8");
|
|
9167
9295
|
}
|
|
9168
9296
|
function loadQuery(workspaceRoot, name) {
|
|
9169
9297
|
const dir = getQueriesDir(workspaceRoot);
|
|
9170
9298
|
const filePath = path39.join(dir, `${name}.gql`);
|
|
9171
|
-
if (!
|
|
9172
|
-
return
|
|
9299
|
+
if (!fs39.existsSync(filePath)) return null;
|
|
9300
|
+
return fs39.readFileSync(filePath, "utf-8");
|
|
9173
9301
|
}
|
|
9174
9302
|
function listQueries(workspaceRoot) {
|
|
9175
9303
|
const dir = getQueriesDir(workspaceRoot);
|
|
9176
|
-
if (!
|
|
9177
|
-
const files =
|
|
9304
|
+
if (!fs39.existsSync(dir)) return [];
|
|
9305
|
+
const files = fs39.readdirSync(dir).filter((f) => f.endsWith(".gql"));
|
|
9178
9306
|
return files.map((f) => {
|
|
9179
9307
|
const filePath = path39.join(dir, f);
|
|
9180
9308
|
const name = f.replace(/\.gql$/, "");
|
|
9181
|
-
const content =
|
|
9182
|
-
const stat =
|
|
9309
|
+
const content = fs39.readFileSync(filePath, "utf-8");
|
|
9310
|
+
const stat = fs39.statSync(filePath);
|
|
9183
9311
|
return {
|
|
9184
9312
|
name,
|
|
9185
9313
|
content,
|
|
@@ -9191,14 +9319,14 @@ function listQueries(workspaceRoot) {
|
|
|
9191
9319
|
function deleteQuery(workspaceRoot, name) {
|
|
9192
9320
|
const dir = getQueriesDir(workspaceRoot);
|
|
9193
9321
|
const filePath = path39.join(dir, `${name}.gql`);
|
|
9194
|
-
if (!
|
|
9195
|
-
|
|
9322
|
+
if (!fs39.existsSync(filePath)) return false;
|
|
9323
|
+
fs39.unlinkSync(filePath);
|
|
9196
9324
|
return true;
|
|
9197
9325
|
}
|
|
9198
9326
|
function queryExists(workspaceRoot, name) {
|
|
9199
9327
|
const dir = getQueriesDir(workspaceRoot);
|
|
9200
9328
|
const filePath = path39.join(dir, `${name}.gql`);
|
|
9201
|
-
return
|
|
9329
|
+
return fs39.existsSync(filePath);
|
|
9202
9330
|
}
|
|
9203
9331
|
var init_saved_queries = __esm({
|
|
9204
9332
|
"src/query/saved-queries.ts"() {
|
|
@@ -9266,6 +9394,324 @@ var init_sarif_builder = __esm({
|
|
|
9266
9394
|
}
|
|
9267
9395
|
});
|
|
9268
9396
|
|
|
9397
|
+
// src/context/token-counter.ts
|
|
9398
|
+
var token_counter_exports = {};
|
|
9399
|
+
__export(token_counter_exports, {
|
|
9400
|
+
estimateTokens: () => estimateTokens,
|
|
9401
|
+
measureBlocks: () => measureBlocks
|
|
9402
|
+
});
|
|
9403
|
+
function estimateTokens(text) {
|
|
9404
|
+
if (!text) return 0;
|
|
9405
|
+
const words = text.split(/\s+/).filter(Boolean).length;
|
|
9406
|
+
const chars = text.length;
|
|
9407
|
+
return Math.ceil((words * 1.3 + chars * 0.25) / 2);
|
|
9408
|
+
}
|
|
9409
|
+
function measureBlocks(doc) {
|
|
9410
|
+
const summary = estimateTokens(doc.summary);
|
|
9411
|
+
const logic = estimateTokens(doc.logic);
|
|
9412
|
+
const relation = estimateTokens(doc.relation);
|
|
9413
|
+
const focusCode = estimateTokens(doc.focusCode);
|
|
9414
|
+
return { summary, logic, relation, focusCode, total: summary + logic + relation + focusCode };
|
|
9415
|
+
}
|
|
9416
|
+
var init_token_counter = __esm({
|
|
9417
|
+
"src/context/token-counter.ts"() {
|
|
9418
|
+
}
|
|
9419
|
+
});
|
|
9420
|
+
|
|
9421
|
+
// src/context/builder.ts
|
|
9422
|
+
var builder_exports = {};
|
|
9423
|
+
__export(builder_exports, {
|
|
9424
|
+
build: () => build,
|
|
9425
|
+
detectQueryIntent: () => detectQueryIntent
|
|
9426
|
+
});
|
|
9427
|
+
function detectQueryIntent(question) {
|
|
9428
|
+
const q = question.toLowerCase();
|
|
9429
|
+
if (/\b(show|code|implement|source|how is written|function body|method body)\b/.test(q)) return "code";
|
|
9430
|
+
if (/\b(who calls|callers?|depends on|blast radius|impact|upstream)\b/.test(q)) return "callers";
|
|
9431
|
+
if (/\b(architecture|overview|structure|design|how is built|system)\b/.test(q)) return "architecture";
|
|
9432
|
+
return "auto";
|
|
9433
|
+
}
|
|
9434
|
+
function last2Segments(filePath) {
|
|
9435
|
+
const parts = filePath.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
9436
|
+
return parts.slice(-2).join("/");
|
|
9437
|
+
}
|
|
9438
|
+
function firstSentence(text) {
|
|
9439
|
+
if (!text) return "";
|
|
9440
|
+
const sentence = text.split(/[.!?]/)[0]?.trim() ?? "";
|
|
9441
|
+
const words = sentence.split(/\s+/);
|
|
9442
|
+
return words.slice(0, 15).join(" ");
|
|
9443
|
+
}
|
|
9444
|
+
function getCluster(graph, nodeId) {
|
|
9445
|
+
for (const edge of graph.findEdgesFrom(nodeId)) {
|
|
9446
|
+
if (edge.kind === "belongs_to") return graph.getNode(edge.target)?.name;
|
|
9447
|
+
}
|
|
9448
|
+
return void 0;
|
|
9449
|
+
}
|
|
9450
|
+
function dirOf(filePath) {
|
|
9451
|
+
const parts = filePath.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
9452
|
+
return parts.slice(0, -1).join("/") || ".";
|
|
9453
|
+
}
|
|
9454
|
+
function meaningfulLines(content) {
|
|
9455
|
+
return content.split("\n").filter((l) => {
|
|
9456
|
+
const t = l.trim();
|
|
9457
|
+
return t.length > 0 && !t.startsWith("//") && !t.startsWith("*") && !t.startsWith("#");
|
|
9458
|
+
});
|
|
9459
|
+
}
|
|
9460
|
+
function adaptiveSnippet(content) {
|
|
9461
|
+
if (!content) return { lines: "", truncated: false };
|
|
9462
|
+
const stripped = content.replace(/^\n+|\n+$/g, "");
|
|
9463
|
+
const rawLines = stripped.split("\n");
|
|
9464
|
+
const ml = meaningfulLines(stripped).length;
|
|
9465
|
+
if (ml <= 10) return { lines: stripped, truncated: false };
|
|
9466
|
+
if (ml <= 25) {
|
|
9467
|
+
const out2 = rawLines.slice(0, 25);
|
|
9468
|
+
const truncated = rawLines.length > 25;
|
|
9469
|
+
return { lines: out2.join("\n") + (truncated ? "\n// ..." : ""), truncated };
|
|
9470
|
+
}
|
|
9471
|
+
const out = rawLines.slice(0, 40);
|
|
9472
|
+
const remaining = rawLines.length - 40;
|
|
9473
|
+
return {
|
|
9474
|
+
lines: out.join("\n") + (remaining > 0 ? `
|
|
9475
|
+
// ... (${remaining} more lines)` : ""),
|
|
9476
|
+
truncated: remaining > 0
|
|
9477
|
+
};
|
|
9478
|
+
}
|
|
9479
|
+
function buildSummaryBlock(nodes, graph, dedup) {
|
|
9480
|
+
if (nodes.length === 0) return "";
|
|
9481
|
+
const byDir = /* @__PURE__ */ new Map();
|
|
9482
|
+
for (const node of nodes) {
|
|
9483
|
+
const dir = dirOf(node.filePath);
|
|
9484
|
+
if (!byDir.has(dir)) byDir.set(dir, []);
|
|
9485
|
+
byDir.get(dir).push(node);
|
|
9486
|
+
}
|
|
9487
|
+
const lines = ["[SUMMARY]"];
|
|
9488
|
+
for (const [dir, group] of byDir) {
|
|
9489
|
+
const useHeader = group.length >= 3;
|
|
9490
|
+
if (useHeader) lines.push(`${dir}/:`);
|
|
9491
|
+
for (const node of group) {
|
|
9492
|
+
const summary = firstSentence(node.metadata?.["summary"]);
|
|
9493
|
+
const callerCount = [...graph.findEdgesTo(node.id)].filter((e) => e.kind === "calls").length;
|
|
9494
|
+
getCluster(graph, node.id);
|
|
9495
|
+
const badges = [];
|
|
9496
|
+
if (callerCount >= 10) badges.push("\u26A0");
|
|
9497
|
+
if (callerCount === 0) badges.push("\u{1F47B}");
|
|
9498
|
+
const badgeStr = badges.join("");
|
|
9499
|
+
const path210 = last2Segments(node.filePath);
|
|
9500
|
+
const line = node.startLine ? `:${node.startLine}` : "";
|
|
9501
|
+
const fullFmt = `${node.name} [${node.kind}] ${path210}${line}${badgeStr ? " " + badgeStr : ""}${summary ? " \u2014 " + summary : ""}`;
|
|
9502
|
+
const formatted = dedup.formatSymbol(node.name, node.filePath, fullFmt);
|
|
9503
|
+
lines.push(useHeader ? ` ${formatted}` : formatted);
|
|
9504
|
+
}
|
|
9505
|
+
}
|
|
9506
|
+
return lines.join("\n");
|
|
9507
|
+
}
|
|
9508
|
+
function buildLogicBlock(nodes, graph, dedup) {
|
|
9509
|
+
if (nodes.length === 0) return "";
|
|
9510
|
+
const lines = ["[LOGIC]"];
|
|
9511
|
+
const nodeCallees = /* @__PURE__ */ new Map();
|
|
9512
|
+
const calleeUsage = /* @__PURE__ */ new Map();
|
|
9513
|
+
for (const node of nodes) {
|
|
9514
|
+
const callees = [];
|
|
9515
|
+
for (const edge of graph.findEdgesFrom(node.id)) {
|
|
9516
|
+
if (edge.kind === "calls") {
|
|
9517
|
+
const callee = graph.getNode(edge.target);
|
|
9518
|
+
if (callee && callee.name !== node.name) {
|
|
9519
|
+
callees.push(callee.name);
|
|
9520
|
+
calleeUsage.set(callee.name, (calleeUsage.get(callee.name) ?? 0) + 1);
|
|
9521
|
+
}
|
|
9522
|
+
}
|
|
9523
|
+
}
|
|
9524
|
+
nodeCallees.set(node.id, [...new Set(callees)]);
|
|
9525
|
+
}
|
|
9526
|
+
const sharedCallees = new Set(
|
|
9527
|
+
[...calleeUsage.entries()].filter(([, cnt]) => cnt >= 3).map(([name]) => name)
|
|
9528
|
+
);
|
|
9529
|
+
if (sharedCallees.size > 0) {
|
|
9530
|
+
lines.push(`(all above \u2192 ${[...sharedCallees].join(", ")})`);
|
|
9531
|
+
}
|
|
9532
|
+
for (const node of nodes) {
|
|
9533
|
+
const callees = (nodeCallees.get(node.id) ?? []).filter((c) => !sharedCallees.has(c));
|
|
9534
|
+
for (const callee of callees) {
|
|
9535
|
+
dedup.markCallPair(node.name, callee);
|
|
9536
|
+
}
|
|
9537
|
+
if (callees.length === 0) continue;
|
|
9538
|
+
if (callees.length <= 5) {
|
|
9539
|
+
for (const callee of callees) dedup.markInLogic(callee);
|
|
9540
|
+
lines.push(`${node.name} \u2192 ${callees.join(", ")}`);
|
|
9541
|
+
} else {
|
|
9542
|
+
lines.push(`${node.name} \u2192`);
|
|
9543
|
+
for (const callee of callees) {
|
|
9544
|
+
dedup.markInLogic(callee);
|
|
9545
|
+
if (dedup.hasSymbol(callee)) {
|
|
9546
|
+
lines.push(` ${callee}`);
|
|
9547
|
+
} else {
|
|
9548
|
+
const calleeNode = [...graph.allNodes()].find((n) => n.name === callee);
|
|
9549
|
+
const path40 = calleeNode ? ` (${last2Segments(calleeNode.filePath)})` : "";
|
|
9550
|
+
lines.push(` ${callee}${path40}`);
|
|
9551
|
+
}
|
|
9552
|
+
}
|
|
9553
|
+
}
|
|
9554
|
+
}
|
|
9555
|
+
return lines.length > 1 ? lines.join("\n") : "";
|
|
9556
|
+
}
|
|
9557
|
+
function buildRelationBlock(nodes, graph, dedup) {
|
|
9558
|
+
if (nodes.length === 0) return "";
|
|
9559
|
+
const lines = ["[RELATION]"];
|
|
9560
|
+
for (const node of nodes) {
|
|
9561
|
+
const callers = [...graph.findEdgesTo(node.id)].filter((e) => e.kind === "calls").map((e) => graph.getNode(e.source)?.name).filter((n) => Boolean(n));
|
|
9562
|
+
const extendsNodes = [...graph.findEdgesFrom(node.id)].filter((e) => e.kind === "extends").map((e) => graph.getNode(e.target)?.name).filter((n) => Boolean(n));
|
|
9563
|
+
const implementsNodes = [...graph.findEdgesFrom(node.id)].filter((e) => e.kind === "implements").map((e) => graph.getNode(e.target)?.name).filter((n) => Boolean(n));
|
|
9564
|
+
const highBlast = callers.length >= 5;
|
|
9565
|
+
const prefix = highBlast ? "\u26A1 " : "";
|
|
9566
|
+
if (callers.length > 0) {
|
|
9567
|
+
const nonDupCallers = callers.filter(
|
|
9568
|
+
(c) => highBlast || !dedup.hasCallPair(c, node.name)
|
|
9569
|
+
);
|
|
9570
|
+
if (nonDupCallers.length > 0) {
|
|
9571
|
+
const top3 = nonDupCallers.slice(0, 3);
|
|
9572
|
+
const rest = nonDupCallers.length - 3;
|
|
9573
|
+
const callerStr = top3.join(", ") + (rest > 0 ? ` (+${rest} more \u2014 use blast_radius for full list)` : "");
|
|
9574
|
+
lines.push(`${prefix}${node.name} \u2190 ${callerStr}`);
|
|
9575
|
+
}
|
|
9576
|
+
}
|
|
9577
|
+
const heritage = [];
|
|
9578
|
+
if (extendsNodes.length > 0) heritage.push(`extends ${extendsNodes.join(", ")}`);
|
|
9579
|
+
if (implementsNodes.length > 0) heritage.push(`implements ${implementsNodes.join(" \xB7 ")}`);
|
|
9580
|
+
if (heritage.length > 0) lines.push(`${node.name}: ${heritage.join(" \xB7 ")}`);
|
|
9581
|
+
}
|
|
9582
|
+
return lines.length > 1 ? lines.join("\n") : "";
|
|
9583
|
+
}
|
|
9584
|
+
function buildFocusCodeBlock(seeds, nodes, dedup, signatureOnlyThreshold, tokenBudget) {
|
|
9585
|
+
if (nodes.length === 0) return { text: "", truncated: false };
|
|
9586
|
+
const lines = ["[FOCUS CODE]"];
|
|
9587
|
+
let usedTokens = estimateTokens("[FOCUS CODE]");
|
|
9588
|
+
let truncated = false;
|
|
9589
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
9590
|
+
const node = nodes[i];
|
|
9591
|
+
const seed = seeds.find((s) => s.nodeId === node.id);
|
|
9592
|
+
const score = seed?.refinedScore ?? 1;
|
|
9593
|
+
const content = node.content;
|
|
9594
|
+
const ml = content ? meaningfulLines(content).length : 0;
|
|
9595
|
+
if (ml <= 5 && dedup.isInLogic(node.name)) continue;
|
|
9596
|
+
const header = `// ${node.name} \u2014 ${last2Segments(node.filePath)}${node.startLine ? ":" + node.startLine : ""}`;
|
|
9597
|
+
if (score < signatureOnlyThreshold) {
|
|
9598
|
+
const sig = content?.split("\n").find((l) => l.trim().length > 0) ?? "";
|
|
9599
|
+
const sigLine = sig ? sig.trimEnd() + (sig.includes("{") ? " ... }" : "") : "";
|
|
9600
|
+
const entry2 = `${header}
|
|
9601
|
+
// (low relevance)
|
|
9602
|
+
${sigLine}`;
|
|
9603
|
+
const toks2 = estimateTokens(entry2);
|
|
9604
|
+
if (usedTokens + toks2 > tokenBudget) {
|
|
9605
|
+
truncated = true;
|
|
9606
|
+
break;
|
|
9607
|
+
}
|
|
9608
|
+
lines.push(entry2);
|
|
9609
|
+
usedTokens += toks2;
|
|
9610
|
+
continue;
|
|
9611
|
+
}
|
|
9612
|
+
const { lines: snippet, truncated: snipTruncated } = adaptiveSnippet(content);
|
|
9613
|
+
const entry = `${header}
|
|
9614
|
+
\`\`\`
|
|
9615
|
+
${snippet}
|
|
9616
|
+
\`\`\``;
|
|
9617
|
+
const toks = estimateTokens(entry);
|
|
9618
|
+
if (usedTokens + toks > tokenBudget) {
|
|
9619
|
+
truncated = true;
|
|
9620
|
+
break;
|
|
9621
|
+
}
|
|
9622
|
+
lines.push(entry);
|
|
9623
|
+
usedTokens += toks;
|
|
9624
|
+
if (snipTruncated) truncated = true;
|
|
9625
|
+
}
|
|
9626
|
+
return { text: lines.length > 1 ? lines.join("\n\n") : "", truncated };
|
|
9627
|
+
}
|
|
9628
|
+
function build(seeds, graph, options = {}) {
|
|
9629
|
+
const maxTokens = options.maxTokens ?? 6e3;
|
|
9630
|
+
const signatureOnlyThreshold = options.signatureOnlyThreshold ?? 0.3;
|
|
9631
|
+
const intent = options.queryIntent ?? "auto";
|
|
9632
|
+
const budgets = BUDGET_PRESETS[intent];
|
|
9633
|
+
const nodes = seeds.map((s) => graph.getNode(s.nodeId)).filter((n) => n !== void 0);
|
|
9634
|
+
const dedup = new DedupeRegistry();
|
|
9635
|
+
let available = maxTokens;
|
|
9636
|
+
const summaryText = buildSummaryBlock(nodes, graph, dedup);
|
|
9637
|
+
const summaryToks = estimateTokens(summaryText);
|
|
9638
|
+
const summaryUsed = Math.min(summaryToks, Math.min(budgets.summary, available));
|
|
9639
|
+
available -= summaryUsed;
|
|
9640
|
+
const logicText = buildLogicBlock(nodes, graph, dedup);
|
|
9641
|
+
const logicToks = estimateTokens(logicText);
|
|
9642
|
+
const logicBudget = Math.min(budgets.logic, Math.floor(available * 0.35));
|
|
9643
|
+
const logicUsed = Math.min(logicToks, logicBudget);
|
|
9644
|
+
available -= logicUsed;
|
|
9645
|
+
const relationText = buildRelationBlock(nodes, graph, dedup);
|
|
9646
|
+
const relationToks = estimateTokens(relationText);
|
|
9647
|
+
const relationBudget = Math.min(budgets.relation, Math.floor(available * 0.35));
|
|
9648
|
+
const relationUsed = Math.min(relationToks, relationBudget);
|
|
9649
|
+
available -= relationUsed;
|
|
9650
|
+
const focusBudget = available;
|
|
9651
|
+
const { text: focusText, truncated } = buildFocusCodeBlock(
|
|
9652
|
+
seeds,
|
|
9653
|
+
nodes,
|
|
9654
|
+
dedup,
|
|
9655
|
+
signatureOnlyThreshold,
|
|
9656
|
+
focusBudget
|
|
9657
|
+
);
|
|
9658
|
+
return {
|
|
9659
|
+
summary: summaryText,
|
|
9660
|
+
logic: logicText,
|
|
9661
|
+
relation: relationText,
|
|
9662
|
+
focusCode: focusText,
|
|
9663
|
+
truncated,
|
|
9664
|
+
intent
|
|
9665
|
+
};
|
|
9666
|
+
}
|
|
9667
|
+
var BUDGET_PRESETS, DedupeRegistry;
|
|
9668
|
+
var init_builder = __esm({
|
|
9669
|
+
"src/context/builder.ts"() {
|
|
9670
|
+
init_token_counter();
|
|
9671
|
+
BUDGET_PRESETS = {
|
|
9672
|
+
code: { summary: 300, logic: 400, relation: 300, focusCode: 5e3 },
|
|
9673
|
+
callers: { summary: 500, logic: 300, relation: 2500, focusCode: 700 },
|
|
9674
|
+
architecture: { summary: 1200, logic: 800, relation: 800, focusCode: 1200 },
|
|
9675
|
+
auto: { summary: 800, logic: 600, relation: 500, focusCode: 1500 }
|
|
9676
|
+
};
|
|
9677
|
+
DedupeRegistry = class {
|
|
9678
|
+
seenSymbols = /* @__PURE__ */ new Set();
|
|
9679
|
+
seenFilePaths = /* @__PURE__ */ new Set();
|
|
9680
|
+
seenCallPairs = /* @__PURE__ */ new Set();
|
|
9681
|
+
logicSymbols = /* @__PURE__ */ new Set();
|
|
9682
|
+
// B.4.2: symbols referenced in LOGIC
|
|
9683
|
+
/** Returns full format on first mention, name-only on repeats. */
|
|
9684
|
+
formatSymbol(name, filePath, extra) {
|
|
9685
|
+
const key = name;
|
|
9686
|
+
if (this.seenSymbols.has(key)) return name;
|
|
9687
|
+
this.seenSymbols.add(key);
|
|
9688
|
+
this.seenFilePaths.add(filePath);
|
|
9689
|
+
return extra;
|
|
9690
|
+
}
|
|
9691
|
+
hasSymbol(name) {
|
|
9692
|
+
return this.seenSymbols.has(name);
|
|
9693
|
+
}
|
|
9694
|
+
markCallPair(caller, callee) {
|
|
9695
|
+
this.seenCallPairs.add(`${caller}\u2192${callee}`);
|
|
9696
|
+
}
|
|
9697
|
+
hasCallPair(caller, callee) {
|
|
9698
|
+
return this.seenCallPairs.has(`${caller}\u2192${callee}`);
|
|
9699
|
+
}
|
|
9700
|
+
hasFilePath(fp) {
|
|
9701
|
+
return this.seenFilePaths.has(fp);
|
|
9702
|
+
}
|
|
9703
|
+
/** Mark a symbol as referenced in the LOGIC block (B.4.2). */
|
|
9704
|
+
markInLogic(name) {
|
|
9705
|
+
this.logicSymbols.add(name);
|
|
9706
|
+
}
|
|
9707
|
+
/** Returns true only if symbol was referenced in LOGIC (B.4.2). */
|
|
9708
|
+
isInLogic(name) {
|
|
9709
|
+
return this.logicSymbols.has(name);
|
|
9710
|
+
}
|
|
9711
|
+
};
|
|
9712
|
+
}
|
|
9713
|
+
});
|
|
9714
|
+
|
|
9269
9715
|
// src/cli/main.ts
|
|
9270
9716
|
init_logger();
|
|
9271
9717
|
init_knowledge_graph();
|
|
@@ -9763,7 +10209,7 @@ init_embedder();
|
|
|
9763
10209
|
async function hybridSearch(graph, query, limit, options = {}) {
|
|
9764
10210
|
const { vectorDbPath, bm25Limit = 50, vectorLimit = 50, bm25Results: precomputedBm25 } = options;
|
|
9765
10211
|
const bm25Promise = precomputedBm25 ? Promise.resolve(precomputedBm25) : Promise.resolve(textSearch(graph, query, bm25Limit));
|
|
9766
|
-
const hasVectorDb = Boolean(vectorDbPath &&
|
|
10212
|
+
const hasVectorDb = Boolean(vectorDbPath && fs39.existsSync(vectorDbPath));
|
|
9767
10213
|
if (!hasVectorDb) {
|
|
9768
10214
|
const bm25Results2 = await bm25Promise;
|
|
9769
10215
|
return {
|
|
@@ -9831,7 +10277,7 @@ async function queryGroup(group, query, limit = 20) {
|
|
|
9831
10277
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
9832
10278
|
if (!regEntry) continue;
|
|
9833
10279
|
const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
|
|
9834
|
-
if (!
|
|
10280
|
+
if (!fs39.existsSync(dbPath)) continue;
|
|
9835
10281
|
const graph = createKnowledgeGraph();
|
|
9836
10282
|
const db = new DbManager(dbPath, true);
|
|
9837
10283
|
try {
|
|
@@ -9873,7 +10319,7 @@ var STUCK_THRESHOLD_MINUTES = 30;
|
|
|
9873
10319
|
var JobsDB = class {
|
|
9874
10320
|
db;
|
|
9875
10321
|
constructor(dbPath) {
|
|
9876
|
-
|
|
10322
|
+
fs39.mkdirSync(path39.dirname(dbPath), { recursive: true });
|
|
9877
10323
|
this.db = new Database2(dbPath);
|
|
9878
10324
|
this.db.pragma("journal_mode = WAL");
|
|
9879
10325
|
this.db.pragma("foreign_keys = ON");
|
|
@@ -10015,7 +10461,7 @@ var JobsDB = class {
|
|
|
10015
10461
|
}
|
|
10016
10462
|
};
|
|
10017
10463
|
function getJobsDBPath() {
|
|
10018
|
-
return path39.join(
|
|
10464
|
+
return path39.join(os18.homedir(), ".code-intel", "jobs.db");
|
|
10019
10465
|
}
|
|
10020
10466
|
var _jobsDB = null;
|
|
10021
10467
|
function getOrCreateJobsDB() {
|
|
@@ -10110,14 +10556,14 @@ var BACKUP_VERSION = "1.0";
|
|
|
10110
10556
|
var ALGORITHM = "aes-256-gcm";
|
|
10111
10557
|
var IV_LENGTH = 16;
|
|
10112
10558
|
function getBackupDir() {
|
|
10113
|
-
return path39.join(
|
|
10559
|
+
return path39.join(os18.homedir(), ".code-intel", "backups");
|
|
10114
10560
|
}
|
|
10115
10561
|
function getBackupKey() {
|
|
10116
10562
|
const keyHex = process.env["CODE_INTEL_BACKUP_KEY"];
|
|
10117
10563
|
if (keyHex && keyHex.length >= 64) {
|
|
10118
10564
|
return Buffer.from(keyHex.slice(0, 64), "hex");
|
|
10119
10565
|
}
|
|
10120
|
-
const seed = `code-intel-backup-${
|
|
10566
|
+
const seed = `code-intel-backup-${os18.hostname()}-${os18.homedir()}`;
|
|
10121
10567
|
return crypto5.createHash("sha256").update(seed).digest();
|
|
10122
10568
|
}
|
|
10123
10569
|
function encryptBuffer(data, key) {
|
|
@@ -10141,7 +10587,7 @@ var BackupService = class {
|
|
|
10141
10587
|
constructor(backupDir) {
|
|
10142
10588
|
this.backupDir = backupDir ?? getBackupDir();
|
|
10143
10589
|
this.key = getBackupKey();
|
|
10144
|
-
|
|
10590
|
+
fs39.mkdirSync(this.backupDir, { recursive: true });
|
|
10145
10591
|
}
|
|
10146
10592
|
/**
|
|
10147
10593
|
* Create a backup for a repository.
|
|
@@ -10155,16 +10601,16 @@ var BackupService = class {
|
|
|
10155
10601
|
const candidates = ["graph.db", "vector.db", "meta.json"];
|
|
10156
10602
|
for (const f of candidates) {
|
|
10157
10603
|
const fp = path39.join(codeIntelDir, f);
|
|
10158
|
-
if (
|
|
10604
|
+
if (fs39.existsSync(fp)) {
|
|
10159
10605
|
filesToBackup.push({ name: f, localPath: fp });
|
|
10160
10606
|
}
|
|
10161
10607
|
}
|
|
10162
|
-
const registryPath = path39.join(
|
|
10163
|
-
if (
|
|
10608
|
+
const registryPath = path39.join(os18.homedir(), ".code-intel", "registry.json");
|
|
10609
|
+
if (fs39.existsSync(registryPath)) {
|
|
10164
10610
|
filesToBackup.push({ name: "registry.json", localPath: registryPath });
|
|
10165
10611
|
}
|
|
10166
|
-
const usersDbPath = path39.join(
|
|
10167
|
-
if (
|
|
10612
|
+
const usersDbPath = path39.join(os18.homedir(), ".code-intel", "users.db");
|
|
10613
|
+
if (fs39.existsSync(usersDbPath)) {
|
|
10168
10614
|
filesToBackup.push({ name: "users.db", localPath: usersDbPath });
|
|
10169
10615
|
}
|
|
10170
10616
|
if (filesToBackup.length === 0) {
|
|
@@ -10175,7 +10621,7 @@ var BackupService = class {
|
|
|
10175
10621
|
createdAt,
|
|
10176
10622
|
version: BACKUP_VERSION,
|
|
10177
10623
|
files: filesToBackup.map((f) => {
|
|
10178
|
-
const data =
|
|
10624
|
+
const data = fs39.readFileSync(f.localPath);
|
|
10179
10625
|
return {
|
|
10180
10626
|
name: f.name,
|
|
10181
10627
|
sha256: crypto5.createHash("sha256").update(data).digest("hex"),
|
|
@@ -10189,7 +10635,7 @@ var BackupService = class {
|
|
|
10189
10635
|
manifestLenBuf.writeUInt32BE(manifestBuf.length, 0);
|
|
10190
10636
|
parts.push(manifestLenBuf, manifestBuf);
|
|
10191
10637
|
for (const f of filesToBackup) {
|
|
10192
|
-
const data =
|
|
10638
|
+
const data = fs39.readFileSync(f.localPath);
|
|
10193
10639
|
const nameBuf = Buffer.from(f.name, "utf-8");
|
|
10194
10640
|
const nameLenBuf = Buffer.alloc(2);
|
|
10195
10641
|
nameLenBuf.writeUInt16BE(nameBuf.length, 0);
|
|
@@ -10201,7 +10647,7 @@ var BackupService = class {
|
|
|
10201
10647
|
const encrypted = encryptBuffer(plaintext, this.key);
|
|
10202
10648
|
const backupFileName = `backup-${id}.cib`;
|
|
10203
10649
|
const backupPath = path39.join(this.backupDir, backupFileName);
|
|
10204
|
-
|
|
10650
|
+
fs39.writeFileSync(backupPath, encrypted);
|
|
10205
10651
|
const entry = {
|
|
10206
10652
|
id,
|
|
10207
10653
|
createdAt,
|
|
@@ -10230,7 +10676,7 @@ var BackupService = class {
|
|
|
10230
10676
|
if (!cfg) throw new Error("S3 not configured. Set CODE_INTEL_BACKUP_S3_BUCKET, CODE_INTEL_BACKUP_S3_ACCESS_KEY_ID, CODE_INTEL_BACKUP_S3_SECRET_ACCESS_KEY.");
|
|
10231
10677
|
const fileName = path39.basename(entry.path);
|
|
10232
10678
|
const s3Key = `${cfg.prefix}${fileName}`;
|
|
10233
|
-
const body =
|
|
10679
|
+
const body = fs39.readFileSync(entry.path);
|
|
10234
10680
|
const result = await s3Request({ method: "PUT", cfg, key: s3Key, body });
|
|
10235
10681
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
10236
10682
|
throw new Error(`S3 upload failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
@@ -10247,8 +10693,8 @@ var BackupService = class {
|
|
|
10247
10693
|
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
10248
10694
|
throw new Error(`S3 download failed (HTTP ${result.statusCode}): ${result.body.slice(0, 200)}`);
|
|
10249
10695
|
}
|
|
10250
|
-
|
|
10251
|
-
|
|
10696
|
+
fs39.mkdirSync(path39.dirname(destPath), { recursive: true });
|
|
10697
|
+
fs39.writeFileSync(destPath, Buffer.from(result.body, "binary"));
|
|
10252
10698
|
}
|
|
10253
10699
|
/**
|
|
10254
10700
|
* List backup objects in S3 with the configured prefix.
|
|
@@ -10294,10 +10740,10 @@ var BackupService = class {
|
|
|
10294
10740
|
if (!entry) {
|
|
10295
10741
|
throw new Error(`Backup "${backupId}" not found.`);
|
|
10296
10742
|
}
|
|
10297
|
-
if (!
|
|
10743
|
+
if (!fs39.existsSync(entry.path)) {
|
|
10298
10744
|
throw new Error(`Backup file not found at: ${entry.path}`);
|
|
10299
10745
|
}
|
|
10300
|
-
const encrypted =
|
|
10746
|
+
const encrypted = fs39.readFileSync(entry.path);
|
|
10301
10747
|
let plaintext;
|
|
10302
10748
|
try {
|
|
10303
10749
|
plaintext = decryptBuffer(encrypted, this.key);
|
|
@@ -10312,7 +10758,7 @@ var BackupService = class {
|
|
|
10312
10758
|
const manifest = JSON.parse(manifestStr);
|
|
10313
10759
|
const restoreBase = targetRepoPath ?? entry.repoPath;
|
|
10314
10760
|
const codeIntelDir = path39.join(restoreBase, ".code-intel");
|
|
10315
|
-
|
|
10761
|
+
fs39.mkdirSync(codeIntelDir, { recursive: true });
|
|
10316
10762
|
for (const fileEntry of manifest.files) {
|
|
10317
10763
|
const nameLen = plaintext.readUInt16BE(offset);
|
|
10318
10764
|
offset += 2;
|
|
@@ -10329,18 +10775,18 @@ var BackupService = class {
|
|
|
10329
10775
|
}
|
|
10330
10776
|
let destPath;
|
|
10331
10777
|
if (name === "registry.json" || name === "users.db") {
|
|
10332
|
-
destPath = path39.join(
|
|
10778
|
+
destPath = path39.join(os18.homedir(), ".code-intel", name);
|
|
10333
10779
|
} else {
|
|
10334
10780
|
destPath = path39.join(codeIntelDir, name);
|
|
10335
10781
|
}
|
|
10336
|
-
|
|
10782
|
+
fs39.writeFileSync(destPath, data);
|
|
10337
10783
|
}
|
|
10338
10784
|
}
|
|
10339
10785
|
/**
|
|
10340
10786
|
* Apply retention policy: keep N daily, M weekly, L monthly backups.
|
|
10341
10787
|
*/
|
|
10342
10788
|
applyRetention(options = { daily: 7, weekly: 4, monthly: 12 }) {
|
|
10343
|
-
const entries = this._loadIndex().filter((e) =>
|
|
10789
|
+
const entries = this._loadIndex().filter((e) => fs39.existsSync(e.path)).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
10344
10790
|
const keep = /* @__PURE__ */ new Set();
|
|
10345
10791
|
const now = /* @__PURE__ */ new Date();
|
|
10346
10792
|
const dailyCutoff = new Date(now);
|
|
@@ -10370,7 +10816,7 @@ var BackupService = class {
|
|
|
10370
10816
|
for (const e of entries) {
|
|
10371
10817
|
if (!keep.has(e.id)) {
|
|
10372
10818
|
try {
|
|
10373
|
-
|
|
10819
|
+
fs39.unlinkSync(e.path);
|
|
10374
10820
|
deleted++;
|
|
10375
10821
|
} catch {
|
|
10376
10822
|
}
|
|
@@ -10386,13 +10832,13 @@ var BackupService = class {
|
|
|
10386
10832
|
}
|
|
10387
10833
|
_loadIndex() {
|
|
10388
10834
|
try {
|
|
10389
|
-
return JSON.parse(
|
|
10835
|
+
return JSON.parse(fs39.readFileSync(this._indexPath(), "utf-8"));
|
|
10390
10836
|
} catch {
|
|
10391
10837
|
return [];
|
|
10392
10838
|
}
|
|
10393
10839
|
}
|
|
10394
10840
|
_saveIndex(entries) {
|
|
10395
|
-
|
|
10841
|
+
fs39.writeFileSync(this._indexPath(), JSON.stringify(entries, null, 2));
|
|
10396
10842
|
}
|
|
10397
10843
|
_appendIndex(entry) {
|
|
10398
10844
|
const entries = this._loadIndex();
|
|
@@ -11036,7 +11482,7 @@ var openApiSpec = {
|
|
|
11036
11482
|
var __dirname$1 = path39.dirname(fileURLToPath(import.meta.url));
|
|
11037
11483
|
var WEB_DIST = (() => {
|
|
11038
11484
|
const bundled = path39.resolve(__dirname$1, "..", "web");
|
|
11039
|
-
if (
|
|
11485
|
+
if (fs39.existsSync(bundled)) return bundled;
|
|
11040
11486
|
return path39.resolve(__dirname$1, "..", "..", "..", "web", "dist");
|
|
11041
11487
|
})();
|
|
11042
11488
|
function getAllowedOrigins() {
|
|
@@ -11121,8 +11567,8 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11121
11567
|
const metaFilePath = path39.join(workspaceRoot, ".code-intel", "meta.json");
|
|
11122
11568
|
let metaOk = false;
|
|
11123
11569
|
try {
|
|
11124
|
-
if (
|
|
11125
|
-
const raw =
|
|
11570
|
+
if (fs39.existsSync(metaFilePath)) {
|
|
11571
|
+
const raw = fs39.readFileSync(metaFilePath, "utf-8");
|
|
11126
11572
|
const meta = JSON.parse(raw);
|
|
11127
11573
|
if (meta?.indexVersion) res.setHeader("X-Index-Version", meta.indexVersion);
|
|
11128
11574
|
}
|
|
@@ -11313,12 +11759,12 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11313
11759
|
return;
|
|
11314
11760
|
}
|
|
11315
11761
|
const user = db.createUser(username, password, "admin");
|
|
11316
|
-
const sessionId = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11317
|
-
res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
|
|
11762
|
+
const { sessionId, ttlMs } = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11763
|
+
res.setHeader("Set-Cookie", buildSessionCookie(sessionId, ttlMs));
|
|
11318
11764
|
res.status(201).json({ user: { id: user.id, username: user.username, role: user.role } });
|
|
11319
11765
|
});
|
|
11320
11766
|
app.post("/auth/login", async (req, res) => {
|
|
11321
|
-
const { username, password } = req.body;
|
|
11767
|
+
const { username, password, rememberMe } = req.body;
|
|
11322
11768
|
if (!username || !password) {
|
|
11323
11769
|
res.status(400).json({
|
|
11324
11770
|
error: {
|
|
@@ -11362,10 +11808,10 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11362
11808
|
});
|
|
11363
11809
|
return;
|
|
11364
11810
|
}
|
|
11365
|
-
const sessionId = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11811
|
+
const { sessionId, ttlMs } = createSession({ id: user.id, username: user.username, role: user.role }, rememberMe === true);
|
|
11366
11812
|
db.logAccess(user.id, "/auth/login", "login", "allow", req.ip ?? "unknown");
|
|
11367
11813
|
authAttemptsTotal.inc({ method: "local", outcome: "success" });
|
|
11368
|
-
res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
|
|
11814
|
+
res.setHeader("Set-Cookie", buildSessionCookie(sessionId, ttlMs));
|
|
11369
11815
|
res.json({ user: { id: user.id, username: user.username, role: user.role } });
|
|
11370
11816
|
});
|
|
11371
11817
|
app.post("/auth/logout", (req, res) => {
|
|
@@ -11487,9 +11933,9 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11487
11933
|
authAttemptsTotal.inc({ method: "oidc", outcome: "success" });
|
|
11488
11934
|
logger_default.info(`[oidc] Auto-provisioned new user: ${finalUsername} (${cfg.defaultRole})`);
|
|
11489
11935
|
}
|
|
11490
|
-
const sessionId = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11936
|
+
const { sessionId, ttlMs } = createSession({ id: user.id, username: user.username, role: user.role });
|
|
11491
11937
|
db.logAccess(user.id, "/auth/callback", "oidc-login", "allow", req.ip ?? "unknown");
|
|
11492
|
-
res.setHeader("Set-Cookie", buildSessionCookie(sessionId));
|
|
11938
|
+
res.setHeader("Set-Cookie", buildSessionCookie(sessionId, ttlMs));
|
|
11493
11939
|
res.redirect(302, "/");
|
|
11494
11940
|
} catch (err) {
|
|
11495
11941
|
logger_default.warn("[oidc] Callback failed:", err instanceof Error ? err.message : err);
|
|
@@ -11607,7 +12053,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11607
12053
|
const entry = registry.find((r) => r.name === requestedRepo || r.path === requestedRepo);
|
|
11608
12054
|
if (!entry) return null;
|
|
11609
12055
|
const dbPath = path39.join(entry.path, ".code-intel", "graph.db");
|
|
11610
|
-
if (!
|
|
12056
|
+
if (!fs39.existsSync(dbPath)) return null;
|
|
11611
12057
|
const repoGraph = createKnowledgeGraph();
|
|
11612
12058
|
const db = new DbManager(dbPath, true);
|
|
11613
12059
|
try {
|
|
@@ -11629,7 +12075,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11629
12075
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
11630
12076
|
if (!regEntry) continue;
|
|
11631
12077
|
const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
|
|
11632
|
-
if (!
|
|
12078
|
+
if (!fs39.existsSync(dbPath)) continue;
|
|
11633
12079
|
const db = new DbManager(dbPath, true);
|
|
11634
12080
|
try {
|
|
11635
12081
|
await db.init();
|
|
@@ -11711,7 +12157,21 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11711
12157
|
});
|
|
11712
12158
|
});
|
|
11713
12159
|
app.post("/api/v1/search", requireToolScope("search"), async (req, res) => {
|
|
11714
|
-
const { query, limit, repo } = req.body;
|
|
12160
|
+
const { query, limit, repo, group } = req.body;
|
|
12161
|
+
if (group) {
|
|
12162
|
+
const grp = loadGroup(group);
|
|
12163
|
+
if (!grp) {
|
|
12164
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: `Group '${group}' not found`, hint: "Use /api/v1/groups to list available groups" } });
|
|
12165
|
+
return;
|
|
12166
|
+
}
|
|
12167
|
+
try {
|
|
12168
|
+
const { perRepo, merged } = await queryGroup(grp, query ?? "", limit ?? 20);
|
|
12169
|
+
res.json({ results: merged, perRepo, searchMode: "bm25", group });
|
|
12170
|
+
} catch (err) {
|
|
12171
|
+
res.status(500).json({ error: { code: ErrorCodes.INTERNAL_ERROR, message: err instanceof Error ? err.message : String(err) } });
|
|
12172
|
+
}
|
|
12173
|
+
return;
|
|
12174
|
+
}
|
|
11715
12175
|
const g = await getGraphForRepo(repo);
|
|
11716
12176
|
const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
|
|
11717
12177
|
const bm25 = !repo || repo === repoName ? ensureBm25Index() : null;
|
|
@@ -11720,7 +12180,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11720
12180
|
vectorDbPath: vdbPath,
|
|
11721
12181
|
bm25Results: bm25Results ?? void 0
|
|
11722
12182
|
});
|
|
11723
|
-
res.json({ results, searchMode });
|
|
12183
|
+
res.json({ results, searchMode, repo: repo ?? repoName });
|
|
11724
12184
|
});
|
|
11725
12185
|
app.post("/api/v1/vector-search", async (req, res) => {
|
|
11726
12186
|
const { query, limit = 10 } = req.body;
|
|
@@ -11762,7 +12222,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11762
12222
|
return;
|
|
11763
12223
|
}
|
|
11764
12224
|
try {
|
|
11765
|
-
const content =
|
|
12225
|
+
const content = fs39.readFileSync(file_path, "utf-8");
|
|
11766
12226
|
res.json({ content });
|
|
11767
12227
|
} catch {
|
|
11768
12228
|
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "File not found" } });
|
|
@@ -11994,6 +12454,97 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
11994
12454
|
}
|
|
11995
12455
|
res.json(result);
|
|
11996
12456
|
});
|
|
12457
|
+
app.post("/api/v1/groups", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12458
|
+
const { name } = req.body;
|
|
12459
|
+
if (!name || !name.trim()) {
|
|
12460
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "Group name is required" } });
|
|
12461
|
+
return;
|
|
12462
|
+
}
|
|
12463
|
+
const trimmed = name.trim();
|
|
12464
|
+
if (groupExists(trimmed)) {
|
|
12465
|
+
res.status(409).json({ error: { code: ErrorCodes.CONFLICT, message: `Group "${trimmed}" already exists` } });
|
|
12466
|
+
return;
|
|
12467
|
+
}
|
|
12468
|
+
const group = { name: trimmed, createdAt: (/* @__PURE__ */ new Date()).toISOString(), members: [] };
|
|
12469
|
+
saveGroup(group);
|
|
12470
|
+
res.status(201).json(group);
|
|
12471
|
+
});
|
|
12472
|
+
app.delete("/api/v1/groups/:name", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12473
|
+
const groupName = req.params["name"];
|
|
12474
|
+
if (!groupExists(groupName)) {
|
|
12475
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
12476
|
+
return;
|
|
12477
|
+
}
|
|
12478
|
+
deleteGroup(groupName);
|
|
12479
|
+
res.status(204).end();
|
|
12480
|
+
});
|
|
12481
|
+
app.patch("/api/v1/groups/:name", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12482
|
+
const groupName = req.params["name"];
|
|
12483
|
+
const group = loadGroup(groupName);
|
|
12484
|
+
if (!group) {
|
|
12485
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
12486
|
+
return;
|
|
12487
|
+
}
|
|
12488
|
+
const { name } = req.body;
|
|
12489
|
+
if (!name || !name.trim()) {
|
|
12490
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "New name is required" } });
|
|
12491
|
+
return;
|
|
12492
|
+
}
|
|
12493
|
+
const newName = name.trim();
|
|
12494
|
+
if (newName !== group.name && groupExists(newName)) {
|
|
12495
|
+
res.status(409).json({ error: { code: ErrorCodes.CONFLICT, message: `Group "${newName}" already exists` } });
|
|
12496
|
+
return;
|
|
12497
|
+
}
|
|
12498
|
+
if (newName !== group.name) {
|
|
12499
|
+
deleteGroup(group.name);
|
|
12500
|
+
group.name = newName;
|
|
12501
|
+
}
|
|
12502
|
+
saveGroup(group);
|
|
12503
|
+
res.json(group);
|
|
12504
|
+
});
|
|
12505
|
+
app.post("/api/v1/groups/:name/members", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12506
|
+
const groupName = req.params["name"];
|
|
12507
|
+
const group = loadGroup(groupName);
|
|
12508
|
+
if (!group) {
|
|
12509
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
12510
|
+
return;
|
|
12511
|
+
}
|
|
12512
|
+
const { groupPath, registryName } = req.body;
|
|
12513
|
+
if (!groupPath || !registryName) {
|
|
12514
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "groupPath and registryName are required" } });
|
|
12515
|
+
return;
|
|
12516
|
+
}
|
|
12517
|
+
const registry = loadRegistry();
|
|
12518
|
+
if (!registry.find((r) => r.name === registryName)) {
|
|
12519
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: `Repo "${registryName}" not found in registry. Run code-intel analyze first.` } });
|
|
12520
|
+
return;
|
|
12521
|
+
}
|
|
12522
|
+
try {
|
|
12523
|
+
const updated = addMember(groupName, { groupPath, registryName });
|
|
12524
|
+
res.json(updated);
|
|
12525
|
+
} catch (err) {
|
|
12526
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: err instanceof Error ? err.message : String(err) } });
|
|
12527
|
+
}
|
|
12528
|
+
});
|
|
12529
|
+
app.delete("/api/v1/groups/:name/members", requireAuth, requireRole("analyst"), (req, res) => {
|
|
12530
|
+
const groupName = req.params["name"];
|
|
12531
|
+
const group = loadGroup(groupName);
|
|
12532
|
+
if (!group) {
|
|
12533
|
+
res.status(404).json({ error: { code: ErrorCodes.NOT_FOUND, message: "Group not found" } });
|
|
12534
|
+
return;
|
|
12535
|
+
}
|
|
12536
|
+
const { groupPath } = req.body;
|
|
12537
|
+
if (!groupPath) {
|
|
12538
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: "groupPath is required" } });
|
|
12539
|
+
return;
|
|
12540
|
+
}
|
|
12541
|
+
try {
|
|
12542
|
+
const updated = removeMember(groupName, groupPath);
|
|
12543
|
+
res.json(updated);
|
|
12544
|
+
} catch (err) {
|
|
12545
|
+
res.status(400).json({ error: { code: ErrorCodes.INVALID_REQUEST, message: err instanceof Error ? err.message : String(err) } });
|
|
12546
|
+
}
|
|
12547
|
+
});
|
|
11997
12548
|
app.post("/api/v1/groups/:name/sync", async (req, res) => {
|
|
11998
12549
|
const group = loadGroup(req.params.name);
|
|
11999
12550
|
if (!group) {
|
|
@@ -12004,8 +12555,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12004
12555
|
const result = await syncGroup(group);
|
|
12005
12556
|
saveSyncResult(result);
|
|
12006
12557
|
group.lastSync = result.syncedAt;
|
|
12007
|
-
|
|
12008
|
-
saveGroup2(group);
|
|
12558
|
+
saveGroup(group);
|
|
12009
12559
|
res.json(result);
|
|
12010
12560
|
} catch (err) {
|
|
12011
12561
|
res.status(500).json({ error: { code: ErrorCodes.INTERNAL_ERROR, message: err instanceof Error ? err.message : String(err) } });
|
|
@@ -12041,7 +12591,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12041
12591
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
12042
12592
|
if (!regEntry) continue;
|
|
12043
12593
|
const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
|
|
12044
|
-
if (!
|
|
12594
|
+
if (!fs39.existsSync(dbPath)) continue;
|
|
12045
12595
|
const db = new DbManager(dbPath, true);
|
|
12046
12596
|
try {
|
|
12047
12597
|
await db.init();
|
|
@@ -12068,7 +12618,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12068
12618
|
let edgeCount = 0;
|
|
12069
12619
|
if (regEntry) {
|
|
12070
12620
|
const dbPath = path39.join(regEntry.path, ".code-intel", "graph.db");
|
|
12071
|
-
if (
|
|
12621
|
+
if (fs39.existsSync(dbPath)) {
|
|
12072
12622
|
try {
|
|
12073
12623
|
const db = new DbManager(dbPath, true);
|
|
12074
12624
|
await db.init();
|
|
@@ -12131,7 +12681,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12131
12681
|
const regEntry = registry.find((r) => r.name === member.registryName);
|
|
12132
12682
|
if (!regEntry) continue;
|
|
12133
12683
|
const candidate = path39.resolve(path39.join(regEntry.path, normalizedFile));
|
|
12134
|
-
if (
|
|
12684
|
+
if (fs39.existsSync(candidate)) {
|
|
12135
12685
|
baseDir = regEntry.path;
|
|
12136
12686
|
break;
|
|
12137
12687
|
}
|
|
@@ -12183,7 +12733,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12183
12733
|
}
|
|
12184
12734
|
let fileContent;
|
|
12185
12735
|
try {
|
|
12186
|
-
fileContent =
|
|
12736
|
+
fileContent = fs39.readFileSync(resolvedFile, "utf-8");
|
|
12187
12737
|
} catch {
|
|
12188
12738
|
res.status(404).json({
|
|
12189
12739
|
error: {
|
|
@@ -12349,7 +12899,7 @@ function createApp(graph, repoName, workspaceRoot, watcherState) {
|
|
|
12349
12899
|
res.status(500).json({ error: { code: ErrorCodes.INTERNAL_ERROR, message: err instanceof Error ? err.message : String(err), requestId: req.requestId, timestamp: (/* @__PURE__ */ new Date()).toISOString() } });
|
|
12350
12900
|
}
|
|
12351
12901
|
});
|
|
12352
|
-
if (
|
|
12902
|
+
if (fs39.existsSync(WEB_DIST)) {
|
|
12353
12903
|
app.use(express.static(WEB_DIST));
|
|
12354
12904
|
app.get("/{*path}", (_req, res) => {
|
|
12355
12905
|
res.sendFile(path39.join(WEB_DIST, "index.html"));
|
|
@@ -12503,6 +13053,7 @@ async function startHttpServer(graph, repoName, port = 4747, workspaceRoot, watc
|
|
|
12503
13053
|
});
|
|
12504
13054
|
});
|
|
12505
13055
|
}
|
|
13056
|
+
init_bm25_index();
|
|
12506
13057
|
init_storage();
|
|
12507
13058
|
init_repo_registry();
|
|
12508
13059
|
init_metadata();
|
|
@@ -13029,11 +13580,30 @@ function summarizeCluster(graph, cluster) {
|
|
|
13029
13580
|
}
|
|
13030
13581
|
|
|
13031
13582
|
// src/mcp-server/server.ts
|
|
13583
|
+
function compact(obj) {
|
|
13584
|
+
return JSON.stringify(obj, (_key, value) => value === null || value === void 0 ? void 0 : value);
|
|
13585
|
+
}
|
|
13032
13586
|
function createMcpServer(graph, repoName, workspaceRoot) {
|
|
13033
13587
|
const server = new Server(
|
|
13034
13588
|
{ name: "code-intel", version: "0.1.0" },
|
|
13035
13589
|
{ capabilities: { tools: {}, resources: {} } }
|
|
13036
13590
|
);
|
|
13591
|
+
let bm25Index = null;
|
|
13592
|
+
function ensureBm25Index() {
|
|
13593
|
+
if (bm25Index) return bm25Index;
|
|
13594
|
+
if (!workspaceRoot) return null;
|
|
13595
|
+
try {
|
|
13596
|
+
const idx = new Bm25Index(getBm25DbPath(workspaceRoot));
|
|
13597
|
+
idx.load();
|
|
13598
|
+
bm25Index = idx;
|
|
13599
|
+
return bm25Index;
|
|
13600
|
+
} catch {
|
|
13601
|
+
return null;
|
|
13602
|
+
}
|
|
13603
|
+
}
|
|
13604
|
+
if (workspaceRoot) {
|
|
13605
|
+
setImmediate(() => ensureBm25Index());
|
|
13606
|
+
}
|
|
13037
13607
|
const _tokenProp = {
|
|
13038
13608
|
_token: { type: "string", description: "Required if CODE_INTEL_TOKEN is configured" }
|
|
13039
13609
|
};
|
|
@@ -13053,13 +13623,15 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
13053
13623
|
// ── Search & inspect ─────────────────────────────────────────────────
|
|
13054
13624
|
{
|
|
13055
13625
|
name: "search",
|
|
13056
|
-
description: "BM25 keyword search across all indexed symbols \u2014 functions, classes, files, routes, etc.",
|
|
13626
|
+
description: "BM25 keyword search across all indexed symbols \u2014 functions, classes, files, routes, etc. Optionally scope to a specific repo or group.",
|
|
13057
13627
|
inputSchema: {
|
|
13058
13628
|
type: "object",
|
|
13059
13629
|
properties: {
|
|
13060
13630
|
query: { type: "string", description: "Search query (symbol name, keyword, or partial match)" },
|
|
13061
13631
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
13062
|
-
limit: { type: "number", description: "Max results per page (default:
|
|
13632
|
+
limit: { type: "number", description: "Max results per page (default: 10, max: 500)" },
|
|
13633
|
+
repo: { type: "string", description: "Scope search to a specific indexed repo name (optional; defaults to current repo)" },
|
|
13634
|
+
group: { type: "string", description: "Scope search across all repos in a group via cross-repo RRF merge (optional; overrides repo)" },
|
|
13063
13635
|
..._tokenProp
|
|
13064
13636
|
},
|
|
13065
13637
|
required: ["query"]
|
|
@@ -13089,7 +13661,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
13089
13661
|
enum: ["callers", "callees", "both"],
|
|
13090
13662
|
description: "Which direction to trace \u2014 callers (who depends on it), callees (what it depends on), or both (default: both)"
|
|
13091
13663
|
},
|
|
13092
|
-
max_hops: { type: "number", description: "Maximum traversal depth (default:
|
|
13664
|
+
max_hops: { type: "number", description: "Maximum traversal depth (default: 2, max: 10)" },
|
|
13093
13665
|
..._tokenProp
|
|
13094
13666
|
},
|
|
13095
13667
|
required: ["target"]
|
|
@@ -13103,7 +13675,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
13103
13675
|
properties: {
|
|
13104
13676
|
file_path: { type: "string", description: 'File path (partial match is supported, e.g. "auth/login.ts")' },
|
|
13105
13677
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
13106
|
-
limit: { type: "number", description: "Max results per page (default:
|
|
13678
|
+
limit: { type: "number", description: "Max results per page (default: 10, max: 500)" },
|
|
13107
13679
|
..._tokenProp
|
|
13108
13680
|
},
|
|
13109
13681
|
required: ["file_path"]
|
|
@@ -13134,7 +13706,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
13134
13706
|
description: "Filter by node kind: function | class | interface | method | type_alias | constant | enum (optional)"
|
|
13135
13707
|
},
|
|
13136
13708
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
13137
|
-
limit: { type: "number", description: "Max results per page (default:
|
|
13709
|
+
limit: { type: "number", description: "Max results per page (default: 10, max: 500)" },
|
|
13138
13710
|
..._tokenProp
|
|
13139
13711
|
}
|
|
13140
13712
|
}
|
|
@@ -13152,7 +13724,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
13152
13724
|
type: "object",
|
|
13153
13725
|
properties: {
|
|
13154
13726
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
13155
|
-
limit: { type: "number", description: "Max clusters per page (default:
|
|
13727
|
+
limit: { type: "number", description: "Max clusters per page (default: 10, max: 500)" },
|
|
13156
13728
|
..._tokenProp
|
|
13157
13729
|
}
|
|
13158
13730
|
}
|
|
@@ -13164,7 +13736,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
13164
13736
|
type: "object",
|
|
13165
13737
|
properties: {
|
|
13166
13738
|
offset: { type: "number", description: "Number of results to skip for pagination (default: 0)" },
|
|
13167
|
-
limit: { type: "number", description: "Max flows per page (default:
|
|
13739
|
+
limit: { type: "number", description: "Max flows per page (default: 10, max: 500)" },
|
|
13168
13740
|
..._tokenProp
|
|
13169
13741
|
}
|
|
13170
13742
|
}
|
|
@@ -13318,7 +13890,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
13318
13890
|
},
|
|
13319
13891
|
maxHops: {
|
|
13320
13892
|
type: "number",
|
|
13321
|
-
description: "Maximum BFS depth for blast radius (default:
|
|
13893
|
+
description: "Maximum BFS depth for blast radius (default: 2, max: 10)"
|
|
13322
13894
|
},
|
|
13323
13895
|
..._tokenProp
|
|
13324
13896
|
}
|
|
@@ -13446,13 +14018,13 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
13446
14018
|
const providedToken = a._token;
|
|
13447
14019
|
if (providedToken !== expectedToken) {
|
|
13448
14020
|
return {
|
|
13449
|
-
content: [{ type: "text", text:
|
|
14021
|
+
content: [{ type: "text", text: compact({ error: "Unauthorized: invalid or missing CODE_INTEL_TOKEN" }) }],
|
|
13450
14022
|
isError: true
|
|
13451
14023
|
};
|
|
13452
14024
|
}
|
|
13453
14025
|
}
|
|
13454
14026
|
const startMs = Date.now();
|
|
13455
|
-
const dispatch = () => dispatchTool(name, a, graph, repoName, workspaceRoot);
|
|
14027
|
+
const dispatch = () => dispatchTool(name, a, graph, repoName, workspaceRoot, ensureBm25Index);
|
|
13456
14028
|
const MCP_TIMEOUT_MS = parseInt(process.env["CODE_INTEL_MCP_TIMEOUT_MS"] ?? "30000", 10);
|
|
13457
14029
|
let timeoutHandle = null;
|
|
13458
14030
|
let timedOut = false;
|
|
@@ -13484,7 +14056,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
13484
14056
|
mcpToolDurationSeconds.observe({ tool: name }, (Date.now() - startMs) / 1e3);
|
|
13485
14057
|
if (timedOut) {
|
|
13486
14058
|
return {
|
|
13487
|
-
content: [{ type: "text", text:
|
|
14059
|
+
content: [{ type: "text", text: compact({ truncated: true, reason: `Tool '${name}' timed out after ${MCP_TIMEOUT_MS}ms`, partialResults: [] }) }],
|
|
13488
14060
|
isError: false
|
|
13489
14061
|
};
|
|
13490
14062
|
}
|
|
@@ -13499,7 +14071,7 @@ function createMcpServer(graph, repoName, workspaceRoot) {
|
|
|
13499
14071
|
registerResources(server, graph, repoName);
|
|
13500
14072
|
return server;
|
|
13501
14073
|
}
|
|
13502
|
-
async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
14074
|
+
async function dispatchTool(name, a, graph, repoName, workspaceRoot, bm25Resolver) {
|
|
13503
14075
|
switch (name) {
|
|
13504
14076
|
// ── repos ──────────────────────────────────────────────────────────────
|
|
13505
14077
|
case "repos": {
|
|
@@ -13507,10 +14079,8 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13507
14079
|
return {
|
|
13508
14080
|
content: [{
|
|
13509
14081
|
type: "text",
|
|
13510
|
-
text:
|
|
13511
|
-
registry.map((r) => ({ name: r.name, path: r.path, indexedAt: r.indexedAt, stats: r.stats }))
|
|
13512
|
-
null,
|
|
13513
|
-
2
|
|
14082
|
+
text: compact(
|
|
14083
|
+
registry.map((r) => ({ name: r.name, path: r.path, indexedAt: r.indexedAt, stats: r.stats }))
|
|
13514
14084
|
)
|
|
13515
14085
|
}]
|
|
13516
14086
|
};
|
|
@@ -13538,13 +14108,13 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13538
14108
|
return {
|
|
13539
14109
|
content: [{
|
|
13540
14110
|
type: "text",
|
|
13541
|
-
text:
|
|
14111
|
+
text: compact({
|
|
13542
14112
|
repo: repoName,
|
|
13543
14113
|
stats: graph.size,
|
|
13544
14114
|
nodeCounts: kindCounts,
|
|
13545
14115
|
edgeCounts,
|
|
13546
14116
|
health
|
|
13547
|
-
}
|
|
14117
|
+
})
|
|
13548
14118
|
}]
|
|
13549
14119
|
};
|
|
13550
14120
|
}
|
|
@@ -13552,15 +14122,59 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13552
14122
|
case "search": {
|
|
13553
14123
|
const query = a.query;
|
|
13554
14124
|
const offset = a.offset ?? 0;
|
|
13555
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
14125
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
14126
|
+
if (a.group) {
|
|
14127
|
+
const grp = loadGroup(a.group);
|
|
14128
|
+
if (!grp) {
|
|
14129
|
+
return { content: [{ type: "text", text: `Group "${a.group}" not found. Use list_groups to see available groups.` }] };
|
|
14130
|
+
}
|
|
14131
|
+
const { perRepo, merged } = await queryGroup(grp, query, effectiveLimit + offset);
|
|
14132
|
+
const paged = merged.slice(offset, offset + effectiveLimit);
|
|
14133
|
+
return {
|
|
14134
|
+
content: [{
|
|
14135
|
+
type: "text",
|
|
14136
|
+
text: compact({
|
|
14137
|
+
results: paged,
|
|
14138
|
+
perRepo,
|
|
14139
|
+
searchMode: "bm25-cross-repo",
|
|
14140
|
+
group: a.group,
|
|
14141
|
+
total: merged.length,
|
|
14142
|
+
offset,
|
|
14143
|
+
limit: effectiveLimit,
|
|
14144
|
+
hasMore: offset + effectiveLimit < merged.length
|
|
14145
|
+
})
|
|
14146
|
+
}]
|
|
14147
|
+
};
|
|
14148
|
+
}
|
|
14149
|
+
const repoGraph = a.repo ? await (async () => {
|
|
14150
|
+
const registry = loadRegistry();
|
|
14151
|
+
const entry = registry.find((r) => r.name === a.repo || r.path === a.repo);
|
|
14152
|
+
if (!entry) return graph;
|
|
14153
|
+
const { DbManager: DbMgr } = await Promise.resolve().then(() => (init_db_manager(), db_manager_exports));
|
|
14154
|
+
const { loadGraphFromDB: loadG } = await Promise.resolve().then(() => (init_graph_from_db(), graph_from_db_exports));
|
|
14155
|
+
const { createKnowledgeGraph: createG } = await Promise.resolve().then(() => (init_knowledge_graph(), knowledge_graph_exports));
|
|
14156
|
+
const dbPath = path39.join(entry.path, ".code-intel", "graph.db");
|
|
14157
|
+
if (!fs39.existsSync(dbPath)) return graph;
|
|
14158
|
+
const db = new DbMgr(dbPath, true);
|
|
14159
|
+
await db.init();
|
|
14160
|
+
const g = createG();
|
|
14161
|
+
await loadG(g, db);
|
|
14162
|
+
db.close();
|
|
14163
|
+
return g;
|
|
14164
|
+
})() : graph;
|
|
13556
14165
|
const vdbPath = workspaceRoot ? getVectorDbPath(workspaceRoot) : void 0;
|
|
13557
14166
|
const fetchLimit = Math.min(offset + effectiveLimit, 500);
|
|
13558
|
-
const
|
|
14167
|
+
const bm25 = !a.repo || a.repo === repoName ? bm25Resolver ? bm25Resolver() : null : null;
|
|
14168
|
+
const bm25Results = bm25 ? bm25.search(query, fetchLimit * 3) : void 0;
|
|
14169
|
+
const { results: allResults, searchMode } = await hybridSearch(repoGraph, query, fetchLimit, {
|
|
14170
|
+
vectorDbPath: vdbPath,
|
|
14171
|
+
bm25Results: bm25Results ?? void 0
|
|
14172
|
+
});
|
|
13559
14173
|
const total = allResults.length;
|
|
13560
14174
|
const results = allResults.slice(offset, offset + effectiveLimit);
|
|
13561
14175
|
const hasMore = offset + effectiveLimit < total;
|
|
13562
14176
|
const suggestNextTools = [];
|
|
13563
|
-
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"]
|
|
14177
|
+
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] === "true";
|
|
13564
14178
|
if (suggestEnabled && results.length > 0) {
|
|
13565
14179
|
const topName = results[0].name;
|
|
13566
14180
|
suggestNextTools.push(
|
|
@@ -13571,15 +14185,16 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13571
14185
|
return {
|
|
13572
14186
|
content: [{
|
|
13573
14187
|
type: "text",
|
|
13574
|
-
text:
|
|
14188
|
+
text: compact({
|
|
13575
14189
|
results,
|
|
13576
14190
|
searchMode,
|
|
14191
|
+
repo: a.repo ?? repoName,
|
|
13577
14192
|
total,
|
|
13578
14193
|
offset,
|
|
13579
14194
|
limit: effectiveLimit,
|
|
13580
14195
|
hasMore,
|
|
13581
14196
|
...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
|
|
13582
|
-
}
|
|
14197
|
+
})
|
|
13583
14198
|
}]
|
|
13584
14199
|
};
|
|
13585
14200
|
}
|
|
@@ -13601,7 +14216,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13601
14216
|
file: graph.getNode(e.target)?.filePath
|
|
13602
14217
|
}));
|
|
13603
14218
|
const cluster = incoming.filter((e) => e.kind === "belongs_to").map((e) => graph.getNode(e.target)?.name)[0];
|
|
13604
|
-
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"]
|
|
14219
|
+
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] === "true";
|
|
13605
14220
|
const suggestNextTools = [];
|
|
13606
14221
|
if (suggestEnabled) {
|
|
13607
14222
|
const topCallerName = callers[0]?.name;
|
|
@@ -13613,7 +14228,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13613
14228
|
return {
|
|
13614
14229
|
content: [{
|
|
13615
14230
|
type: "text",
|
|
13616
|
-
text:
|
|
14231
|
+
text: compact({
|
|
13617
14232
|
node: {
|
|
13618
14233
|
id: node.id,
|
|
13619
14234
|
kind: node.kind,
|
|
@@ -13636,7 +14251,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13636
14251
|
cluster,
|
|
13637
14252
|
content: node.content?.slice(0, 500),
|
|
13638
14253
|
...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
|
|
13639
|
-
}
|
|
14254
|
+
})
|
|
13640
14255
|
}]
|
|
13641
14256
|
};
|
|
13642
14257
|
}
|
|
@@ -13644,7 +14259,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13644
14259
|
case "blast_radius": {
|
|
13645
14260
|
const target = a.target;
|
|
13646
14261
|
const direction = a.direction ?? "both";
|
|
13647
|
-
const maxHops = a.max_hops ??
|
|
14262
|
+
const maxHops = a.max_hops ?? 2;
|
|
13648
14263
|
const node = findNodeByName(graph, target);
|
|
13649
14264
|
if (!node) return { content: [{ type: "text", text: `Symbol "${target}" not found.` }] };
|
|
13650
14265
|
const affected = /* @__PURE__ */ new Set();
|
|
@@ -13671,7 +14286,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13671
14286
|
return n ? { id, name: n.name, kind: n.kind, filePath: n.filePath } : { id };
|
|
13672
14287
|
});
|
|
13673
14288
|
const risk = affected.size > 10 ? "HIGH" : affected.size > 5 ? "MEDIUM" : "LOW";
|
|
13674
|
-
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"]
|
|
14289
|
+
const suggestEnabled = process.env["CODE_INTEL_SUGGEST_NEXT_TOOLS"] === "true";
|
|
13675
14290
|
const suggestNextTools = [];
|
|
13676
14291
|
if (suggestEnabled) {
|
|
13677
14292
|
const highestRiskSymbol = node.name;
|
|
@@ -13684,13 +14299,13 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13684
14299
|
return {
|
|
13685
14300
|
content: [{
|
|
13686
14301
|
type: "text",
|
|
13687
|
-
text:
|
|
14302
|
+
text: compact({
|
|
13688
14303
|
target: node.name,
|
|
13689
14304
|
affectedCount: affected.size,
|
|
13690
14305
|
riskLevel: risk,
|
|
13691
14306
|
affected: affectedDetails,
|
|
13692
14307
|
...suggestEnabled ? { suggested_next_tools: suggestNextTools } : {}
|
|
13693
|
-
}
|
|
14308
|
+
})
|
|
13694
14309
|
}]
|
|
13695
14310
|
};
|
|
13696
14311
|
}
|
|
@@ -13698,7 +14313,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13698
14313
|
case "file_symbols": {
|
|
13699
14314
|
const filePath = a.file_path;
|
|
13700
14315
|
const offset = a.offset ?? 0;
|
|
13701
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
14316
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
13702
14317
|
const allMatches = [];
|
|
13703
14318
|
for (const node of graph.allNodes()) {
|
|
13704
14319
|
if (node.filePath && node.filePath.includes(filePath)) {
|
|
@@ -13715,7 +14330,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13715
14330
|
return {
|
|
13716
14331
|
content: [{
|
|
13717
14332
|
type: "text",
|
|
13718
|
-
text:
|
|
14333
|
+
text: compact({ symbols: matches, total, offset, limit: effectiveLimit, hasMore })
|
|
13719
14334
|
}]
|
|
13720
14335
|
};
|
|
13721
14336
|
}
|
|
@@ -13756,7 +14371,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13756
14371
|
return {
|
|
13757
14372
|
content: [{
|
|
13758
14373
|
type: "text",
|
|
13759
|
-
text:
|
|
14374
|
+
text: compact({ from: fromName, to: toName, hops: foundPath.length - 1, path: pathDetails })
|
|
13760
14375
|
}]
|
|
13761
14376
|
};
|
|
13762
14377
|
}
|
|
@@ -13764,7 +14379,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13764
14379
|
case "list_exports": {
|
|
13765
14380
|
const kindFilter = a.kind;
|
|
13766
14381
|
const offset = a.offset ?? 0;
|
|
13767
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
14382
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
13768
14383
|
const allExports = [];
|
|
13769
14384
|
for (const node of graph.allNodes()) {
|
|
13770
14385
|
if (!node.exported) continue;
|
|
@@ -13777,7 +14392,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13777
14392
|
return {
|
|
13778
14393
|
content: [{
|
|
13779
14394
|
type: "text",
|
|
13780
|
-
text:
|
|
14395
|
+
text: compact({ exports: exports$1, total, offset, limit: effectiveLimit, hasMore })
|
|
13781
14396
|
}]
|
|
13782
14397
|
};
|
|
13783
14398
|
}
|
|
@@ -13789,12 +14404,12 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13789
14404
|
routes.push({ name: node.name, filePath: node.filePath, startLine: node.startLine });
|
|
13790
14405
|
}
|
|
13791
14406
|
}
|
|
13792
|
-
return { content: [{ type: "text", text:
|
|
14407
|
+
return { content: [{ type: "text", text: compact(routes) }] };
|
|
13793
14408
|
}
|
|
13794
14409
|
// ── clusters ───────────────────────────────────────────────────────────
|
|
13795
14410
|
case "clusters": {
|
|
13796
14411
|
const offset = a.offset ?? 0;
|
|
13797
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
14412
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
13798
14413
|
const allClusters = [];
|
|
13799
14414
|
for (const node of graph.allNodes()) {
|
|
13800
14415
|
if (node.kind === "cluster") {
|
|
@@ -13821,14 +14436,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13821
14436
|
return {
|
|
13822
14437
|
content: [{
|
|
13823
14438
|
type: "text",
|
|
13824
|
-
text:
|
|
14439
|
+
text: compact({ clusters, total, offset, limit: effectiveLimit, hasMore })
|
|
13825
14440
|
}]
|
|
13826
14441
|
};
|
|
13827
14442
|
}
|
|
13828
14443
|
// ── flows ──────────────────────────────────────────────────────────────
|
|
13829
14444
|
case "flows": {
|
|
13830
14445
|
const offset = a.offset ?? 0;
|
|
13831
|
-
const effectiveLimit = Math.min(a.limit ??
|
|
14446
|
+
const effectiveLimit = Math.min(a.limit ?? 10, 500);
|
|
13832
14447
|
const allFlows = [];
|
|
13833
14448
|
for (const node of graph.allNodes()) {
|
|
13834
14449
|
if (node.kind === "flow") {
|
|
@@ -13848,7 +14463,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13848
14463
|
return {
|
|
13849
14464
|
content: [{
|
|
13850
14465
|
type: "text",
|
|
13851
|
-
text:
|
|
14466
|
+
text: compact({ flows, total, offset, limit: effectiveLimit, hasMore })
|
|
13852
14467
|
}]
|
|
13853
14468
|
};
|
|
13854
14469
|
}
|
|
@@ -13916,14 +14531,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13916
14531
|
return {
|
|
13917
14532
|
content: [{
|
|
13918
14533
|
type: "text",
|
|
13919
|
-
text:
|
|
14534
|
+
text: compact({
|
|
13920
14535
|
baseRef,
|
|
13921
14536
|
changedFiles: changedFiles.map((f) => f.filePath),
|
|
13922
14537
|
directlyChangedSymbols: changedSymbols,
|
|
13923
14538
|
transitivelyAffectedSymbols: affectedSymbols,
|
|
13924
14539
|
totalAffected: allAffected.size,
|
|
13925
14540
|
riskLevel: risk
|
|
13926
|
-
}
|
|
14541
|
+
})
|
|
13927
14542
|
}]
|
|
13928
14543
|
};
|
|
13929
14544
|
}
|
|
@@ -13931,14 +14546,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13931
14546
|
case "query": {
|
|
13932
14547
|
const gqlInput = a.gql;
|
|
13933
14548
|
if (!gqlInput) {
|
|
13934
|
-
return { content: [{ type: "text", text:
|
|
14549
|
+
return { content: [{ type: "text", text: compact({ error: "Missing required parameter: gql" }) }], isError: true };
|
|
13935
14550
|
}
|
|
13936
14551
|
const { parseGQL: parseGQL2, isGQLParseError: isGQLParseError2 } = await Promise.resolve().then(() => (init_gql_parser(), gql_parser_exports));
|
|
13937
14552
|
const { executeGQL: executeGQL2 } = await Promise.resolve().then(() => (init_gql_executor(), gql_executor_exports));
|
|
13938
14553
|
const ast = parseGQL2(gqlInput);
|
|
13939
14554
|
if (isGQLParseError2(ast)) {
|
|
13940
14555
|
return {
|
|
13941
|
-
content: [{ type: "text", text:
|
|
14556
|
+
content: [{ type: "text", text: compact({ error: `GQL parse error: ${ast.message}`, pos: ast.pos, expected: ast.expected, got: ast.got }) }],
|
|
13942
14557
|
isError: true
|
|
13943
14558
|
};
|
|
13944
14559
|
}
|
|
@@ -13949,7 +14564,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13949
14564
|
return {
|
|
13950
14565
|
content: [{
|
|
13951
14566
|
type: "text",
|
|
13952
|
-
text:
|
|
14567
|
+
text: compact({
|
|
13953
14568
|
nodes: result.nodes,
|
|
13954
14569
|
edges: result.edges,
|
|
13955
14570
|
groups: result.groups,
|
|
@@ -13957,7 +14572,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13957
14572
|
executionTimeMs: result.executionTimeMs,
|
|
13958
14573
|
truncated: result.truncated,
|
|
13959
14574
|
totalCount: result.totalCount
|
|
13960
|
-
}
|
|
14575
|
+
})
|
|
13961
14576
|
}]
|
|
13962
14577
|
};
|
|
13963
14578
|
}
|
|
@@ -13971,7 +14586,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13971
14586
|
for (const node of graph.allNodes()) {
|
|
13972
14587
|
if (node.name === nameMatch[1]) results.push(node);
|
|
13973
14588
|
}
|
|
13974
|
-
return { content: [{ type: "text", text:
|
|
14589
|
+
return { content: [{ type: "text", text: compact({ deprecation: deprecationWarning, results }) }] };
|
|
13975
14590
|
}
|
|
13976
14591
|
const kindMatch = q?.match(/:\s*(\w+)/);
|
|
13977
14592
|
if (kindMatch) {
|
|
@@ -13980,9 +14595,9 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13980
14595
|
if (node.kind === kindMatch[1]) results.push(node);
|
|
13981
14596
|
if (results.length >= 50) break;
|
|
13982
14597
|
}
|
|
13983
|
-
return { content: [{ type: "text", text:
|
|
14598
|
+
return { content: [{ type: "text", text: compact({ deprecation: deprecationWarning, results }) }] };
|
|
13984
14599
|
}
|
|
13985
|
-
return { content: [{ type: "text", text:
|
|
14600
|
+
return { content: [{ type: "text", text: compact({ deprecation: deprecationWarning, error: "Query not recognized. Use name='X' or :kind syntax. Or use the query tool with GQL instead." }) }] };
|
|
13986
14601
|
}
|
|
13987
14602
|
// ── group_list ─────────────────────────────────────────────────────────
|
|
13988
14603
|
case "group_list": {
|
|
@@ -13990,16 +14605,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
13990
14605
|
if (groupName) {
|
|
13991
14606
|
const group = loadGroup(groupName);
|
|
13992
14607
|
if (!group) return { content: [{ type: "text", text: `Group "${groupName}" not found.` }] };
|
|
13993
|
-
return { content: [{ type: "text", text:
|
|
14608
|
+
return { content: [{ type: "text", text: compact(group) }] };
|
|
13994
14609
|
}
|
|
13995
14610
|
const groups = listGroups();
|
|
13996
14611
|
return {
|
|
13997
14612
|
content: [{
|
|
13998
14613
|
type: "text",
|
|
13999
|
-
text:
|
|
14000
|
-
groups.map((g) => ({ name: g.name, createdAt: g.createdAt, lastSync: g.lastSync, memberCount: g.members.length, members: g.members }))
|
|
14001
|
-
null,
|
|
14002
|
-
2
|
|
14614
|
+
text: compact(
|
|
14615
|
+
groups.map((g) => ({ name: g.name, createdAt: g.createdAt, lastSync: g.lastSync, memberCount: g.members.length, members: g.members }))
|
|
14003
14616
|
)
|
|
14004
14617
|
}]
|
|
14005
14618
|
};
|
|
@@ -14017,14 +14630,14 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
14017
14630
|
return {
|
|
14018
14631
|
content: [{
|
|
14019
14632
|
type: "text",
|
|
14020
|
-
text:
|
|
14633
|
+
text: compact({
|
|
14021
14634
|
groupName: result.groupName,
|
|
14022
14635
|
syncedAt: result.syncedAt,
|
|
14023
14636
|
memberCount: result.memberCount,
|
|
14024
14637
|
contractCount: result.contracts.length,
|
|
14025
14638
|
linkCount: result.links.length,
|
|
14026
14639
|
topLinks: result.links.slice(0, 20)
|
|
14027
|
-
}
|
|
14640
|
+
})
|
|
14028
14641
|
}]
|
|
14029
14642
|
};
|
|
14030
14643
|
}
|
|
@@ -14044,7 +14657,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
14044
14657
|
return {
|
|
14045
14658
|
content: [{
|
|
14046
14659
|
type: "text",
|
|
14047
|
-
text:
|
|
14660
|
+
text: compact({ syncedAt: result.syncedAt, contracts, links })
|
|
14048
14661
|
}]
|
|
14049
14662
|
};
|
|
14050
14663
|
}
|
|
@@ -14059,7 +14672,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
14059
14672
|
return {
|
|
14060
14673
|
content: [{
|
|
14061
14674
|
type: "text",
|
|
14062
|
-
text:
|
|
14675
|
+
text: compact({ query, merged, perRepo })
|
|
14063
14676
|
}]
|
|
14064
14677
|
};
|
|
14065
14678
|
}
|
|
@@ -14091,12 +14704,12 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
14091
14704
|
return {
|
|
14092
14705
|
content: [{
|
|
14093
14706
|
type: "text",
|
|
14094
|
-
text:
|
|
14707
|
+
text: compact({
|
|
14095
14708
|
group: groupName,
|
|
14096
14709
|
lastSync: group.lastSync ?? null,
|
|
14097
14710
|
syncAgeMinutes: syncAge,
|
|
14098
14711
|
members: memberStatus
|
|
14099
|
-
}
|
|
14712
|
+
})
|
|
14100
14713
|
}]
|
|
14101
14714
|
};
|
|
14102
14715
|
}
|
|
@@ -14105,11 +14718,11 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
14105
14718
|
const fromName = a.from;
|
|
14106
14719
|
const toName = a.to;
|
|
14107
14720
|
const result = explainRelationship(graph, fromName, toName);
|
|
14108
|
-
return { content: [{ type: "text", text:
|
|
14721
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
14109
14722
|
}
|
|
14110
14723
|
// ── pr_impact ──────────────────────────────────────────────────────────
|
|
14111
14724
|
case "pr_impact": {
|
|
14112
|
-
const maxHops = a.maxHops ??
|
|
14725
|
+
const maxHops = a.maxHops ?? 2;
|
|
14113
14726
|
let changedFiles = a.changedFiles ?? [];
|
|
14114
14727
|
if (a.diff && typeof a.diff === "string") {
|
|
14115
14728
|
const diffFiles = parseDiffFiles(a.diff);
|
|
@@ -14119,37 +14732,37 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
14119
14732
|
return {
|
|
14120
14733
|
content: [{
|
|
14121
14734
|
type: "text",
|
|
14122
|
-
text:
|
|
14735
|
+
text: compact({ error: 'No changed files provided. Supply "changedFiles" or "diff".' })
|
|
14123
14736
|
}]
|
|
14124
14737
|
};
|
|
14125
14738
|
}
|
|
14126
14739
|
const result = computePRImpact(graph, changedFiles, maxHops);
|
|
14127
|
-
return { content: [{ type: "text", text:
|
|
14740
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
14128
14741
|
}
|
|
14129
14742
|
// ── similar_symbols ────────────────────────────────────────────────────
|
|
14130
14743
|
case "similar_symbols": {
|
|
14131
14744
|
const symbolName = a.symbol;
|
|
14132
14745
|
const limit = a.limit ?? 10;
|
|
14133
14746
|
const result = findSimilarSymbols(graph, symbolName, limit);
|
|
14134
|
-
return { content: [{ type: "text", text:
|
|
14747
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
14135
14748
|
}
|
|
14136
14749
|
// ── health_report ──────────────────────────────────────────────────────
|
|
14137
14750
|
case "health_report": {
|
|
14138
14751
|
const scope = a.scope ?? ".";
|
|
14139
14752
|
const result = computeHealthReport(graph, scope);
|
|
14140
|
-
return { content: [{ type: "text", text:
|
|
14753
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
14141
14754
|
}
|
|
14142
14755
|
// ── suggest_tests ──────────────────────────────────────────────────────
|
|
14143
14756
|
case "suggest_tests": {
|
|
14144
14757
|
const sym = a.symbol;
|
|
14145
14758
|
const result = suggestTests(graph, sym);
|
|
14146
|
-
return { content: [{ type: "text", text:
|
|
14759
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
14147
14760
|
}
|
|
14148
14761
|
// ── cluster_summary ────────────────────────────────────────────────────
|
|
14149
14762
|
case "cluster_summary": {
|
|
14150
14763
|
const cluster = a.cluster;
|
|
14151
14764
|
const result = summarizeCluster(graph, cluster);
|
|
14152
|
-
return { content: [{ type: "text", text:
|
|
14765
|
+
return { content: [{ type: "text", text: compact(result) }] };
|
|
14153
14766
|
}
|
|
14154
14767
|
case "deprecated_usage": {
|
|
14155
14768
|
const scope = a.scope;
|
|
@@ -14157,7 +14770,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
14157
14770
|
const detector = new DeprecatedDetector2();
|
|
14158
14771
|
detector.tagDeprecated(graph);
|
|
14159
14772
|
const findings = detector.detect(graph, scope);
|
|
14160
|
-
return { content: [{ type: "text", text:
|
|
14773
|
+
return { content: [{ type: "text", text: compact({ findings, total: findings.length }) }] };
|
|
14161
14774
|
}
|
|
14162
14775
|
// ── complexity_hotspots ────────────────────────────────────────────────
|
|
14163
14776
|
case "complexity_hotspots": {
|
|
@@ -14165,7 +14778,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
14165
14778
|
const scope = a.scope;
|
|
14166
14779
|
const limit = typeof a.limit === "number" ? a.limit : 20;
|
|
14167
14780
|
const hotspots = computeComplexity2(graph, scope).slice(0, limit);
|
|
14168
|
-
return { content: [{ type: "text", text:
|
|
14781
|
+
return { content: [{ type: "text", text: compact({ hotspots, total: hotspots.length }) }] };
|
|
14169
14782
|
}
|
|
14170
14783
|
// ── coverage_gaps ──────────────────────────────────────────────────────
|
|
14171
14784
|
case "coverage_gaps": {
|
|
@@ -14177,12 +14790,12 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
14177
14790
|
return {
|
|
14178
14791
|
content: [{
|
|
14179
14792
|
type: "text",
|
|
14180
|
-
text:
|
|
14793
|
+
text: compact({
|
|
14181
14794
|
untestedByRisk,
|
|
14182
14795
|
coveragePct: summary.coveragePct,
|
|
14183
14796
|
totalExported: summary.totalExported,
|
|
14184
14797
|
testedExported: summary.testedExported
|
|
14185
|
-
}
|
|
14798
|
+
})
|
|
14186
14799
|
}]
|
|
14187
14800
|
};
|
|
14188
14801
|
}
|
|
@@ -14193,7 +14806,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
14193
14806
|
const scope = a.scope;
|
|
14194
14807
|
const includeTestFiles = a.includeTestFiles ?? false;
|
|
14195
14808
|
const findings = scanner.scan(graph, { scope, includeTestFiles });
|
|
14196
|
-
return { content: [{ type: "text", text:
|
|
14809
|
+
return { content: [{ type: "text", text: compact({ findings, total: findings.length }) }] };
|
|
14197
14810
|
}
|
|
14198
14811
|
// ── vulnerability_scan ─────────────────────────────────────────────────
|
|
14199
14812
|
case "vulnerability_scan": {
|
|
@@ -14206,7 +14819,7 @@ async function dispatchTool(name, a, graph, repoName, workspaceRoot) {
|
|
|
14206
14819
|
const minRank = sevRank[minSev] ?? 1;
|
|
14207
14820
|
let findings = detector.detect(graph, { scope, types });
|
|
14208
14821
|
findings = findings.filter((f) => (sevRank[f.severity] ?? 1) >= minRank);
|
|
14209
|
-
return { content: [{ type: "text", text:
|
|
14822
|
+
return { content: [{ type: "text", text: compact({ findings, total: findings.length }) }] };
|
|
14210
14823
|
}
|
|
14211
14824
|
default:
|
|
14212
14825
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
@@ -14227,21 +14840,21 @@ function registerResources(server, graph, repoName) {
|
|
|
14227
14840
|
for (const node of graph.allNodes()) {
|
|
14228
14841
|
kindCounts[node.kind] = (kindCounts[node.kind] ?? 0) + 1;
|
|
14229
14842
|
}
|
|
14230
|
-
return { contents: [{ uri, mimeType: "application/json", text:
|
|
14843
|
+
return { contents: [{ uri, mimeType: "application/json", text: compact({ repo: repoName, stats: graph.size, nodeCounts: kindCounts }) }] };
|
|
14231
14844
|
}
|
|
14232
14845
|
if (uri.endsWith("/clusters")) {
|
|
14233
14846
|
const clusters = [];
|
|
14234
14847
|
for (const node of graph.allNodes()) {
|
|
14235
14848
|
if (node.kind === "cluster") clusters.push({ id: node.id, name: node.name, memberCount: node.metadata?.memberCount });
|
|
14236
14849
|
}
|
|
14237
|
-
return { contents: [{ uri, mimeType: "application/json", text:
|
|
14850
|
+
return { contents: [{ uri, mimeType: "application/json", text: compact(clusters) }] };
|
|
14238
14851
|
}
|
|
14239
14852
|
if (uri.endsWith("/flows")) {
|
|
14240
14853
|
const flows = [];
|
|
14241
14854
|
for (const node of graph.allNodes()) {
|
|
14242
14855
|
if (node.kind === "flow") flows.push({ id: node.id, name: node.name, steps: node.metadata?.steps, entryPoint: node.metadata?.entryPoint });
|
|
14243
14856
|
}
|
|
14244
|
-
return { contents: [{ uri, mimeType: "application/json", text:
|
|
14857
|
+
return { contents: [{ uri, mimeType: "application/json", text: compact(flows) }] };
|
|
14245
14858
|
}
|
|
14246
14859
|
throw new Error(`Unknown resource: ${uri}`);
|
|
14247
14860
|
});
|
|
@@ -14297,8 +14910,8 @@ async function writeSkillFiles(graph, workspaceRoot, projectName) {
|
|
|
14297
14910
|
const outputDir = path39.join(workspaceRoot, ".claude", "skills", "code-intel");
|
|
14298
14911
|
const areas = buildAreaMap(graph, workspaceRoot);
|
|
14299
14912
|
if (areas.length === 0) return { skills: [], outputDir };
|
|
14300
|
-
|
|
14301
|
-
|
|
14913
|
+
fs39.rmSync(outputDir, { recursive: true, force: true });
|
|
14914
|
+
fs39.mkdirSync(outputDir, { recursive: true });
|
|
14302
14915
|
const skills = [];
|
|
14303
14916
|
const usedNames = /* @__PURE__ */ new Set();
|
|
14304
14917
|
for (const area of areas) {
|
|
@@ -14306,8 +14919,8 @@ async function writeSkillFiles(graph, workspaceRoot, projectName) {
|
|
|
14306
14919
|
usedNames.add(kebab);
|
|
14307
14920
|
const content = renderSkill(area, projectName, kebab);
|
|
14308
14921
|
const dir = path39.join(outputDir, kebab);
|
|
14309
|
-
|
|
14310
|
-
|
|
14922
|
+
fs39.mkdirSync(dir, { recursive: true });
|
|
14923
|
+
fs39.writeFileSync(path39.join(dir, "SKILL.md"), content, "utf-8");
|
|
14311
14924
|
skills.push({ name: kebab, label: area.label, symbolCount: area.nodes.length, fileCount: area.files.size });
|
|
14312
14925
|
}
|
|
14313
14926
|
return { skills, outputDir };
|
|
@@ -14490,14 +15103,22 @@ function writeContextFiles(workspaceRoot, projectName, stats, skills) {
|
|
|
14490
15103
|
upsertFile(path39.join(workspaceRoot, "AGENTS.md"), block, "AGENTS.md");
|
|
14491
15104
|
upsertFile(path39.join(workspaceRoot, "CLAUDE.md"), block, "CLAUDE.md");
|
|
14492
15105
|
const githubDir = path39.join(workspaceRoot, ".github");
|
|
14493
|
-
if (!
|
|
15106
|
+
if (!fs39.existsSync(githubDir)) fs39.mkdirSync(githubDir, { recursive: true });
|
|
14494
15107
|
upsertFile(path39.join(githubDir, "copilot-instructions.md"), block, "copilot-instructions.md");
|
|
14495
15108
|
const cursorDir = path39.join(workspaceRoot, ".cursor", "rules");
|
|
14496
|
-
if (!
|
|
15109
|
+
if (!fs39.existsSync(cursorDir)) fs39.mkdirSync(cursorDir, { recursive: true });
|
|
14497
15110
|
upsertFile(path39.join(cursorDir, "code-intel.mdc"), block, "code-intel.mdc");
|
|
14498
15111
|
const kiroDir = path39.join(workspaceRoot, ".kiro", "steering");
|
|
14499
|
-
if (!
|
|
15112
|
+
if (!fs39.existsSync(kiroDir)) fs39.mkdirSync(kiroDir, { recursive: true });
|
|
14500
15113
|
upsertFile(path39.join(kiroDir, "code-intel.md"), block, "code-intel.md");
|
|
15114
|
+
upsertFile(path39.join(workspaceRoot, ".clinerules"), block, ".clinerules");
|
|
15115
|
+
upsertFile(path39.join(workspaceRoot, ".windsurfrules"), block, ".windsurfrules");
|
|
15116
|
+
const kilocodeDir = path39.join(workspaceRoot, ".kilocode", "rules");
|
|
15117
|
+
if (!fs39.existsSync(kilocodeDir)) fs39.mkdirSync(kilocodeDir, { recursive: true });
|
|
15118
|
+
upsertFile(path39.join(kilocodeDir, "code-intel-rules.md"), block, "code-intel-rules.md");
|
|
15119
|
+
const agentsDir = path39.join(workspaceRoot, ".agents", "rules");
|
|
15120
|
+
if (!fs39.existsSync(agentsDir)) fs39.mkdirSync(agentsDir, { recursive: true });
|
|
15121
|
+
upsertFile(path39.join(agentsDir, "code-intel-rules.md"), block, "code-intel-rules.md");
|
|
14501
15122
|
}
|
|
14502
15123
|
function buildBlock(projectName, stats, skills) {
|
|
14503
15124
|
const skillTableRows = skills.map(
|
|
@@ -14523,6 +15144,14 @@ ${skills.map((s) => `- Working in **${s.label}**? \u2192 Load \`.claude/skills/c
|
|
|
14523
15144
|
> \u26A0 This section is auto-managed by \`code-intel analyze\`. Do **not** edit between the markers \u2014 your changes will be overwritten.
|
|
14524
15145
|
> Add your own notes below the \`${BLOCK_END}\` marker.
|
|
14525
15146
|
|
|
15147
|
+
<!-- TOOL POLICY: ENFORCED -->
|
|
15148
|
+
FORBIDDEN: grep, rg, find, cat, sed, ls (for symbol/code discovery)
|
|
15149
|
+
REQUIRED BEFORE ANY CODE ACTION:
|
|
15150
|
+
1. code-intel search "<concept>" \u2190 replaces grep/rg/find
|
|
15151
|
+
2. code-intel inspect <symbol> \u2190 replaces cat + manual file reading
|
|
15152
|
+
3. code-intel impact <symbol> \u2190 replaces manual caller tracing
|
|
15153
|
+
<!-- /TOOL POLICY -->
|
|
15154
|
+
|
|
14526
15155
|
Indexed: **${stats.nodes.toLocaleString()} nodes** | **${stats.edges.toLocaleString()} edges** | **${stats.files} files** | analyzed in ${(stats.duration / 1e3).toFixed(1)}s
|
|
14527
15156
|
|
|
14528
15157
|
> Index stale? Re-run: \`code-intel analyze\`
|
|
@@ -14558,7 +15187,9 @@ These rules apply to **every coding agent or AI assistant** working in this repo
|
|
|
14558
15187
|
## Never Do
|
|
14559
15188
|
|
|
14560
15189
|
- NEVER ignore impact warnings \u2014 always report blast radius to the user.
|
|
14561
|
-
-
|
|
15190
|
+
- **STOP** \u2014 do not call grep, rg, find, cat, sed, or read a file cold.
|
|
15191
|
+
Always run \`code-intel search "<concept>"\` first.
|
|
15192
|
+
Violating this wastes ~3,000 tokens per lookup and degrades session quality.
|
|
14562
15193
|
- NEVER make changes to a symbol with \u2265 5 callers without running \`code-intel impact\` first.
|
|
14563
15194
|
- NEVER use find-and-replace for symbol renames.
|
|
14564
15195
|
|
|
@@ -14634,7 +15265,7 @@ ${skillTable}
|
|
|
14634
15265
|
${BLOCK_END}`;
|
|
14635
15266
|
}
|
|
14636
15267
|
function upsertFile(filePath, block, fileName) {
|
|
14637
|
-
if (!
|
|
15268
|
+
if (!fs39.existsSync(filePath)) {
|
|
14638
15269
|
const newContent = [
|
|
14639
15270
|
`# ${fileName}`,
|
|
14640
15271
|
"",
|
|
@@ -14645,17 +15276,17 @@ function upsertFile(filePath, block, fileName) {
|
|
|
14645
15276
|
"<!-- Add your own custom notes below this line. They will never be overwritten by code-intel. -->",
|
|
14646
15277
|
""
|
|
14647
15278
|
].join("\n");
|
|
14648
|
-
|
|
15279
|
+
fs39.writeFileSync(filePath, newContent, "utf-8");
|
|
14649
15280
|
return;
|
|
14650
15281
|
}
|
|
14651
|
-
const existing =
|
|
15282
|
+
const existing = fs39.readFileSync(filePath, "utf-8");
|
|
14652
15283
|
const startIdx = findLineMarker(existing, BLOCK_START);
|
|
14653
15284
|
const endIdx = findLineMarker(existing, BLOCK_END, startIdx === -1 ? 0 : startIdx);
|
|
14654
15285
|
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
14655
15286
|
const before = existing.slice(0, startIdx);
|
|
14656
15287
|
const after = existing.slice(endIdx + BLOCK_END.length);
|
|
14657
15288
|
const updated = (before + block + after).trimEnd() + "\n";
|
|
14658
|
-
|
|
15289
|
+
fs39.writeFileSync(filePath, updated, "utf-8");
|
|
14659
15290
|
return;
|
|
14660
15291
|
}
|
|
14661
15292
|
const appended = [
|
|
@@ -14668,7 +15299,7 @@ function upsertFile(filePath, block, fileName) {
|
|
|
14668
15299
|
block,
|
|
14669
15300
|
""
|
|
14670
15301
|
].join("\n");
|
|
14671
|
-
|
|
15302
|
+
fs39.writeFileSync(filePath, appended, "utf-8");
|
|
14672
15303
|
}
|
|
14673
15304
|
function findLineMarker(content, marker, startFrom = 0) {
|
|
14674
15305
|
let idx = content.indexOf(marker, startFrom);
|
|
@@ -14717,7 +15348,7 @@ function filterChangedByMtime(allFilePaths, workspaceRoot, storedMtimes) {
|
|
|
14717
15348
|
continue;
|
|
14718
15349
|
}
|
|
14719
15350
|
try {
|
|
14720
|
-
const { mtimeMs } =
|
|
15351
|
+
const { mtimeMs } = fs39.statSync(absPath);
|
|
14721
15352
|
if (mtimeMs > stored) changed.push(absPath);
|
|
14722
15353
|
} catch {
|
|
14723
15354
|
changed.push(absPath);
|
|
@@ -14729,7 +15360,7 @@ function buildMtimeSnapshot(filePaths, workspaceRoot) {
|
|
|
14729
15360
|
const snap = {};
|
|
14730
15361
|
for (const absPath of filePaths) {
|
|
14731
15362
|
try {
|
|
14732
|
-
const { mtimeMs } =
|
|
15363
|
+
const { mtimeMs } = fs39.statSync(absPath);
|
|
14733
15364
|
snap[path39.relative(workspaceRoot, absPath)] = mtimeMs;
|
|
14734
15365
|
} catch {
|
|
14735
15366
|
}
|
|
@@ -14770,10 +15401,10 @@ function expandGlob(root, pattern) {
|
|
|
14770
15401
|
const parts = pattern.replace(/\/\*\*?$/, "").split("/").filter(Boolean);
|
|
14771
15402
|
if (parts.length === 0) return [];
|
|
14772
15403
|
const dir = path39.join(root, ...parts);
|
|
14773
|
-
if (!
|
|
14774
|
-
return
|
|
15404
|
+
if (!fs39.existsSync(dir)) return [];
|
|
15405
|
+
return fs39.readdirSync(dir).map((entry) => path39.join(dir, entry)).filter((p) => {
|
|
14775
15406
|
try {
|
|
14776
|
-
return
|
|
15407
|
+
return fs39.statSync(p).isDirectory();
|
|
14777
15408
|
} catch {
|
|
14778
15409
|
return false;
|
|
14779
15410
|
}
|
|
@@ -14785,9 +15416,9 @@ function resolvePackages(root, patterns) {
|
|
|
14785
15416
|
const dirs = expandGlob(root, pattern);
|
|
14786
15417
|
for (const dir of dirs) {
|
|
14787
15418
|
const pkgJsonPath = path39.join(dir, "package.json");
|
|
14788
|
-
if (!
|
|
15419
|
+
if (!fs39.existsSync(pkgJsonPath)) continue;
|
|
14789
15420
|
try {
|
|
14790
|
-
const pkgJson = JSON.parse(
|
|
15421
|
+
const pkgJson = JSON.parse(fs39.readFileSync(pkgJsonPath, "utf-8"));
|
|
14791
15422
|
const name = pkgJson.name ?? path39.basename(dir);
|
|
14792
15423
|
packages.push({ name, path: dir });
|
|
14793
15424
|
} catch {
|
|
@@ -14798,12 +15429,12 @@ function resolvePackages(root, patterns) {
|
|
|
14798
15429
|
}
|
|
14799
15430
|
async function detectWorkspace(root) {
|
|
14800
15431
|
const turboJsonPath = path39.join(root, "turbo.json");
|
|
14801
|
-
if (
|
|
15432
|
+
if (fs39.existsSync(turboJsonPath)) {
|
|
14802
15433
|
let patterns = [];
|
|
14803
15434
|
const pkgJsonPath = path39.join(root, "package.json");
|
|
14804
|
-
if (
|
|
15435
|
+
if (fs39.existsSync(pkgJsonPath)) {
|
|
14805
15436
|
try {
|
|
14806
|
-
const pkgJson = JSON.parse(
|
|
15437
|
+
const pkgJson = JSON.parse(fs39.readFileSync(pkgJsonPath, "utf-8"));
|
|
14807
15438
|
if (pkgJson.workspaces) {
|
|
14808
15439
|
patterns = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces.packages;
|
|
14809
15440
|
}
|
|
@@ -14812,15 +15443,15 @@ async function detectWorkspace(root) {
|
|
|
14812
15443
|
}
|
|
14813
15444
|
if (patterns.length === 0) {
|
|
14814
15445
|
const fallbackDir = path39.join(root, "packages");
|
|
14815
|
-
if (
|
|
15446
|
+
if (fs39.existsSync(fallbackDir)) patterns = ["packages/*"];
|
|
14816
15447
|
}
|
|
14817
15448
|
const packages = resolvePackages(root, patterns);
|
|
14818
15449
|
return { type: "turborepo", root, packages };
|
|
14819
15450
|
}
|
|
14820
15451
|
const npmPkgJsonPath = path39.join(root, "package.json");
|
|
14821
|
-
if (
|
|
15452
|
+
if (fs39.existsSync(npmPkgJsonPath)) {
|
|
14822
15453
|
try {
|
|
14823
|
-
const pkgJson = JSON.parse(
|
|
15454
|
+
const pkgJson = JSON.parse(fs39.readFileSync(npmPkgJsonPath, "utf-8"));
|
|
14824
15455
|
if (pkgJson.workspaces) {
|
|
14825
15456
|
const patterns = Array.isArray(pkgJson.workspaces) ? pkgJson.workspaces : pkgJson.workspaces.packages;
|
|
14826
15457
|
const packages = resolvePackages(root, patterns);
|
|
@@ -14830,10 +15461,10 @@ async function detectWorkspace(root) {
|
|
|
14830
15461
|
}
|
|
14831
15462
|
}
|
|
14832
15463
|
const pnpmYamlPath = path39.join(root, "pnpm-workspace.yaml");
|
|
14833
|
-
if (
|
|
15464
|
+
if (fs39.existsSync(pnpmYamlPath)) {
|
|
14834
15465
|
const patterns = [];
|
|
14835
15466
|
try {
|
|
14836
|
-
const content =
|
|
15467
|
+
const content = fs39.readFileSync(pnpmYamlPath, "utf-8");
|
|
14837
15468
|
let inPackages = false;
|
|
14838
15469
|
for (const line of content.split("\n")) {
|
|
14839
15470
|
if (/^packages\s*:/.test(line)) {
|
|
@@ -14854,13 +15485,13 @@ async function detectWorkspace(root) {
|
|
|
14854
15485
|
return { type: "pnpm", root, packages };
|
|
14855
15486
|
}
|
|
14856
15487
|
const nxJsonPath = path39.join(root, "nx.json");
|
|
14857
|
-
if (
|
|
15488
|
+
if (fs39.existsSync(nxJsonPath)) {
|
|
14858
15489
|
const packages = [];
|
|
14859
15490
|
const scanForProjects = (dir, depth) => {
|
|
14860
15491
|
if (depth > 2) return;
|
|
14861
15492
|
let entries;
|
|
14862
15493
|
try {
|
|
14863
|
-
entries =
|
|
15494
|
+
entries = fs39.readdirSync(dir);
|
|
14864
15495
|
} catch {
|
|
14865
15496
|
return;
|
|
14866
15497
|
}
|
|
@@ -14868,14 +15499,14 @@ async function detectWorkspace(root) {
|
|
|
14868
15499
|
if (entry === "node_modules" || entry.startsWith(".")) continue;
|
|
14869
15500
|
const fullPath = path39.join(dir, entry);
|
|
14870
15501
|
try {
|
|
14871
|
-
if (!
|
|
15502
|
+
if (!fs39.statSync(fullPath).isDirectory()) continue;
|
|
14872
15503
|
} catch {
|
|
14873
15504
|
continue;
|
|
14874
15505
|
}
|
|
14875
15506
|
const projectJsonPath = path39.join(fullPath, "project.json");
|
|
14876
|
-
if (
|
|
15507
|
+
if (fs39.existsSync(projectJsonPath)) {
|
|
14877
15508
|
try {
|
|
14878
|
-
const proj = JSON.parse(
|
|
15509
|
+
const proj = JSON.parse(fs39.readFileSync(projectJsonPath, "utf-8"));
|
|
14879
15510
|
const name = proj.name ?? path39.basename(fullPath);
|
|
14880
15511
|
packages.push({ name, path: fullPath });
|
|
14881
15512
|
} catch {
|
|
@@ -14984,9 +15615,9 @@ var MigrationRunner = class {
|
|
|
14984
15615
|
autoBackupBeforeMigration() {
|
|
14985
15616
|
try {
|
|
14986
15617
|
const dbFile = this.db.name;
|
|
14987
|
-
if (!dbFile || !
|
|
14988
|
-
const backupDir = path39.join(
|
|
14989
|
-
|
|
15618
|
+
if (!dbFile || !fs39.existsSync(dbFile)) return;
|
|
15619
|
+
const backupDir = path39.join(os18.homedir(), ".code-intel", "backups", "pre-migration");
|
|
15620
|
+
fs39.mkdirSync(backupDir, { recursive: true });
|
|
14990
15621
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
14991
15622
|
const baseName = path39.basename(dbFile, ".db");
|
|
14992
15623
|
const backupPath = path39.join(backupDir, `${baseName}-pre-migration-${ts}.db`);
|
|
@@ -14994,7 +15625,7 @@ var MigrationRunner = class {
|
|
|
14994
15625
|
this.db.pragma("wal_checkpoint(TRUNCATE)");
|
|
14995
15626
|
} catch {
|
|
14996
15627
|
}
|
|
14997
|
-
|
|
15628
|
+
fs39.copyFileSync(dbFile, backupPath);
|
|
14998
15629
|
} catch {
|
|
14999
15630
|
}
|
|
15000
15631
|
}
|
|
@@ -15141,10 +15772,10 @@ init_tracing();
|
|
|
15141
15772
|
init_init_wizard();
|
|
15142
15773
|
init_config_manager();
|
|
15143
15774
|
init_codes();
|
|
15144
|
-
var GLOBAL_DIR3 = path39.join(
|
|
15775
|
+
var GLOBAL_DIR3 = path39.join(os18.homedir(), ".code-intel");
|
|
15145
15776
|
function loadRepoPaths() {
|
|
15146
15777
|
try {
|
|
15147
|
-
const data =
|
|
15778
|
+
const data = fs39.readFileSync(path39.join(GLOBAL_DIR3, "repos.json"), "utf-8");
|
|
15148
15779
|
const repos = JSON.parse(data);
|
|
15149
15780
|
return repos.map((r) => r.path);
|
|
15150
15781
|
} catch {
|
|
@@ -15154,7 +15785,7 @@ function loadRepoPaths() {
|
|
|
15154
15785
|
function loadGroupNames() {
|
|
15155
15786
|
const groupsDir = path39.join(GLOBAL_DIR3, "groups");
|
|
15156
15787
|
try {
|
|
15157
|
-
return
|
|
15788
|
+
return fs39.readdirSync(groupsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
|
|
15158
15789
|
} catch {
|
|
15159
15790
|
return [];
|
|
15160
15791
|
}
|
|
@@ -15415,10 +16046,10 @@ function autoInstallCompletion() {
|
|
|
15415
16046
|
}
|
|
15416
16047
|
console.log(` Detected shell: ${shell}`);
|
|
15417
16048
|
if (shell === "fish") {
|
|
15418
|
-
const dir = path39.join(
|
|
16049
|
+
const dir = path39.join(os18.homedir(), ".config", "fish", "completions");
|
|
15419
16050
|
const dest = path39.join(dir, "code-intel.fish");
|
|
15420
|
-
|
|
15421
|
-
|
|
16051
|
+
fs39.mkdirSync(dir, { recursive: true });
|
|
16052
|
+
fs39.writeFileSync(dest, fishCompletion(), "utf-8");
|
|
15422
16053
|
console.log(` \u2705 Fish completion installed \u2192 ${dest}
|
|
15423
16054
|
`);
|
|
15424
16055
|
return;
|
|
@@ -15428,15 +16059,15 @@ source <(code-intel completion zsh)
|
|
|
15428
16059
|
` : `
|
|
15429
16060
|
source <(code-intel completion bash)
|
|
15430
16061
|
`;
|
|
15431
|
-
const rcFile = shell === "zsh" ? path39.join(
|
|
16062
|
+
const rcFile = shell === "zsh" ? path39.join(os18.homedir(), ".zshrc") : path39.join(os18.homedir(), ".bashrc");
|
|
15432
16063
|
try {
|
|
15433
|
-
const existing =
|
|
16064
|
+
const existing = fs39.existsSync(rcFile) ? fs39.readFileSync(rcFile, "utf-8") : "";
|
|
15434
16065
|
if (existing.includes("code-intel completion")) {
|
|
15435
16066
|
console.log(` \u2139 Completion already configured in ${rcFile}
|
|
15436
16067
|
`);
|
|
15437
16068
|
return;
|
|
15438
16069
|
}
|
|
15439
|
-
|
|
16070
|
+
fs39.appendFileSync(rcFile, script, "utf-8");
|
|
15440
16071
|
console.log(` \u2705 ${shell} completion added to ${rcFile}`);
|
|
15441
16072
|
console.log(` Restart your shell or run: source ${rcFile}
|
|
15442
16073
|
`);
|
|
@@ -15455,20 +16086,20 @@ function generateCompletion(shell) {
|
|
|
15455
16086
|
return fishCompletion();
|
|
15456
16087
|
}
|
|
15457
16088
|
}
|
|
15458
|
-
var GLOBAL_DIR4 = path39.join(
|
|
16089
|
+
var GLOBAL_DIR4 = path39.join(os18.homedir(), ".code-intel");
|
|
15459
16090
|
var META_PATH = path39.join(GLOBAL_DIR4, "update-meta.json");
|
|
15460
16091
|
var PACKAGE_NAME = "code-intel";
|
|
15461
16092
|
var NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
15462
16093
|
function loadMeta() {
|
|
15463
16094
|
try {
|
|
15464
|
-
return JSON.parse(
|
|
16095
|
+
return JSON.parse(fs39.readFileSync(META_PATH, "utf-8"));
|
|
15465
16096
|
} catch {
|
|
15466
16097
|
return null;
|
|
15467
16098
|
}
|
|
15468
16099
|
}
|
|
15469
16100
|
function saveMeta(meta) {
|
|
15470
|
-
|
|
15471
|
-
|
|
16101
|
+
fs39.mkdirSync(GLOBAL_DIR4, { recursive: true });
|
|
16102
|
+
fs39.writeFileSync(META_PATH, JSON.stringify(meta, null, 2) + "\n", "utf-8");
|
|
15472
16103
|
}
|
|
15473
16104
|
function isNewer(current, candidate) {
|
|
15474
16105
|
const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
|
|
@@ -15581,14 +16212,170 @@ async function runUpdate(opts = {}) {
|
|
|
15581
16212
|
process.exit(1);
|
|
15582
16213
|
}
|
|
15583
16214
|
}
|
|
16215
|
+
var SOURCE_EXT = /* @__PURE__ */ new Set([
|
|
16216
|
+
"ts",
|
|
16217
|
+
"tsx",
|
|
16218
|
+
"js",
|
|
16219
|
+
"jsx",
|
|
16220
|
+
"mjs",
|
|
16221
|
+
"cjs",
|
|
16222
|
+
"py",
|
|
16223
|
+
"pyi",
|
|
16224
|
+
"rs",
|
|
16225
|
+
"go",
|
|
16226
|
+
"java",
|
|
16227
|
+
"kt",
|
|
16228
|
+
"kts",
|
|
16229
|
+
"rb",
|
|
16230
|
+
"cs",
|
|
16231
|
+
"cpp",
|
|
16232
|
+
"cc",
|
|
16233
|
+
"cxx",
|
|
16234
|
+
"c",
|
|
16235
|
+
"h",
|
|
16236
|
+
"hpp",
|
|
16237
|
+
"swift",
|
|
16238
|
+
"scala",
|
|
16239
|
+
"php"
|
|
16240
|
+
]);
|
|
16241
|
+
var REGEX_META_RE = /[.*+?^${}()|[\]\\]/;
|
|
16242
|
+
var SYMBOL_ID_RE = /^[A-Za-z_$][A-Za-z0-9_$.-]*$/;
|
|
16243
|
+
function isSymbolLike(term) {
|
|
16244
|
+
return SYMBOL_ID_RE.test(term) && !REGEX_META_RE.test(term);
|
|
16245
|
+
}
|
|
16246
|
+
function isSourceFile(filePath) {
|
|
16247
|
+
const dot = filePath.lastIndexOf(".");
|
|
16248
|
+
if (dot === -1) return false;
|
|
16249
|
+
return SOURCE_EXT.has(filePath.slice(dot + 1).toLowerCase());
|
|
16250
|
+
}
|
|
16251
|
+
function fileStem(filePath) {
|
|
16252
|
+
const base = filePath.includes("/") ? filePath.slice(filePath.lastIndexOf("/") + 1) : filePath;
|
|
16253
|
+
const dot = base.lastIndexOf(".");
|
|
16254
|
+
return dot === -1 ? base : base.slice(0, dot);
|
|
16255
|
+
}
|
|
16256
|
+
function extractGrepSymbol(cmd) {
|
|
16257
|
+
if (/(?:^|\s)-[a-zA-Z]*[cvlLoZ]/.test(cmd)) return null;
|
|
16258
|
+
const quoted = cmd.match(/(?:^|\s)["']([^"']+)["'](?:\s|$)/);
|
|
16259
|
+
if (quoted) {
|
|
16260
|
+
const term = quoted[1];
|
|
16261
|
+
return isSymbolLike(term) ? term : null;
|
|
16262
|
+
}
|
|
16263
|
+
const tokens = cmd.split(/\s+/).slice(1);
|
|
16264
|
+
for (const tok of tokens) {
|
|
16265
|
+
if (tok.startsWith("-")) continue;
|
|
16266
|
+
if (tok.startsWith("/")) continue;
|
|
16267
|
+
if (tok.startsWith("./") || tok.startsWith("../")) continue;
|
|
16268
|
+
if (tok.includes("/")) continue;
|
|
16269
|
+
if (tok === "." || tok === "..") continue;
|
|
16270
|
+
return isSymbolLike(tok) ? tok : null;
|
|
16271
|
+
}
|
|
16272
|
+
return null;
|
|
16273
|
+
}
|
|
16274
|
+
function rewriteCommand(cmd) {
|
|
16275
|
+
try {
|
|
16276
|
+
const trimmed = cmd.trim();
|
|
16277
|
+
if (!trimmed) return null;
|
|
16278
|
+
if (trimmed.startsWith("code-intel ") || trimmed === "code-intel") return null;
|
|
16279
|
+
if (/(?:&&|\|\||;|\|)/.test(trimmed)) return null;
|
|
16280
|
+
if (/^grep\s/.test(trimmed)) {
|
|
16281
|
+
const sym = extractGrepSymbol(trimmed);
|
|
16282
|
+
if (sym) return `code-intel search "${sym}"`;
|
|
16283
|
+
return null;
|
|
16284
|
+
}
|
|
16285
|
+
if (/^rg\s/.test(trimmed)) {
|
|
16286
|
+
if (/\s--files(?:\s|$)/.test(trimmed)) return null;
|
|
16287
|
+
if (/\s--files-with-matches(?:\s|$)/.test(trimmed)) return null;
|
|
16288
|
+
if (/\s--type-not\b/.test(trimmed)) return null;
|
|
16289
|
+
const sym = extractGrepSymbol(trimmed);
|
|
16290
|
+
if (sym) return `code-intel search "${sym}"`;
|
|
16291
|
+
return null;
|
|
16292
|
+
}
|
|
16293
|
+
if (/^cat\s/.test(trimmed)) {
|
|
16294
|
+
const m = trimmed.match(/^cat\s+(\S+)$/);
|
|
16295
|
+
if (!m) return null;
|
|
16296
|
+
const filePath = m[1];
|
|
16297
|
+
if (filePath === "-") return null;
|
|
16298
|
+
if (filePath.startsWith(">")) return null;
|
|
16299
|
+
if (!isSourceFile(filePath)) return null;
|
|
16300
|
+
return `code-intel inspect ${fileStem(filePath)}`;
|
|
16301
|
+
}
|
|
16302
|
+
if (/^(?:head|tail)\s/.test(trimmed)) {
|
|
16303
|
+
if (/\s-f\b/.test(trimmed)) return null;
|
|
16304
|
+
const m = trimmed.match(
|
|
16305
|
+
/^(?:head|tail)\s+(?:-\d+\s+|-n\s+\d+\s+|--lines=\d+\s+)?(\S+)$/
|
|
16306
|
+
);
|
|
16307
|
+
if (!m) return null;
|
|
16308
|
+
const filePath = m[1];
|
|
16309
|
+
if (!isSourceFile(filePath)) return null;
|
|
16310
|
+
return `code-intel inspect ${fileStem(filePath)}`;
|
|
16311
|
+
}
|
|
16312
|
+
return null;
|
|
16313
|
+
} catch {
|
|
16314
|
+
return null;
|
|
16315
|
+
}
|
|
16316
|
+
}
|
|
16317
|
+
function runRewrite(cmd) {
|
|
16318
|
+
const rewritten = rewriteCommand(cmd.trim());
|
|
16319
|
+
if (rewritten === null) {
|
|
16320
|
+
process2.exit(1);
|
|
16321
|
+
}
|
|
16322
|
+
process2.stdout.write(rewritten);
|
|
16323
|
+
process2.exit(0);
|
|
16324
|
+
}
|
|
16325
|
+
function runClaudeHook() {
|
|
16326
|
+
process2.on("uncaughtException", () => process2.exit(0));
|
|
16327
|
+
process2.on("unhandledRejection", () => process2.exit(0));
|
|
16328
|
+
let input = "";
|
|
16329
|
+
process2.stdin.setEncoding("utf-8");
|
|
16330
|
+
process2.stdin.on("data", (chunk) => {
|
|
16331
|
+
input += chunk;
|
|
16332
|
+
});
|
|
16333
|
+
process2.stdin.on("end", () => {
|
|
16334
|
+
try {
|
|
16335
|
+
if (!input.trim()) {
|
|
16336
|
+
process2.exit(0);
|
|
16337
|
+
}
|
|
16338
|
+
const parsed = JSON.parse(input);
|
|
16339
|
+
const cmd = parsed?.tool_input?.command;
|
|
16340
|
+
if (typeof cmd !== "string" || !cmd.trim()) {
|
|
16341
|
+
process2.exit(0);
|
|
16342
|
+
}
|
|
16343
|
+
const rewritten = rewriteCommand(cmd);
|
|
16344
|
+
if (rewritten === null || rewritten === cmd) {
|
|
16345
|
+
process2.exit(0);
|
|
16346
|
+
}
|
|
16347
|
+
const response = {
|
|
16348
|
+
hookSpecificOutput: {
|
|
16349
|
+
hookEventName: "PreToolUse",
|
|
16350
|
+
permissionDecision: "allow",
|
|
16351
|
+
permissionDecisionReason: "code-intel: semantic search replaces grep/cat",
|
|
16352
|
+
updatedInput: {
|
|
16353
|
+
...parsed.tool_input,
|
|
16354
|
+
command: rewritten
|
|
16355
|
+
}
|
|
16356
|
+
}
|
|
16357
|
+
};
|
|
16358
|
+
process2.stdout.write(JSON.stringify(response));
|
|
16359
|
+
process2.exit(0);
|
|
16360
|
+
} catch {
|
|
16361
|
+
process2.exit(0);
|
|
16362
|
+
}
|
|
16363
|
+
});
|
|
16364
|
+
process2.stdin.on("error", () => {
|
|
16365
|
+
process2.exit(0);
|
|
16366
|
+
});
|
|
16367
|
+
}
|
|
15584
16368
|
|
|
15585
16369
|
// src/cli/main.ts
|
|
15586
16370
|
var __filename$1 = fileURLToPath(import.meta.url);
|
|
15587
16371
|
var __dirname2 = dirname(__filename$1);
|
|
15588
16372
|
var _pkg = JSON.parse(readFileSync(join(__dirname2, "../../package.json"), "utf-8"));
|
|
15589
|
-
|
|
16373
|
+
var IS_HOOK_MODE = process.argv[2] === "hook";
|
|
16374
|
+
if (!IS_HOOK_MODE) {
|
|
16375
|
+
initTracing();
|
|
16376
|
+
}
|
|
15590
16377
|
var debugMode = process.argv.includes("--debug");
|
|
15591
|
-
{
|
|
16378
|
+
if (!IS_HOOK_MODE) {
|
|
15592
16379
|
const checks = runPrerequisiteChecks();
|
|
15593
16380
|
for (const c of checks) {
|
|
15594
16381
|
const icon = c.level === "error" ? "\u2717" : "\u26A0";
|
|
@@ -15596,20 +16383,28 @@ var debugMode = process.argv.includes("--debug");
|
|
|
15596
16383
|
`);
|
|
15597
16384
|
}
|
|
15598
16385
|
}
|
|
15599
|
-
if (!configExists()) {
|
|
16386
|
+
if (!IS_HOOK_MODE && !configExists()) {
|
|
15600
16387
|
process.stderr.write(
|
|
15601
16388
|
" \u2139 No config found. Run `code-intel init` to set up your environment.\n"
|
|
15602
16389
|
);
|
|
15603
16390
|
}
|
|
15604
16391
|
process.on("uncaughtException", (err) => {
|
|
16392
|
+
if (IS_HOOK_MODE) {
|
|
16393
|
+
process.exit(0);
|
|
16394
|
+
}
|
|
15605
16395
|
process.stderr.write(formatCliError(err, debugMode));
|
|
15606
16396
|
process.exit(1);
|
|
15607
16397
|
});
|
|
15608
16398
|
process.on("unhandledRejection", (err) => {
|
|
16399
|
+
if (IS_HOOK_MODE) {
|
|
16400
|
+
process.exit(0);
|
|
16401
|
+
}
|
|
15609
16402
|
process.stderr.write(formatCliError(err, debugMode));
|
|
15610
16403
|
process.exit(1);
|
|
15611
16404
|
});
|
|
15612
|
-
|
|
16405
|
+
if (!IS_HOOK_MODE) {
|
|
16406
|
+
backgroundVersionCheck(_pkg.version);
|
|
16407
|
+
}
|
|
15613
16408
|
var program = new Command();
|
|
15614
16409
|
var BANNER = `
|
|
15615
16410
|
\u25C8 Code Intelligence Platform v${_pkg.version}
|
|
@@ -15659,6 +16454,8 @@ program.name("code-intel").description("Code Intelligence Platform \u2014 Static
|
|
|
15659
16454
|
\u2502 server \u2502
|
|
15660
16455
|
\u2502 code-intel mcp [path] Launch the MCP stdio server consumed by AI-enabled editors \u2502
|
|
15661
16456
|
\u2502 code-intel serve [path] --port <n> Start the HTTP API and serve the interactive web UI (default :4747) \u2502
|
|
16457
|
+
\u2502 code-intel serve --detach Start the server in the background (frees the terminal) \u2502
|
|
16458
|
+
\u2502 code-intel stop [path] Stop a background server started with --detach \u2502
|
|
15662
16459
|
\u2502 \u2502
|
|
15663
16460
|
\u2502 registry \u2502
|
|
15664
16461
|
\u2502 code-intel list Display all repositories that have been indexed \u2502
|
|
@@ -15688,14 +16485,37 @@ program.name("code-intel").description("Code Intelligence Platform \u2014 Static
|
|
|
15688
16485
|
|
|
15689
16486
|
Docs: https://github.com/vohongtho/code-intel-platform
|
|
15690
16487
|
`);
|
|
16488
|
+
function ensureGitignore(workspaceRoot, silent) {
|
|
16489
|
+
const gitignorePath = path39.join(workspaceRoot, ".gitignore");
|
|
16490
|
+
const entry = ".code-intel/";
|
|
16491
|
+
try {
|
|
16492
|
+
let existing = "";
|
|
16493
|
+
if (fs39.existsSync(gitignorePath)) {
|
|
16494
|
+
existing = fs39.readFileSync(gitignorePath, "utf-8");
|
|
16495
|
+
}
|
|
16496
|
+
const lines = existing.split("\n").map((l) => l.trim());
|
|
16497
|
+
if (lines.includes(".code-intel/") || lines.includes(".code-intel")) {
|
|
16498
|
+
return;
|
|
16499
|
+
}
|
|
16500
|
+
const suffix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
16501
|
+
fs39.appendFileSync(gitignorePath, `${suffix}${entry}
|
|
16502
|
+
`, "utf-8");
|
|
16503
|
+
logger_default.info(".gitignore updated: added .code-intel/");
|
|
16504
|
+
if (!silent) {
|
|
16505
|
+
console.log(` \u2713 .gitignore: added .code-intel/`);
|
|
16506
|
+
}
|
|
16507
|
+
} catch (err) {
|
|
16508
|
+
logger_default.warn(`.gitignore update failed: ${err instanceof Error ? err.message : err}`);
|
|
16509
|
+
}
|
|
16510
|
+
}
|
|
15691
16511
|
async function analyzeWorkspace(targetPath, options) {
|
|
15692
16512
|
const workspaceRoot = path39.resolve(targetPath);
|
|
15693
|
-
if (!
|
|
16513
|
+
if (!fs39.existsSync(workspaceRoot)) {
|
|
15694
16514
|
logger_default.error(`Path does not exist: ${workspaceRoot}`);
|
|
15695
16515
|
console.error(` \u2717 Path does not exist: ${workspaceRoot}`);
|
|
15696
16516
|
process.exit(1);
|
|
15697
16517
|
}
|
|
15698
|
-
if (!
|
|
16518
|
+
if (!fs39.statSync(workspaceRoot).isDirectory()) {
|
|
15699
16519
|
logger_default.error(`Path is not a directory: ${workspaceRoot}`);
|
|
15700
16520
|
console.error(` \u2717 Path is not a directory: ${workspaceRoot}`);
|
|
15701
16521
|
process.exit(1);
|
|
@@ -15720,14 +16540,14 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
15720
16540
|
];
|
|
15721
16541
|
for (const f of wipeFiles) {
|
|
15722
16542
|
try {
|
|
15723
|
-
if (
|
|
16543
|
+
if (fs39.existsSync(f)) fs39.unlinkSync(f);
|
|
15724
16544
|
} catch {
|
|
15725
16545
|
}
|
|
15726
16546
|
}
|
|
15727
16547
|
}
|
|
15728
16548
|
if (!options?.skipGit) {
|
|
15729
16549
|
const gitDir = path39.join(workspaceRoot, ".git");
|
|
15730
|
-
if (!
|
|
16550
|
+
if (!fs39.existsSync(gitDir)) {
|
|
15731
16551
|
logger_default.warn(`${workspaceRoot} is not a Git repository`);
|
|
15732
16552
|
}
|
|
15733
16553
|
}
|
|
@@ -15783,7 +16603,9 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
15783
16603
|
provider: options.llmProvider ?? "ollama",
|
|
15784
16604
|
model: options.llmModel,
|
|
15785
16605
|
batchSize: options.llmBatchSize,
|
|
15786
|
-
maxNodesPerRun: options.llmMaxNodes
|
|
16606
|
+
maxNodesPerRun: options.llmMaxNodes,
|
|
16607
|
+
baseUrl: options.llmBaseUrl,
|
|
16608
|
+
apiKey: options.llmApiKey
|
|
15787
16609
|
} : void 0,
|
|
15788
16610
|
onProgress: options?.silent ? void 0 : (phase, msg) => {
|
|
15789
16611
|
if (!options?.silent) {
|
|
@@ -15866,8 +16688,8 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
15866
16688
|
};
|
|
15867
16689
|
const profilePath = path39.join(workspaceRoot, ".code-intel", "profile.json");
|
|
15868
16690
|
try {
|
|
15869
|
-
|
|
15870
|
-
|
|
16691
|
+
fs39.mkdirSync(path39.join(workspaceRoot, ".code-intel"), { recursive: true });
|
|
16692
|
+
fs39.writeFileSync(profilePath, JSON.stringify(profileJson, null, 2));
|
|
15871
16693
|
if (!options?.silent) console.log(` \u2713 Profile written: ${profilePath}`);
|
|
15872
16694
|
} catch (err) {
|
|
15873
16695
|
logger_default.warn(`Failed to write profile.json: ${err instanceof Error ? err.message : err}`);
|
|
@@ -15894,7 +16716,7 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
15894
16716
|
}
|
|
15895
16717
|
if (isIncremental && incrementalChangedFiles && incrementalChangedFiles.length > 0) {
|
|
15896
16718
|
const dbPath = getDbPath(workspaceRoot);
|
|
15897
|
-
if (
|
|
16719
|
+
if (fs39.existsSync(dbPath)) {
|
|
15898
16720
|
try {
|
|
15899
16721
|
const db = new DbManager(dbPath);
|
|
15900
16722
|
await db.init();
|
|
@@ -15967,7 +16789,7 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
15967
16789
|
];
|
|
15968
16790
|
for (const f of newStaleFiles) {
|
|
15969
16791
|
try {
|
|
15970
|
-
if (
|
|
16792
|
+
if (fs39.existsSync(f)) fs39.unlinkSync(f);
|
|
15971
16793
|
} catch {
|
|
15972
16794
|
}
|
|
15973
16795
|
}
|
|
@@ -15984,21 +16806,21 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
15984
16806
|
];
|
|
15985
16807
|
for (const f of staleFiles) {
|
|
15986
16808
|
try {
|
|
15987
|
-
if (
|
|
16809
|
+
if (fs39.existsSync(f)) fs39.unlinkSync(f);
|
|
15988
16810
|
} catch {
|
|
15989
16811
|
}
|
|
15990
16812
|
}
|
|
15991
16813
|
for (const f of newStaleFiles) {
|
|
15992
16814
|
if (f === dbPathNew) continue;
|
|
15993
|
-
if (
|
|
16815
|
+
if (fs39.existsSync(f)) {
|
|
15994
16816
|
const dest = f.replace(dbPathNew, dbPath);
|
|
15995
16817
|
try {
|
|
15996
|
-
|
|
16818
|
+
fs39.renameSync(f, dest);
|
|
15997
16819
|
} catch {
|
|
15998
16820
|
}
|
|
15999
16821
|
}
|
|
16000
16822
|
}
|
|
16001
|
-
|
|
16823
|
+
fs39.renameSync(dbPathNew, dbPath);
|
|
16002
16824
|
stopSpinner();
|
|
16003
16825
|
logger_default.info(`DB persisted: ${nodeCount} nodes, ${edgeCount} edges`);
|
|
16004
16826
|
if (!options?.silent) {
|
|
@@ -16030,7 +16852,7 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
16030
16852
|
const staleVdb = [vdbPath, `${vdbPath}-shm`, `${vdbPath}-wal`];
|
|
16031
16853
|
for (const f of staleVdb) {
|
|
16032
16854
|
try {
|
|
16033
|
-
if (
|
|
16855
|
+
if (fs39.existsSync(f)) fs39.unlinkSync(f);
|
|
16034
16856
|
} catch {
|
|
16035
16857
|
}
|
|
16036
16858
|
}
|
|
@@ -16093,6 +16915,7 @@ async function analyzeWorkspace(targetPath, options) {
|
|
|
16093
16915
|
logger_default.warn(`Context file write failed: ${err instanceof Error ? err.message : err}`);
|
|
16094
16916
|
}
|
|
16095
16917
|
}
|
|
16918
|
+
ensureGitignore(workspaceRoot, options?.silent ?? false);
|
|
16096
16919
|
if (!options?.silent) {
|
|
16097
16920
|
const dur = result.totalDuration;
|
|
16098
16921
|
const durStr = dur >= 1e3 ? `${(dur / 1e3).toFixed(1)}s` : `${dur}ms`;
|
|
@@ -16216,6 +17039,396 @@ program.command("completion").description("Generate shell completion scripts (ba
|
|
|
16216
17039
|
}
|
|
16217
17040
|
process.stdout.write(generateCompletion(shell));
|
|
16218
17041
|
});
|
|
17042
|
+
var CODE_INTEL_HOOK_CMD = "code-intel-hook claude";
|
|
17043
|
+
var CODE_INTEL_CURSOR_HOOK_CMD = "code-intel-hook cursor";
|
|
17044
|
+
var CODE_INTEL_GEMINI_HOOK_CMD = "code-intel-hook gemini";
|
|
17045
|
+
function hookAlreadyPresent(root) {
|
|
17046
|
+
const entries = root?.hooks?.PreToolUse;
|
|
17047
|
+
if (!Array.isArray(entries)) return false;
|
|
17048
|
+
return entries.some(
|
|
17049
|
+
(entry) => Array.isArray(entry?.hooks) && entry.hooks.some(
|
|
17050
|
+
(h) => typeof h?.command === "string" && (h.command.includes("code-intel-hook") || h.command.includes("code-intel hook"))
|
|
17051
|
+
)
|
|
17052
|
+
);
|
|
17053
|
+
}
|
|
17054
|
+
function insertHookEntry(root) {
|
|
17055
|
+
const existingHooks = root.hooks ?? {};
|
|
17056
|
+
const existingPreToolUse = Array.isArray(
|
|
17057
|
+
existingHooks.PreToolUse
|
|
17058
|
+
) ? existingHooks.PreToolUse : [];
|
|
17059
|
+
const newEntry = {
|
|
17060
|
+
matcher: "Bash",
|
|
17061
|
+
hooks: [{ type: "command", command: CODE_INTEL_HOOK_CMD }]
|
|
17062
|
+
};
|
|
17063
|
+
return {
|
|
17064
|
+
...root,
|
|
17065
|
+
hooks: {
|
|
17066
|
+
...existingHooks,
|
|
17067
|
+
PreToolUse: [newEntry, ...existingPreToolUse]
|
|
17068
|
+
}
|
|
17069
|
+
};
|
|
17070
|
+
}
|
|
17071
|
+
function installClaudeHook() {
|
|
17072
|
+
const settingsPath = path39.join(os18.homedir(), ".claude", "settings.json");
|
|
17073
|
+
let root = {};
|
|
17074
|
+
if (fs39.existsSync(settingsPath)) {
|
|
17075
|
+
try {
|
|
17076
|
+
const raw = fs39.readFileSync(settingsPath, "utf-8");
|
|
17077
|
+
root = JSON.parse(raw);
|
|
17078
|
+
} catch (err) {
|
|
17079
|
+
return {
|
|
17080
|
+
result: "skipped",
|
|
17081
|
+
reason: `Could not parse ~/.claude/settings.json: ${err instanceof Error ? err.message : String(err)}`
|
|
17082
|
+
};
|
|
17083
|
+
}
|
|
17084
|
+
}
|
|
17085
|
+
if (hookAlreadyPresent(root)) {
|
|
17086
|
+
return { result: "already-present" };
|
|
17087
|
+
}
|
|
17088
|
+
const updated = insertHookEntry(root);
|
|
17089
|
+
if (fs39.existsSync(settingsPath)) {
|
|
17090
|
+
try {
|
|
17091
|
+
fs39.copyFileSync(settingsPath, `${settingsPath}.bak`);
|
|
17092
|
+
} catch {
|
|
17093
|
+
logger_default.warn(" \u26A0 Could not create settings.json backup \u2014 proceeding anyway");
|
|
17094
|
+
}
|
|
17095
|
+
}
|
|
17096
|
+
try {
|
|
17097
|
+
fs39.mkdirSync(path39.dirname(settingsPath), { recursive: true });
|
|
17098
|
+
const tmp = `${settingsPath}.tmp.${Date.now()}`;
|
|
17099
|
+
fs39.writeFileSync(tmp, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
17100
|
+
fs39.renameSync(tmp, settingsPath);
|
|
17101
|
+
} catch (err) {
|
|
17102
|
+
return {
|
|
17103
|
+
result: "skipped",
|
|
17104
|
+
reason: `Could not write ~/.claude/settings.json: ${err instanceof Error ? err.message : String(err)}`
|
|
17105
|
+
};
|
|
17106
|
+
}
|
|
17107
|
+
return { result: "installed" };
|
|
17108
|
+
}
|
|
17109
|
+
var COPILOT_HOOK_JSON_CONTENT = JSON.stringify({
|
|
17110
|
+
hooks: {
|
|
17111
|
+
PreToolUse: [
|
|
17112
|
+
{ type: "command", command: CODE_INTEL_HOOK_CMD.replace("claude", "copilot"), cwd: ".", timeout: 5 }
|
|
17113
|
+
]
|
|
17114
|
+
}
|
|
17115
|
+
}, null, 2) + "\n";
|
|
17116
|
+
function installCopilotHook(workspaceRoot = ".") {
|
|
17117
|
+
const githubDir = path39.join(workspaceRoot, ".github");
|
|
17118
|
+
const hooksDir = path39.join(githubDir, "hooks");
|
|
17119
|
+
const hookFile = path39.join(hooksDir, "code-intel-rewrite.json");
|
|
17120
|
+
try {
|
|
17121
|
+
fs39.mkdirSync(hooksDir, { recursive: true });
|
|
17122
|
+
if (fs39.existsSync(hookFile)) {
|
|
17123
|
+
try {
|
|
17124
|
+
const existing = JSON.parse(fs39.readFileSync(hookFile, "utf-8"));
|
|
17125
|
+
const hooks = existing?.hooks?.PreToolUse;
|
|
17126
|
+
if (Array.isArray(hooks) && hooks.some(
|
|
17127
|
+
(h) => typeof h?.command === "string" && h.command.includes("code-intel")
|
|
17128
|
+
)) {
|
|
17129
|
+
return { result: "already-present" };
|
|
17130
|
+
}
|
|
17131
|
+
} catch {
|
|
17132
|
+
}
|
|
17133
|
+
}
|
|
17134
|
+
const tmp = `${hookFile}.tmp.${Date.now()}`;
|
|
17135
|
+
fs39.writeFileSync(tmp, COPILOT_HOOK_JSON_CONTENT, "utf-8");
|
|
17136
|
+
fs39.renameSync(tmp, hookFile);
|
|
17137
|
+
return { result: "installed" };
|
|
17138
|
+
} catch (err) {
|
|
17139
|
+
return { result: "skipped", reason: `Could not write .github/hooks/code-intel-rewrite.json: ${err instanceof Error ? err.message : String(err)}` };
|
|
17140
|
+
}
|
|
17141
|
+
}
|
|
17142
|
+
var OPENCODE_PLUGIN_CONTENT = `import type { Plugin } from "@opencode-ai/plugin"
|
|
17143
|
+
import { execSync } from "node:child_process"
|
|
17144
|
+
|
|
17145
|
+
// code-intel OpenCode plugin \u2014 rewrites symbol-discovery commands to semantic equivalents.
|
|
17146
|
+
// Requires: code-intel installed and in PATH.
|
|
17147
|
+
// To change rewrite rules, edit hook-rewriter.ts in the code-intel source.
|
|
17148
|
+
|
|
17149
|
+
let codeIntelAvailable: boolean | null = null
|
|
17150
|
+
|
|
17151
|
+
function checkCodeIntel(): boolean {
|
|
17152
|
+
if (codeIntelAvailable !== null) return codeIntelAvailable
|
|
17153
|
+
try {
|
|
17154
|
+
execSync("which code-intel", { stdio: "ignore" })
|
|
17155
|
+
codeIntelAvailable = true
|
|
17156
|
+
} catch {
|
|
17157
|
+
codeIntelAvailable = false
|
|
17158
|
+
}
|
|
17159
|
+
return codeIntelAvailable
|
|
17160
|
+
}
|
|
17161
|
+
|
|
17162
|
+
function tryRewrite(command: string): string | null {
|
|
17163
|
+
try {
|
|
17164
|
+
const result = execSync(\`code-intel rewrite \${JSON.stringify(command)}\`, {
|
|
17165
|
+
encoding: "utf-8",
|
|
17166
|
+
timeout: 2000,
|
|
17167
|
+
}).trim()
|
|
17168
|
+
return result && result !== command ? result : null
|
|
17169
|
+
} catch {
|
|
17170
|
+
return null
|
|
17171
|
+
}
|
|
17172
|
+
}
|
|
17173
|
+
|
|
17174
|
+
export const CodeIntelOpenCodePlugin: Plugin = async ({ $ }) => {
|
|
17175
|
+
if (!checkCodeIntel()) {
|
|
17176
|
+
console.warn("[code-intel] code-intel binary not found in PATH \u2014 plugin disabled")
|
|
17177
|
+
return {}
|
|
17178
|
+
}
|
|
17179
|
+
return {
|
|
17180
|
+
"tool.execute.before": async (input, output) => {
|
|
17181
|
+
const tool = String(input?.tool ?? "").toLowerCase()
|
|
17182
|
+
if (tool !== "bash" && tool !== "shell") return
|
|
17183
|
+
const args = output?.args
|
|
17184
|
+
if (!args || typeof args !== "object") return
|
|
17185
|
+
const command = (args as Record<string, unknown>).command
|
|
17186
|
+
if (typeof command !== "string" || !command) return
|
|
17187
|
+
try {
|
|
17188
|
+
const rewritten = tryRewrite(command)
|
|
17189
|
+
if (rewritten) {
|
|
17190
|
+
;(args as Record<string, unknown>).command = rewritten
|
|
17191
|
+
}
|
|
17192
|
+
} catch {
|
|
17193
|
+
// non-blocking
|
|
17194
|
+
}
|
|
17195
|
+
},
|
|
17196
|
+
}
|
|
17197
|
+
}
|
|
17198
|
+
`;
|
|
17199
|
+
function installOpenCodePlugin() {
|
|
17200
|
+
const pluginDir = path39.join(os18.homedir(), ".config", "opencode", "plugins");
|
|
17201
|
+
const pluginFile = path39.join(pluginDir, "code-intel.ts");
|
|
17202
|
+
const opencodeCfg = path39.join(os18.homedir(), ".config", "opencode");
|
|
17203
|
+
if (!fs39.existsSync(opencodeCfg)) {
|
|
17204
|
+
return { result: "skipped", reason: "OpenCode not installed (~/.config/opencode not found)" };
|
|
17205
|
+
}
|
|
17206
|
+
if (fs39.existsSync(pluginFile)) {
|
|
17207
|
+
return { result: "already-present" };
|
|
17208
|
+
}
|
|
17209
|
+
try {
|
|
17210
|
+
fs39.mkdirSync(pluginDir, { recursive: true });
|
|
17211
|
+
const tmp = `${pluginFile}.tmp.${Date.now()}`;
|
|
17212
|
+
fs39.writeFileSync(tmp, OPENCODE_PLUGIN_CONTENT, "utf-8");
|
|
17213
|
+
fs39.renameSync(tmp, pluginFile);
|
|
17214
|
+
return { result: "installed" };
|
|
17215
|
+
} catch (err) {
|
|
17216
|
+
return { result: "skipped", reason: `Could not write plugin: ${err instanceof Error ? err.message : String(err)}` };
|
|
17217
|
+
}
|
|
17218
|
+
}
|
|
17219
|
+
var OPENCLAW_PLUGIN_CONTENT = `import { execSync } from "node:child_process";
|
|
17220
|
+
|
|
17221
|
+
// code-intel OpenClaw plugin \u2014 rewrites symbol-discovery commands to semantic equivalents.
|
|
17222
|
+
// Requires: code-intel installed and in PATH.
|
|
17223
|
+
// To change rewrite rules, edit hook-rewriter.ts in the code-intel source.
|
|
17224
|
+
|
|
17225
|
+
let codeIntelAvailable: boolean | null = null;
|
|
17226
|
+
|
|
17227
|
+
function checkCodeIntel(): boolean {
|
|
17228
|
+
if (codeIntelAvailable !== null) return codeIntelAvailable;
|
|
17229
|
+
try {
|
|
17230
|
+
execSync("which code-intel", { stdio: "ignore" });
|
|
17231
|
+
codeIntelAvailable = true;
|
|
17232
|
+
} catch {
|
|
17233
|
+
codeIntelAvailable = false;
|
|
17234
|
+
}
|
|
17235
|
+
return codeIntelAvailable;
|
|
17236
|
+
}
|
|
17237
|
+
|
|
17238
|
+
function tryRewrite(command: string): string | null {
|
|
17239
|
+
try {
|
|
17240
|
+
const result = execSync(\`code-intel rewrite \${JSON.stringify(command)}\`, {
|
|
17241
|
+
encoding: "utf-8",
|
|
17242
|
+
timeout: 2000,
|
|
17243
|
+
}).trim();
|
|
17244
|
+
return result && result !== command ? result : null;
|
|
17245
|
+
} catch {
|
|
17246
|
+
return null;
|
|
17247
|
+
}
|
|
17248
|
+
}
|
|
17249
|
+
|
|
17250
|
+
export default function register(api: any) {
|
|
17251
|
+
const pluginConfig = api.config ?? {};
|
|
17252
|
+
const enabled = pluginConfig.enabled !== false;
|
|
17253
|
+
const verbose = pluginConfig.verbose === true;
|
|
17254
|
+
|
|
17255
|
+
if (!enabled) return;
|
|
17256
|
+
|
|
17257
|
+
if (!checkCodeIntel()) {
|
|
17258
|
+
console.warn("[code-intel] code-intel binary not found in PATH \u2014 plugin disabled");
|
|
17259
|
+
return;
|
|
17260
|
+
}
|
|
17261
|
+
|
|
17262
|
+
api.on(
|
|
17263
|
+
"before_tool_call",
|
|
17264
|
+
(event: { toolName: string; params: Record<string, unknown> }) => {
|
|
17265
|
+
if (event.toolName !== "exec") return;
|
|
17266
|
+
const command = event.params?.command;
|
|
17267
|
+
if (typeof command !== "string") return;
|
|
17268
|
+
const rewritten = tryRewrite(command);
|
|
17269
|
+
if (!rewritten) return;
|
|
17270
|
+
if (verbose) console.log(\`[code-intel] \${command} -> \${rewritten}\`);
|
|
17271
|
+
return { params: { ...event.params, command: rewritten } };
|
|
17272
|
+
},
|
|
17273
|
+
{ priority: 10 }
|
|
17274
|
+
);
|
|
17275
|
+
|
|
17276
|
+
if (verbose) console.log("[code-intel] OpenClaw plugin registered");
|
|
17277
|
+
}
|
|
17278
|
+
`;
|
|
17279
|
+
function installOpenClawPlugin() {
|
|
17280
|
+
const openclawDir = path39.join(os18.homedir(), ".openclaw");
|
|
17281
|
+
if (!fs39.existsSync(openclawDir)) {
|
|
17282
|
+
return { result: "skipped", reason: "OpenClaw not installed (~/.openclaw directory not found)" };
|
|
17283
|
+
}
|
|
17284
|
+
const pluginDir = path39.join(openclawDir, "extensions", "code-intel");
|
|
17285
|
+
const pluginFile = path39.join(pluginDir, "index.ts");
|
|
17286
|
+
if (fs39.existsSync(pluginFile)) {
|
|
17287
|
+
return { result: "already-present" };
|
|
17288
|
+
}
|
|
17289
|
+
try {
|
|
17290
|
+
fs39.mkdirSync(pluginDir, { recursive: true });
|
|
17291
|
+
const tmp = `${pluginFile}.tmp.${Date.now()}`;
|
|
17292
|
+
fs39.writeFileSync(tmp, OPENCLAW_PLUGIN_CONTENT, "utf-8");
|
|
17293
|
+
fs39.renameSync(tmp, pluginFile);
|
|
17294
|
+
return { result: "installed" };
|
|
17295
|
+
} catch (err) {
|
|
17296
|
+
return { result: "skipped", reason: `Could not write plugin: ${err instanceof Error ? err.message : String(err)}` };
|
|
17297
|
+
}
|
|
17298
|
+
}
|
|
17299
|
+
var CODE_INTEL_RULES_CONTENT = `# Code Intelligence \u2014 Tool Policy
|
|
17300
|
+
|
|
17301
|
+
## MANDATORY: Use code-intel before any code action
|
|
17302
|
+
|
|
17303
|
+
**FORBIDDEN** (never use for symbol/code discovery): \`grep\`, \`rg\`, \`find\`, \`cat\`, \`sed\`, \`ls\`
|
|
17304
|
+
|
|
17305
|
+
**REQUIRED workflow**:
|
|
17306
|
+
\`\`\`bash
|
|
17307
|
+
code-intel search "<concept>" # replaces grep/rg/find
|
|
17308
|
+
code-intel inspect <symbol> # replaces cat + manual reading
|
|
17309
|
+
code-intel impact <symbol> # replaces manual caller tracing
|
|
17310
|
+
\`\`\`
|
|
17311
|
+
|
|
17312
|
+
Rules:
|
|
17313
|
+
- NEVER open a file cold \u2014 always \`code-intel search\` first
|
|
17314
|
+
- NEVER use grep for symbols \u2014 use \`code-intel search\` instead
|
|
17315
|
+
- If impact shows \u2265 5 callers \u2192 warn user: HIGH blast radius
|
|
17316
|
+
`;
|
|
17317
|
+
function installRulesFile(filePath, agentName) {
|
|
17318
|
+
try {
|
|
17319
|
+
const dir = path39.dirname(filePath);
|
|
17320
|
+
if (!fs39.existsSync(dir)) {
|
|
17321
|
+
fs39.mkdirSync(dir, { recursive: true });
|
|
17322
|
+
}
|
|
17323
|
+
if (fs39.existsSync(filePath)) {
|
|
17324
|
+
const existing = fs39.readFileSync(filePath, "utf-8");
|
|
17325
|
+
if (existing.includes("code-intel search") || existing.includes("code-intel inspect")) {
|
|
17326
|
+
return { result: "already-present" };
|
|
17327
|
+
}
|
|
17328
|
+
fs39.appendFileSync(filePath, "\n---\n\n" + CODE_INTEL_RULES_CONTENT);
|
|
17329
|
+
return { result: "installed" };
|
|
17330
|
+
}
|
|
17331
|
+
fs39.writeFileSync(filePath, CODE_INTEL_RULES_CONTENT, "utf-8");
|
|
17332
|
+
return { result: "installed" };
|
|
17333
|
+
} catch (err) {
|
|
17334
|
+
return { result: "skipped", reason: `Could not write ${agentName} rules: ${err instanceof Error ? err.message : String(err)}` };
|
|
17335
|
+
}
|
|
17336
|
+
}
|
|
17337
|
+
function cursorHookAlreadyPresent(root) {
|
|
17338
|
+
const hooks = root?.hooks;
|
|
17339
|
+
const preToolUse = hooks?.preToolUse;
|
|
17340
|
+
if (!Array.isArray(preToolUse)) return false;
|
|
17341
|
+
return preToolUse.some((entry) => {
|
|
17342
|
+
const cmd = entry?.command;
|
|
17343
|
+
return typeof cmd === "string" && (cmd.includes("code-intel-hook") || cmd.includes("code-intel hook"));
|
|
17344
|
+
});
|
|
17345
|
+
}
|
|
17346
|
+
function installCursorHook() {
|
|
17347
|
+
const cursorDir = path39.join(os18.homedir(), ".cursor");
|
|
17348
|
+
const hooksPath = path39.join(cursorDir, "hooks.json");
|
|
17349
|
+
let root = { version: 1 };
|
|
17350
|
+
if (fs39.existsSync(hooksPath)) {
|
|
17351
|
+
try {
|
|
17352
|
+
const raw = fs39.readFileSync(hooksPath, "utf-8");
|
|
17353
|
+
if (raw.trim()) root = JSON.parse(raw);
|
|
17354
|
+
} catch (err) {
|
|
17355
|
+
return { result: "skipped", reason: `Could not parse ~/.cursor/hooks.json: ${err instanceof Error ? err.message : String(err)}` };
|
|
17356
|
+
}
|
|
17357
|
+
} else if (!fs39.existsSync(cursorDir)) {
|
|
17358
|
+
return { result: "skipped", reason: "Cursor not installed (~/.cursor directory not found)" };
|
|
17359
|
+
}
|
|
17360
|
+
if (cursorHookAlreadyPresent(root)) return { result: "already-present" };
|
|
17361
|
+
if (fs39.existsSync(hooksPath)) {
|
|
17362
|
+
try {
|
|
17363
|
+
fs39.copyFileSync(hooksPath, `${hooksPath}.bak`);
|
|
17364
|
+
} catch {
|
|
17365
|
+
}
|
|
17366
|
+
}
|
|
17367
|
+
const existingHooks = root.hooks ?? {};
|
|
17368
|
+
const existingPreToolUse = Array.isArray(existingHooks.preToolUse) ? existingHooks.preToolUse : [];
|
|
17369
|
+
const newEntry = { command: CODE_INTEL_CURSOR_HOOK_CMD, matcher: "Shell" };
|
|
17370
|
+
const updated = { ...root, hooks: { ...existingHooks, preToolUse: [newEntry, ...existingPreToolUse] } };
|
|
17371
|
+
try {
|
|
17372
|
+
fs39.mkdirSync(cursorDir, { recursive: true });
|
|
17373
|
+
const tmp = `${hooksPath}.tmp.${Date.now()}`;
|
|
17374
|
+
fs39.writeFileSync(tmp, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
17375
|
+
fs39.renameSync(tmp, hooksPath);
|
|
17376
|
+
} catch (err) {
|
|
17377
|
+
return { result: "skipped", reason: `Could not write ~/.cursor/hooks.json: ${err instanceof Error ? err.message : String(err)}` };
|
|
17378
|
+
}
|
|
17379
|
+
return { result: "installed" };
|
|
17380
|
+
}
|
|
17381
|
+
function geminiHookAlreadyPresent(root) {
|
|
17382
|
+
const hooks = root?.hooks;
|
|
17383
|
+
const beforeTool = hooks?.BeforeTool;
|
|
17384
|
+
if (!Array.isArray(beforeTool)) return false;
|
|
17385
|
+
return beforeTool.some((entry) => {
|
|
17386
|
+
const entryHooks = entry?.hooks;
|
|
17387
|
+
if (!Array.isArray(entryHooks)) return false;
|
|
17388
|
+
return entryHooks.some((h) => {
|
|
17389
|
+
const cmd = h?.command;
|
|
17390
|
+
return typeof cmd === "string" && (cmd.includes("code-intel-hook") || cmd.includes("code-intel hook"));
|
|
17391
|
+
});
|
|
17392
|
+
});
|
|
17393
|
+
}
|
|
17394
|
+
function installGeminiHook() {
|
|
17395
|
+
const geminiDir = path39.join(os18.homedir(), ".gemini");
|
|
17396
|
+
const settingsPath = path39.join(geminiDir, "settings.json");
|
|
17397
|
+
if (!fs39.existsSync(geminiDir)) {
|
|
17398
|
+
return { result: "skipped", reason: "Gemini CLI not installed (~/.gemini directory not found)" };
|
|
17399
|
+
}
|
|
17400
|
+
let root = {};
|
|
17401
|
+
if (fs39.existsSync(settingsPath)) {
|
|
17402
|
+
try {
|
|
17403
|
+
const raw = fs39.readFileSync(settingsPath, "utf-8");
|
|
17404
|
+
if (raw.trim()) root = JSON.parse(raw);
|
|
17405
|
+
} catch (err) {
|
|
17406
|
+
return { result: "skipped", reason: `Could not parse ~/.gemini/settings.json: ${err instanceof Error ? err.message : String(err)}` };
|
|
17407
|
+
}
|
|
17408
|
+
}
|
|
17409
|
+
if (geminiHookAlreadyPresent(root)) return { result: "already-present" };
|
|
17410
|
+
if (fs39.existsSync(settingsPath)) {
|
|
17411
|
+
try {
|
|
17412
|
+
fs39.copyFileSync(settingsPath, `${settingsPath}.bak`);
|
|
17413
|
+
} catch {
|
|
17414
|
+
}
|
|
17415
|
+
}
|
|
17416
|
+
const existingHooks = root.hooks ?? {};
|
|
17417
|
+
const existingBeforeTool = Array.isArray(existingHooks.BeforeTool) ? existingHooks.BeforeTool : [];
|
|
17418
|
+
const newEntry = {
|
|
17419
|
+
matcher: "run_shell_command",
|
|
17420
|
+
hooks: [{ type: "command", command: CODE_INTEL_GEMINI_HOOK_CMD }]
|
|
17421
|
+
};
|
|
17422
|
+
const updated = { ...root, hooks: { ...existingHooks, BeforeTool: [newEntry, ...existingBeforeTool] } };
|
|
17423
|
+
try {
|
|
17424
|
+
const tmp = `${settingsPath}.tmp.${Date.now()}`;
|
|
17425
|
+
fs39.writeFileSync(tmp, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
17426
|
+
fs39.renameSync(tmp, settingsPath);
|
|
17427
|
+
} catch (err) {
|
|
17428
|
+
return { result: "skipped", reason: `Could not write ~/.gemini/settings.json: ${err instanceof Error ? err.message : String(err)}` };
|
|
17429
|
+
}
|
|
17430
|
+
return { result: "installed" };
|
|
17431
|
+
}
|
|
16219
17432
|
program.command("setup").description("Configure MCP server for your editors (one-time setup)").option("--completion", "Auto-install shell completion for the detected shell").addHelpText("after", `
|
|
16220
17433
|
Configure the code-intel MCP server for Claude Desktop, VS Code, or any
|
|
16221
17434
|
editor that supports the Model Context Protocol.
|
|
@@ -16247,8 +17460,8 @@ program.command("setup").description("Configure MCP server for your editors (one
|
|
|
16247
17460
|
const configFile = `${configDir}/claude_desktop_config.json`;
|
|
16248
17461
|
try {
|
|
16249
17462
|
let existing = {};
|
|
16250
|
-
if (
|
|
16251
|
-
existing = JSON.parse(
|
|
17463
|
+
if (fs39.existsSync(configFile)) {
|
|
17464
|
+
existing = JSON.parse(fs39.readFileSync(configFile, "utf-8"));
|
|
16252
17465
|
}
|
|
16253
17466
|
const merged = {
|
|
16254
17467
|
...existing,
|
|
@@ -16257,8 +17470,8 @@ program.command("setup").description("Configure MCP server for your editors (one
|
|
|
16257
17470
|
...mcpConfig.mcpServers
|
|
16258
17471
|
}
|
|
16259
17472
|
};
|
|
16260
|
-
|
|
16261
|
-
|
|
17473
|
+
fs39.mkdirSync(configDir, { recursive: true });
|
|
17474
|
+
fs39.writeFileSync(configFile, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
16262
17475
|
console.log(`
|
|
16263
17476
|
\u2705 Written to ${configFile}`);
|
|
16264
17477
|
} catch (err) {
|
|
@@ -16271,8 +17484,129 @@ program.command("setup").description("Configure MCP server for your editors (one
|
|
|
16271
17484
|
console.log(" " + JSON.stringify({ servers: { "code-intel": { type: "stdio", command: "npx", args: ["code-intel", "mcp", "."] } } }, null, 2).split("\n").join("\n "));
|
|
16272
17485
|
console.log('\n To verify in VS Code: open Command Palette \u2192 "MCP: List Servers" and confirm code-intel is Running.');
|
|
16273
17486
|
console.log("\n Next: run `code-intel analyze` inside your project to build the knowledge graph.\n");
|
|
17487
|
+
const hookResult = installClaudeHook();
|
|
17488
|
+
if (hookResult.result === "installed") {
|
|
17489
|
+
const backupPath = path39.join(os18.homedir(), ".claude", "settings.json.bak");
|
|
17490
|
+
console.log("\n \u2705 Claude Code hook installed");
|
|
17491
|
+
console.log(" grep/rg/cat on source files \u2192 code-intel search/inspect");
|
|
17492
|
+
if (fs39.existsSync(backupPath)) {
|
|
17493
|
+
console.log(` Backup: ${backupPath}`);
|
|
17494
|
+
}
|
|
17495
|
+
console.log("\n \u21BA Restart Claude Code for the hook to take effect.");
|
|
17496
|
+
} else if (hookResult.result === "already-present") {
|
|
17497
|
+
console.log("\n \u2705 Claude Code hook already installed");
|
|
17498
|
+
} else {
|
|
17499
|
+
logger_default.warn(`
|
|
17500
|
+
\u26A0 Claude Code hook skipped: ${hookResult.reason}`);
|
|
17501
|
+
console.log("\n Add manually to ~/.claude/settings.json under hooks.PreToolUse[]:");
|
|
17502
|
+
console.log(" (insert as the FIRST entry so it runs before any existing hooks)\n");
|
|
17503
|
+
console.log(" " + JSON.stringify({
|
|
17504
|
+
matcher: "Bash",
|
|
17505
|
+
hooks: [{ type: "command", command: CODE_INTEL_HOOK_CMD }]
|
|
17506
|
+
}, null, 2).split("\n").join("\n "));
|
|
17507
|
+
}
|
|
17508
|
+
const cursorResult = installCursorHook();
|
|
17509
|
+
if (cursorResult.result === "installed") {
|
|
17510
|
+
console.log("\n \u2705 Cursor hook installed");
|
|
17511
|
+
console.log(" grep/rg/cat on source files \u2192 code-intel search/inspect");
|
|
17512
|
+
} else if (cursorResult.result === "already-present") {
|
|
17513
|
+
console.log("\n \u2705 Cursor hook already installed");
|
|
17514
|
+
} else {
|
|
17515
|
+
console.log(`
|
|
17516
|
+
\u2139 Cursor hook skipped: ${cursorResult.reason}`);
|
|
17517
|
+
}
|
|
17518
|
+
const geminiResult = installGeminiHook();
|
|
17519
|
+
if (geminiResult.result === "installed") {
|
|
17520
|
+
console.log("\n \u2705 Gemini CLI hook installed");
|
|
17521
|
+
console.log(" grep/rg/cat on source files \u2192 code-intel search/inspect");
|
|
17522
|
+
} else if (geminiResult.result === "already-present") {
|
|
17523
|
+
console.log("\n \u2705 Gemini CLI hook already installed");
|
|
17524
|
+
} else {
|
|
17525
|
+
console.log(`
|
|
17526
|
+
\u2139 Gemini CLI hook skipped: ${geminiResult.reason}`);
|
|
17527
|
+
}
|
|
17528
|
+
const copilotResult = installCopilotHook(process.cwd());
|
|
17529
|
+
if (copilotResult.result === "installed") {
|
|
17530
|
+
console.log("\n \u2705 GitHub Copilot hook installed (.github/hooks/code-intel-rewrite.json)");
|
|
17531
|
+
console.log(" grep/rg/cat on source files \u2192 code-intel search/inspect");
|
|
17532
|
+
} else if (copilotResult.result === "already-present") {
|
|
17533
|
+
console.log("\n \u2705 GitHub Copilot hook already installed");
|
|
17534
|
+
} else {
|
|
17535
|
+
console.log(`
|
|
17536
|
+
\u2139 GitHub Copilot hook skipped: ${copilotResult.reason}`);
|
|
17537
|
+
}
|
|
17538
|
+
const openCodeResult = installOpenCodePlugin();
|
|
17539
|
+
if (openCodeResult.result === "installed") {
|
|
17540
|
+
console.log("\n \u2705 OpenCode plugin installed (~/.config/opencode/plugins/code-intel.ts)");
|
|
17541
|
+
console.log(" grep/rg/cat on source files \u2192 code-intel search/inspect");
|
|
17542
|
+
} else if (openCodeResult.result === "already-present") {
|
|
17543
|
+
console.log("\n \u2705 OpenCode plugin already installed");
|
|
17544
|
+
} else {
|
|
17545
|
+
console.log(`
|
|
17546
|
+
\u2139 OpenCode plugin skipped: ${openCodeResult.reason}`);
|
|
17547
|
+
}
|
|
17548
|
+
const openClawResult = installOpenClawPlugin();
|
|
17549
|
+
if (openClawResult.result === "installed") {
|
|
17550
|
+
console.log("\n \u2705 OpenClaw plugin installed (~/.openclaw/extensions/code-intel/)");
|
|
17551
|
+
console.log(" grep/rg/cat on source files \u2192 code-intel search/inspect");
|
|
17552
|
+
} else if (openClawResult.result === "already-present") {
|
|
17553
|
+
console.log("\n \u2705 OpenClaw plugin already installed");
|
|
17554
|
+
} else {
|
|
17555
|
+
console.log(`
|
|
17556
|
+
\u2139 OpenClaw plugin skipped: ${openClawResult.reason}`);
|
|
17557
|
+
}
|
|
17558
|
+
console.log("\n \u2500\u2500\u2500 Prompt-level agents (rules files) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
17559
|
+
const cwd = process.cwd();
|
|
17560
|
+
const clineResult = installRulesFile(path39.join(cwd, ".clinerules"), "Cline");
|
|
17561
|
+
if (clineResult.result === "installed") {
|
|
17562
|
+
console.log(" \u2705 Cline/Roo Code rules installed (.clinerules)");
|
|
17563
|
+
} else if (clineResult.result === "already-present") {
|
|
17564
|
+
console.log(" \u2705 Cline/Roo Code rules already present (.clinerules)");
|
|
17565
|
+
} else {
|
|
17566
|
+
console.log(` \u2139 Cline/Roo Code rules skipped: ${clineResult.reason}`);
|
|
17567
|
+
}
|
|
17568
|
+
const windsurfResult = installRulesFile(path39.join(cwd, ".windsurfrules"), "Windsurf");
|
|
17569
|
+
if (windsurfResult.result === "installed") {
|
|
17570
|
+
console.log(" \u2705 Windsurf rules installed (.windsurfrules)");
|
|
17571
|
+
} else if (windsurfResult.result === "already-present") {
|
|
17572
|
+
console.log(" \u2705 Windsurf rules already present (.windsurfrules)");
|
|
17573
|
+
} else {
|
|
17574
|
+
console.log(` \u2139 Windsurf rules skipped: ${windsurfResult.reason}`);
|
|
17575
|
+
}
|
|
17576
|
+
const kiloResult = installRulesFile(
|
|
17577
|
+
path39.join(cwd, ".kilocode", "rules", "code-intel-rules.md"),
|
|
17578
|
+
"Kilo Code"
|
|
17579
|
+
);
|
|
17580
|
+
if (kiloResult.result === "installed") {
|
|
17581
|
+
console.log(" \u2705 Kilo Code rules installed (.kilocode/rules/code-intel-rules.md)");
|
|
17582
|
+
} else if (kiloResult.result === "already-present") {
|
|
17583
|
+
console.log(" \u2705 Kilo Code rules already present");
|
|
17584
|
+
} else {
|
|
17585
|
+
console.log(` \u2139 Kilo Code rules skipped: ${kiloResult.reason}`);
|
|
17586
|
+
}
|
|
17587
|
+
const antigravityResult = installRulesFile(
|
|
17588
|
+
path39.join(cwd, ".agents", "rules", "code-intel-rules.md"),
|
|
17589
|
+
"Antigravity"
|
|
17590
|
+
);
|
|
17591
|
+
if (antigravityResult.result === "installed") {
|
|
17592
|
+
console.log(" \u2705 Google Antigravity rules installed (.agents/rules/code-intel-rules.md)");
|
|
17593
|
+
} else if (antigravityResult.result === "already-present") {
|
|
17594
|
+
console.log(" \u2705 Google Antigravity rules already present");
|
|
17595
|
+
} else {
|
|
17596
|
+
console.log(` \u2139 Google Antigravity rules skipped: ${antigravityResult.reason}`);
|
|
17597
|
+
}
|
|
17598
|
+
const codexResult = installRulesFile(path39.join(cwd, "AGENTS.md"), "Codex");
|
|
17599
|
+
if (codexResult.result === "installed") {
|
|
17600
|
+
console.log(" \u2705 Codex CLI rules appended (AGENTS.md)");
|
|
17601
|
+
} else if (codexResult.result === "already-present") {
|
|
17602
|
+
console.log(" \u2705 Codex CLI rules already present (AGENTS.md)");
|
|
17603
|
+
} else {
|
|
17604
|
+
console.log(` \u2139 Codex CLI rules skipped: ${codexResult.reason}`);
|
|
17605
|
+
}
|
|
17606
|
+
console.log("\n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
17607
|
+
console.log("\n \u25C8 Setup complete. Run `code-intel analyze` to build the knowledge graph.\n");
|
|
16274
17608
|
});
|
|
16275
|
-
program.command("analyze").description("Index a repository and build the knowledge graph").argument("[path]", "Path to the repository (default: current directory)", ".").option("--force", "Force full re-index, ignoring cached data").option("--incremental", "Only re-parse files changed since last analysis (git diff or mtime)").option("--parallel", "Use worker threads for parse + resolve phases (faster on multi-core)").option("--skills", "Generate .claude/skills/ SKILL.md files from detected clusters").option("--embeddings", "Build vector embeddings for semantic search (slower, recommended)").option("--skip-embeddings", "Skip embedding generation (faster, text-search only)").option("--skip-agents-md", "Preserve any custom edits inside AGENTS.md / CLAUDE.md").option("--skip-git", "Allow indexing directories that are not Git repositories").option("--verbose", "Log every file skipped due to missing parser support").option("--summarize", "Generate AI summaries for function/class/method/interface nodes (opt-in)").option("--llm-provider <provider>", "LLM provider for --summarize: openai | anthropic | ollama (default: ollama)").option("--llm-model <model>", "LLM model name (e.g. gpt-4o-mini, claude-haiku-4-5, llama3)").option("--llm-batch-size <n>", "Concurrent LLM calls per batch (default: 20)", "20").option("--llm-max-nodes <n>", "Max nodes to summarize per run (cost guard)").option("--no-group-sync", "Skip automatic group sync after analysis").option("--dry-run", "Preview files that would be scanned + estimated time; no DB write").option("--max-memory <MB>", "Limit graph memory (MB); spill node content to free RAM when exceeded").option("--profile", "Write per-phase profiling data to .code-intel/profile.json").addHelpText("after", `
|
|
17609
|
+
program.command("analyze").description("Index a repository and build the knowledge graph").argument("[path]", "Path to the repository (default: current directory)", ".").option("--force", "Force full re-index, ignoring cached data").option("--incremental", "Only re-parse files changed since last analysis (git diff or mtime)").option("--parallel", "Use worker threads for parse + resolve phases (faster on multi-core)").option("--skills", "Generate .claude/skills/ SKILL.md files from detected clusters").option("--embeddings", "Build vector embeddings for semantic search (slower, recommended)").option("--skip-embeddings", "Skip embedding generation (faster, text-search only)").option("--skip-agents-md", "Preserve any custom edits inside AGENTS.md / CLAUDE.md").option("--skip-git", "Allow indexing directories that are not Git repositories").option("--verbose", "Log every file skipped due to missing parser support").option("--summarize", "Generate AI summaries for function/class/method/interface nodes (opt-in)").option("--llm-provider <provider>", "LLM provider for --summarize: openai | anthropic | ollama | custom (default: ollama)").option("--llm-model <model>", "LLM model name (e.g. gpt-4o-mini, claude-haiku-4-5, llama3, mistral)").option("--llm-base-url <url>", "Base URL for custom OpenAI-compatible API (e.g. http://localhost:1234/v1)").option("--llm-api-key <key>", "API key/token for the LLM provider (custom or override)").option("--llm-batch-size <n>", "Concurrent LLM calls per batch (default: 20)", "20").option("--llm-max-nodes <n>", "Max nodes to summarize per run (cost guard)").option("--no-group-sync", "Skip automatic group sync after analysis").option("--dry-run", "Preview files that would be scanned + estimated time; no DB write").option("--max-memory <MB>", "Limit graph memory (MB); spill node content to free RAM when exceeded").option("--profile", "Write per-phase profiling data to .code-intel/profile.json").addHelpText("after", `
|
|
16276
17610
|
Parses your source code with tree-sitter, builds a Knowledge Graph of
|
|
16277
17611
|
symbols and their relationships, persists it to .code-intel/graph.db,
|
|
16278
17612
|
and auto-generates AGENTS.md + CLAUDE.md context blocks.
|
|
@@ -16292,6 +17626,7 @@ program.command("analyze").description("Index a repository and build the knowled
|
|
|
16292
17626
|
$ code-intel analyze --summarize Generate AI summaries (uses Ollama by default)
|
|
16293
17627
|
$ code-intel analyze --summarize --llm-provider openai --llm-model gpt-4o-mini
|
|
16294
17628
|
$ code-intel analyze --summarize --llm-provider anthropic --llm-max-nodes 500
|
|
17629
|
+
$ code-intel analyze --summarize --llm-provider custom --llm-base-url http://localhost:1234/v1 --llm-model mistral --llm-api-key mytoken
|
|
16295
17630
|
$ code-intel analyze --dry-run Preview files that would be scanned
|
|
16296
17631
|
`).action(async (targetPath, opts) => {
|
|
16297
17632
|
if (opts.dryRun) {
|
|
@@ -16338,6 +17673,8 @@ program.command("analyze").description("Index a repository and build the knowled
|
|
|
16338
17673
|
const v = parseInt(opts.llmMaxNodes ?? "", 10);
|
|
16339
17674
|
return Number.isFinite(v) && v >= 1 ? v : void 0;
|
|
16340
17675
|
})(),
|
|
17676
|
+
llmBaseUrl: opts.llmBaseUrl,
|
|
17677
|
+
llmApiKey: opts.llmApiKey,
|
|
16341
17678
|
maxMemoryMB: (() => {
|
|
16342
17679
|
const v = parseInt(opts.maxMemory ?? "", 10);
|
|
16343
17680
|
return Number.isFinite(v) && v >= 1 ? v : void 0;
|
|
@@ -16360,11 +17697,11 @@ program.command("mcp").description("Start MCP server over stdio \u2014 exposes a
|
|
|
16360
17697
|
const workspaceRoot = path39.resolve(targetPath);
|
|
16361
17698
|
const repoName = path39.basename(workspaceRoot);
|
|
16362
17699
|
const dbPath = getDbPath(workspaceRoot);
|
|
16363
|
-
if (!
|
|
17700
|
+
if (!fs39.existsSync(workspaceRoot) || !fs39.statSync(workspaceRoot).isDirectory()) {
|
|
16364
17701
|
console.error(` \u2717 Path does not exist: ${workspaceRoot}`);
|
|
16365
17702
|
process.exit(1);
|
|
16366
17703
|
}
|
|
16367
|
-
const existingIndex =
|
|
17704
|
+
const existingIndex = fs39.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
16368
17705
|
if (existingIndex) {
|
|
16369
17706
|
const graph = createKnowledgeGraph();
|
|
16370
17707
|
const db = new DbManager(dbPath, true);
|
|
@@ -16377,11 +17714,18 @@ program.command("mcp").description("Start MCP server over stdio \u2014 exposes a
|
|
|
16377
17714
|
await startMcpStdio(graph, name, root);
|
|
16378
17715
|
}
|
|
16379
17716
|
});
|
|
16380
|
-
program.command("serve").description("Start the local HTTP server + web UI for graph exploration").argument("[path]", "Path to analyze (default: current directory)", ".").option("-p, --port <port>", "Port to listen on", "4747").option("--force", "Force re-analysis even if an index already exists").addHelpText("after", `
|
|
17717
|
+
program.command("serve").description("Start the local HTTP server + web UI for graph exploration").argument("[path]", "Path to analyze (default: current directory)", ".").option("-p, --port <port>", "Port to listen on", "4747").option("--force", "Force re-analysis even if an index already exists").option("-d, --detach", "Run server in background (detached); PID written to .code-intel/serve.pid").addHelpText("after", `
|
|
16381
17718
|
If a .code-intel/graph.db index already exists for the path, the server
|
|
16382
17719
|
loads the persisted graph directly and starts immediately \u2014 no re-analysis.
|
|
16383
17720
|
Use --force to discard the existing index and re-analyze from scratch.
|
|
16384
17721
|
|
|
17722
|
+
Background mode:
|
|
17723
|
+
$ code-intel serve --detach # start in background
|
|
17724
|
+
$ code-intel serve --detach --port 8080
|
|
17725
|
+
$ code-intel stop # stop the background server
|
|
17726
|
+
|
|
17727
|
+
PID file: <path>/.code-intel/serve.pid
|
|
17728
|
+
|
|
16385
17729
|
The web UI offers:
|
|
16386
17730
|
\xB7 Force-directed Knowledge Graph with color-coded node types
|
|
16387
17731
|
\xB7 BM25 text search + optional semantic (vector) search
|
|
@@ -16394,15 +17738,54 @@ program.command("serve").description("Start the local HTTP server + web UI for g
|
|
|
16394
17738
|
$ code-intel serve ./my-project
|
|
16395
17739
|
$ code-intel serve --port 8080
|
|
16396
17740
|
$ code-intel serve --force
|
|
17741
|
+
$ code-intel serve --detach
|
|
16397
17742
|
`).action(async (targetPath, options) => {
|
|
16398
17743
|
const workspaceRoot = path39.resolve(targetPath);
|
|
16399
17744
|
const repoName = path39.basename(workspaceRoot);
|
|
16400
17745
|
const dbPath = getDbPath(workspaceRoot);
|
|
16401
|
-
if (!
|
|
17746
|
+
if (!fs39.existsSync(workspaceRoot) || !fs39.statSync(workspaceRoot).isDirectory()) {
|
|
16402
17747
|
console.error(` \u2717 Path does not exist: ${workspaceRoot}`);
|
|
16403
17748
|
process.exit(1);
|
|
16404
17749
|
}
|
|
16405
|
-
|
|
17750
|
+
if (options.detach) {
|
|
17751
|
+
const { spawn } = await import('child_process');
|
|
17752
|
+
const pidDir = path39.join(workspaceRoot, ".code-intel");
|
|
17753
|
+
const pidFile = path39.join(pidDir, "serve.pid");
|
|
17754
|
+
if (fs39.existsSync(pidFile)) {
|
|
17755
|
+
const existingPid = parseInt(fs39.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
17756
|
+
try {
|
|
17757
|
+
process.kill(existingPid, 0);
|
|
17758
|
+
console.log(` \u26A0 Server already running (PID ${existingPid}). Stop it first with: code-intel stop`);
|
|
17759
|
+
process.exit(0);
|
|
17760
|
+
} catch {
|
|
17761
|
+
fs39.unlinkSync(pidFile);
|
|
17762
|
+
}
|
|
17763
|
+
}
|
|
17764
|
+
const selfArgs = ["serve", targetPath, "--port", options.port];
|
|
17765
|
+
if (options.force) selfArgs.push("--force");
|
|
17766
|
+
const logDir = path39.join(os18.homedir(), ".code-intel", "logs");
|
|
17767
|
+
fs39.mkdirSync(logDir, { recursive: true });
|
|
17768
|
+
const logFile = path39.join(logDir, `serve-${Date.now()}.log`);
|
|
17769
|
+
const logFd = fs39.openSync(logFile, "a");
|
|
17770
|
+
const child = spawn(process.execPath, [process.argv[1], ...selfArgs], {
|
|
17771
|
+
detached: true,
|
|
17772
|
+
stdio: ["ignore", logFd, logFd],
|
|
17773
|
+
env: process.env
|
|
17774
|
+
});
|
|
17775
|
+
child.unref();
|
|
17776
|
+
fs39.mkdirSync(pidDir, { recursive: true });
|
|
17777
|
+
fs39.writeFileSync(pidFile, String(child.pid), "utf-8");
|
|
17778
|
+
console.log(` \u2705 Server started in background`);
|
|
17779
|
+
console.log(` PID : ${child.pid}`);
|
|
17780
|
+
console.log(` Port : ${options.port}`);
|
|
17781
|
+
console.log(` URL : http://localhost:${options.port}`);
|
|
17782
|
+
console.log(` Log : ${logFile}`);
|
|
17783
|
+
console.log(` PID file : ${pidFile}`);
|
|
17784
|
+
console.log(`
|
|
17785
|
+
Stop with: code-intel stop`);
|
|
17786
|
+
process.exit(0);
|
|
17787
|
+
}
|
|
17788
|
+
const existingIndex = !options.force && fs39.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
16406
17789
|
if (existingIndex) {
|
|
16407
17790
|
const meta = loadMetadata(workspaceRoot);
|
|
16408
17791
|
if (meta.parser === "regex" || meta.parser === void 0) {
|
|
@@ -16421,10 +17804,81 @@ program.command("serve").description("Start the local HTTP server + web UI for g
|
|
|
16421
17804
|
await startHttpServer(lazyGraph, repoName, parseInt(options.port, 10), workspaceRoot);
|
|
16422
17805
|
}
|
|
16423
17806
|
} else {
|
|
16424
|
-
|
|
16425
|
-
|
|
17807
|
+
console.log(` \u2139 No index found for: ${workspaceRoot}`);
|
|
17808
|
+
const registry = loadRegistry();
|
|
17809
|
+
const available = registry.filter((r) => fs39.existsSync(getDbPath(r.path)) && loadMetadata(r.path) !== null).sort((a, b) => new Date(b.indexedAt).getTime() - new Date(a.indexedAt).getTime());
|
|
17810
|
+
if (available.length > 0) {
|
|
17811
|
+
const fallback = available[0];
|
|
17812
|
+
console.log(` \u25C8 Falling back to most recently indexed repo: ${fallback.path}`);
|
|
17813
|
+
console.log(` (${fallback.stats.nodes} nodes \xB7 ${fallback.stats.edges} edges \xB7 indexed ${fallback.indexedAt})`);
|
|
17814
|
+
console.log(` \u2139 To index this folder run: code-intel analyze
|
|
17815
|
+
`);
|
|
17816
|
+
const fallbackMeta = loadMetadata(fallback.path);
|
|
17817
|
+
const fallbackDbPath = getDbPath(fallback.path);
|
|
17818
|
+
if (fallbackMeta.parser === "regex" || fallbackMeta.parser === void 0) {
|
|
17819
|
+
console.log(` \u26A0 Fallback index was built with regex parser. Starting with empty graph.`);
|
|
17820
|
+
const emptyGraph = createKnowledgeGraph();
|
|
17821
|
+
await startHttpServer(emptyGraph, fallback.name, parseInt(options.port, 10), fallback.path);
|
|
17822
|
+
} else {
|
|
17823
|
+
const lazyGraph = new LazyKnowledgeGraph();
|
|
17824
|
+
const db = new DbManager(fallbackDbPath, true);
|
|
17825
|
+
await db.init();
|
|
17826
|
+
await lazyGraph.init(db, fallbackMeta.stats.nodes, fallbackMeta.stats.edges);
|
|
17827
|
+
setImmediate(() => lazyGraph.warmTopNodes(500).catch(() => {
|
|
17828
|
+
}));
|
|
17829
|
+
await startHttpServer(lazyGraph, fallback.name, parseInt(options.port, 10), fallback.path);
|
|
17830
|
+
}
|
|
17831
|
+
if (available.length > 1) {
|
|
17832
|
+
console.log(`
|
|
17833
|
+
Other indexed repos available (switch via web UI or re-run serve with path):`);
|
|
17834
|
+
for (const r of available.slice(1)) {
|
|
17835
|
+
console.log(` code-intel serve ${r.path}`);
|
|
17836
|
+
}
|
|
17837
|
+
}
|
|
17838
|
+
} else {
|
|
17839
|
+
console.log(` \u2139 No indexed repositories found. Starting server with empty graph.`);
|
|
17840
|
+
console.log(` Run \`code-intel analyze\` to index a repository, then reload the UI.
|
|
17841
|
+
`);
|
|
17842
|
+
const emptyGraph = createKnowledgeGraph();
|
|
17843
|
+
await startHttpServer(emptyGraph, repoName, parseInt(options.port, 10), workspaceRoot);
|
|
17844
|
+
}
|
|
17845
|
+
}
|
|
17846
|
+
});
|
|
17847
|
+
program.command("stop").description("Stop a background server started with `code-intel serve --detach`").argument("[path]", "Project path whose server to stop (default: current directory)", ".").addHelpText("after", `
|
|
17848
|
+
Reads the PID from <path>/.code-intel/serve.pid and sends SIGTERM.
|
|
17849
|
+
The PID file is removed automatically after a successful stop.
|
|
17850
|
+
|
|
17851
|
+
Examples:
|
|
17852
|
+
$ code-intel stop
|
|
17853
|
+
$ code-intel stop ./my-project
|
|
17854
|
+
`).action((targetPath) => {
|
|
17855
|
+
const workspaceRoot = path39.resolve(targetPath);
|
|
17856
|
+
const pidFile = path39.join(workspaceRoot, ".code-intel", "serve.pid");
|
|
17857
|
+
if (!fs39.existsSync(pidFile)) {
|
|
17858
|
+
console.log(` \u2139 No background server found for: ${workspaceRoot}`);
|
|
17859
|
+
console.log(` (PID file not present: ${pidFile})`);
|
|
17860
|
+
process.exit(0);
|
|
17861
|
+
}
|
|
17862
|
+
const pid = parseInt(fs39.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
17863
|
+
if (isNaN(pid)) {
|
|
17864
|
+
console.error(` \u2717 Invalid PID file: ${pidFile}`);
|
|
17865
|
+
fs39.unlinkSync(pidFile);
|
|
16426
17866
|
process.exit(1);
|
|
16427
17867
|
}
|
|
17868
|
+
try {
|
|
17869
|
+
process.kill(pid, "SIGTERM");
|
|
17870
|
+
fs39.unlinkSync(pidFile);
|
|
17871
|
+
console.log(` \u2705 Stopped server (PID ${pid})`);
|
|
17872
|
+
} catch (err) {
|
|
17873
|
+
const code = err.code;
|
|
17874
|
+
if (code === "ESRCH") {
|
|
17875
|
+
fs39.unlinkSync(pidFile);
|
|
17876
|
+
console.log(` \u2139 Server (PID ${pid}) was not running. Stale PID file removed.`);
|
|
17877
|
+
} else {
|
|
17878
|
+
console.error(` \u2717 Failed to stop server (PID ${pid}): ${err.message}`);
|
|
17879
|
+
process.exit(1);
|
|
17880
|
+
}
|
|
17881
|
+
}
|
|
16428
17882
|
});
|
|
16429
17883
|
program.command("watch").description("Start HTTP server + file watcher (auto-reindex on file changes)").argument("[path]", "Path to watch (default: current directory)", ".").option("-p, --port <port>", "Port to listen on", "4747").option("--force", "Force re-analysis even if an index already exists").addHelpText("after", `
|
|
16430
17884
|
Starts the HTTP server and automatically re-indexes changed files.
|
|
@@ -16440,11 +17894,11 @@ program.command("watch").description("Start HTTP server + file watcher (auto-rei
|
|
|
16440
17894
|
const workspaceRoot = path39.resolve(targetPath);
|
|
16441
17895
|
const repoName = path39.basename(workspaceRoot);
|
|
16442
17896
|
const dbPath = getDbPath(workspaceRoot);
|
|
16443
|
-
if (!
|
|
17897
|
+
if (!fs39.existsSync(workspaceRoot) || !fs39.statSync(workspaceRoot).isDirectory()) {
|
|
16444
17898
|
console.error(` \u2717 Path does not exist: ${workspaceRoot}`);
|
|
16445
17899
|
process.exit(1);
|
|
16446
17900
|
}
|
|
16447
|
-
const existingIndex = !options.force &&
|
|
17901
|
+
const existingIndex = !options.force && fs39.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
16448
17902
|
let graph;
|
|
16449
17903
|
if (existingIndex) {
|
|
16450
17904
|
const meta = loadMetadata(workspaceRoot);
|
|
@@ -16564,16 +18018,16 @@ function trashDirName(repoPath) {
|
|
|
16564
18018
|
}
|
|
16565
18019
|
function softDeleteCodeIntel(repoPath) {
|
|
16566
18020
|
const codeIntelDir = path39.join(repoPath, ".code-intel");
|
|
16567
|
-
if (!
|
|
18021
|
+
if (!fs39.existsSync(codeIntelDir)) return;
|
|
16568
18022
|
const trashName = trashDirName();
|
|
16569
18023
|
const trashDir = path39.join(repoPath, trashName);
|
|
16570
18024
|
let dest = trashDir;
|
|
16571
18025
|
let counter = 1;
|
|
16572
|
-
while (
|
|
18026
|
+
while (fs39.existsSync(dest)) {
|
|
16573
18027
|
dest = `${trashDir}-${counter++}`;
|
|
16574
18028
|
}
|
|
16575
|
-
|
|
16576
|
-
|
|
18029
|
+
fs39.renameSync(codeIntelDir, dest);
|
|
18030
|
+
fs39.writeFileSync(
|
|
16577
18031
|
path39.join(dest, "TRASH_META.json"),
|
|
16578
18032
|
JSON.stringify({ deletedAt: (/* @__PURE__ */ new Date()).toISOString(), repoPath, permanent: false }, null, 2)
|
|
16579
18033
|
);
|
|
@@ -16583,15 +18037,15 @@ function softDeleteCodeIntel(repoPath) {
|
|
|
16583
18037
|
function purgeStaleTrashes(repoPath) {
|
|
16584
18038
|
const cutoff = Date.now() - TRASH_TTL_DAYS * 24 * 60 * 60 * 1e3;
|
|
16585
18039
|
try {
|
|
16586
|
-
for (const entry of
|
|
18040
|
+
for (const entry of fs39.readdirSync(repoPath)) {
|
|
16587
18041
|
if (!entry.startsWith(".code-intel-trash-")) continue;
|
|
16588
18042
|
const fullPath = path39.join(repoPath, entry);
|
|
16589
18043
|
const metaPath = path39.join(fullPath, "TRASH_META.json");
|
|
16590
|
-
if (
|
|
18044
|
+
if (fs39.existsSync(metaPath)) {
|
|
16591
18045
|
try {
|
|
16592
|
-
const meta = JSON.parse(
|
|
18046
|
+
const meta = JSON.parse(fs39.readFileSync(metaPath, "utf-8"));
|
|
16593
18047
|
if (new Date(meta.deletedAt).getTime() < cutoff) {
|
|
16594
|
-
|
|
18048
|
+
fs39.rmSync(fullPath, { recursive: true, force: true });
|
|
16595
18049
|
console.log(` \u2713 Auto-purged stale trash: ${fullPath}`);
|
|
16596
18050
|
}
|
|
16597
18051
|
} catch {
|
|
@@ -16618,18 +18072,18 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
16618
18072
|
if (opts.dryRun) {
|
|
16619
18073
|
const showDryRun = (repoPath) => {
|
|
16620
18074
|
const codeIntelDir = path39.join(path39.resolve(repoPath), ".code-intel");
|
|
16621
|
-
if (!
|
|
18075
|
+
if (!fs39.existsSync(codeIntelDir)) {
|
|
16622
18076
|
console.log(` (no .code-intel/ found at ${repoPath})`);
|
|
16623
18077
|
return;
|
|
16624
18078
|
}
|
|
16625
18079
|
let totalBytes = 0;
|
|
16626
18080
|
const countDir = (dir) => {
|
|
16627
18081
|
try {
|
|
16628
|
-
for (const entry of
|
|
18082
|
+
for (const entry of fs39.readdirSync(dir, { withFileTypes: true })) {
|
|
16629
18083
|
const full = path39.join(dir, entry.name);
|
|
16630
18084
|
if (entry.isDirectory()) countDir(full);
|
|
16631
18085
|
else try {
|
|
16632
|
-
totalBytes +=
|
|
18086
|
+
totalBytes += fs39.statSync(full).size;
|
|
16633
18087
|
} catch {
|
|
16634
18088
|
}
|
|
16635
18089
|
}
|
|
@@ -16666,14 +18120,14 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
16666
18120
|
let found = 0;
|
|
16667
18121
|
for (const root of roots) {
|
|
16668
18122
|
try {
|
|
16669
|
-
for (const entry of
|
|
18123
|
+
for (const entry of fs39.readdirSync(root)) {
|
|
16670
18124
|
if (!entry.startsWith(".code-intel-trash-")) continue;
|
|
16671
18125
|
const fullPath = path39.join(root, entry);
|
|
16672
18126
|
const metaPath = path39.join(fullPath, "TRASH_META.json");
|
|
16673
18127
|
let deletedAt = "unknown";
|
|
16674
|
-
if (
|
|
18128
|
+
if (fs39.existsSync(metaPath)) {
|
|
16675
18129
|
try {
|
|
16676
|
-
deletedAt = JSON.parse(
|
|
18130
|
+
deletedAt = JSON.parse(fs39.readFileSync(metaPath, "utf-8")).deletedAt;
|
|
16677
18131
|
} catch {
|
|
16678
18132
|
}
|
|
16679
18133
|
}
|
|
@@ -16702,8 +18156,8 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
16702
18156
|
for (const r of repos) {
|
|
16703
18157
|
if (opts.purge) {
|
|
16704
18158
|
const codeIntelDir = path39.join(r.path, ".code-intel");
|
|
16705
|
-
if (
|
|
16706
|
-
|
|
18159
|
+
if (fs39.existsSync(codeIntelDir)) {
|
|
18160
|
+
fs39.rmSync(codeIntelDir, { recursive: true, force: true });
|
|
16707
18161
|
console.log(` \u2713 Hard-deleted ${codeIntelDir}`);
|
|
16708
18162
|
}
|
|
16709
18163
|
} else {
|
|
@@ -16720,8 +18174,8 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
16720
18174
|
const workspaceRoot = path39.resolve(targetPath);
|
|
16721
18175
|
if (opts.purge) {
|
|
16722
18176
|
const codeIntelDir = path39.join(workspaceRoot, ".code-intel");
|
|
16723
|
-
if (
|
|
16724
|
-
|
|
18177
|
+
if (fs39.existsSync(codeIntelDir)) {
|
|
18178
|
+
fs39.rmSync(codeIntelDir, { recursive: true, force: true });
|
|
16725
18179
|
console.log(`
|
|
16726
18180
|
\u2713 Hard-deleted ${codeIntelDir}`);
|
|
16727
18181
|
}
|
|
@@ -16735,7 +18189,7 @@ program.command("clean").description("Soft-delete the knowledge graph index for
|
|
|
16735
18189
|
async function loadOrAnalyzeWorkspace(targetPath) {
|
|
16736
18190
|
const workspaceRoot = path39.resolve(targetPath);
|
|
16737
18191
|
const dbPath = getDbPath(workspaceRoot);
|
|
16738
|
-
const existingIndex =
|
|
18192
|
+
const existingIndex = fs39.existsSync(dbPath) && loadMetadata(workspaceRoot) !== null;
|
|
16739
18193
|
if (existingIndex) {
|
|
16740
18194
|
const graph = createKnowledgeGraph();
|
|
16741
18195
|
const db = new DbManager(dbPath, true);
|
|
@@ -17223,7 +18677,7 @@ groupCmd.command("status <name>").description("Check index freshness and sync st
|
|
|
17223
18677
|
}
|
|
17224
18678
|
const metaPath = path39.join(regEntry.path, ".code-intel", "meta.json");
|
|
17225
18679
|
try {
|
|
17226
|
-
const meta = JSON.parse(
|
|
18680
|
+
const meta = JSON.parse(fs39.readFileSync(metaPath, "utf-8"));
|
|
17227
18681
|
const indexedAt = meta.indexedAt;
|
|
17228
18682
|
const ageMin = Math.round((now - new Date(indexedAt).getTime()) / 6e4);
|
|
17229
18683
|
const stale = ageMin > 1440 ? " \u26A0 STALE (>24h)" : "";
|
|
@@ -17557,7 +19011,7 @@ backupCmd.command("list").description("List all available backups").action(() =>
|
|
|
17557
19011
|
Backups (${entries.length}):
|
|
17558
19012
|
`);
|
|
17559
19013
|
for (const e of entries) {
|
|
17560
|
-
const exists =
|
|
19014
|
+
const exists = fs39.existsSync(e.path);
|
|
17561
19015
|
const status = exists ? "\u2713" : "\u2717 (missing)";
|
|
17562
19016
|
console.log(` ${status} ${e.id.slice(0, 8)} ${e.createdAt} ${(e.size / 1024).toFixed(1)} KB \u2192 ${e.repoPath}`);
|
|
17563
19017
|
}
|
|
@@ -17589,8 +19043,8 @@ program.command("migrate").description("Manage database schema migrations").opti
|
|
|
17589
19043
|
$ code-intel migrate
|
|
17590
19044
|
$ code-intel migrate --rollback
|
|
17591
19045
|
`).action((opts) => {
|
|
17592
|
-
const dbPath = opts.db ?? path39.join(
|
|
17593
|
-
if (!
|
|
19046
|
+
const dbPath = opts.db ?? path39.join(os18.homedir(), ".code-intel", "users.db");
|
|
19047
|
+
if (!fs39.existsSync(dbPath)) {
|
|
17594
19048
|
console.error(`
|
|
17595
19049
|
\u2717 Database not found: ${dbPath}
|
|
17596
19050
|
Run \`code-intel serve\` or \`code-intel user create\` first.
|
|
@@ -17705,15 +19159,15 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
|
|
|
17705
19159
|
}
|
|
17706
19160
|
try {
|
|
17707
19161
|
const tokens = await pollDeviceFlow3(config, deviceResponse);
|
|
17708
|
-
const tokenPath = path39.join(
|
|
19162
|
+
const tokenPath = path39.join(os18.homedir(), ".code-intel", "oidc-token.json");
|
|
17709
19163
|
const tokenData = {
|
|
17710
19164
|
accessToken: tokens.accessToken,
|
|
17711
19165
|
refreshToken: tokens.refreshToken,
|
|
17712
19166
|
server: serverUrl,
|
|
17713
19167
|
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
17714
19168
|
};
|
|
17715
|
-
|
|
17716
|
-
|
|
19169
|
+
fs39.mkdirSync(path39.dirname(tokenPath), { recursive: true });
|
|
19170
|
+
fs39.writeFileSync(tokenPath, JSON.stringify(tokenData, null, 2), { mode: 384 });
|
|
17717
19171
|
console.log(` \u2705 Authenticated successfully!`);
|
|
17718
19172
|
console.log(` Token stored at: ${tokenPath}`);
|
|
17719
19173
|
console.log(` Use CODE_INTEL_TOKEN env var or --token flag to use it with CLI/MCP.
|
|
@@ -17726,13 +19180,13 @@ authCmd.command("login").description("Authenticate via OIDC device flow \u2014 o
|
|
|
17726
19180
|
}
|
|
17727
19181
|
});
|
|
17728
19182
|
authCmd.command("status").description("Show the current OIDC authentication status").action(() => {
|
|
17729
|
-
const tokenPath = path39.join(
|
|
17730
|
-
if (!
|
|
19183
|
+
const tokenPath = path39.join(os18.homedir(), ".code-intel", "oidc-token.json");
|
|
19184
|
+
if (!fs39.existsSync(tokenPath)) {
|
|
17731
19185
|
console.log("\n Not authenticated via OIDC. Run: code-intel auth login\n");
|
|
17732
19186
|
return;
|
|
17733
19187
|
}
|
|
17734
19188
|
try {
|
|
17735
|
-
const data = JSON.parse(
|
|
19189
|
+
const data = JSON.parse(fs39.readFileSync(tokenPath, "utf-8"));
|
|
17736
19190
|
console.log(`
|
|
17737
19191
|
\u2705 OIDC token stored`);
|
|
17738
19192
|
console.log(` Server : ${data.server ?? "unknown"}`);
|
|
@@ -17744,9 +19198,9 @@ authCmd.command("status").description("Show the current OIDC authentication stat
|
|
|
17744
19198
|
}
|
|
17745
19199
|
});
|
|
17746
19200
|
authCmd.command("logout").description("Remove locally stored OIDC token").action(() => {
|
|
17747
|
-
const tokenPath = path39.join(
|
|
17748
|
-
if (
|
|
17749
|
-
|
|
19201
|
+
const tokenPath = path39.join(os18.homedir(), ".code-intel", "oidc-token.json");
|
|
19202
|
+
if (fs39.existsSync(tokenPath)) {
|
|
19203
|
+
fs39.unlinkSync(tokenPath);
|
|
17750
19204
|
console.log("\n \u2705 OIDC token removed. You are now logged out.\n");
|
|
17751
19205
|
} else {
|
|
17752
19206
|
console.log("\n No stored token found.\n");
|
|
@@ -17871,7 +19325,7 @@ program.command("config-validate <file>").description("Validate a JSON config fi
|
|
|
17871
19325
|
$ code-intel config-validate ~/.code-intel/config.json
|
|
17872
19326
|
`).action((file) => {
|
|
17873
19327
|
const filePath = path39.resolve(file);
|
|
17874
|
-
if (!
|
|
19328
|
+
if (!fs39.existsSync(filePath)) {
|
|
17875
19329
|
console.error(`
|
|
17876
19330
|
\u2717 File not found: ${filePath}
|
|
17877
19331
|
`);
|
|
@@ -17879,7 +19333,7 @@ program.command("config-validate <file>").description("Validate a JSON config fi
|
|
|
17879
19333
|
}
|
|
17880
19334
|
let cfg;
|
|
17881
19335
|
try {
|
|
17882
|
-
cfg = JSON.parse(
|
|
19336
|
+
cfg = JSON.parse(fs39.readFileSync(filePath, "utf-8"));
|
|
17883
19337
|
} catch (err) {
|
|
17884
19338
|
console.error(`
|
|
17885
19339
|
\u2717 Could not parse JSON: ${err instanceof Error ? err.message : err}
|
|
@@ -17900,7 +19354,7 @@ ${err instanceof Error ? err.message : err}
|
|
|
17900
19354
|
});
|
|
17901
19355
|
(function ensurePermissions() {
|
|
17902
19356
|
try {
|
|
17903
|
-
const dir = path39.join(
|
|
19357
|
+
const dir = path39.join(os18.homedir(), ".code-intel");
|
|
17904
19358
|
secureMkdir(dir);
|
|
17905
19359
|
tightenDbFiles(dir);
|
|
17906
19360
|
} catch {
|
|
@@ -17920,7 +19374,7 @@ program.command("health").description("Run code health checks: dead code, circul
|
|
|
17920
19374
|
const workspaceRoot = path39.resolve(targetPath);
|
|
17921
19375
|
const dbPath = getDbPath(workspaceRoot);
|
|
17922
19376
|
const meta = loadMetadata(workspaceRoot);
|
|
17923
|
-
if (!meta || !
|
|
19377
|
+
if (!meta || !fs39.existsSync(dbPath)) {
|
|
17924
19378
|
console.error(`
|
|
17925
19379
|
\u2717 ${workspaceRoot} is not indexed.`);
|
|
17926
19380
|
console.error(" Run `code-intel analyze` first to build the index.\n");
|
|
@@ -18065,13 +19519,13 @@ program.command("query").description("Execute a GQL (Graph Query Language) query
|
|
|
18065
19519
|
gqlInput = content;
|
|
18066
19520
|
} else if (opts.file) {
|
|
18067
19521
|
const filePath = path39.resolve(opts.file);
|
|
18068
|
-
if (!
|
|
19522
|
+
if (!fs39.existsSync(filePath)) {
|
|
18069
19523
|
console.error(`
|
|
18070
19524
|
\u2717 File not found: ${filePath}
|
|
18071
19525
|
`);
|
|
18072
19526
|
process.exit(1);
|
|
18073
19527
|
}
|
|
18074
|
-
gqlInput =
|
|
19528
|
+
gqlInput = fs39.readFileSync(filePath, "utf-8");
|
|
18075
19529
|
} else if (gqlArg) {
|
|
18076
19530
|
gqlInput = gqlArg;
|
|
18077
19531
|
} else {
|
|
@@ -18599,7 +20053,7 @@ program.command("doctor").description("Run diagnostics \u2014 check Node.js, git
|
|
|
18599
20053
|
continue;
|
|
18600
20054
|
}
|
|
18601
20055
|
const vdbPath = getVectorDbPath2(repo.path);
|
|
18602
|
-
if (
|
|
20056
|
+
if (fs39.existsSync(vdbPath)) {
|
|
18603
20057
|
try {
|
|
18604
20058
|
const Database22 = (await import('better-sqlite3')).default;
|
|
18605
20059
|
const vdb = new Database22(vdbPath, { readonly: true, fileMustExist: true });
|
|
@@ -18636,6 +20090,104 @@ program.command("doctor").description("Run diagnostics \u2014 check Node.js, git
|
|
|
18636
20090
|
console.log(" \u2705 All checks passed.\n");
|
|
18637
20091
|
}
|
|
18638
20092
|
});
|
|
20093
|
+
program.command("context").description("Build a token-efficient context document for a set of seed symbols").argument("<symbols...>", "One or more symbol names to use as seeds").option("-p, --path <path>", "Path to the repository (default: current directory)", ".").option("-l, --limit <n>", "Max seeds to resolve (default: 10)", "10").option("--intent <intent>", "Query intent: code | callers | architecture | auto (default: auto)", "auto").option("--max-tokens <n>", "Max total tokens for output (default: 6000)", "6000").option("--show-context", "Print per-block token breakdown after output").addHelpText("after", `
|
|
20094
|
+
Builds a structured [SUMMARY] / [LOGIC] / [RELATION] / [FOCUS CODE] context
|
|
20095
|
+
document for one or more symbols. Designed to give AI assistants dense,
|
|
20096
|
+
token-efficient context instead of raw file reads.
|
|
20097
|
+
|
|
20098
|
+
Examples:
|
|
20099
|
+
$ code-intel context UserService
|
|
20100
|
+
$ code-intel context createUser login --intent callers
|
|
20101
|
+
$ code-intel context AuthService --show-context
|
|
20102
|
+
$ code-intel context handlePayment --max-tokens 3000 --intent code
|
|
20103
|
+
`).action(async (symbols, opts) => {
|
|
20104
|
+
const { graph } = await loadOrAnalyzeWorkspace(opts.path);
|
|
20105
|
+
const { build: build2, detectQueryIntent: detectQueryIntent2 } = await Promise.resolve().then(() => (init_builder(), builder_exports));
|
|
20106
|
+
const { measureBlocks: measureBlocks2 } = await Promise.resolve().then(() => (init_token_counter(), token_counter_exports));
|
|
20107
|
+
const maxSeeds = parseInt(opts.limit, 10);
|
|
20108
|
+
const maxTokens = parseInt(opts.maxTokens, 10);
|
|
20109
|
+
const intent = ["code", "callers", "architecture", "auto"].includes(opts.intent) ? opts.intent : detectQueryIntent2(opts.intent);
|
|
20110
|
+
const seeds = [];
|
|
20111
|
+
for (const symbol of symbols.slice(0, maxSeeds)) {
|
|
20112
|
+
for (const node of graph.allNodes()) {
|
|
20113
|
+
if (node.name === symbol) {
|
|
20114
|
+
seeds.push({ nodeId: node.id, refinedScore: 1 });
|
|
20115
|
+
break;
|
|
20116
|
+
}
|
|
20117
|
+
}
|
|
20118
|
+
}
|
|
20119
|
+
if (seeds.length === 0) {
|
|
20120
|
+
console.log(`
|
|
20121
|
+
No symbols found for: ${symbols.join(", ")}
|
|
20122
|
+
`);
|
|
20123
|
+
console.log(' Try: code-intel search "<name>" to find symbol names.\n');
|
|
20124
|
+
return;
|
|
20125
|
+
}
|
|
20126
|
+
const doc = build2(seeds, graph, { maxTokens, queryIntent: intent });
|
|
20127
|
+
console.log("");
|
|
20128
|
+
if (doc.summary) {
|
|
20129
|
+
console.log(doc.summary);
|
|
20130
|
+
console.log("");
|
|
20131
|
+
}
|
|
20132
|
+
if (doc.logic) {
|
|
20133
|
+
console.log(doc.logic);
|
|
20134
|
+
console.log("");
|
|
20135
|
+
}
|
|
20136
|
+
if (doc.relation) {
|
|
20137
|
+
console.log(doc.relation);
|
|
20138
|
+
console.log("");
|
|
20139
|
+
}
|
|
20140
|
+
if (doc.focusCode) {
|
|
20141
|
+
console.log(doc.focusCode);
|
|
20142
|
+
console.log("");
|
|
20143
|
+
}
|
|
20144
|
+
if (doc.truncated) {
|
|
20145
|
+
console.log(" \u26A0 Output was truncated due to token budget. Use --max-tokens to increase.");
|
|
20146
|
+
console.log("");
|
|
20147
|
+
}
|
|
20148
|
+
if (opts.showContext) {
|
|
20149
|
+
const counts = measureBlocks2(doc);
|
|
20150
|
+
console.log(" \u2500\u2500 Token breakdown \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
20151
|
+
console.log(` [SUMMARY] ${String(counts.summary).padStart(5)} tokens`);
|
|
20152
|
+
console.log(` [LOGIC] ${String(counts.logic).padStart(5)} tokens`);
|
|
20153
|
+
console.log(` [RELATION] ${String(counts.relation).padStart(5)} tokens`);
|
|
20154
|
+
console.log(` [FOCUS CODE] ${String(counts.focusCode).padStart(5)} tokens`);
|
|
20155
|
+
console.log(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
20156
|
+
console.log(` Total ${String(counts.total).padStart(5)} tokens (budget: ${maxTokens})`);
|
|
20157
|
+
console.log(` Intent ${doc.intent}`);
|
|
20158
|
+
console.log(` Truncated ${doc.truncated}`);
|
|
20159
|
+
console.log("");
|
|
20160
|
+
}
|
|
20161
|
+
});
|
|
20162
|
+
{
|
|
20163
|
+
const rewriteCmd = new Command("rewrite").description("Rewrite a shell command to its code-intel equivalent (used by hooks)").argument("<cmd>", "Raw shell command to check").addHelpText("after", `
|
|
20164
|
+
Exit codes:
|
|
20165
|
+
0 Rewrite found \u2014 rewritten command printed to stdout
|
|
20166
|
+
1 No code-intel equivalent \u2014 pass through unchanged
|
|
20167
|
+
|
|
20168
|
+
Examples:
|
|
20169
|
+
$ code-intel rewrite 'grep "AuthService" src/'
|
|
20170
|
+
code-intel search "AuthService"
|
|
20171
|
+
|
|
20172
|
+
$ code-intel rewrite 'git status'
|
|
20173
|
+
(no output, exit 1)
|
|
20174
|
+
`).action((cmd) => {
|
|
20175
|
+
runRewrite(cmd);
|
|
20176
|
+
});
|
|
20177
|
+
program.addCommand(rewriteCmd, { hidden: true });
|
|
20178
|
+
}
|
|
20179
|
+
{
|
|
20180
|
+
const hookCmd = new Command("hook").description("Run as a PreToolUse hook for an AI agent (reads/writes JSON on stdin/stdout)").argument("<agent>", "Agent type: claude").action((agent) => {
|
|
20181
|
+
if (agent === "claude") {
|
|
20182
|
+
runClaudeHook();
|
|
20183
|
+
} else {
|
|
20184
|
+
process.stderr.write(`[code-intel] Unknown hook agent: ${agent}
|
|
20185
|
+
`);
|
|
20186
|
+
process.exit(0);
|
|
20187
|
+
}
|
|
20188
|
+
});
|
|
20189
|
+
program.addCommand(hookCmd, { hidden: true });
|
|
20190
|
+
}
|
|
18639
20191
|
program.parse();
|
|
18640
20192
|
//# sourceMappingURL=main.js.map
|
|
18641
20193
|
//# sourceMappingURL=main.js.map
|