@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.
Files changed (113) hide show
  1. package/README.md +18 -13
  2. package/dist/cli/analyze.d.ts +9 -4
  3. package/dist/cli/analyze.js +37 -13
  4. package/dist/cli/graphpack.d.ts +48 -0
  5. package/dist/cli/graphpack.js +217 -0
  6. package/dist/cli/index.js +81 -3
  7. package/dist/cli/status.d.ts +1 -1
  8. package/dist/cli/status.js +8 -0
  9. package/dist/cli/tool.d.ts +11 -2
  10. package/dist/cli/tool.js +138 -8
  11. package/dist/core/adaptive-profile.d.ts +52 -0
  12. package/dist/core/adaptive-profile.js +180 -0
  13. package/dist/core/cgdb/cgdb-adapter.d.ts +34 -5
  14. package/dist/core/cgdb/cgdb-adapter.js +418 -5
  15. package/dist/core/cgdb/pool-adapter.js +1 -1
  16. package/dist/core/graphpack/index.d.ts +14 -0
  17. package/dist/core/graphpack/index.js +474 -0
  18. package/dist/core/graphpack/types.d.ts +129 -0
  19. package/dist/core/graphpack/types.js +4 -0
  20. package/dist/core/ingestion/pipeline-phases/parse-impl.js +3 -1
  21. package/dist/core/ingestion/pipeline-phases/structure.js +19 -3
  22. package/dist/core/ingestion/pipeline.d.ts +10 -0
  23. package/dist/core/run-analyze.d.ts +27 -2
  24. package/dist/core/run-analyze.js +598 -27
  25. package/dist/core/search/bm25-index.d.ts +19 -0
  26. package/dist/core/search/bm25-index.js +68 -29
  27. package/dist/core/semantic/relationships.d.ts +36 -0
  28. package/dist/core/semantic/relationships.js +261 -0
  29. package/dist/mcp/local/local-backend.js +48 -3
  30. package/dist/mcp/resources.js +125 -0
  31. package/dist/mcp/tools.js +105 -0
  32. package/dist/server/api.js +112 -0
  33. package/dist/storage/repo-manager.d.ts +29 -0
  34. package/dist/web/assets/agent-CQNZQ-hg.js +1139 -0
  35. package/dist/web/assets/architectureDiagram-UL44E2DR-B5_goS_i.js +36 -0
  36. package/dist/web/assets/blockDiagram-7IZFK4PR-D7ZAlDyv.js +132 -0
  37. package/dist/web/assets/{c4Diagram-DFAF54RM-C4Hl3J2U.js → c4Diagram-Y2BXMSZH-Djcgm_54.js} +1 -1
  38. package/dist/web/assets/{chunk-7RZVMHOQ-BitYcNVR.js → chunk-3SSMPTDK-Cv2Zy2FO.js} +1 -1
  39. package/dist/web/assets/{chunk-TBF5ZNIQ-DL5stGM1.js → chunk-6764PJDD-Cppb-jH-.js} +1 -1
  40. package/dist/web/assets/{chunk-KSICW3F5-BYzvDLNI.js → chunk-AZZRMDJM-BHlLC7p3.js} +1 -1
  41. package/dist/web/assets/{chunk-AEOMTBSW-BgTIXPsY.js → chunk-JQRUD6KW-3F8Zg-1N.js} +1 -1
  42. package/dist/web/assets/chunk-KRXBNO2N-C0mbN9a7.js +1 -0
  43. package/dist/web/assets/chunk-LCXTWHL2-BoiuJpIF.js +231 -0
  44. package/dist/web/assets/{chunk-O5ABG6QK-dHwHzA6n.js → chunk-LII3EMHJ-Dqq0Qguw.js} +1 -1
  45. package/dist/web/assets/chunk-RG4AUYOV-Bl5F_gDs.js +206 -0
  46. package/dist/web/assets/{chunk-TU3PZOEN-RLyvLcv-.js → chunk-T5OCTHI4-B2tIcggA.js} +1 -1
  47. package/dist/web/assets/chunk-W44A43WB-BHe37iN7.js +13 -0
  48. package/dist/web/assets/{chunk-RWUO3TPN-BgRTY0_k.js → chunk-ZXARS5L4-wcrIaQvY.js} +1 -1
  49. package/dist/web/assets/classDiagram-KGZ6W3CR-IbI6v_24.js +1 -0
  50. package/dist/web/assets/classDiagram-v2-72OJOZXJ-IbI6v_24.js +1 -0
  51. package/dist/web/assets/{cose-bilkent-PNC4W37J-DVhePRYg.js → cose-bilkent-UX7MHV2Q-BWr7v0Wr.js} +1 -1
  52. package/dist/web/assets/dagre-ND4H6XIP-De5LIh1B.js +4 -0
  53. package/dist/web/assets/diagram-3NCE3AQN-Dd22FSHy.js +43 -0
  54. package/dist/web/assets/diagram-GF46GFSD-Cev3THY8.js +24 -0
  55. package/dist/web/assets/diagram-HNR7UZ2L-D8Z8RQGs.js +3 -0
  56. package/dist/web/assets/diagram-QXG6HAR7-B8VOJOiE.js +24 -0
  57. package/dist/web/assets/diagram-WEQXMOUZ-va1bLoMD.js +10 -0
  58. package/dist/web/assets/{erDiagram-GCSMX5X6-C3dhDFA8.js → erDiagram-L5TCEMPS-B3_9uAoP.js} +5 -5
  59. package/dist/web/assets/{flowDiagram-OTCZ4VVT-CWSFWmhr.js → flowDiagram-H6V6AXG4-98m6maI1.js} +9 -9
  60. package/dist/web/assets/ganttDiagram-JCBTUEKG-vE2nzETb.js +292 -0
  61. package/dist/web/assets/gitGraphDiagram-S2ZK5IYY-DKc8uUg_.js +106 -0
  62. package/dist/web/assets/index-BAhe1HSk.css +1 -0
  63. package/dist/web/assets/index-VTKdaklA.js +1415 -0
  64. package/dist/web/assets/infoDiagram-3YFTVSEB-DYP-Srzx.js +2 -0
  65. package/dist/web/assets/{ishikawaDiagram-YMYX4NHK-DUoJvNP2.js → ishikawaDiagram-BNXS4ZKH-QZnkpmmb.js} +3 -3
  66. package/dist/web/assets/{journeyDiagram-SO5T7YLQ-RMFPNNqz.js → journeyDiagram-M6C3CM3L-B5ojIuqu.js} +1 -1
  67. package/dist/web/assets/{kanban-definition-LJHFXRCJ-BzpDs1K9.js → kanban-definition-75IXJCU3-BJA8liRR.js} +4 -4
  68. package/dist/web/assets/{katex-GD7MH7QM-DBQvrix-.js → katex-K3KEBU37-DUqZiCRL.js} +1 -1
  69. package/dist/web/assets/mindmap-definition-2TDM6QVE-BQj5yylD.js +96 -0
  70. package/dist/web/assets/pieDiagram-CU6KROY3-4eSrPiQz.js +30 -0
  71. package/dist/web/assets/quadrantDiagram-VICAPDV7-PzxN8j55.js +7 -0
  72. package/dist/web/assets/{requirementDiagram-M5DCFWZL-DLHOVTSv.js → requirementDiagram-JXO7QTGE-CtplTc5y.js} +2 -2
  73. package/dist/web/assets/sankeyDiagram-URQDO5SZ-CoSgvkxv.js +40 -0
  74. package/dist/web/assets/sequenceDiagram-VS2MUI6T-D7ygyXvJ.js +162 -0
  75. package/dist/web/assets/stateDiagram-7D4R322I-v01gvwji.js +1 -0
  76. package/dist/web/assets/stateDiagram-v2-36443NZ5-DFD2b8_x.js +1 -0
  77. package/dist/web/assets/{timeline-definition-5SPVSISX-TRSDRgPw.js → timeline-definition-O6YCAMPW-CTI3M65J.js} +4 -4
  78. package/dist/web/assets/{vennDiagram-IE5QUKF5-DNy7HRBM.js → vennDiagram-MWXL3ELB-RnB0XMP7.js} +6 -6
  79. package/dist/web/assets/wardley-L42UT6IY-5TKZOOLJ-C-ZcgEBb.js +173 -0
  80. package/dist/web/assets/wardleyDiagram-CUQ6CDDI-EwRi4kwo.js +78 -0
  81. package/dist/web/assets/{xychartDiagram-ZHJ5623Y-Dr9r7a35.js → xychartDiagram-N2JHSOCM-DA38II6y.js} +4 -4
  82. package/dist/web/index.html +2 -2
  83. package/package.json +2 -2
  84. package/vendor/node_modules/node-addon-api/node_addon_api_except.stamp +0 -0
  85. package/dist/web/assets/agent-D5lb0zXz.js +0 -1089
  86. package/dist/web/assets/architectureDiagram-EMZXCZ2Q-CZtc99v_.js +0 -36
  87. package/dist/web/assets/blockDiagram-IGV67L2C-BtoUp-6Y.js +0 -132
  88. package/dist/web/assets/chunk-3GS5O3IE-DkUjU0WD.js +0 -231
  89. package/dist/web/assets/chunk-3YCYZ6SJ-CQkVgT_z.js +0 -1
  90. package/dist/web/assets/chunk-H3VCZNTA-Cx5XV_aC.js +0 -13
  91. package/dist/web/assets/chunk-HN6EAY2L-BBnyTNdB.js +0 -1
  92. package/dist/web/assets/chunk-PK6DOVAG-CvsEnugt.js +0 -206
  93. package/dist/web/assets/classDiagram-PPOCWD7C-DTr8QIOf.js +0 -1
  94. package/dist/web/assets/classDiagram-v2-23LJLIIU-DTr8QIOf.js +0 -1
  95. package/dist/web/assets/dagre-E77IOHMT-Dzx0A6ZU.js +0 -4
  96. package/dist/web/assets/diagram-H7BISOXX-CC9pRew1.js +0 -43
  97. package/dist/web/assets/diagram-JC5VWROH-Bau_i9tf.js +0 -24
  98. package/dist/web/assets/diagram-LXUTUG65-D9_FM2Gt.js +0 -10
  99. package/dist/web/assets/diagram-WEHSV5V5-BMlayouL.js +0 -24
  100. package/dist/web/assets/ganttDiagram-MUNLMDZQ-D3a67Yol.js +0 -292
  101. package/dist/web/assets/gitGraphDiagram-3HKGZ4G3-7jmry-vM.js +0 -106
  102. package/dist/web/assets/index-BgeqpYgd.js +0 -1415
  103. package/dist/web/assets/index-CT0GtFLZ.css +0 -1
  104. package/dist/web/assets/infoDiagram-MN7RKWGX-G7lhP0Ib.js +0 -2
  105. package/dist/web/assets/mindmap-definition-2EUWGEK5-Bk0O4roa.js +0 -96
  106. package/dist/web/assets/pieDiagram-3IATQBI2-DKU7kpgS.js +0 -30
  107. package/dist/web/assets/quadrantDiagram-E256RVCF-BY0TGWCS.js +0 -7
  108. package/dist/web/assets/sankeyDiagram-L3NBLAOT-DVMj5rX2.js +0 -10
  109. package/dist/web/assets/sequenceDiagram-ZOUHS735-CJC73bV-.js +0 -157
  110. package/dist/web/assets/stateDiagram-MLPALWAM-BCFyESls.js +0 -1
  111. package/dist/web/assets/stateDiagram-v2-B5LQ5ZB2-DahzzIca.js +0 -1
  112. package/dist/web/assets/wardley-RL74JXVD-BCRCBASE-B-eZEzf9.js +0 -161
  113. package/dist/web/assets/wardleyDiagram-XU3VSMPF-BP-r1xzR.js +0 -20
@@ -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, options?: {
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, options?: {
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
- if (!name?.trim() && !options?.uid) {
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: name || undefined,
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
- if (!target?.trim()) {
112
- console.error('Usage: codragraph impact <symbol_name> [--direction upstream|downstream]');
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: target },
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
- output(result);
222
- emitTokenStats(result);
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
- * Lazy-create an FTS index, caching the fact in-process.
177
+ * Create an FTS index if needed, caching the fact in-process.
147
178
  *
148
- * Used by `queryFTS` so that `analyze` doesn't pay the ~440 ms × 5 fixed
149
- * LadybugDB cost up-front (it dominates analyze on small repos). Instead,
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.