@optave/codegraph 3.1.2 → 3.1.4

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 (194) hide show
  1. package/README.md +19 -21
  2. package/package.json +10 -7
  3. package/src/analysis/context.js +408 -0
  4. package/src/analysis/dependencies.js +341 -0
  5. package/src/analysis/exports.js +130 -0
  6. package/src/analysis/impact.js +463 -0
  7. package/src/analysis/module-map.js +322 -0
  8. package/src/analysis/roles.js +45 -0
  9. package/src/analysis/symbol-lookup.js +232 -0
  10. package/src/ast-analysis/shared.js +5 -4
  11. package/src/batch.js +2 -1
  12. package/src/builder/context.js +85 -0
  13. package/src/builder/helpers.js +218 -0
  14. package/src/builder/incremental.js +178 -0
  15. package/src/builder/pipeline.js +130 -0
  16. package/src/builder/stages/build-edges.js +297 -0
  17. package/src/builder/stages/build-structure.js +113 -0
  18. package/src/builder/stages/collect-files.js +44 -0
  19. package/src/builder/stages/detect-changes.js +413 -0
  20. package/src/builder/stages/finalize.js +139 -0
  21. package/src/builder/stages/insert-nodes.js +195 -0
  22. package/src/builder/stages/parse-files.js +28 -0
  23. package/src/builder/stages/resolve-imports.js +143 -0
  24. package/src/builder/stages/run-analyses.js +44 -0
  25. package/src/builder.js +10 -1472
  26. package/src/cfg.js +1 -2
  27. package/src/cli/commands/ast.js +26 -0
  28. package/src/cli/commands/audit.js +46 -0
  29. package/src/cli/commands/batch.js +68 -0
  30. package/src/cli/commands/branch-compare.js +21 -0
  31. package/src/cli/commands/build.js +26 -0
  32. package/src/cli/commands/cfg.js +30 -0
  33. package/src/cli/commands/check.js +79 -0
  34. package/src/cli/commands/children.js +31 -0
  35. package/src/cli/commands/co-change.js +65 -0
  36. package/src/cli/commands/communities.js +23 -0
  37. package/src/cli/commands/complexity.js +45 -0
  38. package/src/cli/commands/context.js +34 -0
  39. package/src/cli/commands/cycles.js +28 -0
  40. package/src/cli/commands/dataflow.js +32 -0
  41. package/src/cli/commands/deps.js +16 -0
  42. package/src/cli/commands/diff-impact.js +30 -0
  43. package/src/cli/commands/embed.js +30 -0
  44. package/src/cli/commands/export.js +75 -0
  45. package/src/cli/commands/exports.js +18 -0
  46. package/src/cli/commands/flow.js +36 -0
  47. package/src/cli/commands/fn-impact.js +30 -0
  48. package/src/cli/commands/impact.js +16 -0
  49. package/src/cli/commands/info.js +76 -0
  50. package/src/cli/commands/map.js +19 -0
  51. package/src/cli/commands/mcp.js +18 -0
  52. package/src/cli/commands/models.js +19 -0
  53. package/src/cli/commands/owners.js +25 -0
  54. package/src/cli/commands/path.js +36 -0
  55. package/src/cli/commands/plot.js +80 -0
  56. package/src/cli/commands/query.js +49 -0
  57. package/src/cli/commands/registry.js +100 -0
  58. package/src/cli/commands/roles.js +34 -0
  59. package/src/cli/commands/search.js +42 -0
  60. package/src/cli/commands/sequence.js +32 -0
  61. package/src/cli/commands/snapshot.js +61 -0
  62. package/src/cli/commands/stats.js +15 -0
  63. package/src/cli/commands/structure.js +32 -0
  64. package/src/cli/commands/triage.js +78 -0
  65. package/src/cli/commands/watch.js +12 -0
  66. package/src/cli/commands/where.js +24 -0
  67. package/src/cli/index.js +118 -0
  68. package/src/cli/shared/options.js +39 -0
  69. package/src/cli/shared/output.js +1 -0
  70. package/src/cli.js +11 -1514
  71. package/src/commands/check.js +5 -5
  72. package/src/commands/manifesto.js +3 -3
  73. package/src/commands/structure.js +1 -1
  74. package/src/communities.js +15 -87
  75. package/src/complexity.js +1 -1
  76. package/src/cycles.js +30 -85
  77. package/src/dataflow.js +1 -2
  78. package/src/db/connection.js +4 -4
  79. package/src/db/migrations.js +41 -0
  80. package/src/db/query-builder.js +6 -5
  81. package/src/db/repository/base.js +201 -0
  82. package/src/db/repository/cached-stmt.js +19 -0
  83. package/src/db/repository/cfg.js +27 -38
  84. package/src/db/repository/cochange.js +16 -3
  85. package/src/db/repository/complexity.js +11 -6
  86. package/src/db/repository/dataflow.js +6 -1
  87. package/src/db/repository/edges.js +120 -98
  88. package/src/db/repository/embeddings.js +14 -3
  89. package/src/db/repository/graph-read.js +32 -9
  90. package/src/db/repository/in-memory-repository.js +584 -0
  91. package/src/db/repository/index.js +6 -1
  92. package/src/db/repository/nodes.js +110 -40
  93. package/src/db/repository/sqlite-repository.js +219 -0
  94. package/src/db.js +5 -0
  95. package/src/embeddings/generator.js +163 -0
  96. package/src/embeddings/index.js +13 -0
  97. package/src/embeddings/models.js +218 -0
  98. package/src/embeddings/search/cli-formatter.js +151 -0
  99. package/src/embeddings/search/filters.js +46 -0
  100. package/src/embeddings/search/hybrid.js +121 -0
  101. package/src/embeddings/search/keyword.js +68 -0
  102. package/src/embeddings/search/prepare.js +66 -0
  103. package/src/embeddings/search/semantic.js +145 -0
  104. package/src/embeddings/stores/fts5.js +27 -0
  105. package/src/embeddings/stores/sqlite-blob.js +24 -0
  106. package/src/embeddings/strategies/source.js +14 -0
  107. package/src/embeddings/strategies/structured.js +43 -0
  108. package/src/embeddings/strategies/text-utils.js +43 -0
  109. package/src/errors.js +78 -0
  110. package/src/export.js +217 -520
  111. package/src/extractors/csharp.js +10 -2
  112. package/src/extractors/go.js +3 -1
  113. package/src/extractors/helpers.js +71 -0
  114. package/src/extractors/java.js +9 -2
  115. package/src/extractors/javascript.js +38 -1
  116. package/src/extractors/php.js +3 -1
  117. package/src/extractors/python.js +14 -3
  118. package/src/extractors/rust.js +3 -1
  119. package/src/graph/algorithms/bfs.js +49 -0
  120. package/src/graph/algorithms/centrality.js +16 -0
  121. package/src/graph/algorithms/index.js +5 -0
  122. package/src/graph/algorithms/louvain.js +26 -0
  123. package/src/graph/algorithms/shortest-path.js +41 -0
  124. package/src/graph/algorithms/tarjan.js +49 -0
  125. package/src/graph/builders/dependency.js +91 -0
  126. package/src/graph/builders/index.js +3 -0
  127. package/src/graph/builders/structure.js +40 -0
  128. package/src/graph/builders/temporal.js +33 -0
  129. package/src/graph/classifiers/index.js +2 -0
  130. package/src/graph/classifiers/risk.js +85 -0
  131. package/src/graph/classifiers/roles.js +64 -0
  132. package/src/graph/index.js +13 -0
  133. package/src/graph/model.js +230 -0
  134. package/src/index.js +33 -204
  135. package/src/infrastructure/result-formatter.js +2 -21
  136. package/src/mcp/index.js +2 -0
  137. package/src/mcp/middleware.js +26 -0
  138. package/src/mcp/server.js +128 -0
  139. package/src/mcp/tool-registry.js +801 -0
  140. package/src/mcp/tools/ast-query.js +14 -0
  141. package/src/mcp/tools/audit.js +21 -0
  142. package/src/mcp/tools/batch-query.js +11 -0
  143. package/src/mcp/tools/branch-compare.js +10 -0
  144. package/src/mcp/tools/cfg.js +21 -0
  145. package/src/mcp/tools/check.js +43 -0
  146. package/src/mcp/tools/co-changes.js +20 -0
  147. package/src/mcp/tools/code-owners.js +12 -0
  148. package/src/mcp/tools/communities.js +15 -0
  149. package/src/mcp/tools/complexity.js +18 -0
  150. package/src/mcp/tools/context.js +17 -0
  151. package/src/mcp/tools/dataflow.js +26 -0
  152. package/src/mcp/tools/diff-impact.js +24 -0
  153. package/src/mcp/tools/execution-flow.js +26 -0
  154. package/src/mcp/tools/export-graph.js +57 -0
  155. package/src/mcp/tools/file-deps.js +12 -0
  156. package/src/mcp/tools/file-exports.js +13 -0
  157. package/src/mcp/tools/find-cycles.js +15 -0
  158. package/src/mcp/tools/fn-impact.js +15 -0
  159. package/src/mcp/tools/impact-analysis.js +12 -0
  160. package/src/mcp/tools/index.js +71 -0
  161. package/src/mcp/tools/list-functions.js +14 -0
  162. package/src/mcp/tools/list-repos.js +11 -0
  163. package/src/mcp/tools/module-map.js +6 -0
  164. package/src/mcp/tools/node-roles.js +14 -0
  165. package/src/mcp/tools/path.js +12 -0
  166. package/src/mcp/tools/query.js +30 -0
  167. package/src/mcp/tools/semantic-search.js +65 -0
  168. package/src/mcp/tools/sequence.js +17 -0
  169. package/src/mcp/tools/structure.js +15 -0
  170. package/src/mcp/tools/symbol-children.js +14 -0
  171. package/src/mcp/tools/triage.js +35 -0
  172. package/src/mcp/tools/where.js +13 -0
  173. package/src/mcp.js +2 -1470
  174. package/src/native.js +34 -10
  175. package/src/parser.js +53 -2
  176. package/src/presentation/colors.js +44 -0
  177. package/src/presentation/export.js +444 -0
  178. package/src/presentation/result-formatter.js +21 -0
  179. package/src/presentation/sequence-renderer.js +43 -0
  180. package/src/presentation/table.js +47 -0
  181. package/src/presentation/viewer.js +634 -0
  182. package/src/queries.js +35 -2276
  183. package/src/resolve.js +1 -1
  184. package/src/sequence.js +2 -38
  185. package/src/shared/file-utils.js +153 -0
  186. package/src/shared/generators.js +125 -0
  187. package/src/shared/hierarchy.js +27 -0
  188. package/src/shared/normalize.js +59 -0
  189. package/src/snapshot.js +6 -5
  190. package/src/structure.js +15 -40
  191. package/src/triage.js +20 -72
  192. package/src/viewer.js +35 -656
  193. package/src/watcher.js +8 -148
  194. package/src/embedder.js +0 -1097
