@codragraph/cli 2.1.5 → 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.
Files changed (101) 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/index.js +9 -3
  5. package/dist/cli/status.d.ts +1 -1
  6. package/dist/cli/status.js +8 -0
  7. package/dist/cli/tool.d.ts +10 -2
  8. package/dist/cli/tool.js +27 -6
  9. package/dist/core/adaptive-profile.d.ts +52 -0
  10. package/dist/core/adaptive-profile.js +180 -0
  11. package/dist/core/cgdb/cgdb-adapter.d.ts +34 -5
  12. package/dist/core/cgdb/cgdb-adapter.js +418 -5
  13. package/dist/core/cgdb/pool-adapter.js +1 -1
  14. package/dist/core/ingestion/pipeline-phases/parse-impl.js +3 -1
  15. package/dist/core/ingestion/pipeline-phases/structure.js +19 -3
  16. package/dist/core/ingestion/pipeline.d.ts +10 -0
  17. package/dist/core/run-analyze.d.ts +27 -2
  18. package/dist/core/run-analyze.js +598 -27
  19. package/dist/core/search/bm25-index.d.ts +19 -0
  20. package/dist/core/search/bm25-index.js +68 -29
  21. package/dist/mcp/local/local-backend.js +6 -3
  22. package/dist/storage/repo-manager.d.ts +29 -0
  23. package/dist/web/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  24. package/dist/web/assets/agent-DcdaQnmu.js +1104 -0
  25. package/dist/web/assets/architectureDiagram-UL44E2DR-DFSpa3Hb.js +36 -0
  26. package/dist/web/assets/blockDiagram-7IZFK4PR-DlFaxH1b.js +132 -0
  27. package/dist/web/assets/{c4Diagram-DFAF54RM-C4Hl3J2U.js → c4Diagram-Y2BXMSZH-BjJ_Yrim.js} +1 -1
  28. package/dist/web/assets/{chunk-7RZVMHOQ-BitYcNVR.js → chunk-3SSMPTDK-KGZSzG3Y.js} +1 -1
  29. package/dist/web/assets/{chunk-TBF5ZNIQ-DL5stGM1.js → chunk-6764PJDD-p1sGJgVm.js} +1 -1
  30. package/dist/web/assets/{chunk-KSICW3F5-BYzvDLNI.js → chunk-AZZRMDJM-DIDkQA4V.js} +1 -1
  31. package/dist/web/assets/{chunk-AEOMTBSW-BgTIXPsY.js → chunk-JQRUD6KW-DAwg-yCU.js} +1 -1
  32. package/dist/web/assets/chunk-KRXBNO2N-ChVO_XdS.js +1 -0
  33. package/dist/web/assets/chunk-LCXTWHL2-DGYdb_Eh.js +231 -0
  34. package/dist/web/assets/{chunk-O5ABG6QK-dHwHzA6n.js → chunk-LII3EMHJ-Bzh9SNgD.js} +1 -1
  35. package/dist/web/assets/chunk-RG4AUYOV-Bcl7U_IV.js +206 -0
  36. package/dist/web/assets/{chunk-TU3PZOEN-RLyvLcv-.js → chunk-T5OCTHI4-CZYMg5sc.js} +1 -1
  37. package/dist/web/assets/chunk-W44A43WB-REOI67PN.js +13 -0
  38. package/dist/web/assets/{chunk-RWUO3TPN-BgRTY0_k.js → chunk-ZXARS5L4-BfFdV1tf.js} +1 -1
  39. package/dist/web/assets/classDiagram-KGZ6W3CR-B-qkKMYi.js +1 -0
  40. package/dist/web/assets/classDiagram-v2-72OJOZXJ-B-qkKMYi.js +1 -0
  41. package/dist/web/assets/{cose-bilkent-PNC4W37J-DVhePRYg.js → cose-bilkent-UX7MHV2Q-D6vANJGG.js} +1 -1
  42. package/dist/web/assets/dagre-ND4H6XIP-BiHe5Lal.js +4 -0
  43. package/dist/web/assets/diagram-3NCE3AQN-CEutBCOW.js +43 -0
  44. package/dist/web/assets/diagram-GF46GFSD-CZns6HPQ.js +24 -0
  45. package/dist/web/assets/diagram-HNR7UZ2L-Vz8fE5of.js +3 -0
  46. package/dist/web/assets/diagram-QXG6HAR7-D60HKZ_y.js +24 -0
  47. package/dist/web/assets/diagram-WEQXMOUZ-vGAf1p3E.js +10 -0
  48. package/dist/web/assets/{erDiagram-GCSMX5X6-C3dhDFA8.js → erDiagram-L5TCEMPS-DZaplJA6.js} +5 -5
  49. package/dist/web/assets/{flowDiagram-OTCZ4VVT-CWSFWmhr.js → flowDiagram-H6V6AXG4-BqUqeAsI.js} +9 -9
  50. package/dist/web/assets/ganttDiagram-JCBTUEKG-XEB6H-0G.js +292 -0
  51. package/dist/web/assets/gitGraphDiagram-S2ZK5IYY-7G50u1Cd.js +106 -0
  52. package/dist/web/assets/index-B5WxtMpv.js +1415 -0
  53. package/dist/web/assets/infoDiagram-3YFTVSEB-Cut_rzaf.js +2 -0
  54. package/dist/web/assets/{ishikawaDiagram-YMYX4NHK-DUoJvNP2.js → ishikawaDiagram-BNXS4ZKH-B4DGfGi3.js} +3 -3
  55. package/dist/web/assets/{journeyDiagram-SO5T7YLQ-RMFPNNqz.js → journeyDiagram-M6C3CM3L-BBFhsL3E.js} +1 -1
  56. package/dist/web/assets/{kanban-definition-LJHFXRCJ-BzpDs1K9.js → kanban-definition-75IXJCU3-DarGRyn3.js} +4 -4
  57. package/dist/web/assets/{katex-GD7MH7QM-DBQvrix-.js → katex-K3KEBU37-W5XTYMhr.js} +1 -1
  58. package/dist/web/assets/mindmap-definition-2TDM6QVE-BgeczIJM.js +96 -0
  59. package/dist/web/assets/pieDiagram-CU6KROY3-Kkoo-Noq.js +30 -0
  60. package/dist/web/assets/quadrantDiagram-VICAPDV7-CDQFeRWN.js +7 -0
  61. package/dist/web/assets/{requirementDiagram-M5DCFWZL-DLHOVTSv.js → requirementDiagram-JXO7QTGE-Cz9-XnkA.js} +2 -2
  62. package/dist/web/assets/sankeyDiagram-URQDO5SZ-CU26z0n7.js +40 -0
  63. package/dist/web/assets/sequenceDiagram-VS2MUI6T-OGK1FLOt.js +162 -0
  64. package/dist/web/assets/stateDiagram-7D4R322I-DJ9brq0U.js +1 -0
  65. package/dist/web/assets/stateDiagram-v2-36443NZ5-DhJ4Ky-7.js +1 -0
  66. package/dist/web/assets/{timeline-definition-5SPVSISX-TRSDRgPw.js → timeline-definition-O6YCAMPW-XZvnjqTT.js} +4 -4
  67. package/dist/web/assets/{vennDiagram-IE5QUKF5-DNy7HRBM.js → vennDiagram-MWXL3ELB-CJUssEjA.js} +6 -6
  68. package/dist/web/assets/wardley-L42UT6IY-5TKZOOLJ-DZr11zBG.js +173 -0
  69. package/dist/web/assets/wardleyDiagram-CUQ6CDDI-C276iqrN.js +78 -0
  70. package/dist/web/assets/{xychartDiagram-ZHJ5623Y-Dr9r7a35.js → xychartDiagram-N2JHSOCM-B9-uCZyP.js} +4 -4
  71. package/dist/web/index.html +1 -1
  72. package/package.json +1 -1
  73. package/vendor/node_modules/node-addon-api/node_addon_api_except.stamp +0 -0
  74. package/dist/web/assets/agent-D5lb0zXz.js +0 -1089
  75. package/dist/web/assets/architectureDiagram-EMZXCZ2Q-CZtc99v_.js +0 -36
  76. package/dist/web/assets/blockDiagram-IGV67L2C-BtoUp-6Y.js +0 -132
  77. package/dist/web/assets/chunk-3GS5O3IE-DkUjU0WD.js +0 -231
  78. package/dist/web/assets/chunk-3YCYZ6SJ-CQkVgT_z.js +0 -1
  79. package/dist/web/assets/chunk-H3VCZNTA-Cx5XV_aC.js +0 -13
  80. package/dist/web/assets/chunk-HN6EAY2L-BBnyTNdB.js +0 -1
  81. package/dist/web/assets/chunk-PK6DOVAG-CvsEnugt.js +0 -206
  82. package/dist/web/assets/classDiagram-PPOCWD7C-DTr8QIOf.js +0 -1
  83. package/dist/web/assets/classDiagram-v2-23LJLIIU-DTr8QIOf.js +0 -1
  84. package/dist/web/assets/dagre-E77IOHMT-Dzx0A6ZU.js +0 -4
  85. package/dist/web/assets/diagram-H7BISOXX-CC9pRew1.js +0 -43
  86. package/dist/web/assets/diagram-JC5VWROH-Bau_i9tf.js +0 -24
  87. package/dist/web/assets/diagram-LXUTUG65-D9_FM2Gt.js +0 -10
  88. package/dist/web/assets/diagram-WEHSV5V5-BMlayouL.js +0 -24
  89. package/dist/web/assets/ganttDiagram-MUNLMDZQ-D3a67Yol.js +0 -292
  90. package/dist/web/assets/gitGraphDiagram-3HKGZ4G3-7jmry-vM.js +0 -106
  91. package/dist/web/assets/index-BgeqpYgd.js +0 -1415
  92. package/dist/web/assets/infoDiagram-MN7RKWGX-G7lhP0Ib.js +0 -2
  93. package/dist/web/assets/mindmap-definition-2EUWGEK5-Bk0O4roa.js +0 -96
  94. package/dist/web/assets/pieDiagram-3IATQBI2-DKU7kpgS.js +0 -30
  95. package/dist/web/assets/quadrantDiagram-E256RVCF-BY0TGWCS.js +0 -7
  96. package/dist/web/assets/sankeyDiagram-L3NBLAOT-DVMj5rX2.js +0 -10
  97. package/dist/web/assets/sequenceDiagram-ZOUHS735-CJC73bV-.js +0 -157
  98. package/dist/web/assets/stateDiagram-MLPALWAM-BCFyESls.js +0 -1
  99. package/dist/web/assets/stateDiagram-v2-B5LQ5ZB2-DahzzIca.js +0 -1
  100. package/dist/web/assets/wardley-RL74JXVD-BCRCBASE-B-eZEzf9.js +0 -161
  101. package/dist/web/assets/wardleyDiagram-XU3VSMPF-BP-r1xzR.js +0 -20
