ucn 4.2.2 → 5.0.1

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 (72) hide show
  1. package/.claude/skills/ucn/SKILL.md +89 -77
  2. package/.claude/skills/ucn/references/commands.md +62 -68
  3. package/.claude/skills/ucn/references/trust-contract.md +31 -6
  4. package/README.md +445 -300
  5. package/assets/demo.svg +31 -0
  6. package/cli/index.js +430 -1385
  7. package/core/account.js +144 -34
  8. package/core/analysis.js +182 -72
  9. package/core/ast-analysis.js +279 -0
  10. package/core/bridge.js +205 -24
  11. package/core/brief.js +27 -58
  12. package/core/build-worker.js +21 -131
  13. package/core/cache.js +533 -11
  14. package/core/callers.js +5533 -494
  15. package/core/check.js +13 -4
  16. package/core/command-contracts.js +402 -0
  17. package/core/compilation-database.js +276 -0
  18. package/core/confidence.js +4 -1
  19. package/core/deadcode.js +421 -20
  20. package/core/discovery.js +359 -46
  21. package/core/entrypoints.js +204 -42
  22. package/core/execute.js +887 -81
  23. package/core/graph-build.js +162 -7
  24. package/core/graph.js +53 -77
  25. package/core/imports.js +65 -6
  26. package/core/index-ir.js +138 -0
  27. package/core/ir.js +195 -0
  28. package/core/output/analysis.js +216 -22
  29. package/core/output/brief.js +23 -0
  30. package/core/output/check.js +4 -0
  31. package/core/output/doctor.js +37 -6
  32. package/core/output/endpoints.js +5 -2
  33. package/core/output/extraction.js +24 -12
  34. package/core/output/find.js +141 -36
  35. package/core/output/graph.js +11 -5
  36. package/core/output/public.js +462 -0
  37. package/core/output/refactoring.js +42 -10
  38. package/core/output/reporting.js +97 -20
  39. package/core/output/search.js +24 -16
  40. package/core/output/shared.js +22 -1
  41. package/core/output/tracing.js +30 -15
  42. package/core/output-budget.js +295 -0
  43. package/core/output.js +1 -0
  44. package/core/parallel-build.js +44 -11
  45. package/core/parser.js +3 -3
  46. package/core/project.js +384 -177
  47. package/core/public-command.js +47 -0
  48. package/core/registry.js +247 -117
  49. package/core/reporting.js +312 -290
  50. package/core/search.js +371 -116
  51. package/core/semantic-provider.js +110 -0
  52. package/core/stacktrace.js +25 -0
  53. package/core/tracing.js +101 -51
  54. package/core/trust-matrix.js +19 -40
  55. package/core/verify.js +534 -37
  56. package/languages/adapter.js +218 -0
  57. package/languages/c-family.js +2791 -0
  58. package/languages/c.js +3 -0
  59. package/languages/cpp.js +3 -0
  60. package/languages/csharp.js +1402 -0
  61. package/languages/go.js +60 -21
  62. package/languages/html.js +2 -2
  63. package/languages/index.js +85 -7
  64. package/languages/java.js +428 -16
  65. package/languages/javascript.js +452 -49
  66. package/languages/python.js +1041 -32
  67. package/languages/rust.js +1415 -152
  68. package/languages/utils.js +40 -3
  69. package/mcp/server.js +254 -636
  70. package/package.json +41 -24
  71. package/eslint.config.js +0 -43
  72. package/jsconfig.json +0 -10
@@ -16,7 +16,7 @@ const fs = require('fs');
16
16
  const { codeUnitCompare, NON_CALLABLE_TYPES } = require('./shared');
17
17
  const path = require('path');
18
18
  const { getCachedCalls } = require('./callers');
19
- const { getLanguageModule } = require('../languages');
19
+ const { getLanguageAdapter } = require('../languages');
20
20
 
21
21
  // ============================================================================
22
22
  // FRAMEWORK PATTERNS
