@leanlabsinnov/codegraph 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/bin.js +2 -2
- package/dist/{chunk-AMJXGXLM.js → chunk-5WYXRWEY.js} +827 -119
- package/dist/chunk-5WYXRWEY.js.map +1 -0
- package/dist/{chunk-VFE242Y7.js → chunk-AVP24SX5.js} +73 -43
- package/dist/chunk-AVP24SX5.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/{src-7P6XREHJ.js → src-M7HSEMBT.js} +4 -4
- package/package.json +3 -3
- package/dist/chunk-AMJXGXLM.js.map +0 -1
- package/dist/chunk-VFE242Y7.js.map +0 -1
- /package/dist/{src-7P6XREHJ.js.map → src-M7HSEMBT.js.map} +0 -0
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
GraphDb,
|
|
7
7
|
defaultDbPath
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-AVP24SX5.js";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_CONFIG,
|
|
11
11
|
LLM_PRESETS,
|
|
@@ -374,20 +374,20 @@ function checkNodeVersion() {
|
|
|
374
374
|
detail: `v${version} (codegraph requires >= 20)`
|
|
375
375
|
};
|
|
376
376
|
}
|
|
377
|
-
async function checkWritable(name,
|
|
377
|
+
async function checkWritable(name, path8) {
|
|
378
378
|
try {
|
|
379
|
-
await mkdir2(dirname(
|
|
379
|
+
await mkdir2(dirname(path8), { recursive: true });
|
|
380
380
|
try {
|
|
381
|
-
await access(
|
|
381
|
+
await access(path8, constants.W_OK);
|
|
382
382
|
} catch {
|
|
383
|
-
await access(dirname(
|
|
383
|
+
await access(dirname(path8), constants.W_OK);
|
|
384
384
|
}
|
|
385
|
-
return { name, status: "ok", detail:
|
|
385
|
+
return { name, status: "ok", detail: path8 };
|
|
386
386
|
} catch (err) {
|
|
387
387
|
return {
|
|
388
388
|
name,
|
|
389
389
|
status: "fail",
|
|
390
|
-
detail: `${
|
|
390
|
+
detail: `${path8} (${err instanceof Error ? err.message : String(err)})`
|
|
391
391
|
};
|
|
392
392
|
}
|
|
393
393
|
}
|
|
@@ -433,17 +433,19 @@ async function selfTestKuzu(dbPath, embeddingDimension) {
|
|
|
433
433
|
try {
|
|
434
434
|
await db.connect();
|
|
435
435
|
await db.migrate();
|
|
436
|
-
const result = await db.query(
|
|
437
|
-
|
|
436
|
+
const result = await db.query(
|
|
437
|
+
`RETURN array_cosine_similarity([1.0, 0.0], [1.0, 0.0]) AS similarity, true AS ok`
|
|
438
|
+
);
|
|
438
439
|
await db.close();
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
status: vectorReady ? "ok" : "warn",
|
|
443
|
-
detail: vectorReady ? "ok (vector index ready)" : "ok (vector extension missing; semantic search disabled)"
|
|
444
|
-
};
|
|
440
|
+
const row = result.data[0];
|
|
441
|
+
if (row?.ok !== true || typeof row.similarity !== "number") {
|
|
442
|
+
return { name: "kuzu round-trip", status: "fail", detail: "unexpected result" };
|
|
445
443
|
}
|
|
446
|
-
return {
|
|
444
|
+
return {
|
|
445
|
+
name: "kuzu round-trip",
|
|
446
|
+
status: "ok",
|
|
447
|
+
detail: "ok (brute-force semantic search ready)"
|
|
448
|
+
};
|
|
447
449
|
} catch (err) {
|
|
448
450
|
await db.close().catch(() => {
|
|
449
451
|
});
|
|
@@ -456,7 +458,8 @@ async function selfTestKuzu(dbPath, embeddingDimension) {
|
|
|
456
458
|
}
|
|
457
459
|
|
|
458
460
|
// src/commands/index.ts
|
|
459
|
-
import
|
|
461
|
+
import { rm } from "fs/promises";
|
|
462
|
+
import path6 from "path";
|
|
460
463
|
|
|
461
464
|
// ../ingestion/src/parser.ts
|
|
462
465
|
import { readFile as readFile2 } from "fs/promises";
|
|
@@ -877,9 +880,11 @@ function functionFromDeclaration(node, input, source) {
|
|
|
877
880
|
if (!nameNode) return null;
|
|
878
881
|
const name = nodeText(nameNode, source);
|
|
879
882
|
const line = startLine(node);
|
|
883
|
+
const isComponent = (input.language === "tsx" || input.language === "jsx") && isPascalCase(name) && containsJsx(node.childForFieldName("body"));
|
|
884
|
+
const kind = isComponent ? "Component" : "Function";
|
|
880
885
|
const id = makeNodeId({
|
|
881
886
|
repoId: input.repoId,
|
|
882
|
-
kind
|
|
887
|
+
kind,
|
|
883
888
|
path: input.relativePath,
|
|
884
889
|
name,
|
|
885
890
|
line
|
|
@@ -887,7 +892,7 @@ function functionFromDeclaration(node, input, source) {
|
|
|
887
892
|
return {
|
|
888
893
|
node: {
|
|
889
894
|
id,
|
|
890
|
-
kind
|
|
895
|
+
kind,
|
|
891
896
|
repoId: input.repoId,
|
|
892
897
|
name,
|
|
893
898
|
path: input.relativePath,
|
|
@@ -896,7 +901,7 @@ function functionFromDeclaration(node, input, source) {
|
|
|
896
901
|
signature: extractSignature(node, source),
|
|
897
902
|
leadingComment: leadingCommentFor(parentForLeadingComment(node), source),
|
|
898
903
|
isExported: isExported(node),
|
|
899
|
-
isAsync: hasAsyncModifier(node, source)
|
|
904
|
+
...kind === "Function" ? { isAsync: hasAsyncModifier(node, source) } : {}
|
|
900
905
|
}
|
|
901
906
|
};
|
|
902
907
|
}
|
|
@@ -966,6 +971,9 @@ function variableOrArrowFromDeclaration(node, input, source) {
|
|
|
966
971
|
const nameNode = decl.childForFieldName("name");
|
|
967
972
|
const value = decl.childForFieldName("value");
|
|
968
973
|
if (!nameNode) return null;
|
|
974
|
+
if (nameNode.type !== "identifier" && nameNode.type !== "property_identifier") {
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
969
977
|
const name = nodeText(nameNode, source);
|
|
970
978
|
const line = startLine(decl);
|
|
971
979
|
const isArrow = value?.type === "arrow_function" || value?.type === "function_expression";
|
|
@@ -1030,6 +1038,10 @@ function extractSignature(node, source) {
|
|
|
1030
1038
|
function extractCalleeName(callee, source) {
|
|
1031
1039
|
if (callee.type === "identifier") return nodeText(callee, source);
|
|
1032
1040
|
if (callee.type === "member_expression") {
|
|
1041
|
+
const object = callee.childForFieldName("object");
|
|
1042
|
+
if (object && object.type === "identifier") {
|
|
1043
|
+
return nodeText(object, source);
|
|
1044
|
+
}
|
|
1033
1045
|
const property = callee.childForFieldName("property");
|
|
1034
1046
|
if (property) return nodeText(property, source);
|
|
1035
1047
|
}
|
|
@@ -1144,7 +1156,8 @@ function resolveEdges(input) {
|
|
|
1144
1156
|
repoId: input.repoId,
|
|
1145
1157
|
fromPath: edge.fromPath ?? "",
|
|
1146
1158
|
spec: edge.unresolvedTargetName,
|
|
1147
|
-
known: input.knownFilePaths
|
|
1159
|
+
known: input.knownFilePaths,
|
|
1160
|
+
...input.tsconfigPaths ? { tsconfigPaths: input.tsconfigPaths } : {}
|
|
1148
1161
|
});
|
|
1149
1162
|
if (!resolved) {
|
|
1150
1163
|
dropped++;
|
|
@@ -1166,10 +1179,22 @@ function resolveEdges(input) {
|
|
|
1166
1179
|
return { resolved: out, dropped };
|
|
1167
1180
|
}
|
|
1168
1181
|
function resolveImportPath(input) {
|
|
1169
|
-
const { fromPath, spec, known } = input;
|
|
1170
|
-
if (
|
|
1171
|
-
|
|
1172
|
-
|
|
1182
|
+
const { fromPath, spec, known, tsconfigPaths } = input;
|
|
1183
|
+
if (spec.startsWith(".") || spec.startsWith("/")) {
|
|
1184
|
+
const baseDir = path2.posix.dirname(toPosix(fromPath));
|
|
1185
|
+
const joined = path2.posix.normalize(path2.posix.join(baseDir, toPosix(spec)));
|
|
1186
|
+
return firstMatchingCandidate(joined, known);
|
|
1187
|
+
}
|
|
1188
|
+
if (tsconfigPaths) {
|
|
1189
|
+
const candidates = expandAliasCandidates(spec, tsconfigPaths);
|
|
1190
|
+
for (const c of candidates) {
|
|
1191
|
+
const resolved = firstMatchingCandidate(c, known);
|
|
1192
|
+
if (resolved) return resolved;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
return null;
|
|
1196
|
+
}
|
|
1197
|
+
function firstMatchingCandidate(joined, known) {
|
|
1173
1198
|
const candidates = [joined];
|
|
1174
1199
|
const ext = path2.posix.extname(joined);
|
|
1175
1200
|
const stem = ext ? joined.slice(0, -ext.length) : joined;
|
|
@@ -1180,12 +1205,37 @@ function resolveImportPath(input) {
|
|
|
1180
1205
|
}
|
|
1181
1206
|
return null;
|
|
1182
1207
|
}
|
|
1208
|
+
function expandAliasCandidates(spec, cfg) {
|
|
1209
|
+
const sortedPatterns = Object.keys(cfg.paths).sort((a, b) => b.length - a.length);
|
|
1210
|
+
const out = [];
|
|
1211
|
+
for (const pattern of sortedPatterns) {
|
|
1212
|
+
const replacements = cfg.paths[pattern] ?? [];
|
|
1213
|
+
if (pattern.endsWith("/*")) {
|
|
1214
|
+
const prefix = pattern.slice(0, -1);
|
|
1215
|
+
if (!spec.startsWith(prefix)) continue;
|
|
1216
|
+
const tail = spec.slice(prefix.length);
|
|
1217
|
+
for (const r of replacements) {
|
|
1218
|
+
const rTail = r.endsWith("/*") ? r.slice(0, -1) : r.endsWith("*") ? r.slice(0, -1) : r;
|
|
1219
|
+
const joined = path2.posix.normalize(
|
|
1220
|
+
path2.posix.join(toPosix(cfg.baseUrl), `${rTail}${tail}`)
|
|
1221
|
+
);
|
|
1222
|
+
out.push(joined);
|
|
1223
|
+
}
|
|
1224
|
+
} else if (spec === pattern) {
|
|
1225
|
+
for (const r of replacements) {
|
|
1226
|
+
const joined = path2.posix.normalize(path2.posix.join(toPosix(cfg.baseUrl), r));
|
|
1227
|
+
out.push(joined);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return out;
|
|
1232
|
+
}
|
|
1183
1233
|
function toPosix(p) {
|
|
1184
1234
|
return p.split(path2.sep).join("/");
|
|
1185
1235
|
}
|
|
1186
1236
|
|
|
1187
1237
|
// ../ingestion/src/orchestrator.ts
|
|
1188
|
-
import { readFile as
|
|
1238
|
+
import { readFile as readFile5, stat as stat2 } from "fs/promises";
|
|
1189
1239
|
import { cpus } from "os";
|
|
1190
1240
|
import { join as join2 } from "path";
|
|
1191
1241
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -1224,25 +1274,127 @@ async function embedNodes(nodes, opts) {
|
|
|
1224
1274
|
return result;
|
|
1225
1275
|
}
|
|
1226
1276
|
|
|
1227
|
-
// ../ingestion/src/
|
|
1228
|
-
import { readFile as readFile3
|
|
1277
|
+
// ../ingestion/src/tsconfig.ts
|
|
1278
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1229
1279
|
import path3 from "path";
|
|
1280
|
+
async function loadTsconfigPaths(repoRoot) {
|
|
1281
|
+
for (const name of ["tsconfig.json", "jsconfig.json"]) {
|
|
1282
|
+
const raw = await readIfExists(path3.join(repoRoot, name));
|
|
1283
|
+
if (!raw) continue;
|
|
1284
|
+
let parsed;
|
|
1285
|
+
try {
|
|
1286
|
+
parsed = JSON.parse(stripJsonComments(raw));
|
|
1287
|
+
} catch {
|
|
1288
|
+
continue;
|
|
1289
|
+
}
|
|
1290
|
+
const co = parsed.compilerOptions ?? {};
|
|
1291
|
+
const paths = co.paths ?? {};
|
|
1292
|
+
if (Object.keys(paths).length === 0) continue;
|
|
1293
|
+
const baseUrl = (co.baseUrl ?? ".").trim();
|
|
1294
|
+
return {
|
|
1295
|
+
baseUrl: toPosix2(baseUrl),
|
|
1296
|
+
paths
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
return null;
|
|
1300
|
+
}
|
|
1301
|
+
async function readIfExists(filePath) {
|
|
1302
|
+
try {
|
|
1303
|
+
return await readFile3(filePath, "utf8");
|
|
1304
|
+
} catch {
|
|
1305
|
+
return null;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
function stripJsonComments(input) {
|
|
1309
|
+
let out = "";
|
|
1310
|
+
let i = 0;
|
|
1311
|
+
let inString = false;
|
|
1312
|
+
let stringChar = "";
|
|
1313
|
+
while (i < input.length) {
|
|
1314
|
+
const c = input[i];
|
|
1315
|
+
const next = input[i + 1];
|
|
1316
|
+
if (inString) {
|
|
1317
|
+
out += c;
|
|
1318
|
+
if (c === "\\" && next !== void 0) {
|
|
1319
|
+
out += next;
|
|
1320
|
+
i += 2;
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
if (c === stringChar) inString = false;
|
|
1324
|
+
i++;
|
|
1325
|
+
continue;
|
|
1326
|
+
}
|
|
1327
|
+
if (c === '"' || c === "'") {
|
|
1328
|
+
inString = true;
|
|
1329
|
+
stringChar = c;
|
|
1330
|
+
out += c;
|
|
1331
|
+
i++;
|
|
1332
|
+
continue;
|
|
1333
|
+
}
|
|
1334
|
+
if (c === "/" && next === "/") {
|
|
1335
|
+
while (i < input.length && input[i] !== "\n") i++;
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
if (c === "/" && next === "*") {
|
|
1339
|
+
i += 2;
|
|
1340
|
+
while (i < input.length && !(input[i] === "*" && input[i + 1] === "/")) i++;
|
|
1341
|
+
i += 2;
|
|
1342
|
+
continue;
|
|
1343
|
+
}
|
|
1344
|
+
out += c;
|
|
1345
|
+
i++;
|
|
1346
|
+
}
|
|
1347
|
+
return removeTrailingCommas(out);
|
|
1348
|
+
}
|
|
1349
|
+
function removeTrailingCommas(s) {
|
|
1350
|
+
return s.replace(/,\s*([}\]])/g, "$1");
|
|
1351
|
+
}
|
|
1352
|
+
function toPosix2(p) {
|
|
1353
|
+
return p.split(path3.sep).join("/");
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// ../ingestion/src/repo-walker.ts
|
|
1357
|
+
import { readFile as readFile4, readdir, stat } from "fs/promises";
|
|
1358
|
+
import path4 from "path";
|
|
1230
1359
|
import ignore from "ignore";
|
|
1231
1360
|
var ALWAYS_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1232
1361
|
".git",
|
|
1362
|
+
".hg",
|
|
1363
|
+
".svn",
|
|
1233
1364
|
"node_modules",
|
|
1234
1365
|
".next",
|
|
1366
|
+
".nuxt",
|
|
1367
|
+
".svelte-kit",
|
|
1235
1368
|
"dist",
|
|
1236
1369
|
"build",
|
|
1237
1370
|
"out",
|
|
1238
1371
|
".turbo",
|
|
1239
1372
|
".cache",
|
|
1373
|
+
".parcel-cache",
|
|
1240
1374
|
"coverage",
|
|
1375
|
+
".nyc_output",
|
|
1241
1376
|
".idea",
|
|
1242
|
-
".vscode"
|
|
1377
|
+
".vscode",
|
|
1378
|
+
".vs",
|
|
1379
|
+
"venv",
|
|
1380
|
+
".venv",
|
|
1381
|
+
"env",
|
|
1382
|
+
"__pycache__",
|
|
1383
|
+
"site-packages",
|
|
1384
|
+
".pytest_cache",
|
|
1385
|
+
".mypy_cache",
|
|
1386
|
+
".ruff_cache",
|
|
1387
|
+
".tox",
|
|
1388
|
+
".eggs",
|
|
1389
|
+
"target",
|
|
1390
|
+
"vendor",
|
|
1391
|
+
"Pods",
|
|
1392
|
+
"DerivedData",
|
|
1393
|
+
".gradle",
|
|
1394
|
+
".terraform"
|
|
1243
1395
|
]);
|
|
1244
1396
|
async function walkRepo(root) {
|
|
1245
|
-
const base =
|
|
1397
|
+
const base = path4.resolve(root);
|
|
1246
1398
|
const matcher = ignore();
|
|
1247
1399
|
await loadGitignoreInto(matcher, base);
|
|
1248
1400
|
const out = [];
|
|
@@ -1259,7 +1411,7 @@ async function walkDir(absDir, relDir, ig, out) {
|
|
|
1259
1411
|
for (const entry of entries) {
|
|
1260
1412
|
const name = entry.name;
|
|
1261
1413
|
const rel = relDir ? `${relDir}/${name}` : name;
|
|
1262
|
-
const abs =
|
|
1414
|
+
const abs = path4.join(absDir, name);
|
|
1263
1415
|
if (entry.isDirectory()) {
|
|
1264
1416
|
if (ALWAYS_SKIP_DIRS.has(name)) continue;
|
|
1265
1417
|
if (ig.ignores(`${rel}/`)) continue;
|
|
@@ -1273,9 +1425,9 @@ async function walkDir(absDir, relDir, ig, out) {
|
|
|
1273
1425
|
}
|
|
1274
1426
|
}
|
|
1275
1427
|
async function loadGitignoreInto(ig, absDir) {
|
|
1276
|
-
const file =
|
|
1428
|
+
const file = path4.join(absDir, ".gitignore");
|
|
1277
1429
|
try {
|
|
1278
|
-
const text = await
|
|
1430
|
+
const text = await readFile4(file, "utf8");
|
|
1279
1431
|
ig.add(text);
|
|
1280
1432
|
} catch {
|
|
1281
1433
|
}
|
|
@@ -1305,7 +1457,7 @@ async function indexRepo(opts) {
|
|
|
1305
1457
|
await runWithConcurrency(parsable, parallelism, async (entry) => {
|
|
1306
1458
|
const abs = join2(opts.repoPath, entry.rel);
|
|
1307
1459
|
try {
|
|
1308
|
-
const source = await
|
|
1460
|
+
const source = await readFile5(abs, "utf8");
|
|
1309
1461
|
const result = await extractFile({
|
|
1310
1462
|
repoId: opts.repoId,
|
|
1311
1463
|
relativePath: entry.rel,
|
|
@@ -1322,25 +1474,33 @@ async function indexRepo(opts) {
|
|
|
1322
1474
|
opts.onProgress?.({ type: "parse", parsed: parsedCount, total: parsable.length });
|
|
1323
1475
|
}
|
|
1324
1476
|
});
|
|
1477
|
+
const tsconfigPaths = await loadTsconfigPaths(opts.repoPath);
|
|
1325
1478
|
const { resolved, dropped } = resolveEdges({
|
|
1326
1479
|
repoId: opts.repoId,
|
|
1327
1480
|
nodes: allNodes,
|
|
1328
1481
|
edges: allEdges,
|
|
1329
|
-
knownFilePaths
|
|
1482
|
+
knownFilePaths,
|
|
1483
|
+
...tsconfigPaths ? { tsconfigPaths } : {}
|
|
1330
1484
|
});
|
|
1331
|
-
|
|
1332
|
-
await opts.graphDb.upsertNodes(allNodes);
|
|
1333
|
-
await opts.graphDb.upsertEdges(resolved);
|
|
1334
|
-
opts.onProgress?.({ type: "upsert", nodes: allNodes.length, edges: resolved.length });
|
|
1335
|
-
let embeddingCount = 0;
|
|
1485
|
+
let embeddedById = /* @__PURE__ */ new Map();
|
|
1336
1486
|
if (!opts.skipEmbeddings && opts.router) {
|
|
1337
1487
|
const embedded = await embedNodes(allNodes, {
|
|
1338
1488
|
router: opts.router,
|
|
1339
1489
|
onBatch: ({ embedded: embedded2, total }) => opts.onProgress?.({ type: "embed", embedded: embedded2, total })
|
|
1340
1490
|
});
|
|
1341
|
-
|
|
1342
|
-
embeddingCount = embedded.length;
|
|
1491
|
+
embeddedById = new Map(embedded.map((e) => [e.id, e]));
|
|
1343
1492
|
}
|
|
1493
|
+
const nodePayload = allNodes.map((n) => {
|
|
1494
|
+
const e = embeddedById.get(n.id);
|
|
1495
|
+
const base = n;
|
|
1496
|
+
if (!e) return base;
|
|
1497
|
+
return { ...base, embedding: e.embedding, embeddingNamespace: e.embeddingNamespace };
|
|
1498
|
+
});
|
|
1499
|
+
await opts.graphDb.deleteByRepo(opts.repoId);
|
|
1500
|
+
await opts.graphDb.upsertNodes(nodePayload);
|
|
1501
|
+
await opts.graphDb.upsertEdges(resolved);
|
|
1502
|
+
opts.onProgress?.({ type: "upsert", nodes: allNodes.length, edges: resolved.length });
|
|
1503
|
+
const embeddingCount = embeddedById.size;
|
|
1344
1504
|
return {
|
|
1345
1505
|
durationMs: Date.now() - start,
|
|
1346
1506
|
parsedFiles: parsedCount - failed,
|
|
@@ -1351,22 +1511,6 @@ async function indexRepo(opts) {
|
|
|
1351
1511
|
droppedEdges: dropped
|
|
1352
1512
|
};
|
|
1353
1513
|
}
|
|
1354
|
-
async function persistEmbeddings(graphDb, embedded) {
|
|
1355
|
-
if (embedded.length === 0) return;
|
|
1356
|
-
const BATCH = 100;
|
|
1357
|
-
for (let i = 0; i < embedded.length; i += BATCH) {
|
|
1358
|
-
const batch = embedded.slice(i, i + BATCH);
|
|
1359
|
-
await graphDb.query(
|
|
1360
|
-
`
|
|
1361
|
-
UNWIND $batch AS e
|
|
1362
|
-
MATCH (n:Symbol { id: e.id })
|
|
1363
|
-
SET n.embedding = e.embedding,
|
|
1364
|
-
n.embeddingNamespace = e.embeddingNamespace
|
|
1365
|
-
`,
|
|
1366
|
-
{ batch }
|
|
1367
|
-
);
|
|
1368
|
-
}
|
|
1369
|
-
}
|
|
1370
1514
|
async function runWithConcurrency(items, concurrency, fn) {
|
|
1371
1515
|
let cursor = 0;
|
|
1372
1516
|
const runners = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
@@ -1385,10 +1529,10 @@ import kleur4 from "kleur";
|
|
|
1385
1529
|
|
|
1386
1530
|
// src/repo-id.ts
|
|
1387
1531
|
import { createHash as createHash3 } from "crypto";
|
|
1388
|
-
import
|
|
1532
|
+
import path5 from "path";
|
|
1389
1533
|
function repoIdFromPath(absPath) {
|
|
1390
|
-
const base =
|
|
1391
|
-
const sha = createHash3("sha1").update(
|
|
1534
|
+
const base = path5.basename(path5.resolve(absPath));
|
|
1535
|
+
const sha = createHash3("sha1").update(path5.resolve(absPath)).digest("hex").slice(0, 8);
|
|
1392
1536
|
const safe = base.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
1393
1537
|
return `${safe}-${sha}`;
|
|
1394
1538
|
}
|
|
@@ -1396,7 +1540,7 @@ function repoIdFromPath(absPath) {
|
|
|
1396
1540
|
// src/commands/index.ts
|
|
1397
1541
|
async function runIndexCommand(opts) {
|
|
1398
1542
|
const config = await loadConfig();
|
|
1399
|
-
const absolutePath =
|
|
1543
|
+
const absolutePath = path6.resolve(opts.repoPath);
|
|
1400
1544
|
const repoId = repoIdFromPath(absolutePath);
|
|
1401
1545
|
const dbPath = config.data.dbPath ?? defaultDbPath();
|
|
1402
1546
|
console.log(kleur4.dim(`repo: ${absolutePath}`));
|
|
@@ -1434,8 +1578,10 @@ async function runIndexCommand(opts) {
|
|
|
1434
1578
|
router = void 0;
|
|
1435
1579
|
}
|
|
1436
1580
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1581
|
+
const bars = {
|
|
1582
|
+
parse: null,
|
|
1583
|
+
embed: null
|
|
1584
|
+
};
|
|
1439
1585
|
const result = await indexRepo({
|
|
1440
1586
|
repoId,
|
|
1441
1587
|
repoPath: absolutePath,
|
|
@@ -1447,8 +1593,8 @@ async function runIndexCommand(opts) {
|
|
|
1447
1593
|
console.log(kleur4.dim(`walked ${event.files} files`));
|
|
1448
1594
|
}
|
|
1449
1595
|
if (event.type === "parse") {
|
|
1450
|
-
if (!
|
|
1451
|
-
|
|
1596
|
+
if (!bars.parse) bars.parse = new ProgressBar("parsing", event.total);
|
|
1597
|
+
bars.parse.update(event.parsed);
|
|
1452
1598
|
}
|
|
1453
1599
|
if (event.type === "upsert") {
|
|
1454
1600
|
console.log(
|
|
@@ -1456,13 +1602,13 @@ async function runIndexCommand(opts) {
|
|
|
1456
1602
|
);
|
|
1457
1603
|
}
|
|
1458
1604
|
if (event.type === "embed") {
|
|
1459
|
-
if (!
|
|
1460
|
-
|
|
1605
|
+
if (!bars.embed) bars.embed = new ProgressBar("embedding", event.total);
|
|
1606
|
+
bars.embed.update(event.embedded);
|
|
1461
1607
|
}
|
|
1462
1608
|
}
|
|
1463
1609
|
});
|
|
1464
|
-
|
|
1465
|
-
|
|
1610
|
+
bars.parse?.done();
|
|
1611
|
+
bars.embed?.done();
|
|
1466
1612
|
await graphDb.close();
|
|
1467
1613
|
console.log();
|
|
1468
1614
|
console.log(
|
|
@@ -1478,7 +1624,7 @@ async function runIndexCommand(opts) {
|
|
|
1478
1624
|
}
|
|
1479
1625
|
async function runStatusCommand(opts) {
|
|
1480
1626
|
const config = await loadConfig();
|
|
1481
|
-
const absolutePath =
|
|
1627
|
+
const absolutePath = path6.resolve(opts.repoPath);
|
|
1482
1628
|
const repoId = repoIdFromPath(absolutePath);
|
|
1483
1629
|
const graphDb = new GraphDb({
|
|
1484
1630
|
dbPath: config.data.dbPath ?? defaultDbPath(),
|
|
@@ -1496,10 +1642,62 @@ async function runStatusCommand(opts) {
|
|
|
1496
1642
|
throw err;
|
|
1497
1643
|
}
|
|
1498
1644
|
}
|
|
1645
|
+
async function runWipeCommand(opts) {
|
|
1646
|
+
const config = await loadConfig();
|
|
1647
|
+
const dbPath = config.data.dbPath ?? defaultDbPath();
|
|
1648
|
+
if (opts.repoPath) {
|
|
1649
|
+
const absolutePath = path6.resolve(opts.repoPath);
|
|
1650
|
+
const repoId = repoIdFromPath(absolutePath);
|
|
1651
|
+
if (!opts.yes) {
|
|
1652
|
+
console.log(
|
|
1653
|
+
kleur4.yellow(
|
|
1654
|
+
`Will delete all graph rows for ${kleur4.bold(repoId)} (${absolutePath}). Pass --yes to confirm.`
|
|
1655
|
+
)
|
|
1656
|
+
);
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
const spinner2 = makeSpinner(`Wiping ${repoId}`).start();
|
|
1660
|
+
const graphDb = new GraphDb({
|
|
1661
|
+
dbPath,
|
|
1662
|
+
embeddingDimension: config.llm.embeddings.dimension
|
|
1663
|
+
});
|
|
1664
|
+
try {
|
|
1665
|
+
await graphDb.connect();
|
|
1666
|
+
await graphDb.migrate();
|
|
1667
|
+
await graphDb.deleteByRepo(repoId);
|
|
1668
|
+
await graphDb.close();
|
|
1669
|
+
spinner2.succeed(`Removed ${repoId} from ${dbPath}`);
|
|
1670
|
+
} catch (err) {
|
|
1671
|
+
spinner2.fail("Wipe failed");
|
|
1672
|
+
throw err;
|
|
1673
|
+
}
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
if (!opts.yes) {
|
|
1677
|
+
console.log(
|
|
1678
|
+
kleur4.yellow(
|
|
1679
|
+
`Will delete the entire graph directory at ${kleur4.bold(dbPath)}. Pass --yes to confirm.`
|
|
1680
|
+
)
|
|
1681
|
+
);
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
const spinner = makeSpinner(`Removing ${dbPath}`).start();
|
|
1685
|
+
try {
|
|
1686
|
+
await rm(dbPath, { recursive: true, force: true });
|
|
1687
|
+
spinner.succeed(`Graph directory removed. Re-run \`codegraph index <repo>\` to rebuild.`);
|
|
1688
|
+
} catch (err) {
|
|
1689
|
+
spinner.fail("Wipe failed");
|
|
1690
|
+
throw err;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// src/commands/init.ts
|
|
1695
|
+
import path7 from "path";
|
|
1696
|
+
import { confirm, password as password2, select as select2 } from "@inquirer/prompts";
|
|
1499
1697
|
|
|
1500
1698
|
// ../mcp-server/dist/index.js
|
|
1501
1699
|
import { createHash as createHash4 } from "crypto";
|
|
1502
|
-
import { mkdir as mkdir3, readFile as
|
|
1700
|
+
import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
|
|
1503
1701
|
import { homedir } from "os";
|
|
1504
1702
|
import { dirname as dirname3, resolve } from "path";
|
|
1505
1703
|
import { randomUUID } from "crypto";
|
|
@@ -1595,8 +1793,8 @@ var affectedByTool = {
|
|
|
1595
1793
|
inputSchema,
|
|
1596
1794
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
1597
1795
|
},
|
|
1598
|
-
handler: async ({ symbol, path:
|
|
1599
|
-
const where =
|
|
1796
|
+
handler: async ({ symbol, path: path8, depth, limit }, deps) => cachedJsonResult("affected_by", { symbol, path: path8, depth, limit }, deps, async () => {
|
|
1797
|
+
const where = path8 ? "WHERE target.name = $symbol AND target.path = $path" : "WHERE target.name = $symbol";
|
|
1600
1798
|
const rows = await deps.graph.query(
|
|
1601
1799
|
`MATCH (target:Symbol) ${where}
|
|
1602
1800
|
WITH target
|
|
@@ -1605,9 +1803,9 @@ var affectedByTool = {
|
|
|
1605
1803
|
RETURN affected.id AS id, affected.name AS name, affected.kind AS kind,
|
|
1606
1804
|
affected.path AS path, distance
|
|
1607
1805
|
ORDER BY distance ASC, affected.path ASC LIMIT $limit`,
|
|
1608
|
-
{ symbol, path:
|
|
1806
|
+
{ symbol, path: path8 ?? null, limit }
|
|
1609
1807
|
);
|
|
1610
|
-
return { symbol, path:
|
|
1808
|
+
return { symbol, path: path8 ?? null, depth, count: rows.length, affected: rows };
|
|
1611
1809
|
})
|
|
1612
1810
|
};
|
|
1613
1811
|
var inputSchema2 = {
|
|
@@ -1624,14 +1822,14 @@ var blastRadiusTool = {
|
|
|
1624
1822
|
inputSchema: inputSchema2,
|
|
1625
1823
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
1626
1824
|
},
|
|
1627
|
-
handler: async ({ symbol, path:
|
|
1628
|
-
const where =
|
|
1825
|
+
handler: async ({ symbol, path: path8, depth, sampleSize }, deps) => cachedJsonResult("blast_radius", { symbol, path: path8, depth, sampleSize }, deps, async () => {
|
|
1826
|
+
const where = path8 ? "WHERE target.name = $symbol AND target.path = $path" : "WHERE target.name = $symbol";
|
|
1629
1827
|
const [stats] = await deps.graph.query(
|
|
1630
1828
|
`MATCH (target:Symbol) ${where}
|
|
1631
1829
|
WITH target
|
|
1632
1830
|
MATCH (dependent:Symbol)-[:CALLS|IMPORTS|RENDERS*1..${depth}]->(target)
|
|
1633
1831
|
RETURN count(DISTINCT dependent) AS total`,
|
|
1634
|
-
{ symbol, path:
|
|
1832
|
+
{ symbol, path: path8 ?? null }
|
|
1635
1833
|
);
|
|
1636
1834
|
const sample = await deps.graph.query(
|
|
1637
1835
|
`MATCH (target:Symbol) ${where}
|
|
@@ -1641,11 +1839,11 @@ var blastRadiusTool = {
|
|
|
1641
1839
|
RETURN dependent.id AS id, dependent.name AS name, dependent.kind AS kind,
|
|
1642
1840
|
dependent.path AS path, distance
|
|
1643
1841
|
ORDER BY distance ASC, dependent.path ASC LIMIT $sampleSize`,
|
|
1644
|
-
{ symbol, path:
|
|
1842
|
+
{ symbol, path: path8 ?? null, sampleSize }
|
|
1645
1843
|
);
|
|
1646
1844
|
return {
|
|
1647
1845
|
symbol,
|
|
1648
|
-
path:
|
|
1846
|
+
path: path8 ?? null,
|
|
1649
1847
|
depth,
|
|
1650
1848
|
totalDependents: stats?.total ?? 0,
|
|
1651
1849
|
sample
|
|
@@ -1712,15 +1910,46 @@ var getComponentTreeTool = {
|
|
|
1712
1910
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
1713
1911
|
},
|
|
1714
1912
|
handler: async ({ name, depth }, deps) => cachedJsonResult("get_component_tree", { name, depth }, deps, async () => {
|
|
1715
|
-
const
|
|
1716
|
-
`MATCH (root:Symbol
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1913
|
+
const rootRows = await deps.graph.query(
|
|
1914
|
+
`MATCH (root:Symbol)
|
|
1915
|
+
WHERE root.name = $name AND (root.kind = 'Component' OR root.kind = 'Function' OR root.kind = 'Class')
|
|
1916
|
+
RETURN root.id AS rootId, root.name AS rootName, root.path AS rootPath, root.kind AS rootKind
|
|
1917
|
+
ORDER BY CASE root.kind WHEN 'Component' THEN 0 WHEN 'Function' THEN 1 ELSE 2 END
|
|
1918
|
+
LIMIT 1`,
|
|
1721
1919
|
{ name }
|
|
1722
1920
|
);
|
|
1723
|
-
|
|
1921
|
+
const root = rootRows[0];
|
|
1922
|
+
if (!root) {
|
|
1923
|
+
return {
|
|
1924
|
+
name,
|
|
1925
|
+
depth,
|
|
1926
|
+
found: false,
|
|
1927
|
+
root: null,
|
|
1928
|
+
children: []
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
const children = await deps.graph.query(
|
|
1932
|
+
`MATCH (root:Symbol {id: $rootId})
|
|
1933
|
+
OPTIONAL MATCH p = (root)-[:RENDERS*1..${depth}]->(child:Symbol)
|
|
1934
|
+
WITH child, min(length(p)) AS distance
|
|
1935
|
+
WHERE child IS NOT NULL
|
|
1936
|
+
RETURN child.id AS id, child.name AS name, child.kind AS kind,
|
|
1937
|
+
child.path AS path, distance
|
|
1938
|
+
ORDER BY distance ASC, child.name ASC`,
|
|
1939
|
+
{ rootId: root.rootId }
|
|
1940
|
+
);
|
|
1941
|
+
return {
|
|
1942
|
+
name,
|
|
1943
|
+
depth,
|
|
1944
|
+
found: true,
|
|
1945
|
+
root: {
|
|
1946
|
+
id: root.rootId,
|
|
1947
|
+
name: root.rootName,
|
|
1948
|
+
path: root.rootPath,
|
|
1949
|
+
kind: root.rootKind
|
|
1950
|
+
},
|
|
1951
|
+
children
|
|
1952
|
+
};
|
|
1724
1953
|
})
|
|
1725
1954
|
};
|
|
1726
1955
|
var inputSchema6 = {
|
|
@@ -1736,7 +1965,7 @@ var getDependenciesTool = {
|
|
|
1736
1965
|
inputSchema: inputSchema6,
|
|
1737
1966
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
1738
1967
|
},
|
|
1739
|
-
handler: async ({ path:
|
|
1968
|
+
handler: async ({ path: path8, depth, limit }, deps) => cachedJsonResult("get_dependencies", { path: path8, depth, limit }, deps, async () => {
|
|
1740
1969
|
const rows = await deps.graph.query(
|
|
1741
1970
|
`MATCH (root:Symbol)
|
|
1742
1971
|
WHERE root.kind = 'File' AND root.path = $path
|
|
@@ -1746,9 +1975,9 @@ var getDependenciesTool = {
|
|
|
1746
1975
|
WHERE dep IS NOT NULL
|
|
1747
1976
|
RETURN dep.id AS id, dep.path AS path, distance
|
|
1748
1977
|
ORDER BY distance ASC, dep.path ASC LIMIT $limit`,
|
|
1749
|
-
{ path:
|
|
1978
|
+
{ path: path8, limit }
|
|
1750
1979
|
);
|
|
1751
|
-
return { path:
|
|
1980
|
+
return { path: path8, depth, count: rows.length, dependencies: rows };
|
|
1752
1981
|
})
|
|
1753
1982
|
};
|
|
1754
1983
|
var inputSchema7 = {
|
|
@@ -1763,15 +1992,15 @@ var getFileContextTool = {
|
|
|
1763
1992
|
inputSchema: inputSchema7,
|
|
1764
1993
|
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
1765
1994
|
},
|
|
1766
|
-
handler: async ({ path:
|
|
1995
|
+
handler: async ({ path: path8, symbolLimit }, deps) => cachedJsonResult("get_file_context", { path: path8, symbolLimit }, deps, async () => {
|
|
1767
1996
|
const fileRows = await deps.graph.query(
|
|
1768
1997
|
`MATCH (f:Symbol)
|
|
1769
1998
|
WHERE f.kind = 'File' AND f.path = $path
|
|
1770
1999
|
RETURN f.id AS id, f.path AS path, f.name AS name LIMIT 1`,
|
|
1771
|
-
{ path:
|
|
2000
|
+
{ path: path8 }
|
|
1772
2001
|
);
|
|
1773
2002
|
if (fileRows.length === 0) {
|
|
1774
|
-
return { path:
|
|
2003
|
+
return { path: path8, found: false };
|
|
1775
2004
|
}
|
|
1776
2005
|
const [defined, imports, exports, importers] = await Promise.all([
|
|
1777
2006
|
deps.graph.query(
|
|
@@ -1779,30 +2008,33 @@ var getFileContextTool = {
|
|
|
1779
2008
|
WHERE f.kind = 'File' AND f.path = $path
|
|
1780
2009
|
RETURN s.id AS id, s.name AS name, s.kind AS kind, s.lineStart AS line, s.signature AS signature
|
|
1781
2010
|
ORDER BY s.lineStart LIMIT $limit`,
|
|
1782
|
-
{ path:
|
|
2011
|
+
{ path: path8, limit: symbolLimit }
|
|
1783
2012
|
),
|
|
1784
2013
|
deps.graph.query(
|
|
2014
|
+
// Kuzu drops the node variable `t` from scope after `RETURN DISTINCT`, so the
|
|
2015
|
+
// ORDER BY references the projection alias instead - using `t.path` here
|
|
2016
|
+
// raises a "Variable t is not in scope" binder exception.
|
|
1785
2017
|
`MATCH (f:Symbol)-[:IMPORTS]->(t:Symbol)
|
|
1786
2018
|
WHERE f.kind = 'File' AND t.kind = 'File' AND f.path = $path
|
|
1787
|
-
RETURN DISTINCT t.path AS path ORDER BY
|
|
1788
|
-
{ path:
|
|
2019
|
+
RETURN DISTINCT t.path AS path ORDER BY path`,
|
|
2020
|
+
{ path: path8 }
|
|
1789
2021
|
),
|
|
1790
2022
|
deps.graph.query(
|
|
1791
2023
|
`MATCH (f:Symbol)-[:EXPORTS]->(s:Symbol)
|
|
1792
2024
|
WHERE f.kind = 'File' AND f.path = $path
|
|
1793
2025
|
RETURN s.id AS id, s.name AS name, s.kind AS kind, s.lineStart AS line
|
|
1794
2026
|
ORDER BY s.name`,
|
|
1795
|
-
{ path:
|
|
2027
|
+
{ path: path8 }
|
|
1796
2028
|
),
|
|
1797
2029
|
deps.graph.query(
|
|
1798
2030
|
`MATCH (other:Symbol)-[:IMPORTS]->(f:Symbol)
|
|
1799
2031
|
WHERE other.kind = 'File' AND f.kind = 'File' AND f.path = $path
|
|
1800
|
-
RETURN DISTINCT other.path AS path ORDER BY
|
|
1801
|
-
{ path:
|
|
2032
|
+
RETURN DISTINCT other.path AS path ORDER BY path`,
|
|
2033
|
+
{ path: path8 }
|
|
1802
2034
|
)
|
|
1803
2035
|
]);
|
|
1804
2036
|
return {
|
|
1805
|
-
path:
|
|
2037
|
+
path: path8,
|
|
1806
2038
|
found: true,
|
|
1807
2039
|
file: fileRows[0],
|
|
1808
2040
|
definedSymbols: defined,
|
|
@@ -2005,14 +2237,16 @@ var searchSemanticTool = {
|
|
|
2005
2237
|
);
|
|
2006
2238
|
}
|
|
2007
2239
|
const namespace = `${deps.llm.embeddingNamespace.provider}:${deps.llm.embeddingNamespace.model}:${deps.llm.embeddingNamespace.dimension}`;
|
|
2240
|
+
const dim = deps.llm.embeddingNamespace.dimension;
|
|
2008
2241
|
return cachedJsonResult("search_semantic", { description, k, namespace }, deps, async () => {
|
|
2009
2242
|
const rows = await deps.graph.query(
|
|
2010
|
-
`
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
ORDER BY
|
|
2243
|
+
`MATCH (s:Symbol)
|
|
2244
|
+
WHERE s.embeddingNamespace = $ns
|
|
2245
|
+
RETURN s.id AS id, s.name AS name, s.kind AS kind, s.path AS path,
|
|
2246
|
+
s.lineStart AS line, s.signature AS signature,
|
|
2247
|
+
array_cosine_similarity(s.embedding, CAST($vec AS FLOAT[${dim}])) AS score
|
|
2248
|
+
ORDER BY score DESC
|
|
2249
|
+
LIMIT $k`,
|
|
2016
2250
|
{ vec: embedding, k, ns: namespace }
|
|
2017
2251
|
);
|
|
2018
2252
|
return { description, k, namespace, count: rows.length, matches: rows };
|
|
@@ -2214,23 +2448,23 @@ var DEFAULT_TTL = 30;
|
|
|
2214
2448
|
function defaultConfigPath() {
|
|
2215
2449
|
return resolve(homedir(), ".codegraph", "config.json");
|
|
2216
2450
|
}
|
|
2217
|
-
async function readCodegraphConfig(
|
|
2451
|
+
async function readCodegraphConfig(path8 = defaultConfigPath()) {
|
|
2218
2452
|
try {
|
|
2219
|
-
const raw = await
|
|
2453
|
+
const raw = await readFile6(path8, "utf8");
|
|
2220
2454
|
return JSON.parse(raw);
|
|
2221
2455
|
} catch (err) {
|
|
2222
2456
|
if (err.code === "ENOENT") return {};
|
|
2223
2457
|
throw err;
|
|
2224
2458
|
}
|
|
2225
2459
|
}
|
|
2226
|
-
async function writeCodegraphConfig(config,
|
|
2227
|
-
await mkdir3(dirname3(
|
|
2228
|
-
await writeFile2(
|
|
2460
|
+
async function writeCodegraphConfig(config, path8 = defaultConfigPath()) {
|
|
2461
|
+
await mkdir3(dirname3(path8), { recursive: true });
|
|
2462
|
+
await writeFile2(path8, `${JSON.stringify(config, null, 2)}
|
|
2229
2463
|
`, "utf8");
|
|
2230
2464
|
}
|
|
2231
2465
|
async function resolveServerConfig(opts) {
|
|
2232
|
-
const
|
|
2233
|
-
const fileConfig = await readCodegraphConfig(
|
|
2466
|
+
const path8 = opts.configPath ?? defaultConfigPath();
|
|
2467
|
+
const fileConfig = await readCodegraphConfig(path8);
|
|
2234
2468
|
const env = process.env;
|
|
2235
2469
|
let bearerToken = opts.overrides?.bearerToken ?? fileConfig.mcpToken ?? fileConfig.server?.bearerToken ?? env.CODEGRAPH_BEARER_TOKEN ?? "";
|
|
2236
2470
|
let created = false;
|
|
@@ -2243,7 +2477,7 @@ async function resolveServerConfig(opts) {
|
|
|
2243
2477
|
mcpToken: bearerToken,
|
|
2244
2478
|
server: { ...fileConfig.server ?? {}, bearerToken }
|
|
2245
2479
|
},
|
|
2246
|
-
|
|
2480
|
+
path8
|
|
2247
2481
|
);
|
|
2248
2482
|
}
|
|
2249
2483
|
const config = {
|
|
@@ -2252,7 +2486,7 @@ async function resolveServerConfig(opts) {
|
|
|
2252
2486
|
bearerToken,
|
|
2253
2487
|
cacheTtlSeconds: opts.overrides?.cacheTtlSeconds ?? fileConfig.server?.cacheTtlSeconds ?? (env.CODEGRAPH_CACHE_TTL ? Number(env.CODEGRAPH_CACHE_TTL) : DEFAULT_TTL)
|
|
2254
2488
|
};
|
|
2255
|
-
return { config, configPath:
|
|
2489
|
+
return { config, configPath: path8, created };
|
|
2256
2490
|
}
|
|
2257
2491
|
function resolveDbPath(fileConfig) {
|
|
2258
2492
|
return fileConfig.data?.dbPath ?? process.env.CODEGRAPH_DB_PATH ?? void 0;
|
|
@@ -2300,7 +2534,7 @@ async function startMcpServer(portOrOptions) {
|
|
|
2300
2534
|
async function loadGraphClient(dbPath) {
|
|
2301
2535
|
let mod;
|
|
2302
2536
|
try {
|
|
2303
|
-
mod = await import("./src-
|
|
2537
|
+
mod = await import("./src-M7HSEMBT.js");
|
|
2304
2538
|
} catch (err) {
|
|
2305
2539
|
throw new Error(
|
|
2306
2540
|
`Failed to import @codegraph/graph-db. Run \`pnpm -r build\` first. Underlying error: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -2358,8 +2592,473 @@ function adaptLlmRouter(router) {
|
|
|
2358
2592
|
};
|
|
2359
2593
|
}
|
|
2360
2594
|
|
|
2595
|
+
// src/commands/init.ts
|
|
2596
|
+
import boxen2 from "boxen";
|
|
2597
|
+
import kleur5 from "kleur";
|
|
2598
|
+
var TOTAL_STEPS = 6;
|
|
2599
|
+
async function runInitCommand() {
|
|
2600
|
+
printWelcome();
|
|
2601
|
+
const start = await confirm({ message: "Ready to get started?", default: true });
|
|
2602
|
+
if (!start) {
|
|
2603
|
+
console.log(kleur5.dim("Aborted. Run `codegraph init` again any time."));
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
runEnvCheck();
|
|
2607
|
+
await runLlmSetup();
|
|
2608
|
+
await runLiveTest();
|
|
2609
|
+
await runIndexStep();
|
|
2610
|
+
const bearerToken = await runServeStep();
|
|
2611
|
+
await runClientStep(bearerToken);
|
|
2612
|
+
await runCelebration();
|
|
2613
|
+
}
|
|
2614
|
+
function printWelcome() {
|
|
2615
|
+
printBanner();
|
|
2616
|
+
const description = [
|
|
2617
|
+
kleur5.bold("CodeGraph") + " parses your JS/TS repo with tree-sitter, stores nodes (Function, Class,",
|
|
2618
|
+
"Route\u2026) and edges (CALLS, IMPORTS, RENDERS\u2026) in an embedded Kuzu graph with",
|
|
2619
|
+
"vector embeddings, then runs a local MCP server your AI assistant can call.",
|
|
2620
|
+
"",
|
|
2621
|
+
kleur5.dim("This wizard will:"),
|
|
2622
|
+
` ${kleur5.cyan("1.")} Configure an LLM provider (cloud or local)`,
|
|
2623
|
+
` ${kleur5.cyan("2.")} Verify your credentials with a live round-trip`,
|
|
2624
|
+
` ${kleur5.cyan("3.")} Index this repo into the graph`,
|
|
2625
|
+
` ${kleur5.cyan("4.")} Show you how to boot the MCP server`,
|
|
2626
|
+
` ${kleur5.cyan("5.")} Print a copy-paste config for your AI client`
|
|
2627
|
+
].join("\n");
|
|
2628
|
+
process.stdout.write(
|
|
2629
|
+
`${boxen2(description, {
|
|
2630
|
+
padding: 1,
|
|
2631
|
+
margin: { top: 0, bottom: 1, left: 0, right: 0 },
|
|
2632
|
+
borderStyle: "round",
|
|
2633
|
+
borderColor: "cyan",
|
|
2634
|
+
title: kleur5.bold("Welcome"),
|
|
2635
|
+
titleAlignment: "left"
|
|
2636
|
+
})}
|
|
2637
|
+
`
|
|
2638
|
+
);
|
|
2639
|
+
}
|
|
2640
|
+
function stepHeader(n, label) {
|
|
2641
|
+
console.log();
|
|
2642
|
+
console.log(
|
|
2643
|
+
kleur5.cyan(`\u2500\u2500\u2500\u2500 Step ${n}/${TOTAL_STEPS}: ${kleur5.bold(label)} ${"\u2500".repeat(Math.max(4, 40 - label.length))}`)
|
|
2644
|
+
);
|
|
2645
|
+
}
|
|
2646
|
+
function runEnvCheck() {
|
|
2647
|
+
stepHeader(1, "Environment check");
|
|
2648
|
+
const version = process.versions.node;
|
|
2649
|
+
const major = Number(version.split(".")[0] ?? 0);
|
|
2650
|
+
if (major < 20) {
|
|
2651
|
+
console.log(`${kleur5.red("\u2717")} node version v${version} ${kleur5.red("(codegraph requires >= 20)")}`);
|
|
2652
|
+
throw new Error(`Node ${version} is too old; install Node 20+ and re-run \`codegraph init\`.`);
|
|
2653
|
+
}
|
|
2654
|
+
console.log(`${kleur5.green("\u2713")} node version ${kleur5.dim(`v${version}`)}`);
|
|
2655
|
+
}
|
|
2656
|
+
async function runLlmSetup() {
|
|
2657
|
+
stepHeader(2, "LLM setup");
|
|
2658
|
+
const mode = await select2({
|
|
2659
|
+
message: "How would you like to power CodeGraph?",
|
|
2660
|
+
choices: [
|
|
2661
|
+
{
|
|
2662
|
+
name: "Cloud provider",
|
|
2663
|
+
value: "cloud",
|
|
2664
|
+
description: "OpenAI, Anthropic, or Google. Fastest setup, requires an API key."
|
|
2665
|
+
},
|
|
2666
|
+
{
|
|
2667
|
+
name: "Local (Ollama)",
|
|
2668
|
+
value: "local",
|
|
2669
|
+
description: "Fully private. Requires Ollama running on :11434."
|
|
2670
|
+
}
|
|
2671
|
+
]
|
|
2672
|
+
});
|
|
2673
|
+
let preset;
|
|
2674
|
+
if (mode === "local") {
|
|
2675
|
+
preset = "local-ollama";
|
|
2676
|
+
} else {
|
|
2677
|
+
preset = await select2({
|
|
2678
|
+
message: "Which cloud provider?",
|
|
2679
|
+
choices: [
|
|
2680
|
+
{
|
|
2681
|
+
name: "OpenAI",
|
|
2682
|
+
value: "byo-openai",
|
|
2683
|
+
description: "gpt-4o-mini + text-embedding-3-small"
|
|
2684
|
+
},
|
|
2685
|
+
{
|
|
2686
|
+
name: "Anthropic",
|
|
2687
|
+
value: "byo-anthropic",
|
|
2688
|
+
description: "claude-3-5-haiku (gen) + OpenAI text-embedding-3-small (embed)"
|
|
2689
|
+
},
|
|
2690
|
+
{
|
|
2691
|
+
name: "Google",
|
|
2692
|
+
value: "byo-google",
|
|
2693
|
+
description: "gemini-1.5-flash + text-embedding-004"
|
|
2694
|
+
}
|
|
2695
|
+
]
|
|
2696
|
+
});
|
|
2697
|
+
}
|
|
2698
|
+
const lookup = LLM_PRESETS[preset];
|
|
2699
|
+
if (!lookup) {
|
|
2700
|
+
throw new Error(`Unknown preset "${preset}".`);
|
|
2701
|
+
}
|
|
2702
|
+
const config = await loadConfig();
|
|
2703
|
+
config.llm = { ...config.llm, ...lookup };
|
|
2704
|
+
await saveConfig(config);
|
|
2705
|
+
console.log();
|
|
2706
|
+
console.log(`${kleur5.green("\u2713")} preset saved ${kleur5.dim(`"${preset}" \u2192 ${configPath()}`)}`);
|
|
2707
|
+
console.log(`${kleur5.dim(" namespace ")}${kleur5.dim(namespaceLabel(config.llm))}`);
|
|
2708
|
+
if (preset === "byo-anthropic") {
|
|
2709
|
+
console.log();
|
|
2710
|
+
console.log(
|
|
2711
|
+
kleur5.yellow(
|
|
2712
|
+
"Note: Anthropic has no embedding API, so codegraph uses OpenAI embeddings."
|
|
2713
|
+
)
|
|
2714
|
+
);
|
|
2715
|
+
console.log(
|
|
2716
|
+
kleur5.yellow("You'll need both ANTHROPIC_API_KEY and OPENAI_API_KEY set.")
|
|
2717
|
+
);
|
|
2718
|
+
await promptApiKey("ANTHROPIC_API_KEY");
|
|
2719
|
+
await promptApiKey("OPENAI_API_KEY");
|
|
2720
|
+
return;
|
|
2721
|
+
}
|
|
2722
|
+
const envVar = apiKeyEnvVarFor2(preset);
|
|
2723
|
+
if (envVar) await promptApiKey(envVar);
|
|
2724
|
+
}
|
|
2725
|
+
function apiKeyEnvVarFor2(preset) {
|
|
2726
|
+
if (preset === "byo-openai" || preset === "managed-stub") return "OPENAI_API_KEY";
|
|
2727
|
+
if (preset === "byo-anthropic") return "ANTHROPIC_API_KEY";
|
|
2728
|
+
if (preset === "byo-google") return "GOOGLE_GENERATIVE_AI_API_KEY";
|
|
2729
|
+
return null;
|
|
2730
|
+
}
|
|
2731
|
+
async function promptApiKey(envVar) {
|
|
2732
|
+
console.log();
|
|
2733
|
+
if (process.env[envVar]) {
|
|
2734
|
+
console.log(`${kleur5.green("\u2713")} ${envVar} ${kleur5.dim("already set in this shell")}`);
|
|
2735
|
+
return;
|
|
2736
|
+
}
|
|
2737
|
+
const provide = await confirm({
|
|
2738
|
+
message: `${envVar} is not set. Provide it now? (input will be hidden)`,
|
|
2739
|
+
default: true
|
|
2740
|
+
});
|
|
2741
|
+
if (!provide) {
|
|
2742
|
+
console.log(
|
|
2743
|
+
kleur5.yellow(`! Skipped. Export ${envVar} before \`codegraph index\` or \`codegraph serve\`.`)
|
|
2744
|
+
);
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
const value = await password2({ message: `${envVar}`, mask: "*" });
|
|
2748
|
+
if (!value) {
|
|
2749
|
+
console.log(kleur5.yellow(`! Empty value \u2014 skipping ${envVar}.`));
|
|
2750
|
+
return;
|
|
2751
|
+
}
|
|
2752
|
+
process.env[envVar] = value;
|
|
2753
|
+
console.log();
|
|
2754
|
+
console.log(kleur5.dim("Add this to your ~/.zshrc or ~/.bashrc so it persists:"));
|
|
2755
|
+
console.log(` ${kleur5.cyan(`export ${envVar}=${maskTail(value)}`)}`);
|
|
2756
|
+
}
|
|
2757
|
+
function maskTail(value) {
|
|
2758
|
+
if (value.length <= 8) return "*".repeat(value.length);
|
|
2759
|
+
return `${"*".repeat(value.length - 4)}${value.slice(-4)}`;
|
|
2760
|
+
}
|
|
2761
|
+
async function runLiveTest() {
|
|
2762
|
+
stepHeader(3, "Verify credentials");
|
|
2763
|
+
while (true) {
|
|
2764
|
+
const config = await loadConfig();
|
|
2765
|
+
console.log(
|
|
2766
|
+
kleur5.dim(
|
|
2767
|
+
` testing ${config.llm.generation.provider}:${config.llm.generation.model} + ${config.llm.embeddings.provider}:${config.llm.embeddings.model}`
|
|
2768
|
+
)
|
|
2769
|
+
);
|
|
2770
|
+
const spinner = makeSpinner("Calling provider").start();
|
|
2771
|
+
try {
|
|
2772
|
+
const router = await createLlmRouter({ config: config.llm });
|
|
2773
|
+
const result = await router.selfTest();
|
|
2774
|
+
spinner.succeed("LLM provider reachable");
|
|
2775
|
+
console.log(
|
|
2776
|
+
kleur5.dim(
|
|
2777
|
+
` embed=${result.embedLatencyMs}ms gen=${result.generateLatencyMs}ms dims=${result.embedDims}`
|
|
2778
|
+
)
|
|
2779
|
+
);
|
|
2780
|
+
return;
|
|
2781
|
+
} catch (err) {
|
|
2782
|
+
spinner.fail("LLM provider unreachable");
|
|
2783
|
+
console.log(kleur5.red(` ${err instanceof Error ? err.message : String(err)}`));
|
|
2784
|
+
const choice = await select2({
|
|
2785
|
+
message: "What now?",
|
|
2786
|
+
choices: [
|
|
2787
|
+
{ name: "Retry (I'll fix it in another terminal)", value: "retry" },
|
|
2788
|
+
{ name: "Skip \u2014 set up LLM later", value: "skip" }
|
|
2789
|
+
]
|
|
2790
|
+
});
|
|
2791
|
+
if (choice === "skip") {
|
|
2792
|
+
console.log(
|
|
2793
|
+
kleur5.yellow(
|
|
2794
|
+
"! Continuing without a verified LLM. Embeddings and semantic search will fail until you fix this."
|
|
2795
|
+
)
|
|
2796
|
+
);
|
|
2797
|
+
return;
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
async function runIndexStep() {
|
|
2803
|
+
stepHeader(4, "Index this repo");
|
|
2804
|
+
const cwd = process.cwd();
|
|
2805
|
+
const display = abbreviateHome(cwd);
|
|
2806
|
+
const yes = await confirm({
|
|
2807
|
+
message: `Index the current directory (${display}) now?`,
|
|
2808
|
+
default: true
|
|
2809
|
+
});
|
|
2810
|
+
if (!yes) {
|
|
2811
|
+
console.log(kleur5.dim("Skipped. Run this when ready:"));
|
|
2812
|
+
console.log(` ${kleur5.cyan(`codegraph index ${display}`)}`);
|
|
2813
|
+
return;
|
|
2814
|
+
}
|
|
2815
|
+
console.log();
|
|
2816
|
+
try {
|
|
2817
|
+
await runIndexCommand({ repoPath: cwd });
|
|
2818
|
+
} catch (err) {
|
|
2819
|
+
console.log();
|
|
2820
|
+
console.log(
|
|
2821
|
+
kleur5.yellow(
|
|
2822
|
+
`! Index failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2823
|
+
)
|
|
2824
|
+
);
|
|
2825
|
+
console.log(
|
|
2826
|
+
kleur5.dim(" You can retry later with `codegraph index <path>`. Continuing wizard.")
|
|
2827
|
+
);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
async function runServeStep() {
|
|
2831
|
+
stepHeader(5, "Boot the MCP server");
|
|
2832
|
+
const { config: serverConfig, created } = await resolveServerConfig({});
|
|
2833
|
+
if (created) {
|
|
2834
|
+
console.log(kleur5.dim(" generated a new bearer token for this machine"));
|
|
2835
|
+
}
|
|
2836
|
+
const url = `http://${serverConfig.host}:${serverConfig.port}/mcp`;
|
|
2837
|
+
const body = [
|
|
2838
|
+
"Open a " + kleur5.bold("new terminal") + " and run:",
|
|
2839
|
+
"",
|
|
2840
|
+
` ${kleur5.cyan("codegraph serve")}`,
|
|
2841
|
+
"",
|
|
2842
|
+
kleur5.dim("Expected output:"),
|
|
2843
|
+
kleur5.dim(` codegraph mcp listening on ${url}`),
|
|
2844
|
+
"",
|
|
2845
|
+
kleur5.dim("Keep that terminal running while you use CodeGraph.")
|
|
2846
|
+
].join("\n");
|
|
2847
|
+
process.stdout.write(
|
|
2848
|
+
`${boxen2(body, {
|
|
2849
|
+
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
2850
|
+
margin: { top: 1, bottom: 1, left: 0, right: 0 },
|
|
2851
|
+
borderStyle: "round",
|
|
2852
|
+
borderColor: "cyan",
|
|
2853
|
+
title: kleur5.bold("Start the server"),
|
|
2854
|
+
titleAlignment: "left"
|
|
2855
|
+
})}
|
|
2856
|
+
`
|
|
2857
|
+
);
|
|
2858
|
+
await confirm({
|
|
2859
|
+
message: "Done \u2014 the server is running in another terminal?",
|
|
2860
|
+
default: true
|
|
2861
|
+
});
|
|
2862
|
+
return serverConfig.bearerToken;
|
|
2863
|
+
}
|
|
2864
|
+
async function runClientStep(bearerToken) {
|
|
2865
|
+
stepHeader(6, "Connect your AI client");
|
|
2866
|
+
const url = "http://127.0.0.1:3748/mcp";
|
|
2867
|
+
const client = await select2({
|
|
2868
|
+
message: "Which AI client are you connecting?",
|
|
2869
|
+
choices: [
|
|
2870
|
+
{ name: "Cursor", value: "cursor" },
|
|
2871
|
+
{ name: "Claude Code", value: "claude" },
|
|
2872
|
+
{ name: "Windsurf", value: "windsurf" },
|
|
2873
|
+
{ name: "Skip for now", value: "skip" }
|
|
2874
|
+
]
|
|
2875
|
+
});
|
|
2876
|
+
if (client === "skip") {
|
|
2877
|
+
console.log(
|
|
2878
|
+
kleur5.dim("Skipped. See docs/clients.md for per-client configuration when ready.")
|
|
2879
|
+
);
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
if (client === "cursor") {
|
|
2883
|
+
const snippet = JSON.stringify(
|
|
2884
|
+
{
|
|
2885
|
+
mcpServers: {
|
|
2886
|
+
codegraph: {
|
|
2887
|
+
url,
|
|
2888
|
+
headers: { Authorization: `Bearer ${bearerToken}` }
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
},
|
|
2892
|
+
null,
|
|
2893
|
+
2
|
|
2894
|
+
);
|
|
2895
|
+
const body = [
|
|
2896
|
+
"Create or edit " + kleur5.cyan(".cursor/mcp.json") + " in your project root:",
|
|
2897
|
+
"",
|
|
2898
|
+
kleur5.green(snippet),
|
|
2899
|
+
"",
|
|
2900
|
+
kleur5.dim("Then open Cursor \u2192 Settings \u2192 MCP."),
|
|
2901
|
+
kleur5.dim("`codegraph` should appear as connected with 10 tools.")
|
|
2902
|
+
].join("\n");
|
|
2903
|
+
process.stdout.write(
|
|
2904
|
+
`${boxen2(body, {
|
|
2905
|
+
padding: 1,
|
|
2906
|
+
margin: { top: 1, bottom: 1, left: 0, right: 0 },
|
|
2907
|
+
borderStyle: "round",
|
|
2908
|
+
borderColor: "magenta",
|
|
2909
|
+
title: kleur5.bold("Cursor config"),
|
|
2910
|
+
titleAlignment: "left"
|
|
2911
|
+
})}
|
|
2912
|
+
`
|
|
2913
|
+
);
|
|
2914
|
+
} else if (client === "claude") {
|
|
2915
|
+
const oneliner = [
|
|
2916
|
+
"claude mcp add --transport sse codegraph \\",
|
|
2917
|
+
` ${url} \\`,
|
|
2918
|
+
` --header "Authorization: Bearer ${bearerToken}"`
|
|
2919
|
+
].join("\n");
|
|
2920
|
+
const body = [
|
|
2921
|
+
"Run this in your terminal:",
|
|
2922
|
+
"",
|
|
2923
|
+
kleur5.green(oneliner),
|
|
2924
|
+
"",
|
|
2925
|
+
kleur5.dim("Then type ") + kleur5.cyan("/mcp") + kleur5.dim(" in Claude Code to confirm 10 tools are listed.")
|
|
2926
|
+
].join("\n");
|
|
2927
|
+
process.stdout.write(
|
|
2928
|
+
`${boxen2(body, {
|
|
2929
|
+
padding: 1,
|
|
2930
|
+
margin: { top: 1, bottom: 1, left: 0, right: 0 },
|
|
2931
|
+
borderStyle: "round",
|
|
2932
|
+
borderColor: "magenta",
|
|
2933
|
+
title: kleur5.bold("Claude Code config"),
|
|
2934
|
+
titleAlignment: "left"
|
|
2935
|
+
})}
|
|
2936
|
+
`
|
|
2937
|
+
);
|
|
2938
|
+
} else {
|
|
2939
|
+
const snippet = JSON.stringify(
|
|
2940
|
+
{
|
|
2941
|
+
mcpServers: {
|
|
2942
|
+
codegraph: {
|
|
2943
|
+
serverUrl: url,
|
|
2944
|
+
headers: { Authorization: `Bearer ${bearerToken}` }
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
},
|
|
2948
|
+
null,
|
|
2949
|
+
2
|
|
2950
|
+
);
|
|
2951
|
+
const body = [
|
|
2952
|
+
"Edit " + kleur5.cyan("~/.codeium/windsurf/mcp_config.json") + ":",
|
|
2953
|
+
"",
|
|
2954
|
+
kleur5.green(snippet),
|
|
2955
|
+
"",
|
|
2956
|
+
kleur5.dim("Then restart Windsurf or run ") + kleur5.cyan("/refresh-tools") + kleur5.dim(" in Cascade.")
|
|
2957
|
+
].join("\n");
|
|
2958
|
+
process.stdout.write(
|
|
2959
|
+
`${boxen2(body, {
|
|
2960
|
+
padding: 1,
|
|
2961
|
+
margin: { top: 1, bottom: 1, left: 0, right: 0 },
|
|
2962
|
+
borderStyle: "round",
|
|
2963
|
+
borderColor: "magenta",
|
|
2964
|
+
title: kleur5.bold("Windsurf config"),
|
|
2965
|
+
titleAlignment: "left"
|
|
2966
|
+
})}
|
|
2967
|
+
`
|
|
2968
|
+
);
|
|
2969
|
+
}
|
|
2970
|
+
await confirm({
|
|
2971
|
+
message: "Done \u2014 your client is configured?",
|
|
2972
|
+
default: true
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2975
|
+
async function runCelebration() {
|
|
2976
|
+
console.log();
|
|
2977
|
+
console.log(kleur5.cyan(`\u2500\u2500\u2500\u2500 ${kleur5.bold("Almost there!")} \u2500\u2500\u2500\u2500`));
|
|
2978
|
+
console.log(
|
|
2979
|
+
kleur5.dim(
|
|
2980
|
+
'Ask your AI assistant something like: "What files are in this repo?"'
|
|
2981
|
+
)
|
|
2982
|
+
);
|
|
2983
|
+
console.log(
|
|
2984
|
+
kleur5.dim("It should call a `codegraph_*` MCP tool to answer.")
|
|
2985
|
+
);
|
|
2986
|
+
console.log();
|
|
2987
|
+
const choice = await select2({
|
|
2988
|
+
message: "How did it go?",
|
|
2989
|
+
choices: [
|
|
2990
|
+
{ name: "It worked!", value: "worked" },
|
|
2991
|
+
{ name: "Skip", value: "skip" }
|
|
2992
|
+
]
|
|
2993
|
+
});
|
|
2994
|
+
const header = choice === "worked" ? kleur5.green().bold("\u2713 CodeGraph is fully connected!") : kleur5.cyan().bold("\u2713 CodeGraph setup complete");
|
|
2995
|
+
const body = [
|
|
2996
|
+
header,
|
|
2997
|
+
"",
|
|
2998
|
+
"Your AI assistant can now call these tools:",
|
|
2999
|
+
` ${kleur5.cyan("\u2022")} find_callers \u2014 what calls a function`,
|
|
3000
|
+
` ${kleur5.cyan("\u2022")} get_component_tree \u2014 render dependency tree`,
|
|
3001
|
+
` ${kleur5.cyan("\u2022")} blast_radius \u2014 impact of a change`,
|
|
3002
|
+
` ${kleur5.cyan("\u2022")} search_semantic \u2014 symbol search by description`,
|
|
3003
|
+
` ${kleur5.cyan("\u2022")} nl_query \u2014 natural-language Cypher`,
|
|
3004
|
+
` ${kleur5.dim("\u2026plus 5 more (search_symbol, find_file, get_file_context,")}`,
|
|
3005
|
+
` ${kleur5.dim(" get_dependencies, affected_by)")}`,
|
|
3006
|
+
"",
|
|
3007
|
+
kleur5.dim("Helpful next steps:"),
|
|
3008
|
+
` ${kleur5.cyan("codegraph doctor")} ${kleur5.dim("\u2014 verify environment + LLM + Kuzu")}`,
|
|
3009
|
+
` ${kleur5.cyan("codegraph status")} ${kleur5.dim("<path>")} ${kleur5.dim("\u2014 node/edge counts + embedding coverage")}`,
|
|
3010
|
+
"",
|
|
3011
|
+
kleur5.dim("Docs: https://github.com/leanlabsinnov/codegraph")
|
|
3012
|
+
].join("\n");
|
|
3013
|
+
process.stdout.write(
|
|
3014
|
+
`${boxen2(body, {
|
|
3015
|
+
padding: 1,
|
|
3016
|
+
margin: { top: 1, bottom: 1, left: 0, right: 0 },
|
|
3017
|
+
borderStyle: "round",
|
|
3018
|
+
borderColor: choice === "worked" ? "green" : "cyan"
|
|
3019
|
+
})}
|
|
3020
|
+
`
|
|
3021
|
+
);
|
|
3022
|
+
}
|
|
3023
|
+
function abbreviateHome(p) {
|
|
3024
|
+
const home = process.env.HOME ?? process.env.USERPROFILE;
|
|
3025
|
+
if (!home) return p;
|
|
3026
|
+
const rel = path7.relative(home, p);
|
|
3027
|
+
if (rel.startsWith("..")) return p;
|
|
3028
|
+
return rel ? `~${path7.sep}${rel}` : "~";
|
|
3029
|
+
}
|
|
3030
|
+
|
|
2361
3031
|
// src/commands/serve.ts
|
|
3032
|
+
import kleur6 from "kleur";
|
|
3033
|
+
function providerEnvVar2(provider) {
|
|
3034
|
+
if (provider === "openai") return "OPENAI_API_KEY";
|
|
3035
|
+
if (provider === "anthropic") return "ANTHROPIC_API_KEY";
|
|
3036
|
+
if (provider === "google") return "GOOGLE_GENERATIVE_AI_API_KEY";
|
|
3037
|
+
return null;
|
|
3038
|
+
}
|
|
2362
3039
|
async function runServeCommand(opts = {}) {
|
|
3040
|
+
try {
|
|
3041
|
+
const config = await loadConfig();
|
|
3042
|
+
const missing = [];
|
|
3043
|
+
for (const provider of [
|
|
3044
|
+
config.llm.generation.provider,
|
|
3045
|
+
config.llm.embeddings.provider
|
|
3046
|
+
]) {
|
|
3047
|
+
const envVar = providerEnvVar2(provider);
|
|
3048
|
+
if (envVar && !process.env[envVar] && !missing.includes(envVar)) {
|
|
3049
|
+
missing.push(envVar);
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
if (missing.length > 0) {
|
|
3053
|
+
process.stderr.write(
|
|
3054
|
+
`${kleur6.yellow(
|
|
3055
|
+
`! ${missing.join(", ")} not set in serve's environment. Semantic search and nl_query will fail until the variable is exported before \`codegraph serve\`.`
|
|
3056
|
+
)}
|
|
3057
|
+
`
|
|
3058
|
+
);
|
|
3059
|
+
}
|
|
3060
|
+
} catch {
|
|
3061
|
+
}
|
|
2363
3062
|
const spinner = makeSpinner("Booting MCP server").start();
|
|
2364
3063
|
let started;
|
|
2365
3064
|
try {
|
|
@@ -2400,13 +3099,16 @@ shutting down (${signal})...
|
|
|
2400
3099
|
// src/program.ts
|
|
2401
3100
|
function buildProgram() {
|
|
2402
3101
|
const program = new Command();
|
|
2403
|
-
program.name("codegraph").description("Live, queryable knowledge graph for your codebase").version("0.1.
|
|
3102
|
+
program.name("codegraph").description("Live, queryable knowledge graph for your codebase").version("0.1.3").option("--verbose", "Print full stack traces on error").hook("preAction", (thisCommand) => {
|
|
2404
3103
|
const opts = thisCommand.optsWithGlobals();
|
|
2405
3104
|
if (opts.verbose) process.env.CODEGRAPH_VERBOSE = "1";
|
|
2406
3105
|
});
|
|
2407
3106
|
program.on("--help", () => {
|
|
2408
3107
|
printBanner();
|
|
2409
3108
|
});
|
|
3109
|
+
program.command("init").description("Interactive setup wizard \u2014 LLM, credentials, index, MCP, client connect").action(async () => {
|
|
3110
|
+
await runInitCommand();
|
|
3111
|
+
});
|
|
2410
3112
|
program.command("index").description("Parse a JS/TS repo into the local embedded graph").argument("<path>", "Path to the repo root").option("--no-embed", "Skip the embedding pass (faster, no LLM calls)").action(async (repoPath, opts) => {
|
|
2411
3113
|
await runIndexCommand({ repoPath, noEmbed: opts.embed === false });
|
|
2412
3114
|
});
|
|
@@ -2423,6 +3125,12 @@ function buildProgram() {
|
|
|
2423
3125
|
program.command("doctor").description("Check environment, config, LLM credentials, and Kuzu DB health").action(async () => {
|
|
2424
3126
|
await runDoctorCommand();
|
|
2425
3127
|
});
|
|
3128
|
+
program.command("wipe").description("Delete a single repo's slice (with [path]) or the entire on-disk graph").argument("[path]", "Repo path. Omit to wipe the whole graph directory.").option("--yes", "Skip the confirmation prompt", false).action(async (repoPath, opts) => {
|
|
3129
|
+
await runWipeCommand({
|
|
3130
|
+
...repoPath ? { repoPath } : {},
|
|
3131
|
+
yes: opts.yes === true
|
|
3132
|
+
});
|
|
3133
|
+
});
|
|
2426
3134
|
const configCmd = program.command("config").description("Manage ~/.codegraph/config.json");
|
|
2427
3135
|
configCmd.command("show").description("Print the resolved config").action(async () => {
|
|
2428
3136
|
await runConfigShow();
|
|
@@ -2446,4 +3154,4 @@ export {
|
|
|
2446
3154
|
renderError,
|
|
2447
3155
|
buildProgram
|
|
2448
3156
|
};
|
|
2449
|
-
//# sourceMappingURL=chunk-
|
|
3157
|
+
//# sourceMappingURL=chunk-5WYXRWEY.js.map
|