package/README.md CHANGED
@@ -25,12 +25,15 @@ That's it. This indexes the codebase, installs agent skills, registers Claude Co
25
25
 
26
26
  The same CLI commands work in Windows PowerShell, macOS bash/zsh, and Linux shells. Use `npx @codragraph/cli ...` for no-install runs or `codragraph ...` after a global install.
27
27
 
28
- Smart analyze rebuilds when indexed source, Markdown/MDX graph docs, language
29
- config, schema, compression, or requested embedding settings changed. Generated
30
- agent context, lockfile-only, and ignored asset changes reuse the existing
31
- graph and advance metadata. Each pass refreshes `.codragraph/structure/`, a
32
- compact what/why/how/when/where markdown pack with branch/index state, bounded
33
- history, and SQLite seed SQL for external agent memory.
28
+ Smart analyze now patches the existing graph for normal day-to-day changes:
29
+ source edits, renames, large diffs, and topology-only files replace only the
30
+ affected file-scoped rows, then recompute communities, execution flows, and
31
+ FeatureCluster packs. Package/config or ignore-rule inputs refresh all
32
+ file-scoped rows without falling back to a full cold rebuild. Generated agent
33
+ context, lockfile-only, and ignored asset changes reuse the existing graph and
34
+ advance metadata. Each pass refreshes `.codragraph/structure/`, a compact
35
+ what/why/how/when/where markdown pack with branch/index state, bounded history,
36
+ and SQLite seed SQL for external agent memory.
34
37
 
