@optave/codegraph 3.1.5 → 3.2.0

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 (91) hide show
  1. package/README.md +3 -2
  2. package/package.json +7 -7
  3. package/src/ast-analysis/engine.js +252 -258
  4. package/src/ast-analysis/shared.js +0 -12
  5. package/src/ast-analysis/visitors/cfg-visitor.js +635 -649
  6. package/src/ast-analysis/visitors/complexity-visitor.js +135 -139
  7. package/src/ast-analysis/visitors/dataflow-visitor.js +230 -224
  8. package/src/cli/commands/ast.js +2 -1
  9. package/src/cli/commands/audit.js +2 -1
  10. package/src/cli/commands/batch.js +2 -1
  11. package/src/cli/commands/brief.js +12 -0
  12. package/src/cli/commands/cfg.js +2 -1
  13. package/src/cli/commands/check.js +20 -23
  14. package/src/cli/commands/children.js +6 -1
  15. package/src/cli/commands/complexity.js +2 -1
  16. package/src/cli/commands/context.js +6 -1
  17. package/src/cli/commands/dataflow.js +2 -1
  18. package/src/cli/commands/deps.js +8 -3
  19. package/src/cli/commands/flow.js +2 -1
  20. package/src/cli/commands/fn-impact.js +6 -1
  21. package/src/cli/commands/owners.js +4 -2
  22. package/src/cli/commands/query.js +6 -1
  23. package/src/cli/commands/roles.js +2 -1
  24. package/src/cli/commands/search.js +8 -2
  25. package/src/cli/commands/sequence.js +2 -1
  26. package/src/cli/commands/triage.js +38 -27
  27. package/src/db/connection.js +18 -12
  28. package/src/db/migrations.js +41 -64
  29. package/src/db/query-builder.js +60 -4
  30. package/src/db/repository/in-memory-repository.js +27 -16
  31. package/src/db/repository/nodes.js +8 -10
  32. package/src/domain/analysis/brief.js +155 -0
  33. package/src/domain/analysis/context.js +174 -190
  34. package/src/domain/analysis/dependencies.js +200 -146
  35. package/src/domain/analysis/exports.js +3 -2
  36. package/src/domain/analysis/impact.js +267 -152
  37. package/src/domain/analysis/module-map.js +247 -221
  38. package/src/domain/analysis/roles.js +8 -5
  39. package/src/domain/analysis/symbol-lookup.js +7 -5
  40. package/src/domain/graph/builder/helpers.js +1 -1
  41. package/src/domain/graph/builder/incremental.js +116 -90
  42. package/src/domain/graph/builder/pipeline.js +106 -80
  43. package/src/domain/graph/builder/stages/build-edges.js +318 -239
  44. package/src/domain/graph/builder/stages/detect-changes.js +198 -177
  45. package/src/domain/graph/builder/stages/insert-nodes.js +147 -139
  46. package/src/domain/graph/watcher.js +2 -2
  47. package/src/domain/parser.js +20 -11
  48. package/src/domain/queries.js +1 -0
  49. package/src/domain/search/search/filters.js +9 -5
  50. package/src/domain/search/search/keyword.js +12 -5
  51. package/src/domain/search/search/prepare.js +13 -5
  52. package/src/extractors/csharp.js +224 -207
  53. package/src/extractors/go.js +176 -172
  54. package/src/extractors/hcl.js +94 -78
  55. package/src/extractors/java.js +213 -207
  56. package/src/extractors/javascript.js +274 -304
  57. package/src/extractors/php.js +234 -221
  58. package/src/extractors/python.js +252 -250
  59. package/src/extractors/ruby.js +192 -185
  60. package/src/extractors/rust.js +182 -167
  61. package/src/features/ast.js +5 -3
  62. package/src/features/audit.js +4 -2
  63. package/src/features/boundaries.js +98 -83
  64. package/src/features/cfg.js +134 -143
  65. package/src/features/communities.js +68 -53
  66. package/src/features/complexity.js +143 -132
  67. package/src/features/dataflow.js +146 -149
  68. package/src/features/export.js +3 -3
  69. package/src/features/graph-enrichment.js +2 -2
  70. package/src/features/manifesto.js +9 -6
  71. package/src/features/owners.js +4 -3
  72. package/src/features/sequence.js +152 -141
  73. package/src/features/shared/find-nodes.js +31 -0
  74. package/src/features/structure.js +130 -99
  75. package/src/features/triage.js +83 -68
  76. package/src/graph/classifiers/risk.js +3 -2
  77. package/src/graph/classifiers/roles.js +6 -3
  78. package/src/index.js +1 -0
  79. package/src/mcp/server.js +65 -56
  80. package/src/mcp/tool-registry.js +13 -0
  81. package/src/mcp/tools/brief.js +8 -0
  82. package/src/mcp/tools/index.js +2 -0
  83. package/src/presentation/brief.js +51 -0
  84. package/src/presentation/queries-cli/exports.js +21 -14
  85. package/src/presentation/queries-cli/impact.js +55 -39
  86. package/src/presentation/queries-cli/inspect.js +184 -189
  87. package/src/presentation/queries-cli/overview.js +57 -58
  88. package/src/presentation/queries-cli/path.js +36 -29
  89. package/src/presentation/table.js +0 -8
  90. package/src/shared/generators.js +7 -3
  91. package/src/shared/kinds.js +1 -1
