@optave/codegraph 3.1.0 → 3.1.2

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 (83) hide show
  1. package/README.md +5 -5
  2. package/grammars/tree-sitter-go.wasm +0 -0
  3. package/package.json +8 -9
  4. package/src/ast-analysis/engine.js +365 -0
  5. package/src/ast-analysis/metrics.js +118 -0
  6. package/src/ast-analysis/rules/csharp.js +201 -0
  7. package/src/ast-analysis/rules/go.js +182 -0
  8. package/src/ast-analysis/rules/index.js +82 -0
  9. package/src/ast-analysis/rules/java.js +175 -0
  10. package/src/ast-analysis/rules/javascript.js +246 -0
  11. package/src/ast-analysis/rules/php.js +219 -0
  12. package/src/ast-analysis/rules/python.js +196 -0
  13. package/src/ast-analysis/rules/ruby.js +204 -0
  14. package/src/ast-analysis/rules/rust.js +173 -0
  15. package/src/ast-analysis/shared.js +223 -0
  16. package/src/ast-analysis/visitor-utils.js +176 -0
  17. package/src/ast-analysis/visitor.js +162 -0
  18. package/src/ast-analysis/visitors/ast-store-visitor.js +150 -0
  19. package/src/ast-analysis/visitors/cfg-visitor.js +792 -0
  20. package/src/ast-analysis/visitors/complexity-visitor.js +243 -0
  21. package/src/ast-analysis/visitors/dataflow-visitor.js +358 -0
  22. package/src/ast.js +26 -166
  23. package/src/audit.js +2 -88
  24. package/src/batch.js +0 -25
  25. package/src/boundaries.js +1 -1
  26. package/src/branch-compare.js +82 -172
  27. package/src/builder.js +48 -184
  28. package/src/cfg.js +148 -1174
  29. package/src/check.js +1 -84
  30. package/src/cli.js +118 -197
  31. package/src/cochange.js +1 -39
  32. package/src/commands/audit.js +88 -0
  33. package/src/commands/batch.js +26 -0
  34. package/src/commands/branch-compare.js +97 -0
  35. package/src/commands/cfg.js +55 -0
  36. package/src/commands/check.js +82 -0
  37. package/src/commands/cochange.js +37 -0
  38. package/src/commands/communities.js +69 -0
  39. package/src/commands/complexity.js +77 -0
  40. package/src/commands/dataflow.js +110 -0
  41. package/src/commands/flow.js +70 -0
  42. package/src/commands/manifesto.js +77 -0
  43. package/src/commands/owners.js +52 -0
  44. package/src/commands/query.js +21 -0
  45. package/src/commands/sequence.js +33 -0
  46. package/src/commands/structure.js +64 -0
  47. package/src/commands/triage.js +49 -0
  48. package/src/communities.js +22 -96
  49. package/src/complexity.js +234 -1591
  50. package/src/cycles.js +1 -1
  51. package/src/dataflow.js +274 -1352
  52. package/src/db/connection.js +88 -0
  53. package/src/db/migrations.js +312 -0
  54. package/src/db/query-builder.js +280 -0
  55. package/src/db/repository/build-stmts.js +104 -0
  56. package/src/db/repository/cfg.js +83 -0
  57. package/src/db/repository/cochange.js +41 -0
  58. package/src/db/repository/complexity.js +15 -0
  59. package/src/db/repository/dataflow.js +12 -0
  60. package/src/db/repository/edges.js +259 -0
  61. package/src/db/repository/embeddings.js +40 -0
  62. package/src/db/repository/graph-read.js +39 -0
  63. package/src/db/repository/index.js +42 -0
  64. package/src/db/repository/nodes.js +236 -0
  65. package/src/db.js +58 -399
  66. package/src/embedder.js +158 -174
  67. package/src/export.js +1 -1
  68. package/src/extractors/javascript.js +130 -5
  69. package/src/flow.js +153 -222
  70. package/src/index.js +53 -16
  71. package/src/infrastructure/result-formatter.js +21 -0
  72. package/src/infrastructure/test-filter.js +7 -0
  73. package/src/kinds.js +50 -0
  74. package/src/manifesto.js +1 -82
  75. package/src/mcp.js +37 -20
  76. package/src/owners.js +127 -182
  77. package/src/queries-cli.js +866 -0
  78. package/src/queries.js +1271 -2416
  79. package/src/sequence.js +179 -223
  80. package/src/structure.js +211 -269
  81. package/src/triage.js +117 -212
  82. package/src/viewer.js +1 -1
  83. package/src/watcher.js +7 -4
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Ruby — AST analysis rules.
3
+ */
4
+
5
+ import { makeCfgRules, makeDataflowRules } from '../shared.js';
6
+
7
+ // ─── Complexity ───────────────────────────────────────────────────────────
8
+
9
+ export const complexity = {
10
+ branchNodes: new Set([
11
+ 'if',
12
+ 'elsif',
13
+ 'else',
14
+ 'unless',
15
+ 'case',
16
+ 'for',
17
+ 'while',
18
+ 'until',
19
+ 'rescue',
20
+ 'conditional',
21
+ ]),
22
+ caseNodes: new Set(['when']),
23
+ logicalOperators: new Set(['and', 'or', '&&', '||']),
24
+ logicalNodeType: 'binary',
25
+ optionalChainType: null,
26
+ nestingNodes: new Set(['if', 'unless', 'case', 'for', 'while', 'until', 'rescue', 'conditional']),
27
+ functionNodes: new Set(['method', 'singleton_method', 'lambda', 'do_block']),
28
+ ifNodeType: 'if',
29
+ elseNodeType: 'else',
30
+ elifNodeType: 'elsif',
31
+ elseViaAlternative: false,
32
+ switchLikeNodes: new Set(['case']),
33
+ };
34
+
35
+ // ─── Halstead ─────────────────────────────────────────────────────────────
36
+
37
+ export const halstead = {
38
+ operatorLeafTypes: new Set([
39
+ '+',
40
+ '-',
41
+ '*',
42
+ '/',
43
+ '%',
44
+ '**',
45
+ '=',
46
+ '+=',
47
+ '-=',
48
+ '*=',
49
+ '/=',
50
+ '%=',
51
+ '**=',
52
+ '&=',
53
+ '|=',
54
+ '^=',
55
+ '<<=',
56
+ '>>=',
57
+ '==',
58
+ '!=',
59
+ '<',
60
+ '>',
61
+ '<=',
62
+ '>=',
63
+ '<=>',
64
+ '===',
65
+ '=~',
66
+ '!~',
67
+ '&&',
68
+ '||',
69
+ '!',
70
+ 'and',
71
+ 'or',
72
+ 'not',
73
+ '&',
74
+ '|',
75
+ '^',
76
+ '~',
77
+ '<<',
78
+ '>>',
79
+ 'if',
80
+ 'else',
81
+ 'elsif',
82
+ 'unless',
83
+ 'case',
84
+ 'when',
85
+ 'for',
86
+ 'while',
87
+ 'until',
88
+ 'do',
89
+ 'begin',
90
+ 'end',
91
+ 'return',
92
+ 'raise',
93
+ 'break',
94
+ 'next',
95
+ 'redo',
96
+ 'retry',
97
+ 'rescue',
98
+ 'ensure',
99
+ 'yield',
100
+ 'def',
101
+ 'class',
102
+ 'module',
103
+ '.',
104
+ ',',
105
+ ':',
106
+ '::',
107
+ '=>',
108
+ '->',
109
+ ]),
110
+ operandLeafTypes: new Set([
111
+ 'identifier',
112
+ 'constant',
113
+ 'instance_variable',
114
+ 'class_variable',
115
+ 'global_variable',
116
+ 'integer',
117
+ 'float',
118
+ 'string_content',
119
+ 'symbol',
120
+ 'true',
121
+ 'false',
122
+ 'nil',
123
+ 'self',
124
+ ]),
125
+ compoundOperators: new Set(['call', 'element_reference']),
126
+ skipTypes: new Set([]),
127
+ };
128
+
129
+ // ─── CFG ──────────────────────────────────────────────────────────────────
130
+
131
+ export const cfg = makeCfgRules({
132
+ ifNode: 'if',
133
+ elifNode: 'elsif',
134
+ elseClause: 'else',
135
+ forNodes: new Set(['for']),
136
+ whileNode: 'while',
137
+ unlessNode: 'unless',
138
+ untilNode: 'until',
139
+ switchNode: 'case',
140
+ caseNode: 'when',
141
+ defaultNode: 'else',
142
+ tryNode: 'begin',
143
+ catchNode: 'rescue',
144
+ finallyNode: 'ensure',
145
+ returnNode: 'return',
146
+ breakNode: 'break',
147
+ continueNode: 'next',
148
+ blockNodes: new Set(['then', 'do', 'body_statement']),
149
+ functionNodes: new Set(['method', 'singleton_method']),
150
+ });
151
+
152
+ // ─── Dataflow ─────────────────────────────────────────────────────────────
153
+
154
+ export const dataflow = makeDataflowRules({
155
+ functionNodes: new Set(['method', 'singleton_method', 'lambda']),
156
+ paramListField: 'parameters',
157
+ returnNode: 'return',
158
+ varDeclaratorNode: null,
159
+ assignmentNode: 'assignment',
160
+ assignLeftField: 'left',
161
+ assignRightField: 'right',
162
+ callNode: 'call',
163
+ callFunctionField: 'method',
164
+ callArgsField: 'arguments',
165
+ spreadType: 'splat_parameter',
166
+ memberNode: 'call',
167
+ memberObjectField: 'receiver',
168
+ memberPropertyField: 'method',
169
+ mutatingMethods: new Set([
170
+ 'push',
171
+ 'pop',
172
+ 'shift',
173
+ 'unshift',
174
+ 'delete',
175
+ 'clear',
176
+ 'sort!',
177
+ 'reverse!',
178
+ 'map!',
179
+ 'select!',
180
+ 'reject!',
181
+ 'compact!',
182
+ 'flatten!',
183
+ 'concat',
184
+ 'replace',
185
+ 'insert',
186
+ ]),
187
+ extractParamName(node) {
188
+ if (node.type === 'identifier') return [node.text];
189
+ if (
190
+ node.type === 'optional_parameter' ||
191
+ node.type === 'keyword_parameter' ||
192
+ node.type === 'splat_parameter' ||
193
+ node.type === 'hash_splat_parameter'
194
+ ) {
195
+ const nameNode = node.childForFieldName('name');
196
+ return nameNode ? [nameNode.text] : null;
197
+ }
198
+ return null;
199
+ },
200
+ });
201
+
202
+ // ─── AST Node Types ───────────────────────────────────────────────────────
203
+
204
+ export const astTypes = null;
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Rust — AST analysis rules.
3
+ */
4
+
5
+ import { makeCfgRules, makeDataflowRules } from '../shared.js';
6
+
7
+ // ─── Complexity ───────────────────────────────────────────────────────────
8
+
9
+ export const complexity = {
10
+ branchNodes: new Set([
11
+ 'if_expression',
12
+ 'else_clause',
13
+ 'for_expression',
14
+ 'while_expression',
15
+ 'loop_expression',
16
+ 'if_let_expression',
17
+ 'while_let_expression',
18
+ 'match_expression',
19
+ ]),
20
+ caseNodes: new Set(['match_arm']),
21
+ logicalOperators: new Set(['&&', '||']),
22
+ logicalNodeType: 'binary_expression',
23
+ optionalChainType: null,
24
+ nestingNodes: new Set([
25
+ 'if_expression',
26
+ 'for_expression',
27
+ 'while_expression',
28
+ 'loop_expression',
29
+ 'if_let_expression',
30
+ 'while_let_expression',
31
+ 'match_expression',
32
+ ]),
33
+ functionNodes: new Set(['function_item', 'closure_expression']),
34
+ ifNodeType: 'if_expression',
35
+ elseNodeType: 'else_clause',
36
+ elifNodeType: null,
37
+ elseViaAlternative: false,
38
+ switchLikeNodes: new Set(['match_expression']),
39
+ };
40
+
41
+ // ─── Halstead ─────────────────────────────────────────────────────────────
42
+
43
+ export const halstead = {
44
+ operatorLeafTypes: new Set([
45
+ '+',
46
+ '-',
47
+ '*',
48
+ '/',
49
+ '%',
50
+ '=',
51
+ '+=',
52
+ '-=',
53
+ '*=',
54
+ '/=',
55
+ '%=',
56
+ '&=',
57
+ '|=',
58
+ '^=',
59
+ '<<=',
60
+ '>>=',
61
+ '==',
62
+ '!=',
63
+ '<',
64
+ '>',
65
+ '<=',
66
+ '>=',
67
+ '&&',
68
+ '||',
69
+ '!',
70
+ '&',
71
+ '|',
72
+ '^',
73
+ '<<',
74
+ '>>',
75
+ 'if',
76
+ 'else',
77
+ 'for',
78
+ 'while',
79
+ 'loop',
80
+ 'match',
81
+ 'return',
82
+ 'break',
83
+ 'continue',
84
+ 'let',
85
+ 'mut',
86
+ 'ref',
87
+ 'as',
88
+ 'in',
89
+ 'move',
90
+ 'fn',
91
+ 'struct',
92
+ 'enum',
93
+ 'trait',
94
+ 'impl',
95
+ 'pub',
96
+ 'mod',
97
+ 'use',
98
+ '.',
99
+ ',',
100
+ ';',
101
+ ':',
102
+ '::',
103
+ '=>',
104
+ '->',
105
+ '?',
106
+ ]),
107
+ operandLeafTypes: new Set([
108
+ 'identifier',
109
+ 'field_identifier',
110
+ 'type_identifier',
111
+ 'integer_literal',
112
+ 'float_literal',
113
+ 'string_content',
114
+ 'char_literal',
115
+ 'true',
116
+ 'false',
117
+ 'self',
118
+ 'Self',
119
+ ]),
120
+ compoundOperators: new Set(['call_expression', 'index_expression', 'field_expression']),
121
+ skipTypes: new Set([]),
122
+ };
123
+
124
+ // ─── CFG ──────────────────────────────────────────────────────────────────
125
+
126
+ export const cfg = makeCfgRules({
127
+ ifNode: 'if_expression',
128
+ ifNodes: new Set(['if_let_expression']),
129
+ elseClause: 'else_clause',
130
+ forNodes: new Set(['for_expression']),
131
+ whileNode: 'while_expression',
132
+ whileNodes: new Set(['while_let_expression']),
133
+ infiniteLoopNode: 'loop_expression',
134
+ switchNode: 'match_expression',
135
+ caseNode: 'match_arm',
136
+ returnNode: 'return_expression',
137
+ breakNode: 'break_expression',
138
+ continueNode: 'continue_expression',
139
+ blockNode: 'block',
140
+ functionNodes: new Set(['function_item', 'closure_expression']),
141
+ });
142
+
143
+ // ─── Dataflow ─────────────────────────────────────────────────────────────
144
+
145
+ export const dataflow = makeDataflowRules({
146
+ functionNodes: new Set(['function_item', 'closure_expression']),
147
+ returnNode: 'return_expression',
148
+ varDeclaratorNode: 'let_declaration',
149
+ varNameField: 'pattern',
150
+ varValueField: 'value',
151
+ assignmentNode: 'assignment_expression',
152
+ callNode: 'call_expression',
153
+ callFunctionField: 'function',
154
+ callArgsField: 'arguments',
155
+ memberNode: 'field_expression',
156
+ memberObjectField: 'value',
157
+ memberPropertyField: 'field',
158
+ awaitNode: 'await_expression',
159
+ mutatingMethods: new Set(['push', 'pop', 'insert', 'remove', 'clear', 'sort', 'reverse']),
160
+ extractParamName(node) {
161
+ if (node.type === 'parameter') {
162
+ const pat = node.childForFieldName('pattern');
163
+ if (pat?.type === 'identifier') return [pat.text];
164
+ return null;
165
+ }
166
+ if (node.type === 'identifier') return [node.text];
167
+ return null;
168
+ },
169
+ });
170
+
171
+ // ─── AST Node Types ───────────────────────────────────────────────────────
172
+
173
+ export const astTypes = null;
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Shared utilities for AST analysis modules (complexity, CFG, dataflow, AST nodes).
3
+ */
4
+
5
+ import { LANGUAGE_REGISTRY } from '../parser.js';
6
+
7
+ // ─── Generic Rule Factory ─────────────────────────────────────────────────
8
+
9
+ /**
10
+ * Merge defaults with overrides, validating that all keys are known.
11
+ *
12
+ * @param {object} defaults - Default rule values (defines the valid key set)
13
+ * @param {object} overrides - Language-specific overrides
14
+ * @param {string} label - Label for error messages (e.g. "CFG", "Dataflow")
15
+ * @returns {object} Merged rules
16
+ */
17
+ export function makeRules(defaults, overrides, label) {
18
+ const validKeys = new Set(Object.keys(defaults));
19
+ for (const key of Object.keys(overrides)) {
20
+ if (!validKeys.has(key)) {
21
+ throw new Error(`${label} rules: unknown key "${key}"`);
22
+ }
23
+ }
24
+ return { ...defaults, ...overrides };
25
+ }
26
+
27
+ // ─── CFG Defaults + Factory ───────────────────────────────────────────────
28
+
29
+ export const CFG_DEFAULTS = {
30
+ ifNode: null,
31
+ ifNodes: null,
32
+ elifNode: null,
33
+ elseClause: null,
34
+ elseViaAlternative: false,
35
+ ifConsequentField: null,
36
+ forNodes: new Set(),
37
+ whileNode: null,
38
+ whileNodes: null,
39
+ doNode: null,
40
+ infiniteLoopNode: null,
41
+ unlessNode: null,
42
+ untilNode: null,
43
+ switchNode: null,
44
+ switchNodes: null,
45
+ caseNode: null,
46
+ caseNodes: null,
47
+ defaultNode: null,
48
+ tryNode: null,
49
+ catchNode: null,
50
+ finallyNode: null,
51
+ returnNode: null,
52
+ throwNode: null,
53
+ breakNode: null,
54
+ continueNode: null,
55
+ blockNode: null,
56
+ blockNodes: null,
57
+ labeledNode: null,
58
+ functionNodes: new Set(),
59
+ };
60
+
61
+ export function makeCfgRules(overrides) {
62
+ const rules = makeRules(CFG_DEFAULTS, overrides, 'CFG');
63
+ if (!(rules.functionNodes instanceof Set) || rules.functionNodes.size === 0) {
64
+ throw new Error('CFG rules: functionNodes must be a non-empty Set');
65
+ }
66
+ if (!(rules.forNodes instanceof Set)) {
67
+ throw new Error('CFG rules: forNodes must be a Set');
68
+ }
69
+ return rules;
70
+ }
71
+
72
+ // ─── Dataflow Defaults + Factory ──────────────────────────────────────────
73
+
74
+ export const DATAFLOW_DEFAULTS = {
75
+ // Scope entry
76
+ functionNodes: new Set(), // REQUIRED: non-empty
77
+
78
+ // Function name extraction
79
+ nameField: 'name',
80
+ varAssignedFnParent: null, // parent type for `const fn = ...` (JS only)
81
+ assignmentFnParent: null, // parent type for `x = function...` (JS only)
82
+ pairFnParent: null, // parent type for `{ key: function }` (JS only)
83
+
84
+ // Parameters
85
+ paramListField: 'parameters',
86
+ paramIdentifier: 'identifier',
87
+ paramWrapperTypes: new Set(),
88
+ defaultParamType: null,
89
+ restParamType: null,
90
+ objectDestructType: null,
91
+ arrayDestructType: null,
92
+ shorthandPropPattern: null,
93
+ pairPatternType: null,
94
+ extractParamName: null, // override: (node) => string[]
95
+
96
+ // Return
97
+ returnNode: null,
98
+
99
+ // Variable declarations
100
+ varDeclaratorNode: null,
101
+ varDeclaratorNodes: null,
102
+ varNameField: 'name',
103
+ varValueField: 'value',
104
+ assignmentNode: null,
105
+ assignLeftField: 'left',
106
+ assignRightField: 'right',
107
+
108
+ // Calls
109
+ callNode: null,
110
+ callNodes: null,
111
+ callFunctionField: 'function',
112
+ callArgsField: 'arguments',
113
+ spreadType: null,
114
+
115
+ // Member access
116
+ memberNode: null,
117
+ memberObjectField: 'object',
118
+ memberPropertyField: 'property',
119
+ optionalChainNode: null,
120
+
121
+ // Await
122
+ awaitNode: null,
123
+
124
+ // Mutation
125
+ mutatingMethods: new Set(),
126
+ expressionStmtNode: 'expression_statement',
127
+ callObjectField: null, // Java: combined call+member has [object] field on call node
128
+
129
+ // Structural wrappers
130
+ expressionListType: null, // Go: expression_list wraps LHS/RHS of short_var_declaration
131
+ equalsClauseType: null, // C#: equals_value_clause wraps variable initializer
132
+ argumentWrapperType: null, // PHP: individual args wrapped in 'argument' nodes
133
+ extraIdentifierTypes: null, // Set of additional identifier-like types (PHP: variable_name, name)
134
+ };
135
+
136
+ export function makeDataflowRules(overrides) {
137
+ const rules = makeRules(DATAFLOW_DEFAULTS, overrides, 'Dataflow');
138
+ if (!(rules.functionNodes instanceof Set) || rules.functionNodes.size === 0) {
139
+ throw new Error('Dataflow rules: functionNodes must be a non-empty Set');
140
+ }
141
+ return rules;
142
+ }
143
+
144
+ // ─── AST Helpers ──────────────────────────────────────────────────────────
145
+
146
+ /**
147
+ * Find the function body node in a parse tree that matches a given line range.
148
+ */
149
+ export function findFunctionNode(rootNode, startLine, _endLine, rules) {
150
+ // tree-sitter lines are 0-indexed
151
+ const targetStart = startLine - 1;
152
+
153
+ let best = null;
154
+
155
+ function search(node) {
156
+ const nodeStart = node.startPosition.row;
157
+ const nodeEnd = node.endPosition.row;
158
+
159
+ // Prune branches outside range
160
+ if (nodeEnd < targetStart || nodeStart > targetStart + 1) return;
161
+
162
+ if (rules.functionNodes.has(node.type) && nodeStart === targetStart) {
163
+ // Found a function node at the right position — pick it
164
+ if (!best || nodeEnd - nodeStart < best.endPosition.row - best.startPosition.row) {
165
+ best = node;
166
+ }
167
+ }
168
+
169
+ for (let i = 0; i < node.childCount; i++) {
170
+ search(node.child(i));
171
+ }
172
+ }
173
+
174
+ search(rootNode);
175
+ return best;
176
+ }
177
+
178
+ /**
179
+ * Truncate a string to a maximum length, appending an ellipsis if truncated.
180
+ *
181
+ * @param {string} str - Input string
182
+ * @param {number} [max=200] - Maximum length
183
+ * @returns {string}
184
+ */
185
+ export function truncate(str, max = 200) {
186
+ if (!str) return '';
187
+ return str.length > max ? `${str.slice(0, max)}…` : str;
188
+ }
189
+
190
+ // ─── Extension / Language Mapping ─────────────────────────────────────────
191
+
192
+ /**
193
+ * Build a Map from file extension → language ID using the parser registry.
194
+ *
195
+ * @param {Iterable} [registry=LANGUAGE_REGISTRY] - Language registry entries
196
+ * @returns {Map<string, string>}
197
+ */
198
+ export function buildExtToLangMap(registry = LANGUAGE_REGISTRY) {
199
+ const map = new Map();
200
+ for (const entry of registry) {
201
+ for (const ext of entry.extensions) {
202
+ map.set(ext, entry.id);
203
+ }
204
+ }
205
+ return map;
206
+ }
207
+
208
+ /**
209
+ * Build a Set of file extensions for languages that have entries in the given rules Map.
210
+ *
211
+ * @param {Map<string, any>} rulesMap - e.g. COMPLEXITY_RULES, CFG_RULES
212
+ * @param {Iterable} [registry=LANGUAGE_REGISTRY] - Language registry entries
213
+ * @returns {Set<string>}
214
+ */
215
+ export function buildExtensionSet(rulesMap, registry = LANGUAGE_REGISTRY) {
216
+ const extensions = new Set();
217
+ for (const entry of registry) {
218
+ if (rulesMap.has(entry.id)) {
219
+ for (const ext of entry.extensions) extensions.add(ext);
220
+ }
221
+ }
222
+ return extensions;
223
+ }