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.
- 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 +445 -300
- 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 -131
- package/core/cache.js +533 -11
- package/core/callers.js +5533 -494
- 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 +421 -20
- package/core/discovery.js +359 -46
- package/core/entrypoints.js +204 -42
- 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 +216 -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 -177
- package/core/public-command.js +47 -0
- package/core/registry.js +247 -117
- package/core/reporting.js +312 -290
- package/core/search.js +371 -116
- 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 +428 -16
- package/languages/javascript.js +452 -49
- package/languages/python.js +1041 -32
- package/languages/rust.js +1415 -152
- package/languages/utils.js +40 -3
- package/mcp/server.js +254 -636
- package/package.json +41 -24
- 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,
|
|
@@ -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
|
-
|
|
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 [
|
|
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 =
|
|
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
|
-
//
|
|
969
|
-
//
|
|
970
|
-
//
|
|
971
|
-
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);
|
|
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
|
-
|
|
1043
|
-
|
|
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 =
|
|
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
|
|