@@ -0,0 +1,75 @@
1
+ import fs from 'node:fs';
2
+ import { openReadonlyOrFail } from '../../db.js';
3
+ import {
4
+ exportDOT,
5
+ exportGraphML,
6
+ exportGraphSON,
7
+ exportJSON,
8
+ exportMermaid,
9
+ exportNeo4jCSV,
10
+ } from '../../export.js';
11
+
12
+ export const command = {
13
+ name: 'export',
14
+ description: 'Export dependency graph as DOT, Mermaid, JSON, GraphML, GraphSON, or Neo4j CSV',
15
+ options: [
16
+ ['-d, --db <path>', 'Path to graph.db'],
17
+ ['-f, --format <format>', 'Output format: dot, mermaid, json, graphml, graphson, neo4j', 'dot'],
18
+ ['--functions', 'Function-level graph instead of file-level'],
19
+ ['-T, --no-tests', 'Exclude test/spec files'],
20
+ ['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
21
+ ['--min-confidence <score>', 'Minimum edge confidence threshold (default: 0.5)', '0.5'],
22
+ ['--direction <dir>', 'Flowchart direction for Mermaid: TB, LR, RL, BT', 'LR'],
23
+ ['-o, --output <file>', 'Write to file instead of stdout'],
24
+ ],
25
+ execute(_args, opts, ctx) {
26
+ const db = openReadonlyOrFail(opts.db);
27
+ const exportOpts = {
28
+ fileLevel: !opts.functions,
29
+ noTests: ctx.resolveNoTests(opts),
30
+ minConfidence: parseFloat(opts.minConfidence),
31
+ direction: opts.direction,
32
+ };
33
+
34
+ let output;
35
+ switch (opts.format) {
36
+ case 'mermaid':
37
+ output = exportMermaid(db, exportOpts);
38
+ break;
39
+ case 'json':
40
+ output = JSON.stringify(exportJSON(db, exportOpts), null, 2);
41
+ break;
42
+ case 'graphml':
43
+ output = exportGraphML(db, exportOpts);
44
+ break;
45
+ case 'graphson':
46
+ output = JSON.stringify(exportGraphSON(db, exportOpts), null, 2);
47
+ break;
48
+ case 'neo4j': {
49
+ const csv = exportNeo4jCSV(db, exportOpts);
50
+ if (opts.output) {
51
+ const base = opts.output.replace(/\.[^.]+$/, '') || opts.output;
52
+ fs.writeFileSync(`${base}-nodes.csv`, csv.nodes, 'utf-8');
53
+ fs.writeFileSync(`${base}-relationships.csv`, csv.relationships, 'utf-8');
54
+ db.close();
55
+ console.log(`Exported to ${base}-nodes.csv and ${base}-relationships.csv`);
56
+ return;
57
+ }
58
+ output = `--- nodes.csv ---\n${csv.nodes}\n\n--- relationships.csv ---\n${csv.relationships}`;
59
+ break;
60
+ }
61
+ default:
62
+ output = exportDOT(db, exportOpts);
63
+ break;
64
+ }
65
+
66
+ db.close();
67
+
68
+ if (opts.output) {
69
+ fs.writeFileSync(opts.output, output, 'utf-8');
70
+ console.log(`Exported ${opts.format} to ${opts.output}`);
71
+ } else {
72
+ console.log(output);
73
+ }
74
+ },
75
+ };
@@ -0,0 +1,18 @@
1
+ import { fileExports } from '../../queries-cli.js';
2
+
3
+ export const command = {
4
+ name: 'exports <file>',
5
+ description: 'Show exported symbols with per-symbol consumers (who calls each export)',
6
+ queryOpts: true,
7
+ options: [['--unused', 'Show only exports with zero consumers (dead exports)']],
8
+ execute([file], opts, ctx) {
9
+ fileExports(file, opts.db, {
10
+ noTests: ctx.resolveNoTests(opts),
11
+ json: opts.json,
12
+ unused: opts.unused || false,
13
+ limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
14
+ offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
15
+ ndjson: opts.ndjson,
16
+ });
17
+ },
18
+ };
@@ -0,0 +1,36 @@
1
+ import { EVERY_SYMBOL_KIND } from '../../queries.js';
2
+
3
+ export const command = {
4
+ name: 'flow [name]',
5
+ description:
6
+ 'Trace execution flow forward from an entry point (route, command, event) through callees to leaves',
7
+ queryOpts: true,
8
+ options: [
9
+ ['--list', 'List all entry points grouped by type'],
10
+ ['--depth <n>', 'Max forward traversal depth', '10'],
11
+ ['-f, --file <path>', 'Scope to a specific file (partial match)'],
12
+ ['-k, --kind <kind>', 'Filter by symbol kind'],
13
+ ],
14
+ validate([name], opts) {
15
+ if (!name && !opts.list) {
16
+ return 'Provide a function/entry point name or use --list to see all entry points.';
17
+ }
18
+ if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
19
+ return `Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`;
20
+ }
21
+ },
22
+ async execute([name], opts, ctx) {
23
+ const { flow } = await import('../../commands/flow.js');
24
+ flow(name, opts.db, {
25
+ list: opts.list,
26
+ depth: parseInt(opts.depth, 10),
27
+ file: opts.file,
28
+ kind: opts.kind,
29
+ noTests: ctx.resolveNoTests(opts),
30
+ json: opts.json,
31
+ limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
32
+ offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
33
+ ndjson: opts.ndjson,
34
+ });
35
+ },
36
+ };
@@ -0,0 +1,30 @@
1
+ import { EVERY_SYMBOL_KIND } from '../../queries.js';
2
+ import { fnImpact } from '../../queries-cli.js';
3
+
4
+ export const command = {
5
+ name: 'fn-impact <name>',
6
+ description: 'Function-level impact: what functions break if this one changes',
7
+ queryOpts: true,
8
+ options: [
9
+ ['--depth <n>', 'Max transitive depth', '5'],
10
+ ['-f, --file <path>', 'Scope search to functions in this file (partial match)'],
11
+ ['-k, --kind <kind>', 'Filter to a specific symbol kind'],
12
+ ],
13
+ validate([_name], opts) {
14
+ if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
15
+ return `Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`;
16
+ }
17
+ },
18
+ execute([name], opts, ctx) {
19
+ fnImpact(name, opts.db, {
20
+ depth: parseInt(opts.depth, 10),
21
+ file: opts.file,
22
+ kind: opts.kind,
23
+ noTests: ctx.resolveNoTests(opts),
24
+ json: opts.json,
25
+ limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
26
+ offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
27
+ ndjson: opts.ndjson,
28
+ });
29
+ },
30
+ };
@@ -0,0 +1,16 @@
1
+ import { impactAnalysis } from '../../queries-cli.js';
2
+
3
+ export const command = {
4
+ name: 'impact <file>',
5
+ description: 'Show what depends on this file (transitive)',
6
+ queryOpts: true,
7
+ execute([file], opts, ctx) {
8
+ impactAnalysis(file, opts.db, {
9
+ noTests: ctx.resolveNoTests(opts),
10
+ json: opts.json,
11
+ limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
12
+ offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
13
+ ndjson: opts.ndjson,
14
+ });
15
+ },
16
+ };
@@ -0,0 +1,76 @@
1
+ export const command = {
2
+ name: 'info',
3
+ description: 'Show codegraph engine info and diagnostics',
4
+ async execute(_args, _opts, ctx) {
5
+ const { getNativePackageVersion, isNativeAvailable, loadNative } = await import(
6
+ '../../native.js'
7
+ );
8
+ const { getActiveEngine } = await import('../../parser.js');
9
+
10
+ const engine = ctx.program.opts().engine;
11
+ const { name: activeName, version: activeVersion } = getActiveEngine({ engine });
12
+ const nativeAvailable = isNativeAvailable();
13
+
14
+ console.log('\nCodegraph Diagnostics');
15
+ console.log('====================');
16
+ console.log(` Version : ${ctx.program.version()}`);
17
+ console.log(` Node.js : ${process.version}`);
18
+ console.log(` Platform : ${process.platform}-${process.arch}`);
19
+ console.log(` Native engine : ${nativeAvailable ? 'available' : 'unavailable'}`);
20
+ if (nativeAvailable) {
21
+ const native = loadNative();
22
+ const binaryVersion =
23
+ typeof native.engineVersion === 'function' ? native.engineVersion() : 'unknown';
24
+ const pkgVersion = getNativePackageVersion();
25
+ const knownBinaryVersion = binaryVersion !== 'unknown' ? binaryVersion : null;
26
+ if (pkgVersion && knownBinaryVersion && pkgVersion !== knownBinaryVersion) {
27
+ console.log(
28
+ ` Native version: ${pkgVersion} (binary reports ${knownBinaryVersion} — stale)`,
29
+ );
30
+ } else {
31
+ console.log(` Native version: ${pkgVersion ?? binaryVersion}`);
32
+ }
33
+ }
34
+ console.log(` Engine flag : --engine ${engine}`);
35
+ console.log(` Active engine : ${activeName}${activeVersion ? ` (v${activeVersion})` : ''}`);
36
+ console.log();
37
+
38
+ try {
39
+ const { findDbPath, getBuildMeta } = await import('../../db.js');
40
+ const Database = (await import('better-sqlite3')).default;
41
+ const dbPath = findDbPath();
42
+ const fs = await import('node:fs');
43
+ if (fs.existsSync(dbPath)) {
44
+ const db = new Database(dbPath, { readonly: true });
45
+ const buildEngine = getBuildMeta(db, 'engine');
46
+ const buildVersion = getBuildMeta(db, 'codegraph_version');
47
+ const builtAt = getBuildMeta(db, 'built_at');
48
+ db.close();
49
+
50
+ if (buildEngine || buildVersion || builtAt) {
51
+ console.log('Build metadata');
52
+ console.log(
53
+ '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500',
54
+ );
55
+ if (buildEngine) console.log(` Engine : ${buildEngine}`);
56
+ if (buildVersion) console.log(` Version : ${buildVersion}`);
57
+ if (builtAt) console.log(` Built at : ${builtAt}`);
58
+
59
+ if (buildVersion && buildVersion !== ctx.program.version()) {
60
+ console.log(
61
+ ` \u26A0 DB was built with v${buildVersion}, current is v${ctx.program.version()}. Consider: codegraph build --no-incremental`,
62
+ );
63
+ }
64
+ if (buildEngine && buildEngine !== activeName) {
65
+ console.log(
66
+ ` \u26A0 DB was built with ${buildEngine} engine, active is ${activeName}. Consider: codegraph build --no-incremental`,
67
+ );
68
+ }
69
+ console.log();
70
+ }
71
+ }
72
+ } catch {
73
+ /* diagnostics must never crash */
74
+ }
75
+ },
76
+ };
@@ -0,0 +1,19 @@
1
+ import { moduleMap } from '../../queries-cli.js';
2
+
3
+ export const command = {
4
+ name: 'map',
5
+ description: 'High-level module overview with most-connected nodes',
6
+ options: [
7
+ ['-d, --db <path>', 'Path to graph.db'],
8
+ ['-n, --limit <number>', 'Number of top nodes', '20'],
9
+ ['-T, --no-tests', 'Exclude test/spec files from results'],
10
+ ['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
11
+ ['-j, --json', 'Output as JSON'],
12
+ ],
13
+ execute(_args, opts, ctx) {
14
+ moduleMap(opts.db, parseInt(opts.limit, 10), {
15
+ noTests: ctx.resolveNoTests(opts),
16
+ json: opts.json,
17
+ });
18
+ },
19
+ };
@@ -0,0 +1,18 @@
1
+ export const command = {
2
+ name: 'mcp',
3
+ description: 'Start MCP (Model Context Protocol) server for AI assistant integration',
4
+ options: [
5
+ ['-d, --db <path>', 'Path to graph.db'],
6
+ ['--multi-repo', 'Enable access to all registered repositories'],
7
+ ['--repos <names>', 'Comma-separated list of allowed repo names (restricts access)'],
8
+ ],
9
+ async execute(_args, opts) {
10
+ const { startMCPServer } = await import('../../mcp.js');
11
+ const mcpOpts = {};
12
+ mcpOpts.multiRepo = opts.multiRepo || !!opts.repos;
13
+ if (opts.repos) {
14
+ mcpOpts.allowedRepos = opts.repos.split(',').map((s) => s.trim());
15
+ }
16
+ await startMCPServer(opts.db, mcpOpts);
17
+ },
18
+ };
@@ -0,0 +1,19 @@
1
+ import { DEFAULT_MODEL, MODELS } from '../../embeddings/index.js';
2
+
3
+ export const command = {
4
+ name: 'models',
5
+ description: 'List available embedding models',
6
+ execute(_args, _opts, ctx) {
7
+ const defaultModel = ctx.config.embeddings?.model || DEFAULT_MODEL;
8
+ console.log('\nAvailable embedding models:\n');
9
+ for (const [key, cfg] of Object.entries(MODELS)) {
10
+ const def = key === defaultModel ? ' (default)' : '';
11
+ const ctxWindow = cfg.contextWindow ? `${cfg.contextWindow} ctx` : '';
12
+ console.log(
13
+ ` ${key.padEnd(12)} ${String(cfg.dim).padStart(4)}d ${ctxWindow.padEnd(9)} ${cfg.desc}${def}`,
14
+ );
15
+ }
16
+ console.log('\nUsage: codegraph embed --model <name> --strategy <structured|source>');
17
+ console.log(' codegraph search "query" --model <name>\n');
18
+ },
19
+ };
@@ -0,0 +1,25 @@
1
+ export const command = {
2
+ name: 'owners [target]',
3
+ description: 'Show CODEOWNERS mapping for files and functions',
4
+ options: [
5
+ ['-d, --db <path>', 'Path to graph.db'],
6
+ ['--owner <owner>', 'Filter to a specific owner'],
7
+ ['--boundary', 'Show cross-owner boundary edges'],
8
+ ['-f, --file <path>', 'Scope to a specific file'],
9
+ ['-k, --kind <kind>', 'Filter by symbol kind'],
10
+ ['-T, --no-tests', 'Exclude test/spec files'],
11
+ ['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
12
+ ['-j, --json', 'Output as JSON'],
13
+ ],
14
+ async execute([target], opts, ctx) {
15
+ const { owners } = await import('../../commands/owners.js');
16
+ owners(opts.db, {
17
+ owner: opts.owner,
18
+ boundary: opts.boundary,
19
+ file: opts.file || target,
20
+ kind: opts.kind,
21
+ noTests: ctx.resolveNoTests(opts),
22
+ json: opts.json,
23
+ });
24
+ },
25
+ };
@@ -0,0 +1,36 @@
1
+ import { EVERY_SYMBOL_KIND } from '../../queries.js';
2
+ import { symbolPath } from '../../queries-cli.js';
3
+
4
+ export const command = {
5
+ name: 'path <from> <to>',
6
+ description: 'Find shortest path between two symbols',
7
+ options: [
8
+ ['-d, --db <path>', 'Path to graph.db'],
9
+ ['--reverse', 'Follow edges backward'],
10
+ ['--kinds <kinds>', 'Comma-separated edge kinds to follow (default: calls)'],
11
+ ['--from-file <path>', 'Disambiguate source symbol by file'],
12
+ ['--to-file <path>', 'Disambiguate target symbol by file'],
13
+ ['--depth <n>', 'Max traversal depth', '10'],
14
+ ['-k, --kind <kind>', 'Filter to a specific symbol kind'],
15
+ ['-T, --no-tests', 'Exclude test/spec files from results'],
16
+ ['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
17
+ ['-j, --json', 'Output as JSON'],
18
+ ],
19
+ validate([_from, _to], opts) {
20
+ if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
21
+ return `Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`;
22
+ }
23
+ },
24
+ execute([from, to], opts, ctx) {
25
+ symbolPath(from, to, opts.db, {
26
+ maxDepth: opts.depth ? parseInt(opts.depth, 10) : 10,
27
+ edgeKinds: opts.kinds ? opts.kinds.split(',').map((s) => s.trim()) : undefined,
28
+ reverse: opts.reverse,
29
+ fromFile: opts.fromFile,
30
+ toFile: opts.toFile,
31
+ kind: opts.kind,
32
+ noTests: ctx.resolveNoTests(opts),
33
+ json: opts.json,
34
+ });
35
+ },
36
+ };
@@ -0,0 +1,80 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { openReadonlyOrFail } from '../../db.js';
4
+
5
+ export const command = {
6
+ name: 'plot',
7
+ description: 'Generate an interactive HTML dependency graph viewer',
8
+ options: [
9
+ ['-d, --db <path>', 'Path to graph.db'],
10
+ ['--functions', 'Function-level graph instead of file-level'],
11
+ ['-T, --no-tests', 'Exclude test/spec files'],
12
+ ['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
13
+ ['--min-confidence <score>', 'Minimum edge confidence threshold (default: 0.5)', '0.5'],
14
+ ['-o, --output <file>', 'Write HTML to file'],
15
+ ['-c, --config <path>', 'Path to .plotDotCfg config file'],
16
+ ['--no-open', 'Do not open in browser'],
17
+ ['--cluster <mode>', 'Cluster nodes: none | community | directory'],
18
+ ['--overlay <list>', 'Comma-separated overlays: complexity,risk'],
19
+ ['--seed <strategy>', 'Seed strategy: all | top-fanin | entry'],
20
+ ['--seed-count <n>', 'Number of seed nodes (default: 30)'],
21
+ ['--size-by <metric>', 'Size nodes by: uniform | fan-in | fan-out | complexity'],
22
+ ['--color-by <mode>', 'Color nodes by: kind | role | community | complexity'],
23
+ ],
24
+ async execute(_args, opts, ctx) {
25
+ const { generatePlotHTML, loadPlotConfig } = await import('../../viewer.js');
26
+ const os = await import('node:os');
27
+ const db = openReadonlyOrFail(opts.db);
28
+
29
+ let plotCfg;
30
+ if (opts.config) {
31
+ try {
32
+ plotCfg = JSON.parse(fs.readFileSync(opts.config, 'utf-8'));
33
+ } catch (e) {
34
+ console.error(`Failed to load config: ${e.message}`);
35
+ db.close();
36
+ process.exitCode = 1;
37
+ return;
38
+ }
39
+ } else {
40
+ plotCfg = loadPlotConfig(process.cwd());
41
+ }
42
+
43
+ if (opts.cluster) plotCfg.clusterBy = opts.cluster;
44
+ if (opts.colorBy) plotCfg.colorBy = opts.colorBy;
45
+ if (opts.sizeBy) plotCfg.sizeBy = opts.sizeBy;
46
+ if (opts.seed) plotCfg.seedStrategy = opts.seed;
47
+ if (opts.seedCount) plotCfg.seedCount = parseInt(opts.seedCount, 10);
48
+ if (opts.overlay) {
49
+ const parts = opts.overlay.split(',').map((s) => s.trim());
50
+ if (!plotCfg.overlays) plotCfg.overlays = {};
51
+ if (parts.includes('complexity')) plotCfg.overlays.complexity = true;
52
+ if (parts.includes('risk')) plotCfg.overlays.risk = true;
53
+ }
54
+
55
+ const html = generatePlotHTML(db, {
56
+ fileLevel: !opts.functions,
57
+ noTests: ctx.resolveNoTests(opts),
58
+ minConfidence: parseFloat(opts.minConfidence),
59
+ config: plotCfg,
60
+ });
61
+ db.close();
62
+
63
+ const outPath = opts.output || path.join(os.tmpdir(), `codegraph-plot-${Date.now()}.html`);
64
+ fs.writeFileSync(outPath, html, 'utf-8');
65
+ console.log(`Plot written to ${outPath}`);
66
+
67
+ if (opts.open !== false) {
68
+ const { execFile } = await import('node:child_process');
69
+ const args =
70
+ process.platform === 'win32'
71
+ ? ['cmd', ['/c', 'start', '', outPath]]
72
+ : process.platform === 'darwin'
73
+ ? ['open', [outPath]]
74
+ : ['xdg-open', [outPath]];
75
+ execFile(args[0], args[1], (err) => {
76
+ if (err) console.error('Could not open browser:', err.message);
77
+ });
78
+ }
79
+ },
80
+ };
@@ -0,0 +1,49 @@
1
+ import { EVERY_SYMBOL_KIND } from '../../queries.js';
2
+ import { fnDeps, symbolPath } from '../../queries-cli.js';
3
+
4
+ export const command = {
5
+ name: 'query <name>',
6
+ description: 'Function-level dependency chain or shortest path between symbols',
7
+ queryOpts: true,
8
+ options: [
9
+ ['--depth <n>', 'Transitive caller depth', '3'],
10
+ ['-f, --file <path>', 'Scope search to functions in this file (partial match)'],
11
+ ['-k, --kind <kind>', 'Filter to a specific symbol kind'],
12
+ ['--path <to>', 'Path mode: find shortest path to <to>'],
13
+ ['--kinds <kinds>', 'Path mode: comma-separated edge kinds to follow (default: calls)'],
14
+ ['--reverse', 'Path mode: follow edges backward'],
15
+ ['--from-file <path>', 'Path mode: disambiguate source symbol by file'],
16
+ ['--to-file <path>', 'Path mode: disambiguate target symbol by file'],
17
+ ],
18
+ validate([_name], opts) {
19
+ if (opts.kind && !EVERY_SYMBOL_KIND.includes(opts.kind)) {
20
+ return `Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`;
21
+ }
22
+ },
23
+ execute([name], opts, ctx) {
24
+ if (opts.path) {
25
+ console.error('Note: "query --path" is deprecated, use "codegraph path <from> <to>" instead');
26
+ symbolPath(name, opts.path, opts.db, {
27
+ maxDepth: opts.depth ? parseInt(opts.depth, 10) : 10,
28
+ edgeKinds: opts.kinds ? opts.kinds.split(',').map((s) => s.trim()) : undefined,
29
+ reverse: opts.reverse,
30
+ fromFile: opts.fromFile,
31
+ toFile: opts.toFile,
32
+ kind: opts.kind,
33
+ noTests: ctx.resolveNoTests(opts),
34
+ json: opts.json,
35
+ });
36
+ } else {
37
+ fnDeps(name, opts.db, {
38
+ depth: parseInt(opts.depth, 10),
39
+ file: opts.file,
40
+ kind: opts.kind,
41
+ noTests: ctx.resolveNoTests(opts),
42
+ json: opts.json,
43
+ limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
44
+ offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
45
+ ndjson: opts.ndjson,
46
+ });
47
+ }
48
+ },
49
+ };
@@ -0,0 +1,100 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { ConfigError } from '../../errors.js';
4
+ import {
5
+ listRepos,
6
+ pruneRegistry,
7
+ REGISTRY_PATH,
8
+ registerRepo,
9
+ unregisterRepo,
10
+ } from '../../registry.js';
11
+
12
+ export const command = {
13
+ name: 'registry',
14
+ description: 'Manage the multi-repo project registry',
15
+ subcommands: [
16
+ {
17
+ name: 'list',
18
+ description: 'List all registered repositories',
19
+ options: [['-j, --json', 'Output as JSON']],
20
+ execute(_args, opts) {
21
+ pruneRegistry();
22
+ const repos = listRepos();
23
+ if (opts.json) {
24
+ console.log(JSON.stringify(repos, null, 2));
25
+ } else if (repos.length === 0) {
26
+ console.log(`No repositories registered.\nRegistry: ${REGISTRY_PATH}`);
27
+ } else {
28
+ console.log(`Registered repositories (${REGISTRY_PATH}):\n`);
29
+ for (const r of repos) {
30
+ const dbExists = fs.existsSync(r.dbPath);
31
+ const status = dbExists ? '' : ' [DB missing]';
32
+ console.log(` ${r.name}${status}`);
33
+ console.log(` Path: ${r.path}`);
34
+ console.log(` DB: ${r.dbPath}`);
35
+ console.log();
36
+ }
37
+ }
38
+ },
39
+ },
40
+ {
41
+ name: 'add <dir>',
42
+ description: 'Register a project directory',
43
+ options: [['-n, --name <name>', 'Custom name (defaults to directory basename)']],
44
+ execute([dir], opts) {
45
+ const absDir = path.resolve(dir);
46
+ const { name, entry } = registerRepo(absDir, opts.name);
47
+ console.log(`Registered "${name}" → ${entry.path}`);
48
+ },
49
+ },
50
+ {
51
+ name: 'remove <name>',
52
+ description: 'Unregister a repository by name',
53
+ execute([name]) {
54
+ const removed = unregisterRepo(name);
55
+ if (removed) {
56
+ console.log(`Removed "${name}" from registry.`);
57
+ } else {
58
+ throw new ConfigError(`Repository "${name}" not found in registry.`);
59
+ }
60
+ },
61
+ },
62
+ {
63
+ name: 'prune',
64
+ description: 'Remove stale registry entries (missing directories or idle beyond TTL)',
65
+ options: [
66
+ ['--ttl <days>', 'Days of inactivity before pruning (default: 30)', '30'],
67
+ ['--exclude <names>', 'Comma-separated repo names to preserve from pruning'],
68
+ ['--dry-run', 'Show what would be pruned without removing anything'],
69
+ ],
70
+ execute(_args, opts) {
71
+ const excludeNames = opts.exclude
72
+ ? opts.exclude
73
+ .split(',')
74
+ .map((s) => s.trim())
75
+ .filter((s) => s.length > 0)
76
+ : [];
77
+ const dryRun = !!opts.dryRun;
78
+ const pruned = pruneRegistry(undefined, parseInt(opts.ttl, 10), excludeNames, dryRun);
79
+ if (pruned.length === 0) {
80
+ console.log('No stale entries found.');
81
+ } else {
82
+ const prefix = dryRun ? 'Would prune' : 'Pruned';
83
+ for (const entry of pruned) {
84
+ const tag = entry.reason === 'expired' ? 'expired' : 'missing';
85
+ console.log(`${prefix} "${entry.name}" (${entry.path}) [${tag}]`);
86
+ }
87
+ if (dryRun) {
88
+ console.log(
89
+ `\nDry run: ${pruned.length} ${pruned.length === 1 ? 'entry' : 'entries'} would be removed.`,
90
+ );
91
+ } else {
92
+ console.log(
93
+ `\nRemoved ${pruned.length} stale ${pruned.length === 1 ? 'entry' : 'entries'}.`,
94
+ );
95
+ }
96
+ }
97
+ },
98
+ },
99
+ ],
100
+ };
@@ -0,0 +1,34 @@
1
+ import { VALID_ROLES } from '../../queries.js';
2
+ import { roles } from '../../queries-cli.js';
3
+
4
+ export const command = {
5
+ name: 'roles',
6
+ description: 'Show node role classification: entry, core, utility, adapter, dead, leaf',
7
+ options: [
8
+ ['-d, --db <path>', 'Path to graph.db'],
9
+ ['--role <role>', `Filter by role (${VALID_ROLES.join(', ')})`],
10
+ ['-f, --file <path>', 'Scope to a specific file (partial match)'],
11
+ ['-T, --no-tests', 'Exclude test/spec files'],
12
+ ['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
13
+ ['-j, --json', 'Output as JSON'],
14
+ ['--limit <number>', 'Max results to return'],
15
+ ['--offset <number>', 'Skip N results (default: 0)'],
16
+ ['--ndjson', 'Newline-delimited JSON output'],
17
+ ],
18
+ validate(_args, opts) {
19
+ if (opts.role && !VALID_ROLES.includes(opts.role)) {
20
+ return `Invalid role "${opts.role}". Valid roles: ${VALID_ROLES.join(', ')}`;
21
+ }
22
+ },
23
+ execute(_args, opts, ctx) {
24
+ roles(opts.db, {
25
+ role: opts.role,
26
+ file: opts.file,
27
+ noTests: ctx.resolveNoTests(opts),
28
+ json: opts.json,
29
+ limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
30
+ offset: opts.offset ? parseInt(opts.offset, 10) : undefined,
31
+ ndjson: opts.ndjson,
32
+ });
33
+ },
34
+ };