35
38
  To configure MCP for your editor, run `npx @codragraph/cli setup` once — or set it up manually below.
36
39
 
@@ -62,16 +65,16 @@ If you prefer to configure manually instead of using `codragraph setup`:
62
65
 
63
66
  ```bash
64
67
  # macOS / Linux
65
- claude mcp add codragraph -- npx -y @codragraph/cli@2.1.5 mcp
68
+ claude mcp add codragraph -- npx -y @codragraph/cli@2.1.6 mcp
66
69
 
67
70
  # Windows
68
- claude mcp add codragraph -- cmd /c npx -y @codragraph/cli@2.1.5 mcp
71
+ claude mcp add codragraph -- cmd /c npx -y @codragraph/cli@2.1.6 mcp
69
72
  ```
70
73
 
71
74
  ### Codex (full support — MCP + skills)
72
75
 
73
76
  ```bash
74
- codex mcp add codragraph -- npx -y @codragraph/cli@2.1.5 mcp
77
+ codex mcp add codragraph -- npx -y @codragraph/cli@2.1.6 mcp
75
78
  ```
76
79
 
77
80
  ### Cursor / Windsurf
@@ -83,7 +86,7 @@ Add to `~/.cursor/mcp.json` (global — works for all projects):
83
86
  "mcpServers": {
84
87
  "codragraph": {
85
88
  "command": "npx",
86
- "args": ["-y", "@codragraph/cli@2.1.5", "mcp"]
89
+ "args": ["-y", "@codragraph/cli@2.1.6", "mcp"]
87
90
  }
88
91
  }
