@optave/codegraph 3.1.4 → 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 (210) hide show
  1. package/README.md +29 -72
  2. package/package.json +10 -8
  3. package/src/ast-analysis/engine.js +260 -246
  4. package/src/ast-analysis/shared.js +2 -14
  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 +4 -7
  9. package/src/cli/commands/audit.js +11 -11
  10. package/src/cli/commands/batch.js +6 -5
  11. package/src/cli/commands/branch-compare.js +1 -1
  12. package/src/cli/commands/brief.js +12 -0
  13. package/src/cli/commands/build.js +1 -1
  14. package/src/cli/commands/cfg.js +5 -8
  15. package/src/cli/commands/check.js +28 -36
  16. package/src/cli/commands/children.js +9 -7
  17. package/src/cli/commands/co-change.js +5 -3
  18. package/src/cli/commands/communities.js +2 -6
  19. package/src/cli/commands/complexity.js +5 -3
  20. package/src/cli/commands/context.js +9 -8
  21. package/src/cli/commands/cycles.js +12 -8
  22. package/src/cli/commands/dataflow.js +5 -8
  23. package/src/cli/commands/deps.js +9 -8
  24. package/src/cli/commands/diff-impact.js +2 -6
  25. package/src/cli/commands/embed.js +1 -1
  26. package/src/cli/commands/export.js +34 -31
  27. package/src/cli/commands/exports.js +2 -6
  28. package/src/cli/commands/flow.js +5 -8
  29. package/src/cli/commands/fn-impact.js +9 -8
  30. package/src/cli/commands/impact.js +2 -6
  31. package/src/cli/commands/info.js +2 -2
  32. package/src/cli/commands/map.js +1 -1
  33. package/src/cli/commands/mcp.js +1 -1
  34. package/src/cli/commands/models.js +1 -1
  35. package/src/cli/commands/owners.js +5 -3
  36. package/src/cli/commands/path.js +2 -2
  37. package/src/cli/commands/plot.js +40 -31
  38. package/src/cli/commands/query.js +9 -8
  39. package/src/cli/commands/registry.js +2 -2
  40. package/src/cli/commands/roles.js +5 -8
  41. package/src/cli/commands/search.js +9 -3
  42. package/src/cli/commands/sequence.js +5 -8
  43. package/src/cli/commands/snapshot.js +6 -1
  44. package/src/cli/commands/stats.js +1 -1
  45. package/src/cli/commands/structure.js +5 -4
  46. package/src/cli/commands/triage.js +41 -30
  47. package/src/cli/commands/watch.js +1 -1
  48. package/src/cli/commands/where.js +2 -6
  49. package/src/cli/index.js +11 -5
  50. package/src/cli/shared/open-graph.js +13 -0
  51. package/src/cli/shared/options.js +22 -2
  52. package/src/cli.js +1 -1
  53. package/src/db/connection.js +140 -11
  54. package/src/{db.js → db/index.js} +12 -5
  55. package/src/db/migrations.js +42 -65
  56. package/src/db/query-builder.js +72 -9
  57. package/src/db/repository/base.js +1 -1
  58. package/src/db/repository/graph-read.js +3 -3
  59. package/src/db/repository/in-memory-repository.js +30 -28
  60. package/src/db/repository/nodes.js +10 -17
  61. package/src/domain/analysis/brief.js +155 -0
  62. package/src/domain/analysis/context.js +392 -0
  63. package/src/domain/analysis/dependencies.js +395 -0
  64. package/src/{analysis → domain/analysis}/exports.js +11 -6
  65. package/src/domain/analysis/impact.js +581 -0
  66. package/src/domain/analysis/module-map.js +348 -0
  67. package/src/{analysis → domain/analysis}/roles.js +12 -9
  68. package/src/{analysis → domain/analysis}/symbol-lookup.js +19 -11
  69. package/src/{builder → domain/graph/builder}/helpers.js +4 -4
  70. package/src/{builder → domain/graph/builder}/incremental.js +119 -93
  71. package/src/domain/graph/builder/pipeline.js +156 -0
  72. package/src/domain/graph/builder/stages/build-edges.js +376 -0
  73. package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
  74. package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
  75. package/src/{builder → domain/graph/builder}/stages/detect-changes.js +204 -183
  76. package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
  77. package/src/domain/graph/builder/stages/insert-nodes.js +203 -0
  78. package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
  79. package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
  80. package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
  81. package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
  82. package/src/{cycles.js → domain/graph/cycles.js} +4 -4
  83. package/src/{journal.js → domain/graph/journal.js} +1 -1
  84. package/src/{resolve.js → domain/graph/resolve.js} +2 -2
  85. package/src/{watcher.js → domain/graph/watcher.js} +7 -7
  86. package/src/{parser.js → domain/parser.js} +24 -15
  87. package/src/{queries.js → domain/queries.js} +17 -16
  88. package/src/{embeddings → domain/search}/generator.js +3 -3
  89. package/src/{embeddings → domain/search}/models.js +2 -2
  90. package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
  91. package/src/{embeddings → domain/search}/search/filters.js +9 -5
  92. package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
  93. package/src/{embeddings → domain/search}/search/keyword.js +13 -6
  94. package/src/{embeddings → domain/search}/search/prepare.js +15 -7
  95. package/src/{embeddings → domain/search}/search/semantic.js +1 -1
  96. package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
  97. package/src/extractors/csharp.js +224 -207
  98. package/src/extractors/go.js +176 -172
  99. package/src/extractors/hcl.js +94 -78
  100. package/src/extractors/java.js +213 -207
  101. package/src/extractors/javascript.js +275 -305
  102. package/src/extractors/php.js +234 -221
  103. package/src/extractors/python.js +252 -250
  104. package/src/extractors/ruby.js +192 -185
  105. package/src/extractors/rust.js +182 -167
  106. package/src/{ast.js → features/ast.js} +13 -11
  107. package/src/{audit.js → features/audit.js} +20 -46
  108. package/src/{batch.js → features/batch.js} +5 -5
  109. package/src/{boundaries.js → features/boundaries.js} +100 -85
  110. package/src/{branch-compare.js → features/branch-compare.js} +3 -3
  111. package/src/{cfg.js → features/cfg.js} +141 -150
  112. package/src/{check.js → features/check.js} +13 -30
  113. package/src/{cochange.js → features/cochange.js} +5 -5
  114. package/src/{communities.js → features/communities.js} +72 -57
  115. package/src/{complexity.js → features/complexity.js} +154 -143
  116. package/src/{dataflow.js → features/dataflow.js} +155 -158
  117. package/src/{export.js → features/export.js} +6 -6
  118. package/src/{flow.js → features/flow.js} +4 -4
  119. package/src/{viewer.js → features/graph-enrichment.js} +8 -8
  120. package/src/{manifesto.js → features/manifesto.js} +15 -12
  121. package/src/{owners.js → features/owners.js} +6 -5
  122. package/src/features/sequence.js +300 -0
  123. package/src/features/shared/find-nodes.js +31 -0
  124. package/src/{snapshot.js → features/snapshot.js} +3 -3
  125. package/src/{structure.js → features/structure.js} +139 -108
  126. package/src/features/triage.js +141 -0
  127. package/src/graph/builders/dependency.js +33 -14
  128. package/src/graph/classifiers/risk.js +3 -2
  129. package/src/graph/classifiers/roles.js +6 -3
  130. package/src/index.cjs +16 -0
  131. package/src/index.js +40 -39
  132. package/src/{native.js → infrastructure/native.js} +1 -1
  133. package/src/mcp/middleware.js +1 -1
  134. package/src/mcp/server.js +68 -59
  135. package/src/mcp/tool-registry.js +15 -2
  136. package/src/mcp/tools/ast-query.js +1 -1
  137. package/src/mcp/tools/audit.js +1 -1
  138. package/src/mcp/tools/batch-query.js +1 -1
  139. package/src/mcp/tools/branch-compare.js +3 -1
  140. package/src/mcp/tools/brief.js +8 -0
  141. package/src/mcp/tools/cfg.js +1 -1
  142. package/src/mcp/tools/check.js +3 -3
  143. package/src/mcp/tools/co-changes.js +1 -1
  144. package/src/mcp/tools/code-owners.js +1 -1
  145. package/src/mcp/tools/communities.js +1 -1
  146. package/src/mcp/tools/complexity.js +1 -1
  147. package/src/mcp/tools/dataflow.js +2 -2
  148. package/src/mcp/tools/execution-flow.js +2 -2
  149. package/src/mcp/tools/export-graph.js +2 -2
  150. package/src/mcp/tools/find-cycles.js +2 -2
  151. package/src/mcp/tools/index.js +2 -0
  152. package/src/mcp/tools/list-repos.js +1 -1
  153. package/src/mcp/tools/sequence.js +1 -1
  154. package/src/mcp/tools/structure.js +1 -1
  155. package/src/mcp/tools/triage.js +2 -2
  156. package/src/{commands → presentation}/audit.js +2 -2
  157. package/src/{commands → presentation}/batch.js +1 -1
  158. package/src/{commands → presentation}/branch-compare.js +2 -2
  159. package/src/presentation/brief.js +51 -0
  160. package/src/{commands → presentation}/cfg.js +1 -1
  161. package/src/{commands → presentation}/check.js +2 -2
  162. package/src/{commands → presentation}/communities.js +1 -1
  163. package/src/{commands → presentation}/complexity.js +1 -1
  164. package/src/{commands → presentation}/dataflow.js +1 -1
  165. package/src/{commands → presentation}/flow.js +2 -2
  166. package/src/{commands → presentation}/manifesto.js +1 -1
  167. package/src/{commands → presentation}/owners.js +1 -1
  168. package/src/presentation/queries-cli/exports.js +53 -0
  169. package/src/presentation/queries-cli/impact.js +214 -0
  170. package/src/presentation/queries-cli/index.js +5 -0
  171. package/src/presentation/queries-cli/inspect.js +329 -0
  172. package/src/presentation/queries-cli/overview.js +196 -0
  173. package/src/presentation/queries-cli/path.js +65 -0
  174. package/src/presentation/queries-cli.js +27 -0
  175. package/src/{commands → presentation}/query.js +1 -1
  176. package/src/presentation/result-formatter.js +126 -3
  177. package/src/{commands → presentation}/sequence.js +2 -2
  178. package/src/{commands → presentation}/structure.js +1 -1
  179. package/src/presentation/table.js +0 -8
  180. package/src/{commands → presentation}/triage.js +1 -1
  181. package/src/{constants.js → shared/constants.js} +1 -1
  182. package/src/shared/file-utils.js +2 -2
  183. package/src/shared/generators.js +9 -5
  184. package/src/shared/hierarchy.js +1 -1
  185. package/src/{kinds.js → shared/kinds.js} +1 -1
  186. package/src/analysis/context.js +0 -408
  187. package/src/analysis/dependencies.js +0 -341
  188. package/src/analysis/impact.js +0 -463
  189. package/src/analysis/module-map.js +0 -322
  190. package/src/builder/pipeline.js +0 -130
  191. package/src/builder/stages/build-edges.js +0 -297
  192. package/src/builder/stages/insert-nodes.js +0 -195
  193. package/src/mcp.js +0 -2
  194. package/src/queries-cli.js +0 -866
  195. package/src/sequence.js +0 -289
  196. package/src/triage.js +0 -126
  197. /package/src/{builder → domain/graph/builder}/context.js +0 -0
  198. /package/src/{builder.js → domain/graph/builder.js} +0 -0
  199. /package/src/{embeddings → domain/search}/index.js +0 -0
  200. /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
  201. /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
  202. /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
  203. /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
  204. /package/src/{config.js → infrastructure/config.js} +0 -0
  205. /package/src/{logger.js → infrastructure/logger.js} +0 -0
  206. /package/src/{registry.js → infrastructure/registry.js} +0 -0
  207. /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
  208. /package/src/{commands → presentation}/cochange.js +0 -0
  209. /package/src/{errors.js → shared/errors.js} +0 -0
  210. /package/src/{paginate.js → shared/paginate.js} +0 -0
