@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,33 @@
1
+ /**
2
+ * Build a co-change (temporal) graph weighted by Jaccard similarity.
3
+ */
4
+
5
+ import { CodeGraph } from '../model.js';
6
+
7
+ /**
8
+ * @param {object} db - Open better-sqlite3 database (readonly)
9
+ * @param {{ minJaccard?: number }} [opts]
10
+ * @returns {CodeGraph} Undirected graph weighted by Jaccard similarity
11
+ */
12
+ export function buildTemporalGraph(db, opts = {}) {
13
+ const minJaccard = opts.minJaccard ?? 0.0;
14
+ const graph = new CodeGraph({ directed: false });
15
+
16
+ // Check if co_changes table exists
17
+ const tableCheck = db
18
+ .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='co_changes'")
19
+ .get();
20
+ if (!tableCheck) return graph;
21
+
22
+ const rows = db
23
+ .prepare('SELECT file_a, file_b, jaccard FROM co_changes WHERE jaccard >= ?')
24
+ .all(minJaccard);
25
+
26
+ for (const r of rows) {
27
+ if (!graph.hasNode(r.file_a)) graph.addNode(r.file_a, { label: r.file_a });
28
+ if (!graph.hasNode(r.file_b)) graph.addNode(r.file_b, { label: r.file_b });
29
+ graph.addEdge(r.file_a, r.file_b, { jaccard: r.jaccard });
30
+ }
31
+
32
+ return graph;
33
+ }
@@ -0,0 +1,2 @@
1
+ export { DEFAULT_WEIGHTS, minMaxNormalize, ROLE_WEIGHTS, scoreRisk } from './risk.js';
2
+ export { classifyRoles, FRAMEWORK_ENTRY_PREFIXES } from './roles.js';
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Risk scoring — pure logic, no DB.
3
+ */
4
+
5
+ // Weights sum to 1.0. Complexity gets the highest weight because cognitive load
6
+ // is the strongest predictor of defect density. Fan-in and churn are next as
7
+ // they reflect coupling and volatility. Role adds architectural context, and MI
8
+ // (maintainability index) is a weaker composite signal, so it gets the least.
9
+ export const DEFAULT_WEIGHTS = {
10
+ fanIn: 0.25,
11
+ complexity: 0.3,
12
+ churn: 0.2,
13
+ role: 0.15,
14
+ mi: 0.1,
15
+ };
16
+
17
+ // Role weights reflect structural importance: core modules are central to the
18
+ // dependency graph, utilities are widely imported, entry points are API
19
+ // surfaces. Adapters bridge subsystems but are replaceable. Leaves and dead
20
+ // code have minimal downstream impact.
21
+ export const ROLE_WEIGHTS = {
22
+ core: 1.0,
23
+ utility: 0.9,
24
+ entry: 0.8,
25
+ adapter: 0.5,
26
+ leaf: 0.2,
27
+ dead: 0.1,
28
+ };
29
+
30
+ const DEFAULT_ROLE_WEIGHT = 0.5;
31
+
32
+ /** Min-max normalize an array of numbers. All-equal → all zeros. */
33
+ export function minMaxNormalize(values) {
34
+ const min = Math.min(...values);
35
+ const max = Math.max(...values);
36
+ if (max === min) return values.map(() => 0);
37
+ const range = max - min;
38
+ return values.map((v) => (v - min) / range);
39
+ }
40
+
41
+ function round4(n) {
42
+ return Math.round(n * 10000) / 10000;
43
+ }
44
+
45
+ /**
46
+ * Score risk for a list of items.
47
+ *
48
+ * @param {{ fan_in: number, cognitive: number, churn: number, mi: number, role: string|null }[]} items
49
+ * @param {object} [weights] - Override DEFAULT_WEIGHTS
50
+ * @returns {{ normFanIn: number, normComplexity: number, normChurn: number, normMI: number, roleWeight: number, riskScore: number }[]}
51
+ * Parallel array with risk metrics for each input item.
52
+ */
53
+ export function scoreRisk(items, weights = {}) {
54
+ const w = { ...DEFAULT_WEIGHTS, ...weights };
55
+
56
+ const fanIns = items.map((r) => r.fan_in);
57
+ const cognitives = items.map((r) => r.cognitive);
58
+ const churns = items.map((r) => r.churn);
59
+ const mis = items.map((r) => r.mi);
60
+
61
+ const normFanIns = minMaxNormalize(fanIns);
62
+ const normCognitives = minMaxNormalize(cognitives);
63
+ const normChurns = minMaxNormalize(churns);
64
+ const normMIsRaw = minMaxNormalize(mis);
65
+ const normMIs = normMIsRaw.map((v) => round4(1 - v));
66
+
67
+ return items.map((r, i) => {
68
+ const roleWeight = ROLE_WEIGHTS[r.role] ?? DEFAULT_ROLE_WEIGHT;
69
+ const riskScore =
70
+ w.fanIn * normFanIns[i] +
71
+ w.complexity * normCognitives[i] +
72
+ w.churn * normChurns[i] +
73
+ w.role * roleWeight +
74
+ w.mi * normMIs[i];
75
+
76
+ return {
77
+ normFanIn: round4(normFanIns[i]),
78
+ normComplexity: round4(normCognitives[i]),
79
+ normChurn: round4(normChurns[i]),
80
+ normMI: round4(normMIs[i]),
81
+ roleWeight,
82
+ riskScore: round4(riskScore),
83
+ };
84
+ });
85
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Node role classification — pure logic, no DB.
3
+ *
4
+ * Roles: entry, core, utility, adapter, leaf, dead
5
+ */
6
+
7
+ export const FRAMEWORK_ENTRY_PREFIXES = ['route:', 'event:', 'command:'];
8
+
9
+ function median(sorted) {
10
+ if (sorted.length === 0) return 0;
11
+ const mid = Math.floor(sorted.length / 2);
12
+ return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
13
+ }
14
+
15
+ /**
16
+ * Classify nodes into architectural roles based on fan-in/fan-out metrics.
17
+ *
18
+ * @param {{ id: string, name: string, fanIn: number, fanOut: number, isExported: boolean }[]} nodes
19
+ * @returns {Map<string, string>} nodeId → role
20
+ */
21
+ export function classifyRoles(nodes) {
22
+ if (nodes.length === 0) return new Map();
23
+
24
+ const nonZeroFanIn = nodes
25
+ .filter((n) => n.fanIn > 0)
26
+ .map((n) => n.fanIn)
27
+ .sort((a, b) => a - b);
28
+ const nonZeroFanOut = nodes
29
+ .filter((n) => n.fanOut > 0)
30
+ .map((n) => n.fanOut)
31
+ .sort((a, b) => a - b);
32
+
33
+ const medFanIn = median(nonZeroFanIn);
34
+ const medFanOut = median(nonZeroFanOut);
35
+
36
+ const result = new Map();
37
+
38
+ for (const node of nodes) {
39
+ const highIn = node.fanIn >= medFanIn && node.fanIn > 0;
40
+ const highOut = node.fanOut >= medFanOut && node.fanOut > 0;
41
+
42
+ let role;
43
+ const isFrameworkEntry = FRAMEWORK_ENTRY_PREFIXES.some((p) => node.name.startsWith(p));
44
+ if (isFrameworkEntry) {
45
+ role = 'entry';
46
+ } else if (node.fanIn === 0 && !node.isExported) {
47
+ role = 'dead';
48
+ } else if (node.fanIn === 0 && node.isExported) {
49
+ role = 'entry';
50
+ } else if (highIn && !highOut) {
51
+ role = 'core';
52
+ } else if (highIn && highOut) {
53
+ role = 'utility';
54
+ } else if (!highIn && highOut) {
55
+ role = 'adapter';
56
+ } else {
57
+ role = 'leaf';
58
+ }
59
+
60
+ result.set(node.id, role);
61
+ }
62
+
63
+ return result;
64
+ }
@@ -0,0 +1,13 @@
1
+ // Graph subsystem barrel export
2
+
3
+ export { bfs, fanInOut, louvainCommunities, shortestPath, tarjan } from './algorithms/index.js';
4
+ export { buildDependencyGraph, buildStructureGraph, buildTemporalGraph } from './builders/index.js';
5
+ export {
6
+ classifyRoles,
7
+ DEFAULT_WEIGHTS,
8
+ FRAMEWORK_ENTRY_PREFIXES,
9
+ minMaxNormalize,
10
+ ROLE_WEIGHTS,
11
+ scoreRisk,
12
+ } from './classifiers/index.js';
13
+ export { CodeGraph } from './model.js';
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Unified in-memory graph model.
3
+ *
4
+ * Stores directed (default) or undirected graphs with node/edge attributes.
5
+ * Node IDs are always strings. DB integer IDs should be stringified before use.
6
+ */
7
+
8
+ import Graph from 'graphology';
9
+
10
+ export class CodeGraph {
11
+ /**
12
+ * @param {{ directed?: boolean }} [opts]
13
+ */
14
+ constructor(opts = {}) {
15
+ this._directed = opts.directed !== false;
16
+ /** @type {Map<string, object>} */
17
+ this._nodes = new Map();
18
+ /** @type {Map<string, Map<string, object>>} node → (target → edgeAttrs) */
19
+ this._successors = new Map();
20
+ /** @type {Map<string, Map<string, object>>} node → (source → edgeAttrs) */
21
+ this._predecessors = new Map();
22
+ }
23
+
24
+ get directed() {
25
+ return this._directed;
26
+ }
27
+
28
+ get nodeCount() {
29
+ return this._nodes.size;
30
+ }
31
+
32
+ get edgeCount() {
33
+ let count = 0;
34
+ for (const targets of this._successors.values()) count += targets.size;
35
+ // Undirected graphs store each edge twice (a→b and b→a)
36
+ return this._directed ? count : count / 2;
37
+ }
38
+
39
+ // ─── Node operations ────────────────────────────────────────────
40
+
41
+ addNode(id, attrs = {}) {
42
+ const key = String(id);
43
+ this._nodes.set(key, attrs);
44
+ if (!this._successors.has(key)) this._successors.set(key, new Map());
45
+ if (!this._predecessors.has(key)) this._predecessors.set(key, new Map());
46
+ return this;
47
+ }
48
+
49
+ hasNode(id) {
50
+ return this._nodes.has(String(id));
51
+ }
52
+
53
+ getNodeAttrs(id) {
54
+ return this._nodes.get(String(id));
55
+ }
56
+
57
+ /** @returns {IterableIterator<[string, object]>} */
58
+ nodes() {
59
+ return this._nodes.entries();
60
+ }
61
+
62
+ /** @returns {string[]} */
63
+ nodeIds() {
64
+ return [...this._nodes.keys()];
65
+ }
66
+
67
+ // ─── Edge operations ────────────────────────────────────────────
68
+
69
+ addEdge(source, target, attrs = {}) {
70
+ const src = String(source);
71
+ const tgt = String(target);
72
+ // Auto-add nodes if missing
73
+ if (!this._nodes.has(src)) this.addNode(src);
74
+ if (!this._nodes.has(tgt)) this.addNode(tgt);
75
+
76
+ this._successors.get(src).set(tgt, attrs);
77
+ this._predecessors.get(tgt).set(src, attrs);
78
+
79
+ if (!this._directed) {
80
+ this._successors.get(tgt).set(src, attrs);
81
+ this._predecessors.get(src).set(tgt, attrs);
82
+ }
83
+ return this;
84
+ }
85
+
86
+ hasEdge(source, target) {
87
+ const src = String(source);
88
+ const tgt = String(target);
89
+ return this._successors.has(src) && this._successors.get(src).has(tgt);
90
+ }
91
+
92
+ getEdgeAttrs(source, target) {
93
+ const src = String(source);
94
+ const tgt = String(target);
95
+ return this._successors.get(src)?.get(tgt);
96
+ }
97
+
98
+ /** @yields {[string, string, object]} source, target, attrs */
99
+ *edges() {
100
+ const seen = this._directed ? null : new Set();
101
+ for (const [src, targets] of this._successors) {
102
+ for (const [tgt, attrs] of targets) {
103
+ if (!this._directed) {
104
+ // \0 is safe as separator — node IDs are file paths/symbols, never contain null bytes
105
+ const key = src < tgt ? `${src}\0${tgt}` : `${tgt}\0${src}`;
106
+ if (seen.has(key)) continue;
107
+ seen.add(key);
108
+ }
109
+ yield [src, tgt, attrs];
110
+ }
111
+ }
112
+ }
113
+
114
+ // ─── Adjacency ──────────────────────────────────────────────────
115
+
116
+ /** Direct successors of a node (outgoing edges). */
117
+ successors(id) {
118
+ const key = String(id);
119
+ const map = this._successors.get(key);
120
+ return map ? [...map.keys()] : [];
121
+ }
122
+
123
+ /** Direct predecessors of a node (incoming edges). */
124
+ predecessors(id) {
125
+ const key = String(id);
126
+ const map = this._predecessors.get(key);
127
+ return map ? [...map.keys()] : [];
128
+ }
129
+
130
+ /** All neighbors (union of successors + predecessors). */
131
+ neighbors(id) {
132
+ const key = String(id);
133
+ const set = new Set();
134
+ const succ = this._successors.get(key);
135
+ if (succ) for (const k of succ.keys()) set.add(k);
136
+ const pred = this._predecessors.get(key);
137
+ if (pred) for (const k of pred.keys()) set.add(k);
138
+ return [...set];
139
+ }
140
+
141
+ outDegree(id) {
142
+ const map = this._successors.get(String(id));
143
+ return map ? map.size : 0;
144
+ }
145
+
146
+ inDegree(id) {
147
+ const map = this._predecessors.get(String(id));
148
+ return map ? map.size : 0;
149
+ }
150
+
151
+ // ─── Filtering ──────────────────────────────────────────────────
152
+
153
+ /** Return a new graph containing only nodes matching the predicate. */
154
+ subgraph(predicate) {
155
+ const g = new CodeGraph({ directed: this._directed });
156
+ for (const [id, attrs] of this._nodes) {
157
+ if (predicate(id, attrs)) g.addNode(id, { ...attrs });
158
+ }
159
+ for (const [src, tgt, attrs] of this.edges()) {
160
+ if (g.hasNode(src) && g.hasNode(tgt)) {
161
+ g.addEdge(src, tgt, { ...attrs });
162
+ }
163
+ }
164
+ return g;
165
+ }
166
+
167
+ /** Return a new graph containing only edges matching the predicate. */
168
+ filterEdges(predicate) {
169
+ const g = new CodeGraph({ directed: this._directed });
170
+ for (const [id, attrs] of this._nodes) {
171
+ g.addNode(id, { ...attrs });
172
+ }
173
+ for (const [src, tgt, attrs] of this.edges()) {
174
+ if (predicate(src, tgt, attrs)) {
175
+ g.addEdge(src, tgt, { ...attrs });
176
+ }
177
+ }
178
+ return g;
179
+ }
180
+
181
+ // ─── Conversion ─────────────────────────────────────────────────
182
+
183
+ /** Convert to flat edge array for native Rust interop. */
184
+ toEdgeArray() {
185
+ const result = [];
186
+ for (const [source, target] of this.edges()) {
187
+ result.push({ source, target });
188
+ }
189
+ return result;
190
+ }
191
+
192
+ /** Convert to graphology instance (for Louvain etc). */
193
+ toGraphology(opts = {}) {
194
+ const type = opts.type || (this._directed ? 'directed' : 'undirected');
195
+ const g = new Graph({ type });
196
+ for (const [id] of this._nodes) {
197
+ g.addNode(id);
198
+ }
199
+
200
+ for (const [src, tgt] of this.edges()) {
201
+ if (src === tgt) continue;
202
+ if (!g.hasEdge(src, tgt)) g.addEdge(src, tgt);
203
+ }
204
+ return g;
205
+ }
206
+
207
+ // ─── Utilities ──────────────────────────────────────────────────
208
+
209
+ clone() {
210
+ const g = new CodeGraph({ directed: this._directed });
211
+ for (const [id, attrs] of this._nodes) {
212
+ g.addNode(id, { ...attrs });
213
+ }
214
+ for (const [src, tgt, attrs] of this.edges()) {
215
+ g.addEdge(src, tgt, { ...attrs });
216
+ }
217
+ return g;
218
+ }
219
+
220
+ /** Merge another graph into this one. Nodes/edges from other override on conflict. */
221
+ merge(other) {
222
+ for (const [id, attrs] of other.nodes()) {
223
+ this.addNode(id, attrs);
224
+ }
225
+ for (const [src, tgt, attrs] of other.edges()) {
226
+ this.addEdge(src, tgt, attrs);
227
+ }
228
+ return this;
229
+ }
230
+ }
package/src/index.js CHANGED
@@ -1,237 +1,66 @@
1
1
  /**
2
2
  * codegraph — Programmatic API
3
3
  *
4
+ * Curated public surface: *Data() query functions, graph building,
5
+ * export formats, and essential constants. CLI formatters and internal
6
+ * utilities are not exported — import them directly if needed.
7
+ *
4
8
  * Usage:
5
- * import { buildGraph, queryNameData, findCycles, exportDOT } from 'codegraph';
9
+ * import { buildGraph, queryNameData, findCycles, exportDOT } from '@optave/codegraph';
6
10
  */
7
11
 
8
- // AST node queries
9
- export { AST_NODE_KINDS, astQuery, astQueryData } from './ast.js';
10
- // Audit (composite report)
12
+ export { astQueryData } from './ast.js';
11
13
  export { auditData } from './audit.js';
12
- // Batch querying
13
- export {
14
- BATCH_COMMANDS,
15
- batchData,
16
- multiBatchData,
17
- splitTargets,
18
- } from './batch.js';
19
- // Architecture boundary rules
20
- export { evaluateBoundaries, PRESETS, validateBoundaryConfig } from './boundaries.js';
21
- // Branch comparison
22
- export { branchCompareData, branchCompareMermaid } from './branch-compare.js';
23
- // Graph building
24
- export { buildGraph, collectFiles, loadPathAliases, resolveImportPath } from './builder.js';
25
- // Control flow graph (intraprocedural)
26
- export {
27
- buildCFGData,
28
- buildFunctionCFG,
29
- CFG_RULES,
30
- cfgData,
31
- cfgToDOT,
32
- cfgToMermaid,
33
- } from './cfg.js';
34
- // Check (CI validation predicates)
14
+ export { batchData } from './batch.js';
15
+ export { branchCompareData } from './branch-compare.js';
16
+ export { buildGraph } from './builder.js';
17
+ export { cfgData } from './cfg.js';
35
18
  export { checkData } from './check.js';
36
- // Co-change analysis
37
- export {
38
- analyzeCoChanges,
39
- coChangeData,
40
- coChangeForFiles,
41
- coChangeTopData,
42
- computeCoChanges,
43
- scanGitHistory,
44
- } from './cochange.js';
45
- export { audit } from './commands/audit.js';
46
- export { batch, batchQuery } from './commands/batch.js';
47
- export { cfg } from './commands/cfg.js';
48
- export { check } from './commands/check.js';
49
- export { communities } from './commands/communities.js';
50
- export { complexity } from './commands/complexity.js';
51
- export { dataflow } from './commands/dataflow.js';
52
- export { manifesto } from './commands/manifesto.js';
53
- export { owners } from './commands/owners.js';
54
- export { sequence } from './commands/sequence.js';
55
- export { formatHotspots, formatModuleBoundaries, formatStructure } from './commands/structure.js';
56
- export { triage } from './commands/triage.js';
57
- // Community detection
58
- export { communitiesData, communitySummaryForStats } from './communities.js';
59
- // Complexity metrics
60
- export {
61
- COMPLEXITY_RULES,
62
- complexityData,
63
- computeFunctionComplexity,
64
- computeHalsteadMetrics,
65
- computeLOCMetrics,
66
- computeMaintainabilityIndex,
67
- findFunctionNode,
68
- HALSTEAD_RULES,
69
- iterComplexity,
70
- } from './complexity.js';
71
- // Configuration
19
+ export { coChangeData } from './cochange.js';
20
+ export { communitiesData } from './communities.js';
21
+ export { complexityData } from './complexity.js';
72
22
  export { loadConfig } from './config.js';
73
- // Shared constants
74
- export { EXTENSIONS, IGNORE_DIRS, normalizePath } from './constants.js';
75
- // Circular dependency detection
76
- export { findCycles, formatCycles } from './cycles.js';
77
- // Dataflow analysis
78
- export {
79
- buildDataflowEdges,
80
- dataflowData,
81
- dataflowImpactData,
82
- dataflowPathData,
83
- extractDataflow,
84
- } from './dataflow.js';
85
- // Database utilities
86
- export {
87
- countEdges,
88
- countFiles,
89
- countNodes,
90
- fanInJoinSQL,
91
- fanOutJoinSQL,
92
- findDbPath,
93
- findNodesForTriage,
94
- findNodesWithFanIn,
95
- getBuildMeta,
96
- initSchema,
97
- iterateFunctionNodes,
98
- kindInClause,
99
- listFunctionNodes,
100
- NodeQuery,
101
- openDb,
102
- openReadonlyOrFail,
103
- setBuildMeta,
104
- testFilterSQL,
105
- } from './db.js';
106
- // Embeddings
23
+ export { EXTENSIONS, IGNORE_DIRS } from './constants.js';
24
+ export { findCycles } from './cycles.js';
25
+ export { dataflowData } from './dataflow.js';
107
26
  export {
108
27
  buildEmbeddings,
109
- cosineSim,
110
- DEFAULT_MODEL,
111
- disposeModel,
112
- EMBEDDING_STRATEGIES,
113
- embed,
114
- estimateTokens,
115
- ftsSearchData,
116
28
  hybridSearchData,
117
- MODELS,
118
29
  multiSearchData,
119
- search,
120
30
  searchData,
121
- } from './embedder.js';
122
- // Export (DOT/Mermaid/JSON/GraphML/GraphSON/Neo4j CSV)
31
+ } from './embeddings/index.js';
123
32
  export {
124
- exportDOT,
125
- exportGraphML,
126
- exportGraphSON,
127
- exportJSON,
128
- exportMermaid,
129
- exportNeo4jCSV,
130
- } from './export.js';
131
- // Execution flow tracing
132
- export { entryPointType, flowData, listEntryPointsData } from './flow.js';
133
- // Result formatting
134
- export { outputResult } from './infrastructure/result-formatter.js';
135
- // Test file detection
136
- export { isTestFile, TEST_PATTERN } from './infrastructure/test-filter.js';
137
- // Logger
138
- export { setVerbose } from './logger.js';
139
- // Manifesto rule engine
140
- export { manifestoData, RULE_DEFS } from './manifesto.js';
141
- // Native engine
142
- export { isNativeAvailable } from './native.js';
143
- // Ownership (CODEOWNERS)
144
- export { matchOwners, ownersData, ownersForFiles, parseCodeowners } from './owners.js';
145
- // Pagination utilities
146
- export { MCP_DEFAULTS, MCP_MAX_LIMIT, paginate, paginateResult, printNdjson } from './paginate.js';
147
- // Unified parser API
148
- export { getActiveEngine, isWasmAvailable, parseFileAuto, parseFilesAuto } from './parser.js';
149
- // Query functions (data-returning)
33
+ AnalysisError,
34
+ BoundaryError,
35
+ CodegraphError,
36
+ ConfigError,
37
+ DbError,
38
+ EngineError,
39
+ ParseError,
40
+ ResolutionError,
41
+ } from './errors.js';
42
+ export { exportDOT, exportJSON, exportMermaid } from './export.js';
43
+ export { flowData, listEntryPointsData } from './flow.js';
44
+ export { EVERY_EDGE_KIND, EVERY_SYMBOL_KIND } from './kinds.js';
45
+ export { manifestoData } from './manifesto.js';
46
+ export { ownersData } from './owners.js';
150
47
  export {
151
- ALL_SYMBOL_KINDS,
152
- CORE_EDGE_KINDS,
153
- CORE_SYMBOL_KINDS,
154
48
  childrenData,
155
49
  contextData,
156
50
  diffImpactData,
157
- diffImpactMermaid,
158
- EVERY_EDGE_KIND,
159
- EVERY_SYMBOL_KIND,
160
- EXTENDED_SYMBOL_KINDS,
161
51
  explainData,
162
52
  exportsData,
163
- FALSE_POSITIVE_CALLER_THRESHOLD,
164
- FALSE_POSITIVE_NAMES,
165
53
  fileDepsData,
166
54
  fnDepsData,
167
55
  fnImpactData,
168
56
  impactAnalysisData,
169
- iterListFunctions,
170
- iterRoles,
171
- iterWhere,
172
- kindIcon,
173
57
  moduleMapData,
174
- normalizeSymbol,
175
58
  pathData,
176
59
  queryNameData,
177
60
  rolesData,
178
- STRUCTURAL_EDGE_KINDS,
179
61
  statsData,
180
- VALID_ROLES,
181
62
  whereData,
182
63
  } from './queries.js';