@@ -4,211 +4,218 @@ import { findChild, nodeEndLine } from './helpers.js';
4
4
  * Extract symbols from Ruby files.
5
5
  */
6
6
  export function extractRubySymbols(tree, _filePath) {
7
- const definitions = [];
8
- const calls = [];
9
- const imports = [];
10
- const classes = [];
11
- const exports = [];
7
+ const ctx = {
8
+ definitions: [],
9
+ calls: [],
10
+ imports: [],
11
+ classes: [],
12
+ exports: [],
13
+ };
12
14
 
13
- function findRubyParentClass(node) {
14
- let current = node.parent;
15
- while (current) {
16
- if (current.type === 'class') {
17
- const nameNode = current.childForFieldName('name');
18
- return nameNode ? nameNode.text : null;
19
- }
20
- if (current.type === 'module') {
21
- const nameNode = current.childForFieldName('name');
22
- return nameNode ? nameNode.text : null;
23
- }
24
- current = current.parent;
25
- }
26
- return null;
15
+ walkRubyNode(tree.rootNode, ctx);
16
+ return ctx;
17
+ }
18
+
19
+ function walkRubyNode(node, ctx) {
20
+ switch (node.type) {
21
+ case 'class':
22
+ handleRubyClass(node, ctx);
23
+ break;
24
+ case 'module':
25
+ handleRubyModule(node, ctx);
26
+ break;
27
+ case 'method':
28
+ handleRubyMethod(node, ctx);
29
+ break;
30
+ case 'singleton_method':
31
+ handleRubySingletonMethod(node, ctx);
32
+ break;
33
+ case 'assignment':
34
+ handleRubyAssignment(node, ctx);
35
+ break;
36
+ case 'call':
37
+ handleRubyCall(node, ctx);
38
+ break;
27
39
  }
28
40
 
29
- function walkRubyNode(node) {
30
- switch (node.type) {
31
- case 'class': {
32
- const nameNode = node.childForFieldName('name');
33
- if (nameNode) {
34
- const classChildren = extractRubyClassChildren(node);
35
- definitions.push({
36
- name: nameNode.text,
37
- kind: 'class',
38
- line: node.startPosition.row + 1,
39
- endLine: nodeEndLine(node),
40
- children: classChildren.length > 0 ? classChildren : undefined,
41
- });
42
- const superclass = node.childForFieldName('superclass');
43
- if (superclass) {
44
- // superclass wraps the < token and class name
45
- for (let i = 0; i < superclass.childCount; i++) {
46
- const child = superclass.child(i);
47
- if (child && (child.type === 'constant' || child.type === 'scope_resolution')) {
48
- classes.push({
49
- name: nameNode.text,
50
- extends: child.text,
51
- line: node.startPosition.row + 1,
52
- });
53
- break;
54
- }
55
- }
56
- // Direct superclass node may be a constant
57
- if (superclass.type === 'superclass') {
58
- for (let i = 0; i < superclass.childCount; i++) {
59
- const child = superclass.child(i);
60
- if (child && (child.type === 'constant' || child.type === 'scope_resolution')) {
61
- classes.push({
62
- name: nameNode.text,
63
- extends: child.text,
64
- line: node.startPosition.row + 1,
65
- });
66
- break;
67
- }
68
- }
69
- }
70
- }
71
- }
41
+ for (let i = 0; i < node.childCount; i++) walkRubyNode(node.child(i), ctx);
42
+ }
43
+
44
+ // ── Walk-path per-node-type handlers ────────────────────────────────────────
45
+
46
+ function handleRubyClass(node, ctx) {
47
+ const nameNode = node.childForFieldName('name');
48
+ if (!nameNode) return;
49
+ const classChildren = extractRubyClassChildren(node);
50
+ ctx.definitions.push({
51
+ name: nameNode.text,
52
+ kind: 'class',
53
+ line: node.startPosition.row + 1,
54
+ endLine: nodeEndLine(node),
55
+ children: classChildren.length > 0 ? classChildren : undefined,
56
+ });
57
+ const superclass = node.childForFieldName('superclass');
58
+ if (superclass) {
59
+ for (let i = 0; i < superclass.childCount; i++) {
60
+ const child = superclass.child(i);
61
+ if (child && (child.type === 'constant' || child.type === 'scope_resolution')) {
62
+ ctx.classes.push({
63
+ name: nameNode.text,
64
+ extends: child.text,
65
+ line: node.startPosition.row + 1,
66
+ });
72
67
  break;
73
68
  }
74
-
75
- case 'module': {
76
- const nameNode = node.childForFieldName('name');
77
- if (nameNode) {
78
- const moduleChildren = extractRubyBodyConstants(node);
79
- definitions.push({
69
+ }
70
+ if (superclass.type === 'superclass') {
71
+ for (let i = 0; i < superclass.childCount; i++) {
72
+ const child = superclass.child(i);
73
+ if (child && (child.type === 'constant' || child.type === 'scope_resolution')) {
74
+ ctx.classes.push({
80
75
  name: nameNode.text,
81
- kind: 'module',
76
+ extends: child.text,
82
77
  line: node.startPosition.row + 1,
83
- endLine: nodeEndLine(node),
84
- children: moduleChildren.length > 0 ? moduleChildren : undefined,
85
78
  });
79
+ break;
86
80
  }
87
- break;
88
81
  }
82
+ }
83
+ }
84
+ }
89
85
 
90
- case 'method': {
91
- const nameNode = node.childForFieldName('name');
92
- if (nameNode) {
93
- const parentClass = findRubyParentClass(node);
94
- const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
95
- const params = extractRubyParameters(node);
96
- definitions.push({
97
- name: fullName,
98
- kind: 'method',
99
- line: node.startPosition.row + 1,
100
- endLine: nodeEndLine(node),
101
- children: params.length > 0 ? params : undefined,
102
- });
103
- }
104
- break;
105
- }
86
+ function handleRubyModule(node, ctx) {
87
+ const nameNode = node.childForFieldName('name');
88
+ if (!nameNode) return;
89
+ const moduleChildren = extractRubyBodyConstants(node);
90
+ ctx.definitions.push({
91
+ name: nameNode.text,
92
+ kind: 'module',
93
+ line: node.startPosition.row + 1,
94
+ endLine: nodeEndLine(node),
95
+ children: moduleChildren.length > 0 ? moduleChildren : undefined,
96
+ });
97
+ }
106
98
 
107
- case 'singleton_method': {
108
- const nameNode = node.childForFieldName('name');
109
- if (nameNode) {
110
- const parentClass = findRubyParentClass(node);
111
- const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
112
- const params = extractRubyParameters(node);
113
- definitions.push({
114
- name: fullName,
115
- kind: 'function',
116
- line: node.startPosition.row + 1,
117
- endLine: nodeEndLine(node),
118
- children: params.length > 0 ? params : undefined,
119
- });
120
- }
121
- break;
122
- }
99
+ function handleRubyMethod(node, ctx) {
100
+ const nameNode = node.childForFieldName('name');
101
+ if (!nameNode) return;
102
+ const parentClass = findRubyParentClass(node);
103
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
104
+ const params = extractRubyParameters(node);
105
+ ctx.definitions.push({
106
+ name: fullName,
107
+ kind: 'method',
108
+ line: node.startPosition.row + 1,
109
+ endLine: nodeEndLine(node),
110
+ children: params.length > 0 ? params : undefined,
111
+ });
112
+ }
123
113
 
124
- case 'assignment': {
125
- // Top-level constant assignments (parent is program)
126
- if (node.parent && node.parent.type === 'program') {
127
- const left = node.childForFieldName('left');
128
- if (left && left.type === 'constant') {
129
- definitions.push({
130
- name: left.text,
131
- kind: 'constant',
132
- line: node.startPosition.row + 1,
133
- endLine: nodeEndLine(node),
134
- });
135
- }
136
- }
137
- break;
138
- }
114
+ function handleRubySingletonMethod(node, ctx) {
115
+ const nameNode = node.childForFieldName('name');
116
+ if (!nameNode) return;
117
+ const parentClass = findRubyParentClass(node);
118
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
119
+ const params = extractRubyParameters(node);
120
+ ctx.definitions.push({
121
+ name: fullName,
122
+ kind: 'function',
123
+ line: node.startPosition.row + 1,
124
+ endLine: nodeEndLine(node),
125
+ children: params.length > 0 ? params : undefined,
126
+ });
127
+ }
139
128
 
140
- case 'call': {
141
- const methodNode = node.childForFieldName('method');
142
- if (methodNode) {
143
- // Check for require/require_relative
144
- if (methodNode.text === 'require' || methodNode.text === 'require_relative') {
145
- const args = node.childForFieldName('arguments');
146
- if (args) {
147
- for (let i = 0; i < args.childCount; i++) {
148
- const arg = args.child(i);
149
- if (arg && (arg.type === 'string' || arg.type === 'string_content')) {
150
- const strContent = arg.text.replace(/^['"]|['"]$/g, '');
151
- imports.push({
152
- source: strContent,
153
- names: [strContent.split('/').pop()],
154
- line: node.startPosition.row + 1,
155
- rubyRequire: true,
156
- });
157
- break;
158
- }
159
- // Look inside string for string_content
160
- if (arg && arg.type === 'string') {
161
- const content = findChild(arg, 'string_content');
162
- if (content) {
163
- imports.push({
164
- source: content.text,
165
- names: [content.text.split('/').pop()],
166
- line: node.startPosition.row + 1,
167
- rubyRequire: true,
168
- });
169
- break;
170
- }
171
- }
172
- }
173
- }
174
- } else if (
175
- methodNode.text === 'include' ||
176
- methodNode.text === 'extend' ||
177
- methodNode.text === 'prepend'
178
- ) {
179
- // Module inclusion treated like implements
180
- const parentClass = findRubyParentClass(node);
181
- if (parentClass) {
182
- const args = node.childForFieldName('arguments');
183
- if (args) {
184
- for (let i = 0; i < args.childCount; i++) {
185
- const arg = args.child(i);
186
- if (arg && (arg.type === 'constant' || arg.type === 'scope_resolution')) {
187
- classes.push({
188
- name: parentClass,
189
- implements: arg.text,
190
- line: node.startPosition.row + 1,
191
- });
192
- }
193
- }
194
- }
195
- }
196
- } else {
197
- const recv = node.childForFieldName('receiver');
198
- const call = { name: methodNode.text, line: node.startPosition.row + 1 };
199
- if (recv) call.receiver = recv.text;
200
- calls.push(call);
201
- }
202
- }
129
+ function handleRubyAssignment(node, ctx) {
130
+ if (node.parent && node.parent.type === 'program') {
131
+ const left = node.childForFieldName('left');
132
+ if (left && left.type === 'constant') {
133
+ ctx.definitions.push({
134
+ name: left.text,
135
+ kind: 'constant',
136
+ line: node.startPosition.row + 1,
137
+ endLine: nodeEndLine(node),
138
+ });
139
+ }
140
+ }
141
+ }
142
+
143
+ function handleRubyCall(node, ctx) {
144
+ const methodNode = node.childForFieldName('method');
145
+ if (!methodNode) return;
146
+ if (methodNode.text === 'require' || methodNode.text === 'require_relative') {
147
+ handleRubyRequire(node, ctx);
148
+ } else if (
149
+ methodNode.text === 'include' ||
150
+ methodNode.text === 'extend' ||
151
+ methodNode.text === 'prepend'
152
+ ) {
153
+ handleRubyModuleInclusion(node, methodNode, ctx);
154
+ } else {
155
+ const recv = node.childForFieldName('receiver');
156
+ const call = { name: methodNode.text, line: node.startPosition.row + 1 };
157
+ if (recv) call.receiver = recv.text;
158
+ ctx.calls.push(call);
159
+ }
160
+ }
161
+
162
+ function handleRubyRequire(node, ctx) {
163
+ const args = node.childForFieldName('arguments');
164
+ if (!args) return;
165
+ for (let i = 0; i < args.childCount; i++) {
166
+ const arg = args.child(i);
167
+ if (arg && (arg.type === 'string' || arg.type === 'string_content')) {
168
+ const strContent = arg.text.replace(/^['"]|['"]$/g, '');
169
+ ctx.imports.push({
170
+ source: strContent,
171
+ names: [strContent.split('/').pop()],
172
+ line: node.startPosition.row + 1,
173
+ rubyRequire: true,
174
+ });
175
+ break;
176
+ }
177
+ if (arg && arg.type === 'string') {
178
+ const content = findChild(arg, 'string_content');
179
+ if (content) {
180
+ ctx.imports.push({
181
+ source: content.text,
182
+ names: [content.text.split('/').pop()],
183
+ line: node.startPosition.row + 1,
184
+ rubyRequire: true,
185
+ });
203
186
  break;
204
187
  }
205
188
  }
189
+ }
190
+ }
206
191
 
207
- for (let i = 0; i < node.childCount; i++) walkRubyNode(node.child(i));
192
+ function handleRubyModuleInclusion(node, _methodNode, ctx) {
193
+ const parentClass = findRubyParentClass(node);
194
+ if (!parentClass) return;
195
+ const args = node.childForFieldName('arguments');
196
+ if (!args) return;
197
+ for (let i = 0; i < args.childCount; i++) {
198
+ const arg = args.child(i);
199
+ if (arg && (arg.type === 'constant' || arg.type === 'scope_resolution')) {
200
+ ctx.classes.push({
201
+ name: parentClass,
202
+ implements: arg.text,
203
+ line: node.startPosition.row + 1,
204
+ });
205
+ }
208
206
  }
207
+ }
209
208
 
210
- walkRubyNode(tree.rootNode);
211
- return { definitions, calls, imports, classes, exports };
209
+ function findRubyParentClass(node) {
210
+ let current = node.parent;
211
+ while (current) {
212
+ if (current.type === 'class' || current.type === 'module') {
213
+ const nameNode = current.childForFieldName('name');
214
+ return nameNode ? nameNode.text : null;
215
+ }
216
+ current = current.parent;
217
+ }
218
+ return null;
212
219
  }
213
220
 
214
221
  // ── Child extraction helpers ────────────────────────────────────────────────