89
92
  }
@@ -98,7 +101,7 @@ Add to `~/.config/opencode/config.json`:
98
101
  "mcp": {
99
102
  "codragraph": {
100
103
  "command": "npx",
101
- "args": ["-y", "@codragraph/cli@2.1.5", "mcp"]
104
+ "args": ["-y", "@codragraph/cli@2.1.6", "mcp"]
102
105
  }
103
106
  }
104
107
  }
@@ -184,6 +187,8 @@ codragraph serve --web hosted # API only; connect from hosted web UI
184
187
  codragraph index # Register an existing .codragraph/ folder into the global registry
185
188
  codragraph list # List all indexed repositories
186
189
  codragraph status # Show index status for current repo
190
+ codragraph detect-changes --scope unstaged # Map working-tree diff to symbols/processes
191
+ codragraph detect-changes --scope all # Include staged + unstaged changes
187
192
  codragraph clean # Delete index for current repo
188
193
  codragraph clean --all --force # Delete all indexes
189
194
  codragraph wiki [path] # Generate LLM-powered docs from knowledge graph
@@ -304,9 +309,9 @@ It is fixed in **codragraph v1.6.2+**. Upgrade to the current workspace
304
309
  version, or pin the version your team has validated:
305
310
 
306
311
  ```bash
307
- npx @codragraph/cli@2.1.5 analyze # no global install
312
+ npx @codragraph/cli@2.1.6 analyze # no global install
308
313
  # or
309
- npm install -g @codragraph/cli@2.1.5 # upgrade a global install
314
+ npm install -g @codragraph/cli@2.1.6 # upgrade a global install
310
315
  ```
311
316
 
312
317
  If you still hit npm install issues after upgrading, these generic workarounds
@@ -7,9 +7,14 @@
7
7
  * This CLI wrapper handles: heap management, progress bar, SIGINT,
8
8
  * skill generation (--skills), summary output, and process.exit().
9
9
  */
