ucn 4.2.3 → 5.0.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.
- package/.claude/skills/ucn/SKILL.md +89 -77
- package/.claude/skills/ucn/references/commands.md +62 -68
- package/.claude/skills/ucn/references/trust-contract.md +31 -6
- package/README.md +438 -305
- package/assets/demo.svg +31 -0
- package/cli/index.js +430 -1385
- package/core/account.js +144 -34
- package/core/analysis.js +182 -72
- package/core/ast-analysis.js +279 -0
- package/core/bridge.js +205 -24
- package/core/brief.js +27 -58
- package/core/build-worker.js +21 -140
- package/core/cache.js +513 -11
- package/core/callers.js +4920 -456
- package/core/check.js +13 -4
- package/core/command-contracts.js +402 -0
- package/core/compilation-database.js +276 -0
- package/core/confidence.js +4 -1
- package/core/deadcode.js +397 -19
- package/core/discovery.js +359 -46
- package/core/entrypoints.js +195 -41
- package/core/execute.js +887 -81
- package/core/graph-build.js +162 -7
- package/core/graph.js +53 -77
- package/core/imports.js +65 -6
- package/core/index-ir.js +138 -0
- package/core/ir.js +195 -0
- package/core/output/analysis.js +212 -22
- package/core/output/brief.js +23 -0
- package/core/output/check.js +4 -0
- package/core/output/doctor.js +37 -6
- package/core/output/endpoints.js +5 -2
- package/core/output/extraction.js +24 -12
- package/core/output/find.js +141 -36
- package/core/output/graph.js +11 -5
- package/core/output/public.js +462 -0
- package/core/output/refactoring.js +42 -10
- package/core/output/reporting.js +97 -20
- package/core/output/search.js +24 -16
- package/core/output/shared.js +22 -1
- package/core/output/tracing.js +30 -15
- package/core/output-budget.js +295 -0
- package/core/output.js +1 -0
- package/core/parallel-build.js +44 -11
- package/core/parser.js +3 -3
- package/core/project.js +384 -187
- package/core/public-command.js +47 -0
- package/core/registry.js +247 -117
- package/core/reporting.js +312 -290
- package/core/search.js +317 -185
- package/core/semantic-provider.js +110 -0
- package/core/stacktrace.js +25 -0
- package/core/tracing.js +101 -51
- package/core/trust-matrix.js +19 -40
- package/core/verify.js +534 -37
- package/languages/adapter.js +218 -0
- package/languages/c-family.js +2791 -0
- package/languages/c.js +3 -0
- package/languages/cpp.js +3 -0
- package/languages/csharp.js +1402 -0
- package/languages/go.js +60 -21
- package/languages/html.js +2 -2
- package/languages/index.js +85 -7
- package/languages/java.js +396 -13
- package/languages/javascript.js +199 -19
- package/languages/python.js +964 -22
- package/languages/rust.js +1317 -152
- package/languages/utils.js +40 -3
- package/mcp/server.js +254 -636
- package/package.json +39 -22
- package/eslint.config.js +0 -43
- package/jsconfig.json +0 -10
package/core/entrypoints.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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)$|(^|\/)(
|
|
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
|
-
//
|
|
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
|
|
476
|
-
//
|
|
477
|
-
//
|
|
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: '
|
|
579
|
+
id: 'flask-event-hook',
|
|
480
580
|
languages: new Set(['python']),
|
|
481
581
|
type: 'events',
|
|
482
|
-
framework: '
|
|
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: '
|
|
492
|
-
languages: new Set(['
|
|
493
|
-
type: '
|
|
494
|
-
framework: '
|
|
495
|
-
detection: '
|
|
496
|
-
pattern: /^(
|
|
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)
|
|
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)
|
|
627
|
+
if (matched) matches.push({ pattern: fp, matchedOn: `@${matched}` });
|
|
525
628
|
}
|
|
526
629
|
}
|
|
527
|
-
|
|
528
|
-
|
|
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.
|
|
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
|
-
|
|
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,
|
|
@@ -653,6 +773,7 @@ function buildCallbackEntrypointMap(index) {
|
|
|
653
773
|
const inlineDef = encDefs.find(d => d.bodyScopedName &&
|
|
654
774
|
d.file === filePath && d.startLine === enc.startLine);
|
|
655
775
|
if (encDefs.length > 0 && !inlineDef) continue;
|
|
776
|
+
attributedLines.add(enc.startLine);
|
|
656
777
|
if (!result.has(enc.name)) {
|
|
657
778
|
result.set(enc.name, {
|
|
658
779
|
framework: route.pattern.framework,
|
|
@@ -666,6 +787,33 @@ function buildCallbackEntrypointMap(index) {
|
|
|
666
787
|
});
|
|
667
788
|
}
|
|
668
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
|
+
}
|
|
669
817
|
}
|
|
670
818
|
}
|
|
671
819
|
|
|
@@ -805,7 +953,7 @@ function detectEntrypoints(index, options = {}) {
|
|
|
805
953
|
if (!fileEntry) continue;
|
|
806
954
|
|
|
807
955
|
// Check decorator/modifier-based patterns
|
|
808
|
-
const match = matchDecoratorOrModifier(symbol, fileEntry.language);
|
|
956
|
+
const match = matchDecoratorOrModifier(symbol, fileEntry.language, fileEntry);
|
|
809
957
|
if (match) {
|
|
810
958
|
const key = `${symbol.file}:${symbol.startLine}:${name}`;
|
|
811
959
|
if (seen.has(key)) continue;
|
|
@@ -828,6 +976,7 @@ function detectEntrypoints(index, options = {}) {
|
|
|
828
976
|
// Check name-based patterns (main, init, TestXxx, etc.)
|
|
829
977
|
for (const np of namePatterns) {
|
|
830
978
|
if (!np.languages.has(fileEntry.language)) continue;
|
|
979
|
+
if (NON_CALLABLE_TYPES.has(symbol.type)) continue;
|
|
831
980
|
// Per-pattern symbol predicate (fix #243) — e.g. main must be
|
|
832
981
|
// a free function (Rust/Go) or a static method (Java)
|
|
833
982
|
if (np.symbolFilter && !np.symbolFilter(symbol)) continue;
|
|
@@ -856,14 +1005,19 @@ function detectEntrypoints(index, options = {}) {
|
|
|
856
1005
|
// 2. Add call-pattern-based entry points (route handlers).
|
|
857
1006
|
// Run BEFORE file-level patterns so framework labels (express, gin, etc.) win
|
|
858
1007
|
// over generic file-level labels (e.g. server.js / index.js js-cli-main).
|
|
859
|
-
for (const [
|
|
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;
|
|
860
1012
|
const fileEntry = index.files.get(info.file);
|
|
861
1013
|
const relPath = fileEntry?.relativePath || info.file;
|
|
862
1014
|
const key = `${info.file}:${info.line}:${name}`;
|
|
863
1015
|
if (seen.has(key)) continue;
|
|
864
1016
|
seen.add(key);
|
|
865
1017
|
|
|
866
|
-
let evidence =
|
|
1018
|
+
let evidence = info.route
|
|
1019
|
+
? `${info.method} ${info.route} — inline anonymous handler`
|
|
1020
|
+
: `${info.method} route handler`;
|
|
867
1021
|
let registeredAt;
|
|
868
1022
|
if (info.registrationFile) {
|
|
869
1023
|
const regEntry = index.files.get(info.registrationFile);
|
|
@@ -973,10 +1127,10 @@ function detectEntrypoints(index, options = {}) {
|
|
|
973
1127
|
// (3) already covers `if (require.main === module) { main(); }`.
|
|
974
1128
|
}
|
|
975
1129
|
|
|
976
|
-
//
|
|
977
|
-
//
|
|
978
|
-
//
|
|
979
|
-
narrowAllowedByFile.set(filePath, allowed
|
|
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);
|
|
980
1134
|
}
|
|
981
1135
|
}
|
|
982
1136
|
|
|
@@ -1047,8 +1201,8 @@ function detectEntrypoints(index, options = {}) {
|
|
|
1047
1201
|
const raw = Array.isArray(options.exclude) ? options.exclude : options.exclude.split(',');
|
|
1048
1202
|
const patterns = raw.map(s => s.trim()).filter(Boolean);
|
|
1049
1203
|
if (patterns.length > 0) {
|
|
1050
|
-
|
|
1051
|
-
|
|
1204
|
+
filtered = filtered.filter(e =>
|
|
1205
|
+
index.matchesFilters(e.file, { exclude: patterns }));
|
|
1052
1206
|
}
|
|
1053
1207
|
}
|
|
1054
1208
|
|
|
@@ -1169,7 +1323,7 @@ function computeReachability(index) {
|
|
|
1169
1323
|
langModule = langModuleCache.get(lang);
|
|
1170
1324
|
} else {
|
|
1171
1325
|
try {
|
|
1172
|
-
langModule =
|
|
1326
|
+
langModule = getLanguageAdapter(lang);
|
|
1173
1327
|
} catch (_e) {
|
|
1174
1328
|
langModule = null;
|
|
1175
1329
|
}
|
|
@@ -1294,7 +1448,7 @@ function isFrameworkEntrypoint(symbol, index) {
|
|
|
1294
1448
|
if (!fileEntry) return false;
|
|
1295
1449
|
|
|
1296
1450
|
// Fast path: check decorator/modifier patterns (no index scan needed)
|
|
1297
|
-
if (matchDecoratorOrModifier(symbol, fileEntry.language)) {
|
|
1451
|
+
if (matchDecoratorOrModifier(symbol, fileEntry.language, fileEntry)) {
|
|
1298
1452
|
return true;
|
|
1299
1453
|
}
|
|
1300
1454
|
|