183
- // Query CLI display wrappers
184
- export {
185
- children,
186
- context,
187
- diffImpact,
188
- explain,
189
- fileDeps,
190
- fileExports,
191
- fnDeps,
192
- fnImpact,
193
- impactAnalysis,
194
- moduleMap,
195
- queryName,
196
- roles,
197
- stats,
198
- symbolPath,
199
- where,
200
- } from './queries-cli.js';
201
- // Registry (multi-repo)
202
- export {
203
- listRepos,
204
- loadRegistry,
205
- pruneRegistry,
206
- REGISTRY_PATH,
207
- registerRepo,
208
- resolveRepoDbPath,
209
- saveRegistry,
210
- unregisterRepo,
211
- } from './registry.js';
212
- // Sequence diagram generation
213
- export { sequenceData, sequenceToMermaid } from './sequence.js';
214
- // Snapshot management
215
- export {
216
- snapshotDelete,
217
- snapshotList,
218
- snapshotRestore,
219
- snapshotSave,
220
- snapshotsDir,
221
- validateSnapshotName,
222
- } from './snapshot.js';
223
- // Structure analysis
224
- export {
225
- buildStructure,
226
- classifyNodeRoles,
227
- FRAMEWORK_ENTRY_PREFIXES,
228
- hotspotsData,
229
- moduleBoundariesData,
230
- structureData,
231
- } from './structure.js';
232
- // Triage — composite risk audit
64
+ export { sequenceData } from './sequence.js';
65
+ export { hotspotsData, moduleBoundariesData, structureData } from './structure.js';
233
66
  export { triageData } from './triage.js';
234
- // Interactive HTML viewer
235
- export { generatePlotHTML, loadPlotConfig } from './viewer.js';
236
- // Watch mode
237
- export { watchProject } from './watcher.js';