@codragraph/cli 2.1.5 → 2.2.0-rc.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 +18 -13
- package/dist/cli/analyze.d.ts +9 -4
- package/dist/cli/analyze.js +37 -13
- package/dist/cli/graphpack.d.ts +48 -0
- package/dist/cli/graphpack.js +217 -0
- package/dist/cli/index.js +81 -3
- package/dist/cli/status.d.ts +1 -1
- package/dist/cli/status.js +8 -0
- package/dist/cli/tool.d.ts +11 -2
- package/dist/cli/tool.js +138 -8
- 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 +1 -1
- package/dist/core/graphpack/index.d.ts +14 -0
- package/dist/core/graphpack/index.js +474 -0
- package/dist/core/graphpack/types.d.ts +129 -0
- package/dist/core/graphpack/types.js +4 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +3 -1
- package/dist/core/ingestion/pipeline-phases/structure.js +19 -3
- package/dist/core/ingestion/pipeline.d.ts +10 -0
- package/dist/core/run-analyze.d.ts +27 -2
- package/dist/core/run-analyze.js +598 -27
- package/dist/core/search/bm25-index.d.ts +19 -0
- package/dist/core/search/bm25-index.js +68 -29
- package/dist/core/semantic/relationships.d.ts +36 -0
- package/dist/core/semantic/relationships.js +261 -0
- package/dist/mcp/local/local-backend.js +48 -3
- package/dist/mcp/resources.js +125 -0
- package/dist/mcp/tools.js +105 -0
- package/dist/server/api.js +112 -0
- package/dist/storage/repo-manager.d.ts +29 -0
- package/dist/web/assets/agent-CQNZQ-hg.js +1139 -0
- package/dist/web/assets/architectureDiagram-UL44E2DR-B5_goS_i.js +36 -0
- package/dist/web/assets/blockDiagram-7IZFK4PR-D7ZAlDyv.js +132 -0
- package/dist/web/assets/{c4Diagram-DFAF54RM-C4Hl3J2U.js → c4Diagram-Y2BXMSZH-Djcgm_54.js} +1 -1
- package/dist/web/assets/{chunk-7RZVMHOQ-BitYcNVR.js → chunk-3SSMPTDK-Cv2Zy2FO.js} +1 -1
- package/dist/web/assets/{chunk-TBF5ZNIQ-DL5stGM1.js → chunk-6764PJDD-Cppb-jH-.js} +1 -1
- package/dist/web/assets/{chunk-KSICW3F5-BYzvDLNI.js → chunk-AZZRMDJM-BHlLC7p3.js} +1 -1
- package/dist/web/assets/{chunk-AEOMTBSW-BgTIXPsY.js → chunk-JQRUD6KW-3F8Zg-1N.js} +1 -1
- package/dist/web/assets/chunk-KRXBNO2N-C0mbN9a7.js +1 -0
- package/dist/web/assets/chunk-LCXTWHL2-BoiuJpIF.js +231 -0
- package/dist/web/assets/{chunk-O5ABG6QK-dHwHzA6n.js → chunk-LII3EMHJ-Dqq0Qguw.js} +1 -1
- package/dist/web/assets/chunk-RG4AUYOV-Bl5F_gDs.js +206 -0
- package/dist/web/assets/{chunk-TU3PZOEN-RLyvLcv-.js → chunk-T5OCTHI4-B2tIcggA.js} +1 -1
- package/dist/web/assets/chunk-W44A43WB-BHe37iN7.js +13 -0
- package/dist/web/assets/{chunk-RWUO3TPN-BgRTY0_k.js → chunk-ZXARS5L4-wcrIaQvY.js} +1 -1
- package/dist/web/assets/classDiagram-KGZ6W3CR-IbI6v_24.js +1 -0
- package/dist/web/assets/classDiagram-v2-72OJOZXJ-IbI6v_24.js +1 -0
- package/dist/web/assets/{cose-bilkent-PNC4W37J-DVhePRYg.js → cose-bilkent-UX7MHV2Q-BWr7v0Wr.js} +1 -1
- package/dist/web/assets/dagre-ND4H6XIP-De5LIh1B.js +4 -0
- package/dist/web/assets/diagram-3NCE3AQN-Dd22FSHy.js +43 -0
- package/dist/web/assets/diagram-GF46GFSD-Cev3THY8.js +24 -0
- package/dist/web/assets/diagram-HNR7UZ2L-D8Z8RQGs.js +3 -0
- package/dist/web/assets/diagram-QXG6HAR7-B8VOJOiE.js +24 -0
- package/dist/web/assets/diagram-WEQXMOUZ-va1bLoMD.js +10 -0
- package/dist/web/assets/{erDiagram-GCSMX5X6-C3dhDFA8.js → erDiagram-L5TCEMPS-B3_9uAoP.js} +5 -5
- package/dist/web/assets/{flowDiagram-OTCZ4VVT-CWSFWmhr.js → flowDiagram-H6V6AXG4-98m6maI1.js} +9 -9
- package/dist/web/assets/ganttDiagram-JCBTUEKG-vE2nzETb.js +292 -0
- package/dist/web/assets/gitGraphDiagram-S2ZK5IYY-DKc8uUg_.js +106 -0
- package/dist/web/assets/index-BAhe1HSk.css +1 -0
- package/dist/web/assets/index-VTKdaklA.js +1415 -0
- package/dist/web/assets/infoDiagram-3YFTVSEB-DYP-Srzx.js +2 -0
- package/dist/web/assets/{ishikawaDiagram-YMYX4NHK-DUoJvNP2.js → ishikawaDiagram-BNXS4ZKH-QZnkpmmb.js} +3 -3
- package/dist/web/assets/{journeyDiagram-SO5T7YLQ-RMFPNNqz.js → journeyDiagram-M6C3CM3L-B5ojIuqu.js} +1 -1
- package/dist/web/assets/{kanban-definition-LJHFXRCJ-BzpDs1K9.js → kanban-definition-75IXJCU3-BJA8liRR.js} +4 -4
- package/dist/web/assets/{katex-GD7MH7QM-DBQvrix-.js → katex-K3KEBU37-DUqZiCRL.js} +1 -1
- package/dist/web/assets/mindmap-definition-2TDM6QVE-BQj5yylD.js +96 -0
- package/dist/web/assets/pieDiagram-CU6KROY3-4eSrPiQz.js +30 -0
- package/dist/web/assets/quadrantDiagram-VICAPDV7-PzxN8j55.js +7 -0
- package/dist/web/assets/{requirementDiagram-M5DCFWZL-DLHOVTSv.js → requirementDiagram-JXO7QTGE-CtplTc5y.js} +2 -2
- package/dist/web/assets/sankeyDiagram-URQDO5SZ-CoSgvkxv.js +40 -0
- package/dist/web/assets/sequenceDiagram-VS2MUI6T-D7ygyXvJ.js +162 -0
- package/dist/web/assets/stateDiagram-7D4R322I-v01gvwji.js +1 -0
- package/dist/web/assets/stateDiagram-v2-36443NZ5-DFD2b8_x.js +1 -0
- package/dist/web/assets/{timeline-definition-5SPVSISX-TRSDRgPw.js → timeline-definition-O6YCAMPW-CTI3M65J.js} +4 -4
- package/dist/web/assets/{vennDiagram-IE5QUKF5-DNy7HRBM.js → vennDiagram-MWXL3ELB-RnB0XMP7.js} +6 -6
- package/dist/web/assets/wardley-L42UT6IY-5TKZOOLJ-C-ZcgEBb.js +173 -0
- package/dist/web/assets/wardleyDiagram-CUQ6CDDI-EwRi4kwo.js +78 -0
- package/dist/web/assets/{xychartDiagram-ZHJ5623Y-Dr9r7a35.js → xychartDiagram-N2JHSOCM-DA38II6y.js} +4 -4
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- 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/index-CT0GtFLZ.css +0 -1
- 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
package/dist/cli/tool.d.ts
CHANGED
|
@@ -21,15 +21,23 @@ export declare function queryCommand(queryText: string, options?: {
|
|
|
21
21
|
limit?: string;
|
|
22
22
|
content?: boolean;
|
|
23
23
|
}): Promise<void>;
|
|
24
|
-
export declare function contextCommand(name: string
|
|
24
|
+
export declare function contextCommand(name: string | undefined | {
|
|
25
|
+
opts?: () => Record<string, unknown>;
|
|
26
|
+
}, options?: {
|
|
25
27
|
repo?: string;
|
|
26
28
|
file?: string;
|
|
27
29
|
uid?: string;
|
|
30
|
+
kind?: string;
|
|
28
31
|
content?: boolean;
|
|
29
32
|
}): Promise<void>;
|
|
30
|
-
export declare function impactCommand(target: string
|
|
33
|
+
export declare function impactCommand(target: string | undefined | {
|
|
34
|
+
opts?: () => Record<string, unknown>;
|
|
35
|
+
}, options?: {
|
|
31
36
|
direction?: string;
|
|
32
37
|
repo?: string;
|
|
38
|
+
uid?: string;
|
|
39
|
+
file?: string;
|
|
40
|
+
kind?: string;
|
|
33
41
|
depth?: string;
|
|
34
42
|
includeTests?: boolean;
|
|
35
43
|
}): Promise<void>;
|
|
@@ -55,6 +63,7 @@ export declare function clusterContextCommand(name: string, options?: {
|
|
|
55
63
|
export declare function contextPackCommand(name: string, options?: {
|
|
56
64
|
repo?: string;
|
|
57
65
|
limit?: string;
|
|
66
|
+
compress?: string;
|
|
58
67
|
}): Promise<void>;
|
|
59
68
|
export declare function clusterImpactCommand(name: string, options?: {
|
|
60
69
|
direction?: string;
|
package/dist/cli/tool.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* native module which captures the Node.js process.stdout stream during init.
|
|
15
15
|
* See the output() function for details (#324).
|
|
16
16
|
*/
|
|
17
|
+
import crypto from 'node:crypto';
|
|
17
18
|
import { writeSync } from 'node:fs';
|
|
18
19
|
import { LocalBackend } from '../mcp/local/local-backend.js';
|
|
19
20
|
import { emitTokenStats } from './compress-stats.js';
|
|
@@ -45,6 +46,12 @@ async function resolveCliRepoParam(repoParam) {
|
|
|
45
46
|
process.exitCode = 1;
|
|
46
47
|
return null;
|
|
47
48
|
}
|
|
49
|
+
function unwrapCommanderOptions(value) {
|
|
50
|
+
if (!value || typeof value !== 'object')
|
|
51
|
+
return value;
|
|
52
|
+
const maybeCommand = value;
|
|
53
|
+
return typeof maybeCommand.opts === 'function' ? maybeCommand.opts() : value;
|
|
54
|
+
}
|
|
48
55
|
/**
|
|
49
56
|
* Write tool output to stdout using low-level fd write.
|
|
50
57
|
*
|
|
@@ -90,7 +97,12 @@ export async function queryCommand(queryText, options) {
|
|
|
90
97
|
emitTokenStats(result);
|
|
91
98
|
}
|
|
92
99
|
export async function contextCommand(name, options) {
|
|
93
|
-
|
|
100
|
+
let symbolName = typeof name === 'string' ? name : undefined;
|
|
101
|
+
if (typeof name !== 'string' && name && !options) {
|
|
102
|
+
options = unwrapCommanderOptions(name);
|
|
103
|
+
symbolName = undefined;
|
|
104
|
+
}
|
|
105
|
+
if (!symbolName?.trim() && !options?.uid) {
|
|
94
106
|
console.error('Usage: codragraph context <symbol_name> [--uid <uid>] [--file <path>]');
|
|
95
107
|
process.exit(1);
|
|
96
108
|
}
|
|
@@ -98,9 +110,10 @@ export async function contextCommand(name, options) {
|
|
|
98
110
|
if (!repo)
|
|
99
111
|
return;
|
|
100
112
|
const result = await callToolOnce('context', {
|
|
101
|
-
name:
|
|
113
|
+
name: symbolName || undefined,
|
|
102
114
|
uid: options?.uid,
|
|
103
115
|
file_path: options?.file,
|
|
116
|
+
kind: options?.kind,
|
|
104
117
|
include_content: options?.content ?? false,
|
|
105
118
|
repo,
|
|
106
119
|
});
|
|
@@ -108,8 +121,13 @@ export async function contextCommand(name, options) {
|
|
|
108
121
|
emitTokenStats(result);
|
|
109
122
|
}
|
|
110
123
|
export async function impactCommand(target, options) {
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
let targetName = typeof target === 'string' ? target : undefined;
|
|
125
|
+
if (typeof target !== 'string' && target && !options) {
|
|
126
|
+
options = unwrapCommanderOptions(target);
|
|
127
|
+
targetName = undefined;
|
|
128
|
+
}
|
|
129
|
+
if (!targetName?.trim() && !options?.uid) {
|
|
130
|
+
console.error('Usage: codragraph impact <symbol_name> [--uid <uid>] [--direction upstream|downstream]');
|
|
113
131
|
process.exit(1);
|
|
114
132
|
}
|
|
115
133
|
try {
|
|
@@ -117,7 +135,10 @@ export async function impactCommand(target, options) {
|
|
|
117
135
|
if (!repo)
|
|
118
136
|
return;
|
|
119
137
|
const result = await callToolOnce('impact', {
|
|
120
|
-
target,
|
|
138
|
+
target: targetName || undefined,
|
|
139
|
+
target_uid: options?.uid,
|
|
140
|
+
file_path: options?.file,
|
|
141
|
+
kind: options?.kind,
|
|
121
142
|
direction: options?.direction || 'upstream',
|
|
122
143
|
maxDepth: options?.depth ? parseInt(options.depth, 10) : undefined,
|
|
123
144
|
includeTests: options?.includeTests ?? false,
|
|
@@ -131,7 +152,7 @@ export async function impactCommand(target, options) {
|
|
|
131
152
|
// The backend's impact() already returns structured errors for graph query failures
|
|
132
153
|
output({
|
|
133
154
|
error: (err instanceof Error ? err.message : String(err)) || 'Impact analysis failed unexpectedly',
|
|
134
|
-
target: { name:
|
|
155
|
+
target: { name: targetName || options?.uid },
|
|
135
156
|
direction: options?.direction || 'upstream',
|
|
136
157
|
suggestion: 'Try reducing --depth or using codragraph context <symbol> as a fallback',
|
|
137
158
|
});
|
|
@@ -218,8 +239,116 @@ export async function contextPackCommand(name, options) {
|
|
|
218
239
|
repo,
|
|
219
240
|
limit: options?.limit ? parseInt(options.limit, 10) : undefined,
|
|
220
241
|
});
|
|
221
|
-
|
|
222
|
-
|
|
242
|
+
const maybeCompressed = options?.compress
|
|
243
|
+
? compressContextPackForCli(result, name, options.compress)
|
|
244
|
+
: result;
|
|
245
|
+
output(maybeCompressed);
|
|
246
|
+
emitTokenStats(maybeCompressed);
|
|
247
|
+
}
|
|
248
|
+
function compressContextPackForCli(result, featureName, rawLevel) {
|
|
249
|
+
const level = normalizeCliCompressionLevel(rawLevel);
|
|
250
|
+
const originalText = JSON.stringify(result);
|
|
251
|
+
const compressed = pruneContextPack(result, level);
|
|
252
|
+
const compressedText = JSON.stringify(compressed);
|
|
253
|
+
const originalTokens = estimateJsonTokens(originalText);
|
|
254
|
+
const compressedTokens = estimateJsonTokens(compressedText);
|
|
255
|
+
const snapshotId = result?.snapshotId ??
|
|
256
|
+
result?.snapshot_id ??
|
|
257
|
+
result?.cluster?.lastIndexedCommit ??
|
|
258
|
+
result?.cluster?.snapshotId ??
|
|
259
|
+
'unknown';
|
|
260
|
+
const clusterId = result?.cluster?.id ?? result?.cluster?.slug ?? featureName;
|
|
261
|
+
const cacheKey = crypto
|
|
262
|
+
.createHash('sha256')
|
|
263
|
+
.update(`${snapshotId}\n${clusterId}\n${level}\ncli-context-pack-compressor-v1`)
|
|
264
|
+
.digest('hex');
|
|
265
|
+
return {
|
|
266
|
+
...compressed,
|
|
267
|
+
compression: {
|
|
268
|
+
level,
|
|
269
|
+
compressorVersion: 'cli-context-pack-compressor-v1',
|
|
270
|
+
cacheKey: `ctxpack:${cacheKey.slice(0, 32)}`,
|
|
271
|
+
originalTokens,
|
|
272
|
+
compressedTokens,
|
|
273
|
+
tokenSavingsPct: originalTokens > 0
|
|
274
|
+
? Number((((originalTokens - compressedTokens) / originalTokens) * 100).toFixed(1))
|
|
275
|
+
: 0,
|
|
276
|
+
preserved: [
|
|
277
|
+
'feature cluster',
|
|
278
|
+
'files',
|
|
279
|
+
'line ranges',
|
|
280
|
+
'symbols',
|
|
281
|
+
'tests',
|
|
282
|
+
'routes',
|
|
283
|
+
'tools',
|
|
284
|
+
'dependencies',
|
|
285
|
+
'warnings',
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function normalizeCliCompressionLevel(raw) {
|
|
291
|
+
if (raw === 'balanced' || raw === 'lean' || raw === 'max')
|
|
292
|
+
return raw;
|
|
293
|
+
throw new Error('context-pack --compress must be one of: balanced, lean, max');
|
|
294
|
+
}
|
|
295
|
+
function pruneContextPack(value, level) {
|
|
296
|
+
if (!value || typeof value !== 'object')
|
|
297
|
+
return value;
|
|
298
|
+
const memberLimit = level === 'max' ? 20 : level === 'lean' ? 40 : 80;
|
|
299
|
+
const processLimit = level === 'max' ? 5 : level === 'lean' ? 8 : 15;
|
|
300
|
+
const supportLimit = level === 'max' ? 5 : level === 'lean' ? 10 : 20;
|
|
301
|
+
return {
|
|
302
|
+
cluster: value.cluster,
|
|
303
|
+
members: Array.isArray(value.members)
|
|
304
|
+
? value.members.slice(0, memberLimit).map(compactContextPackItem)
|
|
305
|
+
: value.members,
|
|
306
|
+
entryPoints: Array.isArray(value.entryPoints)
|
|
307
|
+
? value.entryPoints.slice(0, supportLimit).map(compactContextPackItem)
|
|
308
|
+
: value.entryPoints,
|
|
309
|
+
routes: value.routes,
|
|
310
|
+
tools: value.tools,
|
|
311
|
+
dependencies: value.dependencies,
|
|
312
|
+
processes: Array.isArray(value.processes)
|
|
313
|
+
? value.processes.slice(0, processLimit).map(compactContextPackItem)
|
|
314
|
+
: value.processes,
|
|
315
|
+
tests: Array.isArray(value.tests)
|
|
316
|
+
? value.tests.slice(0, supportLimit).map(compactContextPackItem)
|
|
317
|
+
: value.tests,
|
|
318
|
+
docs: Array.isArray(value.docs)
|
|
319
|
+
? value.docs.slice(0, supportLimit).map(compactContextPackItem)
|
|
320
|
+
: value.docs,
|
|
321
|
+
warnings: value.warnings ?? value.safeEditSurface?.warnings,
|
|
322
|
+
safeEditSurface: value.safeEditSurface,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function compactContextPackItem(item) {
|
|
326
|
+
if (!item || typeof item !== 'object')
|
|
327
|
+
return item;
|
|
328
|
+
const keys = [
|
|
329
|
+
'id',
|
|
330
|
+
'uid',
|
|
331
|
+
'name',
|
|
332
|
+
'type',
|
|
333
|
+
'kind',
|
|
334
|
+
'filePath',
|
|
335
|
+
'file',
|
|
336
|
+
'startLine',
|
|
337
|
+
'endLine',
|
|
338
|
+
'route',
|
|
339
|
+
'method',
|
|
340
|
+
'summary',
|
|
341
|
+
'confidence',
|
|
342
|
+
];
|
|
343
|
+
const compact = {};
|
|
344
|
+
for (const key of keys) {
|
|
345
|
+
if (item[key] !== undefined)
|
|
346
|
+
compact[key] = item[key];
|
|
347
|
+
}
|
|
348
|
+
return compact;
|
|
349
|
+
}
|
|
350
|
+
function estimateJsonTokens(text) {
|
|
351
|
+
return Math.ceil(text.length / 4);
|
|
223
352
|
}
|
|
224
353
|
export async function clusterImpactCommand(name, options) {
|
|
225
354
|
if (!name?.trim()) {
|
|
@@ -272,6 +401,7 @@ function formatDetectChangesResult(result) {
|
|
|
272
401
|
return lines.join('\n').trim();
|
|
273
402
|
}
|
|
274
403
|
export async function detectChangesCommand(options) {
|
|
404
|
+
options = unwrapCommanderOptions(options);
|
|
275
405
|
const repo = await resolveCliRepoParam(options?.repo);
|
|
276
406
|
if (!repo)
|
|
277
407
|
return;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ContentEncoding } from '@codragraph/graphstore';
|
|
2
|
+
import type { RepoMeta } from '../storage/repo-manager.js';
|
|
3
|
+
export type AnalyzeProfileOption = 'auto' | 'lean' | 'balanced' | 'power';
|
|
4
|
+
export type ResolvedAnalyzeProfile = Exclude<AnalyzeProfileOption, 'auto'>;
|
|
5
|
+
export type EmbeddingMode = 'auto' | 'off' | 'on';
|
|
6
|
+
export type CompressionOption = ContentEncoding | 'auto';
|
|
7
|
+
export interface MachineSnapshot {
|
|
8
|
+
platform: NodeJS.Platform;
|
|
9
|
+
arch: NodeJS.Architecture;
|
|
10
|
+
logicalCpus: number;
|
|
11
|
+
availableParallelism: number;
|
|
12
|
+
totalMemoryBytes: number;
|
|
13
|
+
freeMemoryBytes: number;
|
|
14
|
+
heapLimitBytes: number;
|
|
15
|
+
nodeVersion: string;
|
|
16
|
+
httpEmbeddingsConfigured: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface AdaptiveAnalyzePlan {
|
|
19
|
+
requestedProfile: AnalyzeProfileOption;
|
|
20
|
+
profile: ResolvedAnalyzeProfile;
|
|
21
|
+
machine: MachineSnapshot;
|
|
22
|
+
compress: ContentEncoding;
|
|
23
|
+
embeddingMode: EmbeddingMode;
|
|
24
|
+
embeddingNodeLimit: number;
|
|
25
|
+
workerPoolSize: number;
|
|
26
|
+
workerSubBatchSize: number;
|
|
27
|
+
reasons: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface EmbeddingDecision {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
reason: string;
|
|
32
|
+
limit: number;
|
|
33
|
+
}
|
|
34
|
+
export declare const parseAnalyzeProfile: (value?: string) => AnalyzeProfileOption;
|
|
35
|
+
export declare const parseEmbeddingMode: (value?: string) => EmbeddingMode;
|
|
36
|
+
export declare const parseCompressionOption: (value?: string) => CompressionOption;
|
|
37
|
+
export declare const detectMachineSnapshot: () => MachineSnapshot;
|
|
38
|
+
export declare const resolveAnalyzeProfile: (requested: AnalyzeProfileOption, machine: MachineSnapshot) => ResolvedAnalyzeProfile;
|
|
39
|
+
export declare const resolveEmbeddingMode: (embeddingsFlag: boolean | undefined, requestedMode: string | undefined) => EmbeddingMode;
|
|
40
|
+
export declare const resolveCompression: (requested: CompressionOption, profile: ResolvedAnalyzeProfile, existingMeta?: Pick<RepoMeta, "compress"> | null) => ContentEncoding;
|
|
41
|
+
export declare const embeddingLimitForProfile: (profile: ResolvedAnalyzeProfile, httpEmbeddingsConfigured: boolean) => number;
|
|
42
|
+
export declare const workerPlanForProfile: (profile: ResolvedAnalyzeProfile, machine: Pick<MachineSnapshot, "availableParallelism">) => Pick<AdaptiveAnalyzePlan, "workerPoolSize" | "workerSubBatchSize">;
|
|
43
|
+
export declare const resolveAdaptiveAnalyzePlan: (input: {
|
|
44
|
+
profile?: string;
|
|
45
|
+
embeddingMode?: string;
|
|
46
|
+
embeddings?: boolean;
|
|
47
|
+
compress?: string;
|
|
48
|
+
existingMeta?: Pick<RepoMeta, "compress"> | null;
|
|
49
|
+
machine?: MachineSnapshot;
|
|
50
|
+
}) => AdaptiveAnalyzePlan;
|
|
51
|
+
export declare const decideEmbeddingRun: (plan: Pick<AdaptiveAnalyzePlan, "embeddingMode" | "embeddingNodeLimit" | "profile" | "machine">, stats?: Pick<NonNullable<RepoMeta["stats"]>, "nodes" | "embeddings">) => EmbeddingDecision;
|
|
52
|
+
export declare const formatAdaptiveAnalyzePlan: (plan: AdaptiveAnalyzePlan) => string;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import v8 from 'node:v8';
|
|
3
|
+
const GIB = 1024 * 1024 * 1024;
|
|
4
|
+
const validProfiles = new Set(['auto', 'lean', 'balanced', 'power']);
|
|
5
|
+
const validEmbeddingModes = new Set(['auto', 'off', 'on']);
|
|
6
|
+
const validCompressionOptions = new Set(['auto', 'none', 'brotli', 'zstd']);
|
|
7
|
+
const safeAvailableParallelism = () => {
|
|
8
|
+
const fn = os.availableParallelism;
|
|
9
|
+
if (typeof fn === 'function') {
|
|
10
|
+
try {
|
|
11
|
+
return Math.max(1, fn());
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
/* fall back to os.cpus */
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return Math.max(1, os.cpus().length);
|
|
18
|
+
};
|
|
19
|
+
export const parseAnalyzeProfile = (value) => {
|
|
20
|
+
const normalized = (value ?? 'auto').toLowerCase();
|
|
21
|
+
if (validProfiles.has(normalized)) {
|
|
22
|
+
return normalized;
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`profile must be one of: auto, lean, balanced, power (got: ${value})`);
|
|
25
|
+
};
|
|
26
|
+
export const parseEmbeddingMode = (value) => {
|
|
27
|
+
const normalized = (value ?? 'auto').toLowerCase();
|
|
28
|
+
if (validEmbeddingModes.has(normalized)) {
|
|
29
|
+
return normalized;
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`embedding-mode must be one of: auto, off, on (got: ${value})`);
|
|
32
|
+
};
|
|
33
|
+
export const parseCompressionOption = (value) => {
|
|
34
|
+
const normalized = (value ?? 'auto').toLowerCase();
|
|
35
|
+
if (validCompressionOptions.has(normalized)) {
|
|
36
|
+
return normalized;
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`compress must be one of: auto, none, brotli, zstd (got: ${value})`);
|
|
39
|
+
};
|
|
40
|
+
export const detectMachineSnapshot = () => ({
|
|
41
|
+
platform: process.platform,
|
|
42
|
+
arch: process.arch,
|
|
43
|
+
logicalCpus: Math.max(1, os.cpus().length),
|
|
44
|
+
availableParallelism: safeAvailableParallelism(),
|
|
45
|
+
totalMemoryBytes: os.totalmem(),
|
|
46
|
+
freeMemoryBytes: os.freemem(),
|
|
47
|
+
heapLimitBytes: v8.getHeapStatistics().heap_size_limit,
|
|
48
|
+
nodeVersion: process.version,
|
|
49
|
+
httpEmbeddingsConfigured: Boolean(process.env.CODRAGRAPH_EMBEDDING_URL && process.env.CODRAGRAPH_EMBEDDING_MODEL),
|
|
50
|
+
});
|
|
51
|
+
export const resolveAnalyzeProfile = (requested, machine) => {
|
|
52
|
+
if (requested !== 'auto')
|
|
53
|
+
return requested;
|
|
54
|
+
const memoryGiB = machine.totalMemoryBytes / GIB;
|
|
55
|
+
const heapGiB = machine.heapLimitBytes / GIB;
|
|
56
|
+
const parallelism = machine.availableParallelism;
|
|
57
|
+
if (parallelism >= 8 && memoryGiB >= 24 && heapGiB >= 6)
|
|
58
|
+
return 'power';
|
|
59
|
+
if (parallelism >= 4 && memoryGiB >= 8 && heapGiB >= 3)
|
|
60
|
+
return 'balanced';
|
|
61
|
+
return 'lean';
|
|
62
|
+
};
|
|
63
|
+
export const resolveEmbeddingMode = (embeddingsFlag, requestedMode) => {
|
|
64
|
+
if (embeddingsFlag === true)
|
|
65
|
+
return 'on';
|
|
66
|
+
return parseEmbeddingMode(requestedMode);
|
|
67
|
+
};
|
|
68
|
+
export const resolveCompression = (requested, profile, existingMeta) => {
|
|
69
|
+
if (requested !== 'auto')
|
|
70
|
+
return requested;
|
|
71
|
+
if (existingMeta)
|
|
72
|
+
return existingMeta.compress ?? 'none';
|
|
73
|
+
// Lean machines optimize for index footprint and lower read amplification.
|
|
74
|
+
// Balanced/power machines keep full body BM25 by default for maximum recall.
|
|
75
|
+
return profile === 'lean' ? 'brotli' : 'none';
|
|
76
|
+
};
|
|
77
|
+
export const embeddingLimitForProfile = (profile, httpEmbeddingsConfigured) => {
|
|
78
|
+
if (httpEmbeddingsConfigured)
|
|
79
|
+
return profile === 'lean' ? 25_000 : 150_000;
|
|
80
|
+
if (profile === 'power')
|
|
81
|
+
return 100_000;
|
|
82
|
+
if (profile === 'balanced')
|
|
83
|
+
return 35_000;
|
|
84
|
+
return 8_000;
|
|
85
|
+
};
|
|
86
|
+
export const workerPlanForProfile = (profile, machine) => {
|
|
87
|
+
const spare = Math.max(1, machine.availableParallelism - 1);
|
|
88
|
+
if (profile === 'power') {
|
|
89
|
+
return { workerPoolSize: Math.min(8, spare), workerSubBatchSize: 250 };
|
|
90
|
+
}
|
|
91
|
+
if (profile === 'balanced') {
|
|
92
|
+
return { workerPoolSize: Math.min(4, spare), workerSubBatchSize: 160 };
|
|
93
|
+
}
|
|
94
|
+
return { workerPoolSize: 1, workerSubBatchSize: 80 };
|
|
95
|
+
};
|
|
96
|
+
export const resolveAdaptiveAnalyzePlan = (input) => {
|
|
97
|
+
const machine = input.machine ?? detectMachineSnapshot();
|
|
98
|
+
const requestedProfile = parseAnalyzeProfile(input.profile);
|
|
99
|
+
const profile = resolveAnalyzeProfile(requestedProfile, machine);
|
|
100
|
+
const embeddingMode = resolveEmbeddingMode(input.embeddings, input.embeddingMode);
|
|
101
|
+
const compress = resolveCompression(parseCompressionOption(input.compress), profile, input.existingMeta);
|
|
102
|
+
const workerPlan = workerPlanForProfile(profile, machine);
|
|
103
|
+
const embeddingNodeLimit = embeddingLimitForProfile(profile, machine.httpEmbeddingsConfigured);
|
|
104
|
+
const reasons = [
|
|
105
|
+
`${machine.platform}/${machine.arch}`,
|
|
106
|
+
`${machine.availableParallelism} parallel slot(s)`,
|
|
107
|
+
`${(machine.totalMemoryBytes / GIB).toFixed(1)} GiB RAM`,
|
|
108
|
+
`${(machine.heapLimitBytes / GIB).toFixed(1)} GiB heap`,
|
|
109
|
+
];
|
|
110
|
+
if (machine.httpEmbeddingsConfigured)
|
|
111
|
+
reasons.push('HTTP embeddings configured');
|
|
112
|
+
return {
|
|
113
|
+
requestedProfile,
|
|
114
|
+
profile,
|
|
115
|
+
machine,
|
|
116
|
+
compress,
|
|
117
|
+
embeddingMode,
|
|
118
|
+
embeddingNodeLimit,
|
|
119
|
+
workerPoolSize: workerPlan.workerPoolSize,
|
|
120
|
+
workerSubBatchSize: workerPlan.workerSubBatchSize,
|
|
121
|
+
reasons,
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
export const decideEmbeddingRun = (plan, stats) => {
|
|
125
|
+
const nodes = stats?.nodes;
|
|
126
|
+
const existingEmbeddings = stats?.embeddings ?? 0;
|
|
127
|
+
const overLimit = typeof nodes === 'number' && nodes > plan.embeddingNodeLimit;
|
|
128
|
+
if (plan.embeddingMode === 'off') {
|
|
129
|
+
return { enabled: false, reason: 'embedding-mode is off', limit: plan.embeddingNodeLimit };
|
|
130
|
+
}
|
|
131
|
+
if (overLimit) {
|
|
132
|
+
return {
|
|
133
|
+
enabled: false,
|
|
134
|
+
reason: `${nodes.toLocaleString('en-US')} nodes exceeds ${plan.profile} embedding limit ${plan.embeddingNodeLimit.toLocaleString('en-US')}`,
|
|
135
|
+
limit: plan.embeddingNodeLimit,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (plan.embeddingMode === 'on') {
|
|
139
|
+
return { enabled: true, reason: 'explicit embedding request', limit: plan.embeddingNodeLimit };
|
|
140
|
+
}
|
|
141
|
+
if (existingEmbeddings > 0) {
|
|
142
|
+
return {
|
|
143
|
+
enabled: true,
|
|
144
|
+
reason: 'preserving existing embeddings',
|
|
145
|
+
limit: plan.embeddingNodeLimit,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (plan.machine.httpEmbeddingsConfigured) {
|
|
149
|
+
return {
|
|
150
|
+
enabled: true,
|
|
151
|
+
reason: 'HTTP embedding endpoint configured',
|
|
152
|
+
limit: plan.embeddingNodeLimit,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (plan.profile === 'power') {
|
|
156
|
+
return {
|
|
157
|
+
enabled: true,
|
|
158
|
+
reason: 'power profile can run local embeddings',
|
|
159
|
+
limit: plan.embeddingNodeLimit,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (plan.profile === 'balanced' && (nodes ?? Number.POSITIVE_INFINITY) <= 5_000) {
|
|
163
|
+
return {
|
|
164
|
+
enabled: true,
|
|
165
|
+
reason: 'small repo on balanced profile',
|
|
166
|
+
limit: plan.embeddingNodeLimit,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (plan.profile === 'lean' && (nodes ?? Number.POSITIVE_INFINITY) <= 1_000) {
|
|
170
|
+
return { enabled: true, reason: 'small repo on lean profile', limit: plan.embeddingNodeLimit };
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
enabled: false,
|
|
174
|
+
reason: 'auto mode skipped first-time local embeddings on this profile',
|
|
175
|
+
limit: plan.embeddingNodeLimit,
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
export const formatAdaptiveAnalyzePlan = (plan) => `Adaptive analyze: ${plan.profile} profile (${plan.reasons.join(', ')}); ` +
|
|
179
|
+
`workers=${plan.workerPoolSize}, subBatch=${plan.workerSubBatchSize}, ` +
|
|
180
|
+
`compress=${plan.compress}, embeddings=${plan.embeddingMode}.`;
|
|
@@ -60,6 +60,37 @@ export declare const loadGraphToCgdb: (graph: KnowledgeGraph, repoPath: string,
|
|
|
60
60
|
skippedRels: number;
|
|
61
61
|
warnings: string[];
|
|
62
62
|
}>;
|
|
63
|
+
export interface FileGraphPatchResult {
|
|
64
|
+
replacedFiles: number;
|
|
65
|
+
deletedNodeIds: number;
|
|
66
|
+
insertedRels: number;
|
|
67
|
+
restoredRels: number;
|
|
68
|
+
prunedFolders: number;
|
|
69
|
+
}
|
|
70
|
+
export interface FileGraphPatchOptions {
|
|
71
|
+
compress?: ContentEncoding;
|
|
72
|
+
/** Map old relative paths to their new relative paths for rename remapping. */
|
|
73
|
+
pathAliases?: ReadonlyMap<string, string> | Record<string, string>;
|
|
74
|
+
}
|
|
75
|
+
export interface FileGraphReplacementResult {
|
|
76
|
+
deletedNodes: number;
|
|
77
|
+
insertedRels: number;
|
|
78
|
+
}
|
|
79
|
+
export interface GlobalGraphLayerReplaceResult {
|
|
80
|
+
deletedGlobalNodes: number;
|
|
81
|
+
insertedRels: number;
|
|
82
|
+
}
|
|
83
|
+
export interface LoadKnowledgeGraphFromCgdbOptions {
|
|
84
|
+
includeGlobal?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export declare const applyFileGraphPatchToCgdb: (graph: KnowledgeGraph, repoPath: string, storagePath: string, filePaths: readonly string[], onProgress?: CgdbProgressCallback, options?: FileGraphPatchOptions) => Promise<FileGraphPatchResult>;
|
|
87
|
+
export declare const replaceFileScopedGraphInCgdb: (graph: KnowledgeGraph, repoPath: string, storagePath: string, onProgress?: CgdbProgressCallback, options?: {
|
|
88
|
+
compress?: ContentEncoding;
|
|
89
|
+
}) => Promise<FileGraphReplacementResult>;
|
|
90
|
+
export declare const replaceGlobalGraphLayersInCgdb: (graph: KnowledgeGraph, repoPath: string, storagePath: string, onProgress?: CgdbProgressCallback, options?: {
|
|
91
|
+
compress?: ContentEncoding;
|
|
92
|
+
}) => Promise<GlobalGraphLayerReplaceResult>;
|
|
93
|
+
export declare const loadKnowledgeGraphFromCgdb: (options?: LoadKnowledgeGraphFromCgdbOptions) => Promise<KnowledgeGraph>;
|
|
63
94
|
/**
|
|
64
95
|
* Insert a single node to LadybugDB
|
|
65
96
|
* @param label - Node type (File, Function, Class, etc.)
|
|
@@ -143,12 +174,10 @@ export declare const loadVectorExtension: () => Promise<void>;
|
|
|
143
174
|
*/
|
|
144
175
|
export declare const createFTSIndex: (tableName: string, indexName: string, properties: string[], stemmer?: string) => Promise<void>;
|
|
145
176
|
/**
|
|
146
|
-
*
|
|
177
|
+
* Create an FTS index if needed, caching the fact in-process.
|
|
147
178
|
*
|
|
148
|
-
* Used by
|
|
149
|
-
*
|
|
150
|
-
* the cost is moved to the first `query`/`context` call in a session,
|
|
151
|
-
* where it's amortised across many lookups.
|
|
179
|
+
* Used by analyze to warm persisted keyword indexes while the DB is writable,
|
|
180
|
+
* and by direct core search as a defensive fallback for older indexes.
|
|
152
181
|
*
|
|
153
182
|
* Safe to call repeatedly — the in-process Set guarantees only the first
|
|
154
183
|
* call hits LadybugDB. `closeCgdb` clears the cache so re-init starts fresh.
|