@@ -74,7 +74,10 @@ const FRAMEWORK_PATTERNS = [
74
74
  // Listed BEFORE fastapi: both frameworks share the @app.get/@app.post
75
75
  // shortcuts, and @app.route is Flask-only — a file using @app.route +
76
76
  // @app.post on the SAME app object got split across two frameworks
77
- // (fix #245; first match wins in matchDecoratorOrModifier).
77
+ // (fix #245; first match wins in matchDecoratorOrModifier). When the
78
+ // decorator shape is shared, the file's own imports break the tie
79
+ // (importSignal): a fastapi-importing file's @app.get is fastapi, not
80
+ // flask — the label must agree with what `endpoints` reports.
78
81
  {
79
82
  id: 'flask-route',
80
83
  languages: new Set(['python']),
@@ -82,6 +85,7 @@ const FRAMEWORK_PATTERNS = [
82
85
  framework: 'flask',
83
86
  detection: 'decorator',
84
87
  pattern: /^(app|bp|blueprint)\.(route|get|post|put|delete|patch)/,
88
+ importSignal: /^flask\b/,
85
89
  },
86
90
 
87
91
  // FastAPI (Python) — decorators: @app.get('/path'), @router.post('/path')
@@ -92,6 +96,7 @@ const FRAMEWORK_PATTERNS = [
92
96
  framework: 'fastapi',
93
97
  detection: 'decorator',
94
98
  pattern: /^(app|router)\.(get|post|put|delete|patch|options|head)/,
99
+ importSignal: /^fastapi\b/,
95
100
  },
96
101
 
97
102
  // Django (Python) — decorators: @api_view, @action, @permission_classes
@@ -165,6 +170,34 @@ const FRAMEWORK_PATTERNS = [
165
170
  pattern: /^(get|post|put|delete|patch)$/,
166
171
  },
167
172
 
173
+ // ASP.NET Core controllers and Azure Functions.
174
+ {
175
+ id: 'aspnet-controller-route',
176
+ languages: new Set(['csharp']),
177
+ type: 'http',
178
+ framework: 'aspnet',
179
+ detection: 'decorator',
180
+ pattern: /^(ApiController|Route|Http(Get|Post|Put|Delete|Patch|Head|Options)|AcceptVerbs)$/,
181
+ },
182
+ {
183
+ id: 'azure-function',
184
+ languages: new Set(['csharp']),
185
+ type: 'http',
186
+ framework: 'azure-functions',
187
+ detection: 'decorator',
188
+ pattern: /^(FunctionName|Function|HttpTrigger)$/,
189
+ },
190
+ // ASP.NET minimal APIs: app.MapGet("/path", Handler).
191
+ {
192
+ id: 'aspnet-minimal-route',
193
+ languages: new Set(['csharp']),
194
+ type: 'http',
195
+ framework: 'aspnet-minimal',
196
+ detection: 'callPattern',
197
+ receiverPattern: /^(app|endpoints|routes)$/i,
198
+ methodPattern: /^Map(Get|Post|Put|Delete|Patch|Methods|Fallback)$/,
199
+ },
200
+
168
201
  // ── Dependency Injection ────────────────────────────────────────────
169
202
 
170
203
  // Spring DI (Java)
@@ -252,7 +285,11 @@ const FRAMEWORK_PATTERNS = [
252
285
  type: 'jobs',
253
286
  framework: 'celery',
254
287
  detection: 'decorator',
255
- pattern: /^(app\.task|shared_task|celery\.task)/,
288
+ // Celery instances are conventionally named app, celery, tasks, or
289
+ // something project-specific. The import requirement prevents an
290
+ // arbitrary object's `.task` decorator from becoming a job root.
291
+ pattern: /^(?:[A-Za-z_]\w*\.)?task\b|^shared_task\b/,
292
+ requireImportSignal: /^celery\b/,
256
293
  },
257
294
 
258
295
  // ── Test Frameworks ─────────────────────────────────────────────────
@@ -267,6 +304,30 @@ const FRAMEWORK_PATTERNS = [
267
304
  pattern: /^pytest\.fixture/,
268
305
  },
269
306
 
307
+ // pytest/unittest test functions and plugin hooks — the same name
308
+ // convention the Python language module already uses for deadcode entry
309
+ // points (the entrypoints command must agree with it; Go's Test* pattern
310
+ // set the precedent).
311
+ {
312
+ id: 'pytest-test',
313
+ languages: new Set(['python']),
314
+ type: 'test',
315
+ framework: 'pytest',
316
+ detection: 'namePattern',
317
+ pattern: /^(test_|pytest_)/,
318
+ },
319
+
320
+ // C/C++ test conventions (Unity, CTest, GoogleTest wrappers) — mirrors
321
+ // the c-family language module's own entry-point rule.
322
+ {
323
+ id: 'c-family-test',
324
+ languages: new Set(['c', 'cpp']),
325
+ type: 'test',
326
+ framework: 'native-test',
327
+ detection: 'namePattern',
328
+ pattern: /^(test_|Test|TEST_)/,
329
+ },
330
+
270
331
  // ── Runtime ─────────────────────────────────────────────────────────
271
332
 
272
333
  // Tokio (Rust)
@@ -387,6 +448,34 @@ const FRAMEWORK_PATTERNS = [
387
448
  pattern: /^(test|tokio::test|cfg\(test\))$/,
388
449
  },
389
450
 
451
+ // ── C / C++ / C# runtime and test roots ─────────────────────────
452
+ {
453
+ id: 'c-family-main',
454
+ languages: new Set(['c', 'cpp']),
455
+ type: 'runtime',
456
+ framework: 'native',
457
+ detection: 'namePattern',
458
+ pattern: /^(main|WinMain|wWinMain|DllMain)$/,
459
+ symbolFilter: (s) => !s.className,
460
+ },
461
+ {
462
+ id: 'csharp-main',
463
+ languages: new Set(['csharp']),
464
+ type: 'runtime',
465
+ framework: 'dotnet',
466
+ detection: 'namePattern',
467
+ pattern: /^Main$/,
468
+ symbolFilter: (s) => s.modifiers?.includes('static'),
469
+ },
470
+ {
471
+ id: 'csharp-test',
472
+ languages: new Set(['csharp']),
473
+ type: 'test',
474
+ framework: 'dotnet-test',
475
+ detection: 'decorator',
476
+ pattern: /^(Fact|Theory|Test|TestMethod|TestCase|TestCaseSource|SetUp|OneTimeSetUp)$/,
477
+ },
478
+
390
479
  // ── Go Framework Patterns ─────────────────────────────────────────
391
480
 
392
481
  // Cobra CLI framework — RunE, Run, PreRunE etc. assigned to cobra.Command struct fields
@@ -420,7 +509,7 @@ const FRAMEWORK_PATTERNS = [
420
509
  detection: 'filePath',
421
510
  // Match files under bin/ (any depth), or top-level index/main/cli/server in any directory.
422
511
  // The matcher is run against the project-relative path with forward slashes.
423
- pathPattern: /(^|\/)bin\/[^/]+\.(js|ts|mjs|cjs)$|(^|\/)(index|main|cli|server)\.(js|ts|mjs|cjs)$/,
512
+ pathPattern: /(^|\/)bin\/[^/]+\.(js|ts|mjs|cjs)$|(^|\/)(main|cli|server)\.(js|ts|mjs|cjs)$/,
424
513
  },
425
514
 
426
515
  // Node shebang entry: any file whose first bytes are `#!/usr/bin/env node`
@@ -470,31 +559,40 @@ const FRAMEWORK_PATTERNS = [
470
559
  pathPattern: /(^|\/)__main__\.py$/,
471
560
  },
472
561
 
473
- // ── Catch-all fallbacks ─────────────────────────────────────────────
562
+ // Django management commands — discovered by module path
563
+ // (management/commands/<name>.py); the Command class and its handle()
564
+ // are invoked by `manage.py <name>` (the #253 external-contract family,
565
+ // now visible to the entrypoints command too).
566
+ {
567
+ id: 'django-command',
568
+ languages: new Set(['python']),
569
+ type: 'cli',
570
+ framework: 'django',
571
+ detection: 'filePath',
572
+ pathPattern: /(^|\/)management\/commands\/[^/]+\.py$/,
573
+ },
474
574
 
475
- // Python: any decorator with '.' (attribute access) — framework registration heuristic
476
- // Catches @app.route, @router.get, @celery.task, @something.hook, etc.
477
- // Placed last so specific patterns match first (for better type/framework labeling).
575
+ // Explicit Python event/CLI decorators. Dotted syntax by itself is not
576
+ // registration evidence (`typing.overload`, `property.setter`, and
577
+ // `functools.wraps` are counterexamples).
478
578
  {
479
- id: 'python-dotted-decorator',
579
+ id: 'flask-event-hook',
480
580
  languages: new Set(['python']),
481
581
  type: 'events',
482
- framework: 'unknown',
582
+ framework: 'flask',
483
583
  detection: 'decorator',
484
- pattern: /\./,
584
+ pattern: /^(app|bp|blueprint)\.(before_request|after_request|teardown_request|before_app_request|after_app_request|teardown_app_request|context_processor|url_value_preprocessor|url_defaults|errorhandler)/,
585
+ importSignal: /^flask\b/,
485
586
  },
486
-
487
- // Java: any non-standard annotation (not a keyword modifier or standard JDK annotation)
488
- // Catches @Bean, @Scheduled, @EventListener, @Transactional, etc.
489
- // Placed last so specific patterns match first.
490
587
  {
491
- id: 'java-custom-annotation',
492
- languages: new Set(['java']),
493
- type: 'di',
494
- framework: 'unknown',
495
- detection: 'modifier',
496
- pattern: /^(?!public$|private$|protected$|static$|final$|abstract$|synchronized$|native$|default$|override$|deprecated$|suppresswarnings$|functionalinterface$|safevarargs$)/,
588
+ id: 'click-command',
589
+ languages: new Set(['python']),
590
+ type: 'cli',
591
+ framework: 'click',
592
+ detection: 'decorator',
593
+ pattern: /^(click\.)?(command|group)|^[A-Za-z_]\w*\.(command|group)$/,
497
594
  },
595
+
498
596
  ];
499
597
 
500
598
  // ============================================================================
@@ -507,25 +605,41 @@ const FRAMEWORK_PATTERNS = [
507
605
  * @param {string} language - File language
508
606
  * @returns {{ pattern: object, matchedOn: string }|null}
509
607
  */
510
- function matchDecoratorOrModifier(symbol, language) {
608
+ function matchDecoratorOrModifier(symbol, language, fileEntry = null) {
511
609
  const decorators = symbol.decorators || [];
512
610
  const modifiers = symbol.modifiers || [];
611
+ const modules = (fileEntry?.imports || []).map(imp =>
612
+ typeof imp === 'string' ? imp : String(imp?.module || ''));
513
613
 
614
+ const matches = [];
514
615
  for (const fp of FRAMEWORK_PATTERNS) {
515
616
  if (!fp.languages.has(language)) continue;
617
+ if (fp.requireImportSignal &&
618
+ !modules.some(moduleName => fp.requireImportSignal.test(moduleName))) continue;
516
619
 
517
620
  if (fp.detection === 'decorator') {
518
621
  const matched = decorators.find(d => fp.pattern.test(d));
519
- if (matched) return { pattern: fp, matchedOn: `@${matched}` };
622
+ if (matched) matches.push({ pattern: fp, matchedOn: `@${matched}` });
520
623
  }
521
624
 
522
625
  if (fp.detection === 'modifier') {
523
626
  const matched = modifiers.find(m => fp.pattern.test(m));
524
- if (matched) return { pattern: fp, matchedOn: `@${matched}` };
627
+ if (matched) matches.push({ pattern: fp, matchedOn: `@${matched}` });
525
628
  }
526
629
  }
527
-
528
- return null;
630
+ if (matches.length === 0) return null;
631
+
632
+ // Shared decorator shapes (@app.get is both flask and fastapi): the
633
+ // file's own imports break the tie. Without an import signal, declaration
634
+ // order keeps deciding (the #245 rule).
635
+ if (matches.length > 1 && fileEntry) {
636
+ // fileEntry.imports holds module strings; parser-level records hold
637
+ // { module } objects — accept both shapes.
638
+ const importBacked = matches.find(m => m.pattern.importSignal &&
639
+ modules.some(mod => m.pattern.importSignal.test(mod)));
640
+ if (importBacked) return importBacked;
641
+ }
642
+ return matches[0];
529
643
  }
530
644
 
531
645
  /**
@@ -560,7 +674,8 @@ function buildCallbackEntrypointMap(index) {
560
674
  // registration was never seeded. The registration site is kept as
561
675
  // evidence (registrationFile/registrationLine).
562
676
  const resolveHandlerDef = (name, registrationFile) => {
563
- const defs = index.symbols.get(name);
677
+ const defs = (index.symbols.get(name) || []).filter(definition =>
678
+ !NON_CALLABLE_TYPES.has(definition.type));
564
679
  if (!defs || defs.length === 0) return null;
565
680
  // Prefer a def in the registration file; defs are canonical-sorted,
566
681
  // so falling back to the first is deterministic.
@@ -585,7 +700,8 @@ function buildCallbackEntrypointMap(index) {
585
700
  pattern.methodPattern.test(call.name)) {
586
701
  // BUG M2 (interpolated paths): align with bridge.js's
587
702
  // extractServerRoutes — skip routes whose path is interpolated.
588
- if (pattern.type === 'http' && call.firstStringArg && call.firstStringArgInterp) continue;
703
+ if (pattern.type === 'http' && call.name.toLowerCase() !== 'use' &&
704
+ (!call.firstStringArg || call.firstStringArgInterp)) continue;
589
705
  // BUG M5: Express dual-purpose APIs — 1-arg .get('env') is a
590
706
  // config getter, not a route registration.
591
707
  if (pattern.framework === 'express' &&
@@ -603,6 +719,9 @@ function buildCallbackEntrypointMap(index) {
603
719
  // only visible as the enclosingFunction of the handler
604
720
  // body's own calls (fix #247; the route vanished entirely).
605
721
  const handledLines = new Set();
722
+ // Route lines that produced an actual entry (pass 2 or 2b) —
723
+ // the remainder are anonymous-handler routes for pass 2c.
724
+ const attributedLines = new Set();
606
725
 
607
726
  // Pass 2: find callbacks on route-registration lines
608
727
  for (const call of calls) {
@@ -618,10 +737,11 @@ function buildCallbackEntrypointMap(index) {
618
737
  // not exported handler functions — they must not be marked as
619
738
  // entry points. This aligns the HTTP Routes section with
620
739
  // bridge.js's extractServerRoutes.
621
- if (!index.symbols.has(call.name)) continue;
740
+ const def = resolveHandlerDef(call.name, filePath);
741
+ if (!def) continue;
742
+ attributedLines.add(call.line);
622
743
 
623
744
  if (!result.has(call.name)) {
624
- const def = resolveHandlerDef(call.name, filePath);
625
745
  result.set(call.name, {
626
746
  framework: route.pattern.framework,
627
747
  type: route.pattern.type,
@@ -644,7 +764,16 @@ function buildCallbackEntrypointMap(index) {
644
764
  if (!enc || !enc.name || enc.name === '<anonymous>') continue;
645
765
  const route = routeLines.get(enc.startLine);
646
766
  if (!route || handledLines.has(enc.startLine)) continue;
647
- if (index.symbols.has(enc.name)) continue; // real defs took pass 2
767
+ // Real defs took pass 2 (they arrive as function
768
+ // REFERENCES on the route line). The inline expression's
769
+ // own indexed symbol (bodyScopedName, anchored at this
770
+ // registration line) is not a pass-2 shape — it IS the
771
+ // handler this pass exists to surface.
772
+ const encDefs = index.symbols.get(enc.name) || [];
773
+ const inlineDef = encDefs.find(d => d.bodyScopedName &&
774
+ d.file === filePath && d.startLine === enc.startLine);
775
+ if (encDefs.length > 0 && !inlineDef) continue;
776
+ attributedLines.add(enc.startLine);
648
777
  if (!result.has(enc.name)) {
649
778
  result.set(enc.name, {
650
779
  framework: route.pattern.framework,
@@ -658,6 +787,33 @@ function buildCallbackEntrypointMap(index) {
658
787
  });
659
788
  }
660
789
  }
790
+
791
+ // Pass 2c: routes with no attributable handler — anonymous
792
+ // arrows (`app.get('/e', (req, res) => …)`) or handlers UCN
793
+ // cannot name. The ROUTE is still a framework entry point:
794
+ // `endpoints` lists it, and `entrypoints` must agree.
795
+ // Literal-path routes only — the same rule that keeps
796
+ // library-internal registration helpers (gin's
797
+ // `group.GET(relativePath, handler)`) out of the route list.
798
+ for (const [line, route] of routeLines) {
799
+ if (attributedLines.has(line)) continue;
800
+ if (!route.call.firstStringArg || route.call.firstStringArgInterp) continue;
801
+ const key = `<anonymous>@${filePath}:${line}`;
802
+ if (!result.has(key)) {
803
+ result.set(key, {
804
+ name: '<anonymous>',
805
+ framework: route.pattern.framework,
806
+ type: route.pattern.type,
807
+ patternId: route.pattern.id,
808
+ method: route.call.name.toUpperCase(),
809
+ file: filePath,
810
+ line,
811
+ registrationFile: filePath,
812
+ registrationLine: line,
813
+ route: route.call.firstStringArg,
814
+ });
815
+ }
816
+ }
661
817
  }
662
818
  }
663
819
 
@@ -797,7 +953,7 @@ function detectEntrypoints(index, options = {}) {
797
953
  if (!fileEntry) continue;
798
954
 
799
955
  // Check decorator/modifier-based patterns
800
- const match = matchDecoratorOrModifier(symbol, fileEntry.language);
956
+ const match = matchDecoratorOrModifier(symbol, fileEntry.language, fileEntry);
801
957
  if (match) {
802
958
  const key = `${symbol.file}:${symbol.startLine}:${name}`;
803
959
  if (seen.has(key)) continue;
@@ -820,6 +976,7 @@ function detectEntrypoints(index, options = {}) {
820
976
  // Check name-based patterns (main, init, TestXxx, etc.)
821
977
  for (const np of namePatterns) {
822
978
  if (!np.languages.has(fileEntry.language)) continue;
979
+ if (NON_CALLABLE_TYPES.has(symbol.type)) continue;
823
980
  // Per-pattern symbol predicate (fix #243) — e.g. main must be
824
981
  // a free function (Rust/Go) or a static method (Java)
825
982
  if (np.symbolFilter && !np.symbolFilter(symbol)) continue;
@@ -848,14 +1005,19 @@ function detectEntrypoints(index, options = {}) {
848
1005
  // 2. Add call-pattern-based entry points (route handlers).
849
1006
  // Run BEFORE file-level patterns so framework labels (express, gin, etc.) win
850
1007
  // over generic file-level labels (e.g. server.js / index.js js-cli-main).
851
- for (const [name, info] of callbackMap) {
1008
+ for (const [mapKey, info] of callbackMap) {
1009
+ // Anonymous-handler routes are stored under a synthetic unique key;
1010
+ // the display name lives in info.name.
1011
+ const name = info.name || mapKey;
852
1012
  const fileEntry = index.files.get(info.file);
853
1013
  const relPath = fileEntry?.relativePath || info.file;
854
1014
  const key = `${info.file}:${info.line}:${name}`;
855
1015
  if (seen.has(key)) continue;
856
1016
  seen.add(key);
857
1017
 
858
- let evidence = `${info.method} route handler`;
1018
+ let evidence = info.route
1019
+ ? `${info.method} ${info.route} — inline anonymous handler`
1020
+ : `${info.method} route handler`;
859
1021
  let registeredAt;
860
1022
  if (info.registrationFile) {
861
1023
  const regEntry = index.files.get(info.registrationFile);
@@ -965,10 +1127,10 @@ function detectEntrypoints(index, options = {}) {
965
1127
  // (3) already covers `if (require.main === module) { main(); }`.
966
1128
  }
967
1129
 
968
- // If we identified at least one specific entry, use that set.
969
- // Otherwise fall back to permissive (null) so a CLI file with neither
970
- // `main()` nor a clear default-export is still seeded somehow.
971
- narrowAllowedByFile.set(filePath, allowed.size > 0 ? allowed : null);
1130
+ // Empty means no callable entry was proven. Do not turn an
1131
+ // ordinary `index.ts`/`server.ts` module into an entry point by
1132
+ // falling back to every symbol in the file.
1133
+ narrowAllowedByFile.set(filePath, allowed);
972
1134
  }
973
1135
  }
974
1136
 
@@ -1039,8 +1201,8 @@ function detectEntrypoints(index, options = {}) {
1039
1201
  const raw = Array.isArray(options.exclude) ? options.exclude : options.exclude.split(',');
1040
1202
  const patterns = raw.map(s => s.trim()).filter(Boolean);
1041
1203
  if (patterns.length > 0) {
1042
- const regexes = patterns.map(p => new RegExp(`(^|[/._-])${p}s?([/._-]|$)`, 'i'));
1043
- filtered = filtered.filter(e => !regexes.some(r => r.test(e.file)));
1204
+ filtered = filtered.filter(e =>
1205
+ index.matchesFilters(e.file, { exclude: patterns }));
1044
1206
  }
1045
1207
  }
1046
1208
 
@@ -1161,7 +1323,7 @@ function computeReachability(index) {
1161
1323
  langModule = langModuleCache.get(lang);
1162
1324
  } else {
1163
1325
  try {
1164
- langModule = getLanguageModule(lang);
1326
+ langModule = getLanguageAdapter(lang);
1165
1327
  } catch (_e) {
1166
1328
  langModule = null;
1167
1329
  }
@@ -1286,7 +1448,7 @@ function isFrameworkEntrypoint(symbol, index) {
1286
1448
  if (!fileEntry) return false;
1287
1449
 
1288
1450
  // Fast path: check decorator/modifier patterns (no index scan needed)
1289
- if (matchDecoratorOrModifier(symbol, fileEntry.language)) {
1451
+ if (matchDecoratorOrModifier(symbol, fileEntry.language, fileEntry)) {
1290
1452
  return true;
1291
1453
  }
1292
1454