@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
@@ -320,333 +320,303 @@ function handleCommonJSAssignment(left, right, node, imports) {
320
320
  // ── Manual tree walk (fallback when Query not available) ────────────────────
321
321
 
322
322
  function extractSymbolsWalk(tree) {
323
- const definitions = [];
324
- const calls = [];
325
- const imports = [];
326
- const classes = [];
327
- const exports = [];
328
-
329
- function walkJavaScriptNode(node) {
330
- switch (node.type) {
331
- case 'function_declaration': {
332
- const nameNode = node.childForFieldName('name');
333
- if (nameNode) {
334
- const fnChildren = extractParameters(node);
335
- definitions.push({
336
- name: nameNode.text,
337
- kind: 'function',
338
- line: node.startPosition.row + 1,
339
- endLine: nodeEndLine(node),
340
- children: fnChildren.length > 0 ? fnChildren : undefined,
341
- });
342
- }
343
- break;
344
- }
323
+ const ctx = {
324
+ definitions: [],
325
+ calls: [],
326
+ imports: [],
327
+ classes: [],
328
+ exports: [],
329
+ };
330
+
331
+ walkJavaScriptNode(tree.rootNode, ctx);
332
+ return ctx;
333
+ }
345
334
 
346
- case 'class_declaration': {
347
- const nameNode = node.childForFieldName('name');
348
- if (nameNode) {
349
- const className = nameNode.text;
350
- const startLine = node.startPosition.row + 1;
351
- const clsChildren = extractClassProperties(node);
352
- definitions.push({
353
- name: className,
354
- kind: 'class',
355
- line: startLine,
356
- endLine: nodeEndLine(node),
357
- children: clsChildren.length > 0 ? clsChildren : undefined,
358
- });
359
- const heritage = node.childForFieldName('heritage') || findChild(node, 'class_heritage');
360
- if (heritage) {
361
- const superName = extractSuperclass(heritage);
362
- if (superName) {
363
- classes.push({ name: className, extends: superName, line: startLine });
364
- }
365
- const implementsList = extractImplements(heritage);
366
- for (const iface of implementsList) {
367
- classes.push({ name: className, implements: iface, line: startLine });
368
- }
369
- }
370
- }
371
- break;
372
- }
335
+ function walkJavaScriptNode(node, ctx) {
336
+ switch (node.type) {
337
+ case 'function_declaration':
338
+ handleFunctionDecl(node, ctx);
339
+ break;
340
+ case 'class_declaration':
341
+ handleClassDecl(node, ctx);
342
+ break;
343
+ case 'method_definition':
344
+ handleMethodDef(node, ctx);
345
+ break;
346
+ case 'interface_declaration':
347
+ handleInterfaceDecl(node, ctx);
348
+ break;
349
+ case 'type_alias_declaration':
350
+ handleTypeAliasDecl(node, ctx);
351
+ break;
352
+ case 'lexical_declaration':
353
+ case 'variable_declaration':
354
+ handleVariableDecl(node, ctx);
355
+ break;
356
+ case 'enum_declaration':
357
+ handleEnumDecl(node, ctx);
358
+ break;
359
+ case 'call_expression':
360
+ handleCallExpr(node, ctx);
361
+ break;
362
+ case 'import_statement':
363
+ handleImportStmt(node, ctx);
364
+ break;
365
+ case 'export_statement':
366
+ handleExportStmt(node, ctx);
367
+ break;
368
+ case 'expression_statement':
369
+ handleExpressionStmt(node, ctx);
370
+ break;
371
+ }
373
372
 
374
- case 'method_definition': {
375
- const nameNode = node.childForFieldName('name');
376
- if (nameNode) {
377
- const parentClass = findParentClass(node);
378
- const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
379
- const methChildren = extractParameters(node);
380
- const methVis = extractVisibility(node);
381
- definitions.push({
382
- name: fullName,
383
- kind: 'method',
384
- line: node.startPosition.row + 1,
385
- endLine: nodeEndLine(node),
386
- children: methChildren.length > 0 ? methChildren : undefined,
387
- visibility: methVis,
388
- });
389
- }
390
- break;
391
- }
373
+ for (let i = 0; i < node.childCount; i++) {
374
+ walkJavaScriptNode(node.child(i), ctx);
375
+ }
376
+ }
392
377
 
393
- case 'interface_declaration': {
394
- const nameNode = node.childForFieldName('name');
395
- if (nameNode) {
396
- definitions.push({
397
- name: nameNode.text,
398
- kind: 'interface',
399
- line: node.startPosition.row + 1,
400
- endLine: nodeEndLine(node),
401
- });
402
- const body =
403
- node.childForFieldName('body') ||
404
- findChild(node, 'interface_body') ||
405
- findChild(node, 'object_type');
406
- if (body) {
407
- extractInterfaceMethods(body, nameNode.text, definitions);
408
- }
409
- }
410
- break;
411
- }
378
+ // ── Walk-path per-node-type handlers ────────────────────────────────────────
412
379
 
413
- case 'type_alias_declaration': {
414
- const nameNode = node.childForFieldName('name');
415
- if (nameNode) {
416
- definitions.push({
417
- name: nameNode.text,
418
- kind: 'type',
419
- line: node.startPosition.row + 1,
420
- endLine: nodeEndLine(node),
421
- });
422
- }
423
- break;
424
- }
380
+ function handleFunctionDecl(node, ctx) {
381
+ const nameNode = node.childForFieldName('name');
382
+ if (nameNode) {
383
+ const fnChildren = extractParameters(node);
384
+ ctx.definitions.push({
385
+ name: nameNode.text,
386
+ kind: 'function',
387
+ line: node.startPosition.row + 1,
388
+ endLine: nodeEndLine(node),
389
+ children: fnChildren.length > 0 ? fnChildren : undefined,
390
+ });
391
+ }
392
+ }
425
393
 
426
- case 'lexical_declaration':
427
- case 'variable_declaration': {
428
- const isConst = node.text.startsWith('const ');
429
- for (let i = 0; i < node.childCount; i++) {
430
- const declarator = node.child(i);
431
- if (declarator && declarator.type === 'variable_declarator') {
432
- const nameN = declarator.childForFieldName('name');
433
- const valueN = declarator.childForFieldName('value');
434
- if (nameN && valueN) {
435
- const valType = valueN.type;
436
- if (
437
- valType === 'arrow_function' ||
438
- valType === 'function_expression' ||
439
- valType === 'function'
440
- ) {
441
- const varFnChildren = extractParameters(valueN);
442
- definitions.push({
443
- name: nameN.text,
444
- kind: 'function',
445
- line: node.startPosition.row + 1,
446
- endLine: nodeEndLine(valueN),
447
- children: varFnChildren.length > 0 ? varFnChildren : undefined,
448
- });
449
- } else if (isConst && nameN.type === 'identifier' && isConstantValue(valueN)) {
450
- definitions.push({
451
- name: nameN.text,
452
- kind: 'constant',
453
- line: node.startPosition.row + 1,
454
- endLine: nodeEndLine(node),
455
- });
456
- }
457
- } else if (isConst && nameN && nameN.type === 'identifier' && !valueN) {
458
- // const with no value (shouldn't happen but be safe)
459
- }
460
- }
461
- }
462
- break;
463
- }
394
+ function handleClassDecl(node, ctx) {
395
+ const nameNode = node.childForFieldName('name');
396
+ if (!nameNode) return;
397
+ const className = nameNode.text;
398
+ const startLine = node.startPosition.row + 1;
399
+ const clsChildren = extractClassProperties(node);
400
+ ctx.definitions.push({
401
+ name: className,
402
+ kind: 'class',
403
+ line: startLine,
404
+ endLine: nodeEndLine(node),
405
+ children: clsChildren.length > 0 ? clsChildren : undefined,
406
+ });
407
+ const heritage = node.childForFieldName('heritage') || findChild(node, 'class_heritage');
408
+ if (heritage) {
409
+ const superName = extractSuperclass(heritage);
410
+ if (superName) {
411
+ ctx.classes.push({ name: className, extends: superName, line: startLine });
412
+ }
413
+ const implementsList = extractImplements(heritage);
414
+ for (const iface of implementsList) {
415
+ ctx.classes.push({ name: className, implements: iface, line: startLine });
416
+ }
417
+ }
418
+ }
464
419
 
465
- case 'enum_declaration': {
466
- // TypeScript enum
467
- const nameNode = node.childForFieldName('name');
468
- if (nameNode) {
469
- const enumChildren = [];
470
- const body = node.childForFieldName('body') || findChild(node, 'enum_body');
471
- if (body) {
472
- for (let i = 0; i < body.childCount; i++) {
473
- const member = body.child(i);
474
- if (!member) continue;
475
- if (member.type === 'enum_assignment' || member.type === 'property_identifier') {
476
- const mName = member.childForFieldName('name') || member.child(0);
477
- if (mName) {
478
- enumChildren.push({
479
- name: mName.text,
480
- kind: 'constant',
481
- line: member.startPosition.row + 1,
482
- });
483
- }
484
- }
485
- }
486
- }
487
- definitions.push({
488
- name: nameNode.text,
489
- kind: 'enum',
490
- line: node.startPosition.row + 1,
491
- endLine: nodeEndLine(node),
492
- children: enumChildren.length > 0 ? enumChildren : undefined,
493
- });
494
- }
495
- break;
496
- }
420
+ function handleMethodDef(node, ctx) {
421
+ const nameNode = node.childForFieldName('name');
422
+ if (nameNode) {
423
+ const parentClass = findParentClass(node);
424
+ const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
425
+ const methChildren = extractParameters(node);
426
+ const methVis = extractVisibility(node);
427
+ ctx.definitions.push({
428
+ name: fullName,
429
+ kind: 'method',
430
+ line: node.startPosition.row + 1,
431
+ endLine: nodeEndLine(node),
432
+ children: methChildren.length > 0 ? methChildren : undefined,
433
+ visibility: methVis,
434
+ });
435
+ }
436
+ }
497
437
 
498
- case 'call_expression': {
499
- const fn = node.childForFieldName('function');
500
- if (fn) {
501
- // Dynamic import(): import('./foo.js') → extract as an import entry
502
- if (fn.type === 'import') {
503
- const args = node.childForFieldName('arguments') || findChild(node, 'arguments');
504
- if (args) {
505
- const strArg = findChild(args, 'string');
506
- if (strArg) {
507
- const modPath = strArg.text.replace(/['"]/g, '');
508
- // Extract destructured names from parent context:
509
- // const { a, b } = await import('./foo.js')
510
- // (standalone import('./foo.js').then(...) calls produce an edge with empty names)
511
- const names = extractDynamicImportNames(node);
512
- imports.push({
513
- source: modPath,
514
- names,
515
- line: node.startPosition.row + 1,
516
- dynamicImport: true,
517
- });
518
- } else {
519
- debug(
520
- `Skipping non-static dynamic import() at line ${node.startPosition.row + 1} (template literal or variable)`,
521
- );
522
- }
523
- }
524
- } else {
525
- const callInfo = extractCallInfo(fn, node);
526
- if (callInfo) calls.push(callInfo);
527
- if (fn.type === 'member_expression') {
528
- const cbDef = extractCallbackDefinition(node, fn);
529
- if (cbDef) definitions.push(cbDef);
530
- }
531
- }
532
- }
533
- break;
534
- }
438
+ function handleInterfaceDecl(node, ctx) {
439
+ const nameNode = node.childForFieldName('name');
440
+ if (!nameNode) return;
441
+ ctx.definitions.push({
442
+ name: nameNode.text,
443
+ kind: 'interface',
444
+ line: node.startPosition.row + 1,
445
+ endLine: nodeEndLine(node),
446
+ });
447
+ const body =
448
+ node.childForFieldName('body') ||
449
+ findChild(node, 'interface_body') ||
450
+ findChild(node, 'object_type');
451
+ if (body) {
452
+ extractInterfaceMethods(body, nameNode.text, ctx.definitions);
453
+ }
454
+ }
535
455
 
536
- case 'import_statement': {
537
- const isTypeOnly = node.text.startsWith('import type');
538
- const source = node.childForFieldName('source') || findChild(node, 'string');
539
- if (source) {
540
- const modPath = source.text.replace(/['"]/g, '');
541
- const names = extractImportNames(node);
542
- imports.push({
543
- source: modPath,
544
- names,
456
+ function handleTypeAliasDecl(node, ctx) {
457
+ const nameNode = node.childForFieldName('name');
458
+ if (nameNode) {
459
+ ctx.definitions.push({
460
+ name: nameNode.text,
461
+ kind: 'type',
462
+ line: node.startPosition.row + 1,
463
+ endLine: nodeEndLine(node),
464
+ });
465
+ }
466
+ }
467
+
468
+ function handleVariableDecl(node, ctx) {
469
+ const isConst = node.text.startsWith('const ');
470
+ for (let i = 0; i < node.childCount; i++) {
471
+ const declarator = node.child(i);
472
+ if (declarator && declarator.type === 'variable_declarator') {
473
+ const nameN = declarator.childForFieldName('name');
474
+ const valueN = declarator.childForFieldName('value');
475
+ if (nameN && valueN) {
476
+ const valType = valueN.type;
477
+ if (
478
+ valType === 'arrow_function' ||
479
+ valType === 'function_expression' ||
480
+ valType === 'function'
481
+ ) {
482
+ const varFnChildren = extractParameters(valueN);
483
+ ctx.definitions.push({
484
+ name: nameN.text,
485
+ kind: 'function',
486
+ line: node.startPosition.row + 1,
487
+ endLine: nodeEndLine(valueN),
488
+ children: varFnChildren.length > 0 ? varFnChildren : undefined,
489
+ });
490
+ } else if (isConst && nameN.type === 'identifier' && isConstantValue(valueN)) {
491
+ ctx.definitions.push({
492
+ name: nameN.text,
493
+ kind: 'constant',
545
494
  line: node.startPosition.row + 1,
546
- typeOnly: isTypeOnly,
495
+ endLine: nodeEndLine(node),
547
496
  });
548
497
  }
549
- break;
550
498
  }
499
+ }
500
+ }
501
+ }
551
502
 
552
- case 'export_statement': {
553
- const exportLine = node.startPosition.row + 1;
554
- const decl = node.childForFieldName('declaration');
555
- if (decl) {
556
- const declType = decl.type;
557
- const kindMap = {
558
- function_declaration: 'function',
559
- class_declaration: 'class',
560
- interface_declaration: 'interface',
561
- type_alias_declaration: 'type',
562
- };
563
- const kind = kindMap[declType];
564
- if (kind) {
565
- const n = decl.childForFieldName('name');
566
- if (n) exports.push({ name: n.text, kind, line: exportLine });
567
- }
568
- }
569
- const source = node.childForFieldName('source') || findChild(node, 'string');
570
- if (source && !decl) {
571
- const modPath = source.text.replace(/['"]/g, '');
572
- const reexportNames = extractImportNames(node);
573
- const nodeText = node.text;
574
- const isWildcard = nodeText.includes('export *') || nodeText.includes('export*');
575
- imports.push({
576
- source: modPath,
577
- names: reexportNames,
578
- line: exportLine,
579
- reexport: true,
580
- wildcardReexport: isWildcard && reexportNames.length === 0,
503
+ function handleEnumDecl(node, ctx) {
504
+ const nameNode = node.childForFieldName('name');
505
+ if (!nameNode) return;
506
+ const enumChildren = [];
507
+ const body = node.childForFieldName('body') || findChild(node, 'enum_body');
508
+ if (body) {
509
+ for (let i = 0; i < body.childCount; i++) {
510
+ const member = body.child(i);
511
+ if (!member) continue;
512
+ if (member.type === 'enum_assignment' || member.type === 'property_identifier') {
513
+ const mName = member.childForFieldName('name') || member.child(0);
514
+ if (mName) {
515
+ enumChildren.push({
516
+ name: mName.text,
517
+ kind: 'constant',
518
+ line: member.startPosition.row + 1,
581
519
  });
582
520
  }
583
- break;
584
521
  }
522
+ }
523
+ }
524
+ ctx.definitions.push({
525
+ name: nameNode.text,
526
+ kind: 'enum',
527
+ line: node.startPosition.row + 1,
528
+ endLine: nodeEndLine(node),
529
+ children: enumChildren.length > 0 ? enumChildren : undefined,
530
+ });
531
+ }
585
532
 
586
- case 'expression_statement': {
587
- const expr = node.child(0);
588
- if (expr && expr.type === 'assignment_expression') {
589
- const left = expr.childForFieldName('left');
590
- const right = expr.childForFieldName('right');
591
- if (left && right) {
592
- const leftText = left.text;
593
- if (leftText.startsWith('module.exports') || leftText === 'exports') {
594
- if (right.type === 'call_expression') {
595
- const fn = right.childForFieldName('function');
596
- const args = right.childForFieldName('arguments') || findChild(right, 'arguments');
597
- if (fn && fn.text === 'require' && args) {
598
- const strArg = findChild(args, 'string');
599
- if (strArg) {
600
- imports.push({
601
- source: strArg.text.replace(/['"]/g, ''),
602
- names: [],
603
- line: node.startPosition.row + 1,
604
- reexport: true,
605
- wildcardReexport: true,
606
- });
607
- }
608
- }
609
- }
610
- if (right.type === 'object') {
611
- for (let ci = 0; ci < right.childCount; ci++) {
612
- const child = right.child(ci);
613
- if (child && child.type === 'spread_element') {
614
- const spreadExpr = child.child(1) || child.childForFieldName('value');
615
- if (spreadExpr && spreadExpr.type === 'call_expression') {
616
- const fn2 = spreadExpr.childForFieldName('function');
617
- const args2 =
618
- spreadExpr.childForFieldName('arguments') ||
619
- findChild(spreadExpr, 'arguments');
620
- if (fn2 && fn2.text === 'require' && args2) {
621
- const strArg2 = findChild(args2, 'string');
622
- if (strArg2) {
623
- imports.push({
624
- source: strArg2.text.replace(/['"]/g, ''),
625
- names: [],
626
- line: node.startPosition.row + 1,
627
- reexport: true,
628
- wildcardReexport: true,
629
- });
630
- }
631
- }
632
- }
633
- }
634
- }
635
- }
636
- }
637
- }
638
- }
639
- break;
533
+ function handleCallExpr(node, ctx) {
534
+ const fn = node.childForFieldName('function');
535
+ if (!fn) return;
536
+ if (fn.type === 'import') {
537
+ const args = node.childForFieldName('arguments') || findChild(node, 'arguments');
538
+ if (args) {
539
+ const strArg = findChild(args, 'string');
540
+ if (strArg) {
541
+ const modPath = strArg.text.replace(/['"]/g, '');
542
+ const names = extractDynamicImportNames(node);
543
+ ctx.imports.push({
544
+ source: modPath,
545
+ names,
546
+ line: node.startPosition.row + 1,
547
+ dynamicImport: true,
548
+ });
549
+ } else {
550
+ debug(
551
+ `Skipping non-static dynamic import() at line ${node.startPosition.row + 1} (template literal or variable)`,
552
+ );
640
553
  }
641
554
  }
555
+ } else {
556
+ const callInfo = extractCallInfo(fn, node);
557
+ if (callInfo) ctx.calls.push(callInfo);
558
+ if (fn.type === 'member_expression') {
559
+ const cbDef = extractCallbackDefinition(node, fn);
560
+ if (cbDef) ctx.definitions.push(cbDef);
561
+ }
562
+ }
563
+ }
642
564
 
643
- for (let i = 0; i < node.childCount; i++) {
644
- walkJavaScriptNode(node.child(i));
565
+ function handleImportStmt(node, ctx) {
566
+ const isTypeOnly = node.text.startsWith('import type');
567
+ const source = node.childForFieldName('source') || findChild(node, 'string');
568
+ if (source) {
569
+ const modPath = source.text.replace(/['"]/g, '');
570
+ const names = extractImportNames(node);
571
+ ctx.imports.push({
572
+ source: modPath,
573
+ names,
574
+ line: node.startPosition.row + 1,
575
+ typeOnly: isTypeOnly,
576
+ });
577
+ }
578
+ }
579
+
580
+ function handleExportStmt(node, ctx) {
581
+ const exportLine = node.startPosition.row + 1;
582
+ const decl = node.childForFieldName('declaration');
583
+ if (decl) {
584
+ const declType = decl.type;
585
+ const kindMap = {
586
+ function_declaration: 'function',
587
+ class_declaration: 'class',
588
+ interface_declaration: 'interface',
589
+ type_alias_declaration: 'type',
590
+ };
591
+ const kind = kindMap[declType];
592
+ if (kind) {
593
+ const n = decl.childForFieldName('name');
594
+ if (n) ctx.exports.push({ name: n.text, kind, line: exportLine });
645
595
  }
646
596
  }
597
+ const source = node.childForFieldName('source') || findChild(node, 'string');
598
+ if (source && !decl) {
599
+ const modPath = source.text.replace(/['"]/g, '');
600
+ const reexportNames = extractImportNames(node);
601
+ const nodeText = node.text;
602
+ const isWildcard = nodeText.includes('export *') || nodeText.includes('export*');
603
+ ctx.imports.push({
604
+ source: modPath,
605
+ names: reexportNames,
606
+ line: exportLine,
607
+ reexport: true,
608
+ wildcardReexport: isWildcard && reexportNames.length === 0,
609
+ });
610
+ }
611
+ }
647
612
 
648
- walkJavaScriptNode(tree.rootNode);
649
- return { definitions, calls, imports, classes, exports };
613
+ function handleExpressionStmt(node, ctx) {
614
+ const expr = node.child(0);
615
+ if (expr && expr.type === 'assignment_expression') {
616
+ const left = expr.childForFieldName('left');
617
+ const right = expr.childForFieldName('right');
618
+ handleCommonJSAssignment(left, right, node, ctx.imports);
619
+ }
650
620
  }
651
621
 
652
622
  // ── Child extraction helpers ────────────────────────────────────────────────