@@ -4,292 +4,294 @@ import { findChild, nodeEndLine, pythonVisibility } from './helpers.js';
4
4
  * Extract symbols from Python files.
5
5
  */
6
6
  export function extractPythonSymbols(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 walkPythonNode(node) {
14
- switch (node.type) {
15
- case 'function_definition': {
16
- const nameNode = node.childForFieldName('name');
17
- if (nameNode) {
18
- const decorators = [];
19
- if (node.previousSibling && node.previousSibling.type === 'decorator') {
20
- decorators.push(node.previousSibling.text);
21
- }
22
- const parentClass = findPythonParentClass(node);
23
- const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
24
- const kind = parentClass ? 'method' : 'function';
25
- const fnChildren = extractPythonParameters(node);
26
- definitions.push({
27
- name: fullName,
28
- kind,
29
- line: node.startPosition.row + 1,
30
- endLine: nodeEndLine(node),
31
- decorators,
32
- children: fnChildren.length > 0 ? fnChildren : undefined,
33
- visibility: pythonVisibility(nameNode.text),
34
- });
35
- }
36
- break;
37
- }
15
+ walkPythonNode(tree.rootNode, ctx);
16
+ return ctx;
17
+ }
38
18
 
39
- case 'class_definition': {
40
- const nameNode = node.childForFieldName('name');
41
- if (nameNode) {
42
- const clsChildren = extractPythonClassProperties(node);
43
- definitions.push({
44
- name: nameNode.text,
45
- kind: 'class',
46
- line: node.startPosition.row + 1,
47
- endLine: nodeEndLine(node),
48
- children: clsChildren.length > 0 ? clsChildren : undefined,
49
- });
50
- const superclasses =
51
- node.childForFieldName('superclasses') || findChild(node, 'argument_list');
52
- if (superclasses) {
53
- for (let i = 0; i < superclasses.childCount; i++) {
54
- const child = superclasses.child(i);
55
- if (child && child.type === 'identifier') {
56
- classes.push({
57
- name: nameNode.text,
58
- extends: child.text,
59
- line: node.startPosition.row + 1,
60
- });
61
- }
62
- }
63
- }
64
- }
65
- break;
66
- }
19
+ function walkPythonNode(node, ctx) {
20
+ switch (node.type) {
21
+ case 'function_definition':
22
+ handlePyFunctionDef(node, ctx);
23
+ break;
24
+ case 'class_definition':
25
+ handlePyClassDef(node, ctx);
26
+ break;
27
+ case 'decorated_definition':
28
+ for (let i = 0; i < node.childCount; i++) walkPythonNode(node.child(i), ctx);
29
+ return;
30
+ case 'call':
31
+ handlePyCall(node, ctx);
32
+ break;
33
+ case 'import_statement':
34
+ handlePyImport(node, ctx);
35
+ break;
36
+ case 'expression_statement':
37
+ handlePyExpressionStmt(node, ctx);
38
+ break;
39
+ case 'import_from_statement':
40
+ handlePyImportFrom(node, ctx);
41
+ break;
42
+ }
67
43
 
68
- case 'decorated_definition': {
69
- for (let i = 0; i < node.childCount; i++) walkPythonNode(node.child(i));
70
- return;
71
- }
44
+ for (let i = 0; i < node.childCount; i++) walkPythonNode(node.child(i), ctx);
45
+ }
72
46
 
73
- case 'call': {
74
- const fn = node.childForFieldName('function');
75
- if (fn) {
76
- let callName = null;
77
- let receiver;
78
- if (fn.type === 'identifier') callName = fn.text;
79
- else if (fn.type === 'attribute') {
80
- const attr = fn.childForFieldName('attribute');
81
- if (attr) callName = attr.text;
82
- const obj = fn.childForFieldName('object');
83
- if (obj) receiver = obj.text;
84
- }
85
- if (callName) {
86
- const call = { name: callName, line: node.startPosition.row + 1 };
87
- if (receiver) call.receiver = receiver;
88
- calls.push(call);
89
- }
90
- }
91
- break;
92
- }
47
+ // ── Walk-path per-node-type handlers ────────────────────────────────────────
93
48
 
94
- case 'import_statement': {
95
- const names = [];
96
- for (let i = 0; i < node.childCount; i++) {
97
- const child = node.child(i);
98
- if (child && (child.type === 'dotted_name' || child.type === 'aliased_import')) {
99
- const name =
100
- child.type === 'aliased_import'
101
- ? (child.childForFieldName('alias') || child.childForFieldName('name'))?.text
102
- : child.text;
103
- if (name) names.push(name);
104
- }
105
- }
106
- if (names.length > 0)
107
- imports.push({
108
- source: names[0],
109
- names,
110
- line: node.startPosition.row + 1,
111
- pythonImport: true,
112
- });
113
- break;
114
- }
49
+ function handlePyFunctionDef(node, ctx) {
50
+ const nameNode = node.childForFieldName('name');
51
+ if (!nameNode) return;
52
+ const decorators = [];
53
+ if (node.previousSibling && node.previousSibling.type === 'decorator') {
54
+ decorators.push(node.previousSibling.text);
55
+ }
56
+ const parentClass = findPythonParentClass(node);
57
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
58
+ const kind = parentClass ? 'method' : 'function';
59
+ const fnChildren = extractPythonParameters(node);
60
+ ctx.definitions.push({
61
+ name: fullName,
62
+ kind,
63
+ line: node.startPosition.row + 1,
64
+ endLine: nodeEndLine(node),
65
+ decorators,
66
+ children: fnChildren.length > 0 ? fnChildren : undefined,
67
+ visibility: pythonVisibility(nameNode.text),
68
+ });
69
+ }
115
70
 
116
- case 'expression_statement': {
117
- // Module-level UPPER_CASE assignments → constants
118
- if (node.parent && node.parent.type === 'module') {
119
- const assignment = findChild(node, 'assignment');
120
- if (assignment) {
121
- const left = assignment.childForFieldName('left');
122
- if (left && left.type === 'identifier' && /^[A-Z_][A-Z0-9_]*$/.test(left.text)) {
123
- definitions.push({
124
- name: left.text,
125
- kind: 'constant',
126
- line: node.startPosition.row + 1,
127
- });
128
- }
129
- }
130
- }
131
- break;
71
+ function handlePyClassDef(node, ctx) {
72
+ const nameNode = node.childForFieldName('name');
73
+ if (!nameNode) return;
74
+ const clsChildren = extractPythonClassProperties(node);
75
+ ctx.definitions.push({
76
+ name: nameNode.text,
77
+ kind: 'class',
78
+ line: node.startPosition.row + 1,
79
+ endLine: nodeEndLine(node),
80
+ children: clsChildren.length > 0 ? clsChildren : undefined,
81
+ });
82
+ const superclasses = node.childForFieldName('superclasses') || findChild(node, 'argument_list');
83
+ if (superclasses) {
84
+ for (let i = 0; i < superclasses.childCount; i++) {
85
+ const child = superclasses.child(i);
86
+ if (child && child.type === 'identifier') {
87
+ ctx.classes.push({
88
+ name: nameNode.text,
89
+ extends: child.text,
90
+ line: node.startPosition.row + 1,
91
+ });
132
92
  }
93
+ }
94
+ }
95
+ }
133
96
 
134
- case 'import_from_statement': {
135
- let source = '';
136
- const names = [];
137
- for (let i = 0; i < node.childCount; i++) {
138
- const child = node.child(i);
139
- if (!child) continue;
140
- if (child.type === 'dotted_name' || child.type === 'relative_import') {
141
- if (!source) source = child.text;
142
- else names.push(child.text);
143
- }
144
- if (child.type === 'aliased_import') {
145
- const n = child.childForFieldName('name') || child.child(0);
146
- if (n) names.push(n.text);
147
- }
148
- if (child.type === 'wildcard_import') names.push('*');
149
- }
150
- if (source)
151
- imports.push({ source, names, line: node.startPosition.row + 1, pythonImport: true });
152
- break;
97
+ function handlePyCall(node, ctx) {
98
+ const fn = node.childForFieldName('function');
99
+ if (!fn) return;
100
+ let callName = null;
101
+ let receiver;
102
+ if (fn.type === 'identifier') callName = fn.text;
103
+ else if (fn.type === 'attribute') {
104
+ const attr = fn.childForFieldName('attribute');
105
+ if (attr) callName = attr.text;
106
+ const obj = fn.childForFieldName('object');
107
+ if (obj) receiver = obj.text;
108
+ }
109
+ if (callName) {
110
+ const call = { name: callName, line: node.startPosition.row + 1 };
111
+ if (receiver) call.receiver = receiver;
112
+ ctx.calls.push(call);
113
+ }
114
+ }
115
+
116
+ function handlePyImport(node, ctx) {
117
+ const names = [];
118
+ for (let i = 0; i < node.childCount; i++) {
119
+ const child = node.child(i);
120
+ if (child && (child.type === 'dotted_name' || child.type === 'aliased_import')) {
121
+ const name =
122
+ child.type === 'aliased_import'
123
+ ? (child.childForFieldName('alias') || child.childForFieldName('name'))?.text
124
+ : child.text;
125
+ if (name) names.push(name);
126
+ }
127
+ }
128
+ if (names.length > 0)
129
+ ctx.imports.push({
130
+ source: names[0],
131
+ names,
132
+ line: node.startPosition.row + 1,
133
+ pythonImport: true,
134
+ });
135
+ }
136
+
137
+ function handlePyExpressionStmt(node, ctx) {
138
+ if (node.parent && node.parent.type === 'module') {
139
+ const assignment = findChild(node, 'assignment');
140
+ if (assignment) {
141
+ const left = assignment.childForFieldName('left');
142
+ if (left && left.type === 'identifier' && /^[A-Z_][A-Z0-9_]*$/.test(left.text)) {
143
+ ctx.definitions.push({
144
+ name: left.text,
145
+ kind: 'constant',
146
+ line: node.startPosition.row + 1,
147
+ });
153
148
  }
154
149
  }
150
+ }
151
+ }
155
152
 
156
- for (let i = 0; i < node.childCount; i++) walkPythonNode(node.child(i));
153
+ function handlePyImportFrom(node, ctx) {
154
+ let source = '';
155
+ const names = [];
156
+ for (let i = 0; i < node.childCount; i++) {
157
+ const child = node.child(i);
158
+ if (!child) continue;
159
+ if (child.type === 'dotted_name' || child.type === 'relative_import') {
160
+ if (!source) source = child.text;
161
+ else names.push(child.text);
162
+ }
163
+ if (child.type === 'aliased_import') {
164
+ const n = child.childForFieldName('name') || child.child(0);
165
+ if (n) names.push(n.text);
166
+ }
167
+ if (child.type === 'wildcard_import') names.push('*');
157
168
  }
169
+ if (source)
170
+ ctx.imports.push({ source, names, line: node.startPosition.row + 1, pythonImport: true });
171
+ }
158
172
 
159
- function extractPythonParameters(fnNode) {
160
- const params = [];
161
- const paramsNode = fnNode.childForFieldName('parameters') || findChild(fnNode, 'parameters');
162
- if (!paramsNode) return params;
163
- for (let i = 0; i < paramsNode.childCount; i++) {
164
- const child = paramsNode.child(i);
165
- if (!child) continue;
166
- const t = child.type;
167
- if (t === 'identifier') {
168
- params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
169
- } else if (
170
- t === 'typed_parameter' ||
171
- t === 'default_parameter' ||
172
- t === 'typed_default_parameter'
173
- ) {
174
- const nameNode = child.childForFieldName('name') || child.child(0);
175
- if (nameNode && nameNode.type === 'identifier') {
176
- params.push({
177
- name: nameNode.text,
178
- kind: 'parameter',
179
- line: child.startPosition.row + 1,
180
- });
181
- }
182
- } else if (t === 'list_splat_pattern' || t === 'dictionary_splat_pattern') {
183
- // *args, **kwargs
184
- for (let j = 0; j < child.childCount; j++) {
185
- const inner = child.child(j);
186
- if (inner && inner.type === 'identifier') {
187
- params.push({ name: inner.text, kind: 'parameter', line: child.startPosition.row + 1 });
188
- break;
189
- }
173
+ // ── Python-specific helpers ─────────────────────────────────────────────────
174
+
175
+ function extractPythonParameters(fnNode) {
176
+ const params = [];
177
+ const paramsNode = fnNode.childForFieldName('parameters') || findChild(fnNode, 'parameters');
178
+ if (!paramsNode) return params;
179
+ for (let i = 0; i < paramsNode.childCount; i++) {
180
+ const child = paramsNode.child(i);
181
+ if (!child) continue;
182
+ const t = child.type;
183
+ if (t === 'identifier') {
184
+ params.push({ name: child.text, kind: 'parameter', line: child.startPosition.row + 1 });
185
+ } else if (
186
+ t === 'typed_parameter' ||
187
+ t === 'default_parameter' ||
188
+ t === 'typed_default_parameter'
189
+ ) {
190
+ const nameNode = child.childForFieldName('name') || child.child(0);
191
+ if (nameNode && nameNode.type === 'identifier') {
192
+ params.push({
193
+ name: nameNode.text,
194
+ kind: 'parameter',
195
+ line: child.startPosition.row + 1,
196
+ });
197
+ }
198
+ } else if (t === 'list_splat_pattern' || t === 'dictionary_splat_pattern') {
199
+ for (let j = 0; j < child.childCount; j++) {
200
+ const inner = child.child(j);
201
+ if (inner && inner.type === 'identifier') {
202
+ params.push({ name: inner.text, kind: 'parameter', line: child.startPosition.row + 1 });
203
+ break;
190
204
  }
191
205
  }
192
206
  }
193
- return params;
194
207
  }
208
+ return params;
209
+ }
195
210
 
196
- function extractPythonClassProperties(classNode) {
197
- const props = [];
198
- const seen = new Set();
199
- const body = classNode.childForFieldName('body') || findChild(classNode, 'block');
200
- if (!body) return props;
211
+ function extractPythonClassProperties(classNode) {
212
+ const props = [];
213
+ const seen = new Set();
214
+ const body = classNode.childForFieldName('body') || findChild(classNode, 'block');
215
+ if (!body) return props;
201
216
 
202
- for (let i = 0; i < body.childCount; i++) {
203
- const child = body.child(i);
204
- if (!child) continue;
217
+ for (let i = 0; i < body.childCount; i++) {
218
+ const child = body.child(i);
219
+ if (!child) continue;
205
220
 
206
- // Direct class attribute assignments: x = 5
207
- if (child.type === 'expression_statement') {
208
- const assignment = findChild(child, 'assignment');
209
- if (assignment) {
210
- const left = assignment.childForFieldName('left');
211
- if (left && left.type === 'identifier' && !seen.has(left.text)) {
212
- seen.add(left.text);
213
- props.push({
214
- name: left.text,
215
- kind: 'property',
216
- line: child.startPosition.row + 1,
217
- visibility: pythonVisibility(left.text),
218
- });
219
- }
221
+ if (child.type === 'expression_statement') {
222
+ const assignment = findChild(child, 'assignment');
223
+ if (assignment) {
224
+ const left = assignment.childForFieldName('left');
225
+ if (left && left.type === 'identifier' && !seen.has(left.text)) {
226
+ seen.add(left.text);
227
+ props.push({
228
+ name: left.text,
229
+ kind: 'property',
230
+ line: child.startPosition.row + 1,
231
+ visibility: pythonVisibility(left.text),
232
+ });
220
233
  }
221
234
  }
235
+ }
222
236
 
223
- // __init__ method: self.x = ... assignments
224
- if (child.type === 'function_definition') {
225
- const fnName = child.childForFieldName('name');
226
- if (fnName && fnName.text === '__init__') {
227
- const initBody = child.childForFieldName('body') || findChild(child, 'block');
228
- if (initBody) {
229
- walkInitBody(initBody, seen, props);
230
- }
237
+ if (child.type === 'function_definition') {
238
+ const fnName = child.childForFieldName('name');
239
+ if (fnName && fnName.text === '__init__') {
240
+ const initBody = child.childForFieldName('body') || findChild(child, 'block');
241
+ if (initBody) {
242
+ walkInitBody(initBody, seen, props);
231
243
  }
232
244
  }
245
+ }
233
246
 
234
- // decorated __init__
235
- if (child.type === 'decorated_definition') {
236
- for (let j = 0; j < child.childCount; j++) {
237
- const inner = child.child(j);
238
- if (inner && inner.type === 'function_definition') {
239
- const fnName = inner.childForFieldName('name');
240
- if (fnName && fnName.text === '__init__') {
241
- const initBody = inner.childForFieldName('body') || findChild(inner, 'block');
242
- if (initBody) {
243
- walkInitBody(initBody, seen, props);
244
- }
247
+ if (child.type === 'decorated_definition') {
248
+ for (let j = 0; j < child.childCount; j++) {
249
+ const inner = child.child(j);
250
+ if (inner && inner.type === 'function_definition') {
251
+ const fnName = inner.childForFieldName('name');
252
+ if (fnName && fnName.text === '__init__') {
253
+ const initBody = inner.childForFieldName('body') || findChild(inner, 'block');
254
+ if (initBody) {
255
+ walkInitBody(initBody, seen, props);
245
256
  }
246
257
  }
247
258
  }
248
259
  }
249
260
  }
250
- return props;
251
261
  }
262
+ return props;
263
+ }
252
264
 
253
- function walkInitBody(bodyNode, seen, props) {
254
- for (let i = 0; i < bodyNode.childCount; i++) {
255
- const stmt = bodyNode.child(i);
256
- if (!stmt || stmt.type !== 'expression_statement') continue;
257
- const assignment = findChild(stmt, 'assignment');
258
- if (!assignment) continue;
259
- const left = assignment.childForFieldName('left');
260
- if (!left || left.type !== 'attribute') continue;
261
- const obj = left.childForFieldName('object');
262
- const attr = left.childForFieldName('attribute');
263
- if (
264
- obj &&
265
- obj.text === 'self' &&
266
- attr &&
267
- attr.type === 'identifier' &&
268
- !seen.has(attr.text)
269
- ) {
270
- seen.add(attr.text);
271
- props.push({
272
- name: attr.text,
273
- kind: 'property',
274
- line: stmt.startPosition.row + 1,
275
- visibility: pythonVisibility(attr.text),
276
- });
277
- }
265
+ function walkInitBody(bodyNode, seen, props) {
266
+ for (let i = 0; i < bodyNode.childCount; i++) {
267
+ const stmt = bodyNode.child(i);
268
+ if (!stmt || stmt.type !== 'expression_statement') continue;
269
+ const assignment = findChild(stmt, 'assignment');
270
+ if (!assignment) continue;
271
+ const left = assignment.childForFieldName('left');
272
+ if (!left || left.type !== 'attribute') continue;
273
+ const obj = left.childForFieldName('object');
274
+ const attr = left.childForFieldName('attribute');
275
+ if (obj && obj.text === 'self' && attr && attr.type === 'identifier' && !seen.has(attr.text)) {
276
+ seen.add(attr.text);
277
+ props.push({
278
+ name: attr.text,
279
+ kind: 'property',
280
+ line: stmt.startPosition.row + 1,
281
+ visibility: pythonVisibility(attr.text),
282
+ });
278
283
  }
279
284
  }
285
+ }
280
286
 
281
- function findPythonParentClass(node) {
282
- let current = node.parent;
283
- while (current) {
284
- if (current.type === 'class_definition') {
285
- const nameNode = current.childForFieldName('name');
286
- return nameNode ? nameNode.text : null;
287
- }
288
- current = current.parent;
287
+ function findPythonParentClass(node) {
288
+ let current = node.parent;
289
+ while (current) {
290
+ if (current.type === 'class_definition') {
291
+ const nameNode = current.childForFieldName('name');
292
+ return nameNode ? nameNode.text : null;
289
293
  }
290
- return null;
294
+ current = current.parent;
291
295
  }
292
-
293
- walkPythonNode(tree.rootNode);
294
- return { definitions, calls, imports, classes, exports };
296
+ return null;
295
297
  }