10
+ import { type AnalyzeProfileOption, type CompressionOption, type EmbeddingMode } from '../core/adaptive-profile.js';
10
11
  export interface AnalyzeOptions {
11
12
  force?: boolean;
12
13
  embeddings?: boolean;
14
+ /** Adaptive runtime profile. `auto` detects CPU/RAM/heap and chooses lean/balanced/power. */
15
+ profile?: AnalyzeProfileOption;
16
+ /** Embedding policy. `--embeddings` is kept as a shortcut for `on`. */
17
+ embeddingMode?: EmbeddingMode;
13
18
  skills?: boolean;
14
19
  verbose?: boolean;
15
20
  /** Skip AGENTS.md and CLAUDE.md codragraph block updates. */
@@ -54,12 +59,12 @@ export interface AnalyzeOptions {
54
59
  */
55
60
  skillTargets?: string;
56
61
  /**
57
- * RFC 0001 Phase 2 — opt-in per-row content compression. Accepts
58
- * `'none'` (default), `'brotli'` (Node 18), or `'zstd'` (Node ≥
59
- * 22.15). Compressed indexes are still queryable via the standard
62
+ * RFC 0001 Phase 2 - per-row content compression. Accepts `'auto'`
63
+ * (default), `'none'`, `'brotli'` (Node >= 18), or `'zstd'`
64
+ * (Node >= 22.15). Compressed indexes are still queryable via the standard
60
65
  * read path; decode happens at every external-consumer boundary
61
66
  * (MCP, HTTP API, embeddings, CLI tools).
62
67
  */
63
- compress?: 'none' | 'brotli' | 'zstd';
68
+ compress?: CompressionOption;
64
69
  }
65
70
  export declare const analyzeCommand: (inputPath?: string, options?: AnalyzeOptions) => Promise<void>;
@@ -17,6 +17,7 @@ import { closeCgdb } from '../core/cgdb/cgdb-adapter.js';
17
17
  import { getStoragePaths, getGlobalRegistryPath, RegistryNameCollisionError, } from '../storage/repo-manager.js';
18
18
  import { getGitRoot, hasGitDir } from '../storage/git.js';
19
19
  import { runFullAnalysis } from '../core/run-analyze.js';
20
+ import { parseAnalyzeProfile, parseCompressionOption, parseEmbeddingMode, } from '../core/adaptive-profile.js';
20
21
  import { getMaxFileSizeBannerMessage } from '../core/ingestion/utils/max-file-size.js';
21
22
  import fs from 'fs/promises';
22
23
  import { formatBytes, LARGE_INDEX_WARNING_BYTES, summarizeIndexStorage } from './status.js';
@@ -28,10 +29,14 @@ const HEAP_FLAG = `--max-old-space-size=${HEAP_MB}`;
28
29
  /** Increase default stack size (KB) to prevent stack overflow on deep class hierarchies. */
29
30
  const STACK_KB = 4096;
30
31
  const STACK_FLAG = `--stack-size=${STACK_KB}`;
32
+ function hasNodeFlag(flagName, nodeOpts, execArgv) {
33
+ return (nodeOpts.includes(flagName) ||
34
+ execArgv.some((arg) => arg === flagName || arg.startsWith(`${flagName}=`)));
35
+ }
31
36
  /** Re-exec the process with an 8GB heap and larger stack if we're currently below that. */
32
37
  function ensureHeap() {
33
38
  const nodeOpts = process.env.NODE_OPTIONS || '';
34
- if (nodeOpts.includes('--max-old-space-size'))
39
+ if (hasNodeFlag('--max-old-space-size', nodeOpts, process.execArgv))
35
40
  return false;
36
41
  const v8Heap = v8.getHeapStatistics().heap_size_limit;
37
42
  if (v8Heap >= HEAP_MB * 1024 * 1024 * 0.9)
@@ -39,10 +44,14 @@ function ensureHeap() {
39
44
  // --stack-size is a V8 flag not allowed in NODE_OPTIONS on Node 24+,
40
45
  // so pass it only as a direct CLI argument, not via the environment.
41
46
  const cliFlags = [HEAP_FLAG];
42
- if (!nodeOpts.includes('--stack-size'))
47
+ if (!hasNodeFlag('--stack-size', nodeOpts, process.execArgv))
43
48
  cliFlags.push(STACK_FLAG);
44
49
  try {
45
- execFileSync(process.execPath, [...cliFlags, ...process.argv.slice(1)], {
50
+ // Preserve loader/debug flags from the outer process. This is required
51
+ // for source-mode invocations such as `tsx src/cli/index.ts analyze`;
52
+ // otherwise the child runs plain node against .ts files and cannot
53
+ // resolve the emitted .js import specifiers.
54
+ execFileSync(process.execPath, [...process.execArgv, ...cliFlags, ...process.argv.slice(1)], {
46
55
  stdio: 'inherit',
47
56
  env: { ...process.env, NODE_OPTIONS: `${nodeOpts} ${HEAP_FLAG}`.trim() },
48
57
  });
@@ -58,18 +67,31 @@ export const analyzeCommand = async (inputPath, options) => {
58
67
  if (options?.verbose) {
59
68
  process.env.CODRAGRAPH_VERBOSE = '1';
60
69
  }
61
- // RFC 0001 Phase 2 — validate --compress before doing any work. Catching
70
+ try {
71
+ parseAnalyzeProfile(options?.profile);
72
+ parseEmbeddingMode(options?.embeddingMode);
73
+ }
74
+ catch (err) {
75
+ console.error(` ${err.message}`);
76
+ process.exitCode = 2;
77
+ return;
78
+ }
79
+ // RFC 0001 Phase 2 - validate --compress before doing any work. Catching
62
80
  // a typo or an unsupported encoding here is much friendlier than failing
63
81
  // mid-analyze with an opaque CSV-write error. Node-version gating for
64
82
  // zstd lives in @codragraph/graphstore via isEncodingSupported, but we
65
83
  // import the check here so the CLI can offer the brotli fallback hint.
66
- if (options?.compress && options.compress !== 'none') {
67
- if (options.compress !== 'brotli' && options.compress !== 'zstd') {
68
- console.error(` --compress must be one of: none, brotli, zstd (got: ${options.compress})`);
69
- process.exitCode = 2;
70
- return;
71
- }
72
- if (options.compress === 'zstd') {
84
+ const requestedCompress = options?.compress ?? 'auto';
85
+ try {
86
+ parseCompressionOption(requestedCompress);
87
+ }
88
+ catch (err) {
89
+ console.error(` ${err.message}`);
90
+ process.exitCode = 2;
91
+ return;
92
+ }
93
+ if (requestedCompress !== 'auto' && requestedCompress !== 'none') {
94
+ if (requestedCompress === 'zstd') {
73
95
  const { isEncodingSupported } = await import('@codragraph/graphstore');
74
96
  if (!isEncodingSupported('zstd')) {
75
97
  console.error(' --compress zstd requires Node ≥ 22.15.0 (native node:zlib zstd).\n' +
@@ -84,7 +106,7 @@ export const analyzeCommand = async (inputPath, options) => {
84
106
  // gracefully falls back to name-only matches instead of tokenising
85
107
  // base64 garbage. Surface the trade-off so users know what they're
86
108
  // opting into.
87
- console.warn(` Note: --compress ${options.compress} reduces .codragraph/cgdb size.\n` +
109
+ console.warn(` Note: --compress ${requestedCompress} reduces .codragraph/cgdb size.\n` +
88
110
  ` BM25 search will index symbol names only (function bodies are not tokenised\n` +
89
111
  ` when compressed); embeddings, graph queries, and \`context\` / \`impact\` are\n` +
90
112
  ` unaffected. Run with --compress none if you rely on full-text search inside\n` +
@@ -241,8 +263,10 @@ export const analyzeCommand = async (inputPath, options) => {
241
263
  // cost of a full pipeline re-index. See #829 review round 2.
242
264
  allowDuplicateName: options?.allowDuplicateName,
243
265
  // RFC 0001 Phase 2 — pass through the per-row encoding choice.
244
- // Default 'none' / undefined keeps the pre-Phase-2 wire layout.
266
+ // Default 'auto' lets the adaptive profile choose the storage tier.
245
267
  compress: options?.compress,
268
+ profile: options?.profile,
269
+ embeddingMode: options?.embeddingMode,
246
270
  }, {
247
271
  onProgress: (_phase, percent, message) => {
248
272
  updateBar(percent, message);
package/dist/cli/index.js CHANGED
@@ -37,7 +37,9 @@ program
37
37
  .command('analyze [path]')
38
38
  .description('Index a repository (full analysis)')
39
39
  .option('-f, --force', 'Force full re-index even if up to date')
40
- .option('--embeddings', 'Enable embedding generation for semantic search (off by default)')
40
+ .option('--profile <mode>', 'Adaptive runtime profile: auto, lean, balanced, power', 'auto')
41
+ .option('--embedding-mode <mode>', 'Embedding policy: auto, off, on', 'auto')
42
+ .option('--embeddings', 'Enable embedding generation for semantic search (same as --embedding-mode on)')
41
43
  .option('--skills', 'Generate repo-specific skill files from detected communities')
42
44
  .option('--skill-targets <list>', 'CSV of editor targets for --skills (claude, cursor, opencode, codex). Default: claude.')
43
45
  .option('--skip-agents-md', 'Skip updating the codragraph section in AGENTS.md and CLAUDE.md')
@@ -50,7 +52,7 @@ program
50
52
  .option('-v, --verbose', 'Enable verbose ingestion warnings (default: false)')
51
53
  .option('--max-file-size <kb>', 'Skip files larger than this (KB). Default: 512. Hard cap: 32768 (tree-sitter limit).')
52
54
  .option('--no-setup', 'Skip the first-run editor setup (auto-runs once when ~/.codragraph/registry.json is missing)')
53
- .option('--compress <encoding>', 'Compress per-row content (RFC 0001 Phase 2). One of: none (default), brotli, zstd. zstd requires Node 22.15.', 'none')
55
+ .option('--compress <encoding>', 'Compress per-row content. One of: auto (default), none, brotli, zstd. zstd requires Node >= 22.15.', 'auto')
54
56
  .addHelpText('after', '\nEnvironment variables:\n' +
55
57
  ' CODRAGRAPH_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .codragraphignore)\n' +
56
58
  ' CODRAGRAPH_MAX_FILE_SIZE=N Override large-file skip threshold (KB). Default 512, max 32768.\n' +
@@ -141,13 +143,17 @@ program
141
143
  .option('-r, --repo <name>', 'Target repository')
142
144
  .option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
143
145
  .option('-f, --file <path>', 'File path to disambiguate common names')
146
+ .option('-k, --kind <kind>', 'Symbol kind to disambiguate common names')
144
147
  .option('--content', 'Include full symbol source code')
145
148
  .action(createOneShotLazyAction(() => import('./tool.js'), 'contextCommand'));
146
149
  program
147
- .command('impact <target>')
150
+ .command('impact [target]')
148
151
  .description('Blast radius analysis: what breaks if you change a symbol')
149
152
  .option('-d, --direction <dir>', 'upstream (dependants) or downstream (dependencies)', 'upstream')
150
153
  .option('-r, --repo <name>', 'Target repository')
154
+ .option('-u, --uid <uid>', 'Direct target symbol UID (zero-ambiguity lookup)')
155
+ .option('-f, --file <path>', 'File path to disambiguate common names')
156
+ .option('-k, --kind <kind>', 'Symbol kind to disambiguate common names')
151
157
  .option('--depth <n>', 'Max relationship depth (default: 3)')
152
158
  .option('--include-tests', 'Include test files in results')
153
159
  .action(createOneShotLazyAction(() => import('./tool.js'), 'impactCommand'));
@@ -13,7 +13,7 @@ export interface IndexStorageSummary {
13
13
  }
14
14
  export declare const formatBytes: (bytes: number) => string;
15
15
  export declare const summarizeIndexStorage: (storagePath: string) => Promise<IndexStorageSummary | null>;
16
- export declare const formatIndexStatusLines: (summary: IndexStorageSummary | null, meta: Pick<RepoMeta, "stats" | "compress">, options?: {
16
+ export declare const formatIndexStatusLines: (summary: IndexStorageSummary | null, meta: Pick<RepoMeta, "stats" | "compress" | "adaptiveProfile">, options?: {
17
17
  largeIndexWarningBytes?: number;
18
18
  }) => string[];
19
19
  export declare const statusCommand: () => Promise<void>;
@@ -94,6 +94,14 @@ export const formatIndexStatusLines = (summary, meta, options = {}) => {
94
94
  const embeddings = meta.stats?.embeddings;
95
95
  lines.push(`Embeddings: ${typeof embeddings === 'number' ? embeddings.toLocaleString('en-US') : 'unknown'}`);
96
96
  lines.push(`Compression: ${meta.compress ?? 'none'}`);
97
+ if (meta.adaptiveProfile?.resolved) {
98
+ const profile = meta.adaptiveProfile;
99
+ const workerText = typeof profile.workerPoolSize === 'number' ? `, workers ${profile.workerPoolSize}` : '';
100
+ const embeddingText = profile.embeddingDecision
101
+ ? `, embeddings ${profile.embeddingDecision}`
102
+ : '';
103
+ lines.push(`Adaptive profile: ${profile.resolved}${workerText}${embeddingText}`);
104
+ }
97
105
  return lines;
98
106
  };
99
107
  export const statusCommand = async () => {
@@ -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>;
package/dist/cli/tool.js CHANGED
@@ -45,6 +45,12 @@ async function resolveCliRepoParam(repoParam) {
45
45
  process.exitCode = 1;
46
46
  return null;
47
47
  }
48
+ function unwrapCommanderOptions(value) {
49
+ if (!value || typeof value !== 'object')
50
+ return value;
51
+ const maybeCommand = value;
52
+ return typeof maybeCommand.opts === 'function' ? maybeCommand.opts() : value;
53
+ }
48
54
  /**
49
55
  * Write tool output to stdout using low-level fd write.
50
56
  *
@@ -90,7 +96,12 @@ export async function queryCommand(queryText, options) {
90
96
  emitTokenStats(result);
91
97
  }
92
98
  export async function contextCommand(name, options) {
93
- if (!name?.trim() && !options?.uid) {
99
+ let symbolName = typeof name === 'string' ? name : undefined;
100
+ if (typeof name !== 'string' && name && !options) {
101
+ options = unwrapCommanderOptions(name);
102
+ symbolName = undefined;
103
+ }
104
+ if (!symbolName?.trim() && !options?.uid) {
94
105
  console.error('Usage: codragraph context <symbol_name> [--uid <uid>] [--file <path>]');
95
106
  process.exit(1);
96
107
  }
@@ -98,9 +109,10 @@ export async function contextCommand(name, options) {
98
109
  if (!repo)
99
110
  return;
100
111
  const result = await callToolOnce('context', {
101
- name: name || undefined,
112
+ name: symbolName || undefined,
102
113
  uid: options?.uid,
103
114
  file_path: options?.file,
115
+ kind: options?.kind,
104
116
  include_content: options?.content ?? false,
105
117
  repo,
106
118
  });
@@ -108,8 +120,13 @@ export async function contextCommand(name, options) {
108
120
  emitTokenStats(result);
109
121
  }
110
122
  export async function impactCommand(target, options) {
111
- if (!target?.trim()) {
112
- console.error('Usage: codragraph impact <symbol_name> [--direction upstream|downstream]');
123
+ let targetName = typeof target === 'string' ? target : undefined;
124
+ if (typeof target !== 'string' && target && !options) {
125
+ options = unwrapCommanderOptions(target);
126
+ targetName = undefined;
127
+ }
128
+ if (!targetName?.trim() && !options?.uid) {
129
+ console.error('Usage: codragraph impact <symbol_name> [--uid <uid>] [--direction upstream|downstream]');
113
130
  process.exit(1);
114
131
  }
115
132
  try {
@@ -117,7 +134,10 @@ export async function impactCommand(target, options) {
117
134
  if (!repo)
118
135
  return;
119
136
  const result = await callToolOnce('impact', {
120
- target,
137
+ target: targetName || undefined,
138
+ target_uid: options?.uid,
139
+ file_path: options?.file,
140
+ kind: options?.kind,
121
141
  direction: options?.direction || 'upstream',
122
142
  maxDepth: options?.depth ? parseInt(options.depth, 10) : undefined,
123
143
  includeTests: options?.includeTests ?? false,
@@ -131,7 +151,7 @@ export async function impactCommand(target, options) {
131
151
  // The backend's impact() already returns structured errors for graph query failures
132
152
  output({
133
153
  error: (err instanceof Error ? err.message : String(err)) || 'Impact analysis failed unexpectedly',
134
- target: { name: target },
154
+ target: { name: targetName || options?.uid },
135
155
  direction: options?.direction || 'upstream',
136
156
  suggestion: 'Try reducing --depth or using codragraph context <symbol> as a fallback',
137
157
  });
@@ -272,6 +292,7 @@ function formatDetectChangesResult(result) {
272
292
  return lines.join('\n').trim();
273
293
  }
274
294
  export async function detectChangesCommand(options) {
295
+ options = unwrapCommanderOptions(options);
275
296
  const repo = await resolveCliRepoParam(options?.repo);
276
297
  if (!repo)
277
298
  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}.`;