@codragraph/cli 2.1.4 → 2.1.6
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 +36 -7
- package/dist/cli/ai-context.js +297 -0
- package/dist/cli/analyze.d.ts +9 -4
- package/dist/cli/analyze.js +37 -13
- package/dist/cli/index.js +40 -14
- package/dist/cli/status.d.ts +1 -1
- package/dist/cli/status.js +8 -0
- package/dist/cli/tool.d.ts +10 -2
- package/dist/cli/tool.js +100 -39
- package/dist/config/ignore-service.js +1 -0
- package/dist/core/adaptive-profile.d.ts +52 -0
- package/dist/core/adaptive-profile.js +180 -0
- package/dist/core/cgdb/cgdb-adapter.d.ts +34 -5
- package/dist/core/cgdb/cgdb-adapter.js +418 -5
- package/dist/core/cgdb/pool-adapter.js +130 -20
- package/dist/core/ingestion/parsing-processor.js +7 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +7 -1
- package/dist/core/ingestion/pipeline-phases/structure.js +19 -3
- package/dist/core/ingestion/pipeline.d.ts +10 -0
- package/dist/core/ingestion/workers/parse-worker.js +1 -1
- package/dist/core/ingestion/workers/worker-pool.d.ts +14 -1
- package/dist/core/ingestion/workers/worker-pool.js +33 -17
- package/dist/core/run-analyze.d.ts +27 -2
- package/dist/core/run-analyze.js +626 -32
- package/dist/core/search/bm25-index.d.ts +16 -8
- package/dist/core/search/bm25-index.js +72 -110
- package/dist/mcp/local/local-backend.d.ts +2 -0
- package/dist/mcp/local/local-backend.js +241 -21
- package/dist/storage/repo-manager.d.ts +29 -0
- package/dist/web/assets/__vite-browser-external-BIHI7g3E.js +1 -0
- package/dist/web/assets/agent-DcdaQnmu.js +1104 -0
- package/dist/web/assets/architectureDiagram-UL44E2DR-DFSpa3Hb.js +36 -0
- package/dist/web/assets/blockDiagram-7IZFK4PR-DlFaxH1b.js +132 -0
- package/dist/web/assets/{c4Diagram-DFAF54RM-C4Hl3J2U.js → c4Diagram-Y2BXMSZH-BjJ_Yrim.js} +1 -1
- package/dist/web/assets/{chunk-7RZVMHOQ-BitYcNVR.js → chunk-3SSMPTDK-KGZSzG3Y.js} +1 -1
- package/dist/web/assets/{chunk-TBF5ZNIQ-DL5stGM1.js → chunk-6764PJDD-p1sGJgVm.js} +1 -1
- package/dist/web/assets/{chunk-KSICW3F5-BYzvDLNI.js → chunk-AZZRMDJM-DIDkQA4V.js} +1 -1
- package/dist/web/assets/{chunk-AEOMTBSW-BgTIXPsY.js → chunk-JQRUD6KW-DAwg-yCU.js} +1 -1
- package/dist/web/assets/chunk-KRXBNO2N-ChVO_XdS.js +1 -0
- package/dist/web/assets/chunk-LCXTWHL2-DGYdb_Eh.js +231 -0
- package/dist/web/assets/{chunk-O5ABG6QK-dHwHzA6n.js → chunk-LII3EMHJ-Bzh9SNgD.js} +1 -1
- package/dist/web/assets/chunk-RG4AUYOV-Bcl7U_IV.js +206 -0
- package/dist/web/assets/{chunk-TU3PZOEN-RLyvLcv-.js → chunk-T5OCTHI4-CZYMg5sc.js} +1 -1
- package/dist/web/assets/chunk-W44A43WB-REOI67PN.js +13 -0
- package/dist/web/assets/{chunk-RWUO3TPN-BgRTY0_k.js → chunk-ZXARS5L4-BfFdV1tf.js} +1 -1
- package/dist/web/assets/classDiagram-KGZ6W3CR-B-qkKMYi.js +1 -0
- package/dist/web/assets/classDiagram-v2-72OJOZXJ-B-qkKMYi.js +1 -0
- package/dist/web/assets/{cose-bilkent-PNC4W37J-DVhePRYg.js → cose-bilkent-UX7MHV2Q-D6vANJGG.js} +1 -1
- package/dist/web/assets/dagre-ND4H6XIP-BiHe5Lal.js +4 -0
- package/dist/web/assets/diagram-3NCE3AQN-CEutBCOW.js +43 -0
- package/dist/web/assets/diagram-GF46GFSD-CZns6HPQ.js +24 -0
- package/dist/web/assets/diagram-HNR7UZ2L-Vz8fE5of.js +3 -0
- package/dist/web/assets/diagram-QXG6HAR7-D60HKZ_y.js +24 -0
- package/dist/web/assets/diagram-WEQXMOUZ-vGAf1p3E.js +10 -0
- package/dist/web/assets/{erDiagram-GCSMX5X6-C3dhDFA8.js → erDiagram-L5TCEMPS-DZaplJA6.js} +5 -5
- package/dist/web/assets/{flowDiagram-OTCZ4VVT-CWSFWmhr.js → flowDiagram-H6V6AXG4-BqUqeAsI.js} +9 -9
- package/dist/web/assets/ganttDiagram-JCBTUEKG-XEB6H-0G.js +292 -0
- package/dist/web/assets/gitGraphDiagram-S2ZK5IYY-7G50u1Cd.js +106 -0
- package/dist/web/assets/index-B5WxtMpv.js +1415 -0
- package/dist/web/assets/infoDiagram-3YFTVSEB-Cut_rzaf.js +2 -0
- package/dist/web/assets/{ishikawaDiagram-YMYX4NHK-DUoJvNP2.js → ishikawaDiagram-BNXS4ZKH-B4DGfGi3.js} +3 -3
- package/dist/web/assets/{journeyDiagram-SO5T7YLQ-RMFPNNqz.js → journeyDiagram-M6C3CM3L-BBFhsL3E.js} +1 -1
- package/dist/web/assets/{kanban-definition-LJHFXRCJ-BzpDs1K9.js → kanban-definition-75IXJCU3-DarGRyn3.js} +4 -4
- package/dist/web/assets/{katex-GD7MH7QM-DBQvrix-.js → katex-K3KEBU37-W5XTYMhr.js} +1 -1
- package/dist/web/assets/mindmap-definition-2TDM6QVE-BgeczIJM.js +96 -0
- package/dist/web/assets/pieDiagram-CU6KROY3-Kkoo-Noq.js +30 -0
- package/dist/web/assets/quadrantDiagram-VICAPDV7-CDQFeRWN.js +7 -0
- package/dist/web/assets/{requirementDiagram-M5DCFWZL-DLHOVTSv.js → requirementDiagram-JXO7QTGE-Cz9-XnkA.js} +2 -2
- package/dist/web/assets/sankeyDiagram-URQDO5SZ-CU26z0n7.js +40 -0
- package/dist/web/assets/sequenceDiagram-VS2MUI6T-OGK1FLOt.js +162 -0
- package/dist/web/assets/stateDiagram-7D4R322I-DJ9brq0U.js +1 -0
- package/dist/web/assets/stateDiagram-v2-36443NZ5-DhJ4Ky-7.js +1 -0
- package/dist/web/assets/{timeline-definition-5SPVSISX-TRSDRgPw.js → timeline-definition-O6YCAMPW-XZvnjqTT.js} +4 -4
- package/dist/web/assets/{vennDiagram-IE5QUKF5-DNy7HRBM.js → vennDiagram-MWXL3ELB-CJUssEjA.js} +6 -6
- package/dist/web/assets/wardley-L42UT6IY-5TKZOOLJ-DZr11zBG.js +173 -0
- package/dist/web/assets/wardleyDiagram-CUQ6CDDI-C276iqrN.js +78 -0
- package/dist/web/assets/{xychartDiagram-ZHJ5623Y-Dr9r7a35.js → xychartDiagram-N2JHSOCM-B9-uCZyP.js} +4 -4
- package/dist/web/index.html +1 -1
- package/hooks/claude/codragraph-hook.cjs +15 -122
- package/package.json +1 -1
- package/vendor/node_modules/node-addon-api/node_addon_api_except.stamp +0 -0
- package/dist/web/assets/agent-D5lb0zXz.js +0 -1089
- package/dist/web/assets/architectureDiagram-EMZXCZ2Q-CZtc99v_.js +0 -36
- package/dist/web/assets/blockDiagram-IGV67L2C-BtoUp-6Y.js +0 -132
- package/dist/web/assets/chunk-3GS5O3IE-DkUjU0WD.js +0 -231
- package/dist/web/assets/chunk-3YCYZ6SJ-CQkVgT_z.js +0 -1
- package/dist/web/assets/chunk-H3VCZNTA-Cx5XV_aC.js +0 -13
- package/dist/web/assets/chunk-HN6EAY2L-BBnyTNdB.js +0 -1
- package/dist/web/assets/chunk-PK6DOVAG-CvsEnugt.js +0 -206
- package/dist/web/assets/classDiagram-PPOCWD7C-DTr8QIOf.js +0 -1
- package/dist/web/assets/classDiagram-v2-23LJLIIU-DTr8QIOf.js +0 -1
- package/dist/web/assets/dagre-E77IOHMT-Dzx0A6ZU.js +0 -4
- package/dist/web/assets/diagram-H7BISOXX-CC9pRew1.js +0 -43
- package/dist/web/assets/diagram-JC5VWROH-Bau_i9tf.js +0 -24
- package/dist/web/assets/diagram-LXUTUG65-D9_FM2Gt.js +0 -10
- package/dist/web/assets/diagram-WEHSV5V5-BMlayouL.js +0 -24
- package/dist/web/assets/ganttDiagram-MUNLMDZQ-D3a67Yol.js +0 -292
- package/dist/web/assets/gitGraphDiagram-3HKGZ4G3-7jmry-vM.js +0 -106
- package/dist/web/assets/index-BgeqpYgd.js +0 -1415
- package/dist/web/assets/infoDiagram-MN7RKWGX-G7lhP0Ib.js +0 -2
- package/dist/web/assets/mindmap-definition-2EUWGEK5-Bk0O4roa.js +0 -96
- package/dist/web/assets/pieDiagram-3IATQBI2-DKU7kpgS.js +0 -30
- package/dist/web/assets/quadrantDiagram-E256RVCF-BY0TGWCS.js +0 -7
- package/dist/web/assets/sankeyDiagram-L3NBLAOT-DVMj5rX2.js +0 -10
- package/dist/web/assets/sequenceDiagram-ZOUHS735-CJC73bV-.js +0 -157
- package/dist/web/assets/stateDiagram-MLPALWAM-BCFyESls.js +0 -1
- package/dist/web/assets/stateDiagram-v2-B5LQ5ZB2-DahzzIca.js +0 -1
- package/dist/web/assets/wardley-RL74JXVD-BCRCBASE-B-eZEzf9.js +0 -161
- package/dist/web/assets/wardleyDiagram-XU3VSMPF-BP-r1xzR.js +0 -20
|
@@ -79,6 +79,105 @@ export const VALID_NODE_LABELS = new Set([
|
|
|
79
79
|
'Route',
|
|
80
80
|
'Tool',
|
|
81
81
|
]);
|
|
82
|
+
const SIMPLE_LABELLESS_MATCH_RE = /^MATCH\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/i;
|
|
83
|
+
const CYPHER_LIMIT_RE = /\bLIMIT\s+(\d+)\s*;?\s*$/i;
|
|
84
|
+
const CYPHER_RELATION_RE = /--|-\[|\]-|->|<-/;
|
|
85
|
+
const NATIVE_UNSAFE_NODE_LABELS = new Set(['Union']);
|
|
86
|
+
function quoteKnownNodeLabels(query) {
|
|
87
|
+
return query;
|
|
88
|
+
}
|
|
89
|
+
function getNativeUnsafeNodeLabel(query) {
|
|
90
|
+
const labelRe = /\(\s*[A-Za-z_][A-Za-z0-9_]*\s*:\s*`?([A-Za-z_][A-Za-z0-9_]*)`?(?=[\s){])/g;
|
|
91
|
+
for (const match of query.matchAll(labelRe)) {
|
|
92
|
+
const label = match[1];
|
|
93
|
+
if (NATIVE_UNSAFE_NODE_LABELS.has(label))
|
|
94
|
+
return label;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
function safeNodeLabelForCypher(typeOrLabel, nodeId) {
|
|
99
|
+
const explicit = typeof typeOrLabel === 'string' ? typeOrLabel.trim() : '';
|
|
100
|
+
if (explicit && VALID_NODE_LABELS.has(explicit) && !NATIVE_UNSAFE_NODE_LABELS.has(explicit)) {
|
|
101
|
+
return explicit;
|
|
102
|
+
}
|
|
103
|
+
const id = typeof nodeId === 'string' ? nodeId : '';
|
|
104
|
+
const fromId = id.includes(':') ? id.slice(0, id.indexOf(':')) : '';
|
|
105
|
+
if (fromId && VALID_NODE_LABELS.has(fromId) && !NATIVE_UNSAFE_NODE_LABELS.has(fromId)) {
|
|
106
|
+
return fromId;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function firstNonEmptyString(...values) {
|
|
111
|
+
for (const value of values) {
|
|
112
|
+
if (typeof value === 'string' && value.trim().length > 0)
|
|
113
|
+
return value.trim();
|
|
114
|
+
}
|
|
115
|
+
return '';
|
|
116
|
+
}
|
|
117
|
+
function parseSimpleNodeScanQuery(query) {
|
|
118
|
+
const match = /^MATCH\s*\(\s*([A-Za-z_][A-Za-z0-9_]*)(?:\s*:\s*`?([A-Za-z_][A-Za-z0-9_]*)`?)?\s*\)\s*RETURN\s+(.+?)\s+LIMIT\s+(\d+)\s*;?\s*$/i.exec(query.trim());
|
|
119
|
+
if (!match)
|
|
120
|
+
return null;
|
|
121
|
+
const limit = Number.parseInt(match[4], 10);
|
|
122
|
+
if (!Number.isFinite(limit) || limit <= 0)
|
|
123
|
+
return null;
|
|
124
|
+
return {
|
|
125
|
+
alias: match[1],
|
|
126
|
+
label: match[2],
|
|
127
|
+
returnClause: match[3].trim(),
|
|
128
|
+
limit: Math.min(Math.trunc(limit), 1000),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function projectSimpleNodeRow(node, label, scan) {
|
|
132
|
+
const row = {};
|
|
133
|
+
const parts = scan.returnClause.split(/\s*,\s*/).filter(Boolean);
|
|
134
|
+
for (const part of parts) {
|
|
135
|
+
const asMatch = /^(.+?)\s+AS\s+([A-Za-z_][A-Za-z0-9_]*)$/i.exec(part.trim());
|
|
136
|
+
const expr = (asMatch?.[1] ?? part).trim();
|
|
137
|
+
const outKey = asMatch?.[2] ?? expr;
|
|
138
|
+
if (expr === scan.alias) {
|
|
139
|
+
row[outKey] = { ...node, _label: label };
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const labelsMatch = new RegExp(`^labels\\(\\s*${scan.alias}\\s*\\)\\[0\\]$`, 'i').exec(expr);
|
|
143
|
+
if (labelsMatch) {
|
|
144
|
+
row[outKey] = label;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const propMatch = new RegExp(`^${scan.alias}\\.([A-Za-z_][A-Za-z0-9_]*)$`).exec(expr);
|
|
148
|
+
if (propMatch) {
|
|
149
|
+
row[outKey] = node[propMatch[1]];
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
return row;
|
|
155
|
+
}
|
|
156
|
+
function getSimpleLabellessNodeAlias(query) {
|
|
157
|
+
const trimmed = query.trim();
|
|
158
|
+
const match = SIMPLE_LABELLESS_MATCH_RE.exec(trimmed);
|
|
159
|
+
if (!match)
|
|
160
|
+
return null;
|
|
161
|
+
if (CYPHER_RELATION_RE.test(trimmed))
|
|
162
|
+
return null;
|
|
163
|
+
if (/\bMATCH\b/i.test(trimmed.slice(match[0].length)))
|
|
164
|
+
return null;
|
|
165
|
+
return match[1];
|
|
166
|
+
}
|
|
167
|
+
function getCypherLimit(query) {
|
|
168
|
+
const match = CYPHER_LIMIT_RE.exec(query);
|
|
169
|
+
if (!match)
|
|
170
|
+
return null;
|
|
171
|
+
const limit = Number.parseInt(match[1], 10);
|
|
172
|
+
return Number.isFinite(limit) && limit > 0 ? limit : null;
|
|
173
|
+
}
|
|
174
|
+
function withCypherLimit(query, limit) {
|
|
175
|
+
const safeLimit = Math.max(1, Math.trunc(limit));
|
|
176
|
+
if (CYPHER_LIMIT_RE.test(query)) {
|
|
177
|
+
return query.replace(CYPHER_LIMIT_RE, `LIMIT ${safeLimit}`);
|
|
178
|
+
}
|
|
179
|
+
return `${query.replace(/;\s*$/, '')} LIMIT ${safeLimit}`;
|
|
180
|
+
}
|
|
82
181
|
/** Valid relation types for impact analysis filtering */
|
|
83
182
|
export const VALID_RELATION_TYPES = new Set([
|
|
84
183
|
'CALLS',
|
|
@@ -1173,25 +1272,119 @@ export class LocalBackend {
|
|
|
1173
1272
|
const repo = await this.resolveRepo(repoName);
|
|
1174
1273
|
return this.cypher(repo, { query });
|
|
1175
1274
|
}
|
|
1176
|
-
async
|
|
1177
|
-
|
|
1178
|
-
if (!
|
|
1179
|
-
return
|
|
1275
|
+
async trySimpleGraphstoreNodeScan(repo, query) {
|
|
1276
|
+
const scan = parseSimpleNodeScanQuery(query);
|
|
1277
|
+
if (!scan)
|
|
1278
|
+
return null;
|
|
1279
|
+
if (scan.label && NATIVE_UNSAFE_NODE_LABELS.has(scan.label))
|
|
1280
|
+
return [];
|
|
1281
|
+
const objectsRoot = path.join(repo.storagePath, 'graphstore', 'objects');
|
|
1282
|
+
try {
|
|
1283
|
+
const stat = await fs.stat(objectsRoot);
|
|
1284
|
+
if (!stat.isDirectory())
|
|
1285
|
+
return null;
|
|
1180
1286
|
}
|
|
1287
|
+
catch {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
const rows = [];
|
|
1291
|
+
const visitDir = async (dir) => {
|
|
1292
|
+
if (rows.length >= scan.limit)
|
|
1293
|
+
return;
|
|
1294
|
+
let entries;
|
|
1295
|
+
try {
|
|
1296
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1297
|
+
}
|
|
1298
|
+
catch {
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
for (const entry of entries) {
|
|
1302
|
+
if (rows.length >= scan.limit)
|
|
1303
|
+
return;
|
|
1304
|
+
const entryPath = path.join(dir, entry.name);
|
|
1305
|
+
if (entry.isDirectory()) {
|
|
1306
|
+
await visitDir(entryPath);
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
if (!entry.isFile() || !entry.name.endsWith('.json'))
|
|
1310
|
+
continue;
|
|
1311
|
+
try {
|
|
1312
|
+
const raw = await fs.readFile(entryPath, 'utf-8');
|
|
1313
|
+
const obj = JSON.parse(raw);
|
|
1314
|
+
const id = typeof obj.id === 'string' ? obj.id : '';
|
|
1315
|
+
if (!id || typeof obj.from === 'string' || typeof obj.to === 'string')
|
|
1316
|
+
continue;
|
|
1317
|
+
const label = id.includes(':') ? id.slice(0, id.indexOf(':')) : '';
|
|
1318
|
+
if (!label || (scan.label && label !== scan.label))
|
|
1319
|
+
continue;
|
|
1320
|
+
if (NATIVE_UNSAFE_NODE_LABELS.has(label))
|
|
1321
|
+
continue;
|
|
1322
|
+
const row = projectSimpleNodeRow(obj, label, scan);
|
|
1323
|
+
if (row)
|
|
1324
|
+
rows.push(row);
|
|
1325
|
+
}
|
|
1326
|
+
catch {
|
|
1327
|
+
// Ignore malformed graphstore objects; the native path remains available.
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
await visitDir(objectsRoot);
|
|
1332
|
+
return rows;
|
|
1333
|
+
}
|
|
1334
|
+
async cypher(repo, params) {
|
|
1181
1335
|
// Block write operations (defense-in-depth — DB is already read-only)
|
|
1182
1336
|
if (isWriteQuery(params.query)) {
|
|
1183
1337
|
return {
|
|
1184
1338
|
error: 'Write operations (CREATE, DELETE, SET, MERGE, REMOVE, DROP, ALTER, COPY, DETACH) are not allowed. The knowledge graph is read-only.',
|
|
1185
1339
|
};
|
|
1186
1340
|
}
|
|
1341
|
+
const query = quoteKnownNodeLabels(params.query);
|
|
1342
|
+
const unsafeLabel = getNativeUnsafeNodeLabel(query);
|
|
1343
|
+
if (unsafeLabel) {
|
|
1344
|
+
return [];
|
|
1345
|
+
}
|
|
1346
|
+
const graphstoreRows = await this.trySimpleGraphstoreNodeScan(repo, query);
|
|
1347
|
+
if (graphstoreRows) {
|
|
1348
|
+
return graphstoreRows;
|
|
1349
|
+
}
|
|
1350
|
+
await this.ensureInitialized(repo.id);
|
|
1351
|
+
if (!isCgdbReady(repo.id)) {
|
|
1352
|
+
return { error: 'LadybugDB not ready. Index may be corrupted.' };
|
|
1353
|
+
}
|
|
1354
|
+
const labellessAlias = getSimpleLabellessNodeAlias(query);
|
|
1187
1355
|
try {
|
|
1188
|
-
const result =
|
|
1356
|
+
const result = labellessAlias
|
|
1357
|
+
? await this.executeLabellessNodeScan(repo.id, query, labellessAlias)
|
|
1358
|
+
: await executeQuery(repo.id, query);
|
|
1189
1359
|
return result;
|
|
1190
1360
|
}
|
|
1191
1361
|
catch (err) {
|
|
1192
1362
|
return { error: err.message || 'Query failed' };
|
|
1193
1363
|
}
|
|
1194
1364
|
}
|
|
1365
|
+
async executeLabellessNodeScan(repoId, query, alias) {
|
|
1366
|
+
const limit = getCypherLimit(query) ?? 100;
|
|
1367
|
+
const rows = [];
|
|
1368
|
+
let lastError = null;
|
|
1369
|
+
for (const label of VALID_NODE_LABELS) {
|
|
1370
|
+
if (NATIVE_UNSAFE_NODE_LABELS.has(label))
|
|
1371
|
+
continue;
|
|
1372
|
+
if (rows.length >= limit)
|
|
1373
|
+
break;
|
|
1374
|
+
const labelQuery = withCypherLimit(query.replace(SIMPLE_LABELLESS_MATCH_RE, `MATCH (${alias}:\`${label}\`)`), limit - rows.length);
|
|
1375
|
+
try {
|
|
1376
|
+
const labelRows = await executeQuery(repoId, labelQuery);
|
|
1377
|
+
rows.push(...labelRows);
|
|
1378
|
+
}
|
|
1379
|
+
catch (err) {
|
|
1380
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
if (rows.length === 0 && lastError) {
|
|
1384
|
+
throw lastError;
|
|
1385
|
+
}
|
|
1386
|
+
return rows.slice(0, limit);
|
|
1387
|
+
}
|
|
1195
1388
|
/**
|
|
1196
1389
|
* Format raw Cypher result rows as a markdown table for LLM readability.
|
|
1197
1390
|
* Falls back to raw result if rows aren't tabular objects.
|
|
@@ -1436,7 +1629,7 @@ export class LocalBackend {
|
|
|
1436
1629
|
const symbol = {
|
|
1437
1630
|
id: (r.id ?? r[0]),
|
|
1438
1631
|
name: (r.name ?? r[1]),
|
|
1439
|
-
type: (r.type
|
|
1632
|
+
type: firstNonEmptyString(r.type, r.__cgLabel, r[2]),
|
|
1440
1633
|
filePath: (r.filePath ?? r[3]),
|
|
1441
1634
|
startLine: (r.startLine ?? r[4]),
|
|
1442
1635
|
endLine: (r.endLine ?? r[5]),
|
|
@@ -1476,7 +1669,7 @@ export class LocalBackend {
|
|
|
1476
1669
|
const normalized = rows.map((r) => ({
|
|
1477
1670
|
id: (r.id ?? r[0]),
|
|
1478
1671
|
name: (r.name ?? r[1]),
|
|
1479
|
-
type: (r.type
|
|
1672
|
+
type: firstNonEmptyString(r.type, r.__cgLabel, r[2]),
|
|
1480
1673
|
filePath: (r.filePath ?? r[3]),
|
|
1481
1674
|
startLine: (r.startLine ?? r[4]),
|
|
1482
1675
|
endLine: (r.endLine ?? r[5]),
|
|
@@ -1914,10 +2107,12 @@ export class LocalBackend {
|
|
|
1914
2107
|
queryParams[`hunkEnd${i}`] = hunk.endLine;
|
|
1915
2108
|
});
|
|
1916
2109
|
const symbolQuery = `
|
|
1917
|
-
MATCH (n)
|
|
2110
|
+
MATCH (f:File)-[r:CodeRelation]->(n)
|
|
2111
|
+
WHERE r.type IN ['DEFINES', 'CONTAINS']
|
|
2112
|
+
AND f.filePath ENDS WITH $filePath
|
|
1918
2113
|
AND n.startLine IS NOT NULL AND n.endLine IS NOT NULL
|
|
1919
2114
|
AND (${overlapConditions})
|
|
1920
|
-
RETURN n.id AS id, n.name AS name,
|
|
2115
|
+
RETURN n.id AS id, n.name AS name, '' AS type,
|
|
1921
2116
|
n.filePath AS filePath, n.startLine AS startLine, n.endLine AS endLine
|
|
1922
2117
|
`;
|
|
1923
2118
|
try {
|
|
@@ -1926,7 +2121,8 @@ export class LocalBackend {
|
|
|
1926
2121
|
changedSymbols.push({
|
|
1927
2122
|
id: sym.id || sym[0],
|
|
1928
2123
|
name: sym.name || sym[1],
|
|
1929
|
-
type: sym.type || sym[
|
|
2124
|
+
type: safeNodeLabelForCypher(sym.type, sym.id || sym[0]) ??
|
|
2125
|
+
firstNonEmptyString(sym.type, sym.__cgLabel, sym[2]),
|
|
1930
2126
|
filePath: sym.filePath || sym[3],
|
|
1931
2127
|
change_type: 'touched',
|
|
1932
2128
|
});
|
|
@@ -2266,6 +2462,10 @@ export class LocalBackend {
|
|
|
2266
2462
|
const impacted = [];
|
|
2267
2463
|
const visited = new Set([symId]);
|
|
2268
2464
|
let frontier = [symId];
|
|
2465
|
+
const frontierTypes = new Map();
|
|
2466
|
+
const symLabel = safeNodeLabelForCypher(symType, symId);
|
|
2467
|
+
if (symLabel)
|
|
2468
|
+
frontierTypes.set(symId, symLabel);
|
|
2269
2469
|
let traversalComplete = true;
|
|
2270
2470
|
// Fix #480: For Java (and other JVM) Class/Interface nodes, CALLS edges
|
|
2271
2471
|
// point to Constructor nodes and IMPORTS edges point to File nodes — not
|
|
@@ -2279,16 +2479,16 @@ export class LocalBackend {
|
|
|
2279
2479
|
// Run both seed queries in parallel — they are independent.
|
|
2280
2480
|
const [ctorRows, fileRows] = await Promise.all([
|
|
2281
2481
|
executeParameterized(repo.id, `
|
|
2282
|
-
MATCH (n)-[hm:CodeRelation]->(c:Constructor)
|
|
2482
|
+
MATCH (n:${symLabel ?? symType})-[hm:CodeRelation]->(c:Constructor)
|
|
2283
2483
|
WHERE n.id = $symId AND hm.type = 'HAS_METHOD'
|
|
2284
|
-
RETURN c.id AS id, c.name AS name,
|
|
2484
|
+
RETURN c.id AS id, c.name AS name, 'Constructor' AS type, c.filePath AS filePath
|
|
2285
2485
|
`, { symId }),
|
|
2286
2486
|
// Restrict to DEFINES edges only — other File->Class edge types (if
|
|
2287
2487
|
// any) should not be treated as the owning file relationship.
|
|
2288
2488
|
executeParameterized(repo.id, `
|
|
2289
|
-
MATCH (f:File)-[rel:CodeRelation]->(n)
|
|
2489
|
+
MATCH (f:File)-[rel:CodeRelation]->(n:${symLabel ?? symType})
|
|
2290
2490
|
WHERE n.id = $symId AND rel.type = 'DEFINES'
|
|
2291
|
-
RETURN f.id AS id, f.name AS name,
|
|
2491
|
+
RETURN f.id AS id, f.name AS name, 'File' AS type, f.filePath AS filePath
|
|
2292
2492
|
`, { symId }),
|
|
2293
2493
|
]);
|
|
2294
2494
|
for (const r of ctorRows) {
|
|
@@ -2296,6 +2496,9 @@ export class LocalBackend {
|
|
|
2296
2496
|
if (rid && !visited.has(rid)) {
|
|
2297
2497
|
visited.add(rid);
|
|
2298
2498
|
frontier.push(rid);
|
|
2499
|
+
const label = safeNodeLabelForCypher(r.type ?? r[2], rid);
|
|
2500
|
+
if (label)
|
|
2501
|
+
frontierTypes.set(rid, label);
|
|
2299
2502
|
}
|
|
2300
2503
|
}
|
|
2301
2504
|
for (const r of fileRows) {
|
|
@@ -2303,6 +2506,9 @@ export class LocalBackend {
|
|
|
2303
2506
|
if (rid && !visited.has(rid)) {
|
|
2304
2507
|
visited.add(rid);
|
|
2305
2508
|
frontier.push(rid);
|
|
2509
|
+
const label = safeNodeLabelForCypher(r.type ?? r[2], rid);
|
|
2510
|
+
if (label)
|
|
2511
|
+
frontierTypes.set(rid, label);
|
|
2306
2512
|
}
|
|
2307
2513
|
}
|
|
2308
2514
|
}
|
|
@@ -2312,13 +2518,23 @@ export class LocalBackend {
|
|
|
2312
2518
|
}
|
|
2313
2519
|
for (let depth = 1; depth <= maxDepth && frontier.length > 0; depth++) {
|
|
2314
2520
|
const nextFrontier = [];
|
|
2315
|
-
// Batch frontier nodes into a single Cypher query per depth level
|
|
2316
|
-
const idList = frontier.map((id) => `'${id.replace(/'/g, "''")}'`).join(', ');
|
|
2317
|
-
const query = direction === 'upstream'
|
|
2318
|
-
? `MATCH (caller)-[r:CodeRelation]->(n) WHERE n.id IN [${idList}] AND r.type IN [${relTypeFilter}]${confidenceFilter} RETURN n.id AS sourceId, caller.id AS id, caller.name AS name, labels(caller)[0] AS type, caller.filePath AS filePath, r.type AS relType, r.confidence AS confidence`
|
|
2319
|
-
: `MATCH (n)-[r:CodeRelation]->(callee) WHERE n.id IN [${idList}] AND r.type IN [${relTypeFilter}]${confidenceFilter} RETURN n.id AS sourceId, callee.id AS id, callee.name AS name, labels(callee)[0] AS type, callee.filePath AS filePath, r.type AS relType, r.confidence AS confidence`;
|
|
2320
2521
|
try {
|
|
2321
|
-
const
|
|
2522
|
+
const frontierByLabel = new Map();
|
|
2523
|
+
for (const id of frontier) {
|
|
2524
|
+
const label = frontierTypes.get(id) ?? safeNodeLabelForCypher(undefined, id) ?? '';
|
|
2525
|
+
const ids = frontierByLabel.get(label) ?? [];
|
|
2526
|
+
ids.push(id);
|
|
2527
|
+
frontierByLabel.set(label, ids);
|
|
2528
|
+
}
|
|
2529
|
+
const related = [];
|
|
2530
|
+
for (const [label, ids] of frontierByLabel) {
|
|
2531
|
+
const idList = ids.map((id) => `'${id.replace(/'/g, "''")}'`).join(', ');
|
|
2532
|
+
const targetPattern = label ? `(n:${label})` : '(n)';
|
|
2533
|
+
const query = direction === 'upstream'
|
|
2534
|
+
? `MATCH (caller)-[r:CodeRelation]->${targetPattern} WHERE n.id IN [${idList}] AND r.type IN [${relTypeFilter}]${confidenceFilter} RETURN n.id AS sourceId, caller.id AS id, caller.name AS name, labels(caller)[0] AS type, caller.filePath AS filePath, r.type AS relType, r.confidence AS confidence`
|
|
2535
|
+
: `MATCH ${targetPattern}-[r:CodeRelation]->(callee) WHERE n.id IN [${idList}] AND r.type IN [${relTypeFilter}]${confidenceFilter} RETURN n.id AS sourceId, callee.id AS id, callee.name AS name, labels(callee)[0] AS type, callee.filePath AS filePath, r.type AS relType, r.confidence AS confidence`;
|
|
2536
|
+
related.push(...(await executeQuery(repo.id, query)));
|
|
2537
|
+
}
|
|
2322
2538
|
for (const rel of related) {
|
|
2323
2539
|
const relId = rel.id || rel[1];
|
|
2324
2540
|
const filePath = rel.filePath || rel[4] || '';
|
|
@@ -2327,6 +2543,10 @@ export class LocalBackend {
|
|
|
2327
2543
|
if (!visited.has(relId)) {
|
|
2328
2544
|
visited.add(relId);
|
|
2329
2545
|
nextFrontier.push(relId);
|
|
2546
|
+
const relTypeLabel = firstNonEmptyString(rel.type, rel.__cgLabel, rel[3]);
|
|
2547
|
+
const relLabel = safeNodeLabelForCypher(relTypeLabel, relId);
|
|
2548
|
+
if (relLabel)
|
|
2549
|
+
frontierTypes.set(relId, relLabel);
|
|
2330
2550
|
const storedConfidence = rel.confidence ?? rel[6];
|
|
2331
2551
|
const relationType = rel.relType || rel[5];
|
|
2332
2552
|
// Prefer the stored confidence from the graph (set at analysis time);
|
|
@@ -2338,7 +2558,7 @@ export class LocalBackend {
|
|
|
2338
2558
|
depth,
|
|
2339
2559
|
id: relId,
|
|
2340
2560
|
name: rel.name || rel[2],
|
|
2341
|
-
type:
|
|
2561
|
+
type: relTypeLabel,
|
|
2342
2562
|
filePath,
|
|
2343
2563
|
relationType,
|
|
2344
2564
|
confidence: effectiveConfidence,
|
|
@@ -83,6 +83,35 @@ export interface RepoMeta {
|
|
|
83
83
|
* — they decode at the read boundary.
|
|
84
84
|
*/
|
|
85
85
|
compress?: 'none' | 'brotli' | 'zstd';
|
|
86
|
+
/**
|
|
87
|
+
* Query-time index capabilities written by analyze after optional search
|
|
88
|
+
* indexes are successfully created. Read-only MCP/CLI query paths use this
|
|
89
|
+
* to avoid expensive probes for indexes that older analyses never wrote.
|
|
90
|
+
*/
|
|
91
|
+
searchIndexes?: {
|
|
92
|
+
fts?: boolean;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Runtime policy chosen by `analyze --profile auto|lean|balanced|power`.
|
|
96
|
+
* This is diagnostic metadata only; readers must not require it because
|
|
97
|
+
* older indexes do not have a policy record.
|
|
98
|
+
*/
|
|
99
|
+
adaptiveProfile?: {
|
|
100
|
+
requested?: 'auto' | 'lean' | 'balanced' | 'power';
|
|
101
|
+
resolved?: 'lean' | 'balanced' | 'power';
|
|
102
|
+
platform?: NodeJS.Platform;
|
|
103
|
+
arch?: NodeJS.Architecture;
|
|
104
|
+
cpuCount?: number;
|
|
105
|
+
totalMemoryBytes?: number;
|
|
106
|
+
heapLimitBytes?: number;
|
|
107
|
+
compression?: 'none' | 'brotli' | 'zstd';
|
|
108
|
+
embeddingMode?: 'auto' | 'off' | 'on';
|
|
109
|
+
embeddingNodeLimit?: number;
|
|
110
|
+
embeddingDecision?: 'enabled' | 'skipped';
|
|
111
|
+
embeddingReason?: string;
|
|
112
|
+
workerPoolSize?: number;
|
|
113
|
+
workerSubBatchSize?: number;
|
|
114
|
+
};
|
|
86
115
|
/**
|
|
87
116
|
* Canonical `origin` remote URL captured at index time. Used to
|
|
88
117
|
* fingerprint the same logical repo across multiple on-disk clones
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={};export{e as default};
|