@sun-asterisk/sunlint 1.3.48 → 1.3.49
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/core/file-targeting-service.js +148 -15
- package/core/init-command.js +118 -70
- package/core/project-detector.js +517 -0
- package/core/tui-select.js +245 -0
- package/engines/arch-detect/rules/layered/l001-presentation-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l002-business-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l003-data-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l004-model-layer.js +7 -15
- package/engines/arch-detect/rules/layered/l005-layer-separation.js +22 -2
- package/engines/arch-detect/rules/layered/l006-dependency-direction.js +8 -5
- package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +67 -29
- package/engines/arch-detect/rules/presentation/pr001-view-layer.js +16 -9
- package/engines/arch-detect/rules/presentation/pr006-router-layer.js +33 -8
- package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +35 -6
- package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +56 -10
- package/package.json +1 -1
- package/skill-assets/sunlint-code-quality/rules/dart/C006-verb-noun-functions.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C013-no-dead-code.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C014-dependency-injection.md +92 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C017-no-constructor-logic.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C018-generic-errors.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C019-error-log-level.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C020-no-unused-imports.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C022-no-unused-variables.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C023-no-duplicate-names.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C024-centralize-constants.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C029-catch-log-root-cause.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C030-custom-error-classes.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C033-separate-data-access.md +90 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C035-error-context-logging.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C041-no-hardcoded-secrets.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C042-boolean-naming.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C052-widget-parsing.md +84 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C060-superclass-logic.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/dart/C067-no-hardcoded-config.md +108 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/AGENTS.md +149 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN001-abort-after-response.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN002-request-context.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN003-bind-error-handling.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN004-dependency-injection.md +78 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN005-route-groups-middleware.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN006-http-status-codes.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN007-release-mode.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN008-struct-validation-tags.md +90 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN009-recovery-middleware.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN010-context-scope.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN011-middleware-concerns.md +92 -0
- package/skill-assets/sunlint-code-quality/rules/go-gin/GN012-no-log-sensitive.md +84 -0
- package/skill-assets/sunlint-code-quality/rules/java/J001-try-with-resources.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/java/J002-equals-and-hashcode.md +88 -0
- package/skill-assets/sunlint-code-quality/rules/java/J003-string-comparison.md +72 -0
- package/skill-assets/sunlint-code-quality/rules/java/J004-use-java-time.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/java/J005-no-print-stack-trace.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/java/J006-no-system-println.md +89 -0
- package/skill-assets/sunlint-code-quality/rules/java/J007-proper-logger.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/java/J008-thread-safe-singleton.md +119 -0
- package/skill-assets/sunlint-code-quality/rules/java/J009-utility-class-constructor.md +82 -0
- package/skill-assets/sunlint-code-quality/rules/java/J010-preserve-stack-trace.md +119 -0
- package/skill-assets/sunlint-code-quality/rules/java/J011-null-safe-compare.md +88 -0
- package/skill-assets/sunlint-code-quality/rules/java/J012-use-enum-collections.md +104 -0
- package/skill-assets/sunlint-code-quality/rules/java/J013-return-empty-not-null.md +102 -0
- package/skill-assets/sunlint-code-quality/rules/java/J014-hardcoded-crypto-key.md +108 -0
- package/skill-assets/sunlint-code-quality/rules/java/J015-optional-instead-of-null.md +109 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/AGENTS.md +124 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV001-form-request-validation.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV002-eager-load-no-n-plus-1.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV003-config-not-env.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV004-fillable-mass-assignment.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV005-policies-gates-authorization.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV006-queue-heavy-tasks.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV007-hash-passwords.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV008-route-model-binding.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV009-api-resources.md +72 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV010-chunk-large-datasets.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV011-db-transactions.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV012-service-layer.md +78 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV013-testing-factories.md +75 -0
- package/skill-assets/sunlint-code-quality/rules/php-laravel/LV014-service-container.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/python/P001-mutable-default-argument.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/python/P002-specify-file-encoding.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/python/P003-context-manager-for-resources.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/python/P004-no-bare-except.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/python/P005-use-isinstance.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/python/P006-timezone-aware-datetime.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/python/P007-use-pathlib.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/python/P008-no-wildcard-import.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/python/P009-logging-lazy-format.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/python/P010-exception-chaining.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/python/P011-subprocess-check.md +59 -0
- package/skill-assets/sunlint-code-quality/rules/python/P012-requests-timeout.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/python/P013-no-global-statement.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/python/P014-no-modify-collection-while-iterating.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/python/P015-prefer-fstrings.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/AGENTS.md +121 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR001-strong-parameters.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR002-eager-load-includes.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR003-service-objects.md +99 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR004-active-job-background.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR005-pagination.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR006-find-each-batches.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR007-http-status-codes.md +76 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR008-before-action-auth.md +77 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR009-rails-credentials.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR010-scopes.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR011-counter-cache.md +59 -0
- package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR012-render-json-status.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C006-verb-noun-functions.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C013-no-dead-code.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C014-dependency-injection.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C017-no-constructor-logic.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C018-generic-errors.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C019-error-log-level.md +64 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C020-no-unused-imports.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C022-no-unused-variables.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C023-no-duplicate-names.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C024-centralize-constants.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C029-catch-log-root-cause.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C030-custom-error-classes.md +77 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C033-separate-data-access.md +89 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C035-error-context-logging.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C041-no-hardcoded-secrets.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C042-boolean-naming.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C052-controller-parsing.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C060-superclass-logic.md +95 -0
- package/skill-assets/sunlint-code-quality/rules/swift/C067-no-hardcoded-config.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S003-sql-injection.md +65 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S004-no-log-credentials.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S005-server-authorization.md +73 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S006-default-credentials.md +76 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S007-output-encoding.md +96 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S009-approved-crypto.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S010-csprng.md +71 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S011-insecure-deserialization.md +74 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S012-secrets-management.md +81 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S013-tls-connections.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S017-parameterized-queries.md +86 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S019-session-management.md +131 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S020-kvc-injection.md +91 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S025-input-validation.md +125 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S029-brute-force-protection.md +120 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S036-path-traversal.md +102 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S039-tls-certificate-validation.md +109 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S041-logout-invalidation.md +103 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S043-password-hashing.md +116 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S044-critical-changes-reauth.md +145 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S045-debug-info-exposure.md +116 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S046-unvalidated-redirect.md +140 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S051-token-expiry.md +134 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S053-jwt-validation.md +139 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S059-background-snapshot-protection.md +113 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S060-data-protection-api.md +106 -0
- package/skill-assets/sunlint-code-quality/rules/swift/S061-jailbreak-detection.md +132 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SunLint TUI Select Widget
|
|
3
|
+
* Arrow-key driven interactive selection for terminal.
|
|
4
|
+
* Zero external dependencies - uses Node.js built-in readline + raw mode.
|
|
5
|
+
*
|
|
6
|
+
* Rule C005: Single responsibility - terminal selection UI only
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const readline = require('readline');
|
|
12
|
+
|
|
13
|
+
// ─── ANSI helpers ────────────────────────────────────────────────────────────
|
|
14
|
+
const ansi = {
|
|
15
|
+
saveCursor: '\x1b7',
|
|
16
|
+
restoreCursor: '\x1b8',
|
|
17
|
+
clearToEnd: '\x1b[J',
|
|
18
|
+
clearLine: '\x1b[2K\r',
|
|
19
|
+
hideCursor: '\x1b[?25l',
|
|
20
|
+
showCursor: '\x1b[?25h',
|
|
21
|
+
reset: '\x1b[0m',
|
|
22
|
+
bold: '\x1b[1m',
|
|
23
|
+
dim: '\x1b[2m',
|
|
24
|
+
cyan: '\x1b[36m',
|
|
25
|
+
blue: '\x1b[34m',
|
|
26
|
+
green: '\x1b[32m',
|
|
27
|
+
yellow: '\x1b[33m',
|
|
28
|
+
magenta: '\x1b[35m',
|
|
29
|
+
white: '\x1b[37m',
|
|
30
|
+
gray: '\x1b[90m',
|
|
31
|
+
bgCyan: '\x1b[46m',
|
|
32
|
+
bgBlue: '\x1b[44m',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function c(colorKey, text) {
|
|
36
|
+
return `${ansi[colorKey] || ''}${text}${ansi.reset}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function bold(text) { return `${ansi.bold}${text}${ansi.reset}`; }
|
|
40
|
+
function dim(text) { return `${ansi.dim}${text}${ansi.reset}`; }
|
|
41
|
+
|
|
42
|
+
// ─── Banner ──────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Print the SunLint init banner
|
|
46
|
+
*/
|
|
47
|
+
function printBanner() {
|
|
48
|
+
const line = '─'.repeat(58);
|
|
49
|
+
process.stdout.write('\n');
|
|
50
|
+
process.stdout.write(c('cyan', ` ┌${line}┐\n`));
|
|
51
|
+
process.stdout.write(c('cyan', ` │`) + bold(' ☀️ SunLint Init') + c('gray', ' · Interactive Project Setup') + c('cyan', ' │\n'));
|
|
52
|
+
process.stdout.write(c('cyan', ` └${line}┘\n`));
|
|
53
|
+
process.stdout.write('\n');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Detection info ──────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Print the auto-detection result hint.
|
|
60
|
+
* @param {import('./project-detector').DetectionResult | null} detection
|
|
61
|
+
*/
|
|
62
|
+
function printDetectionHint(detection) {
|
|
63
|
+
if (!detection) {
|
|
64
|
+
process.stdout.write(c('gray', ' 🔍 No framework detected — default preset: TypeScript\n\n'));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const badge = detection.confidence === 'high'
|
|
69
|
+
? c('green', '● high confidence')
|
|
70
|
+
: c('yellow', '◐ medium confidence');
|
|
71
|
+
|
|
72
|
+
process.stdout.write(
|
|
73
|
+
` ✨ ${bold('Auto-detected:')} ${c('cyan', detection.framework)} ${badge}\n`
|
|
74
|
+
);
|
|
75
|
+
process.stdout.write(
|
|
76
|
+
c('gray', ` Signals: ${detection.signals.slice(0, 4).join(', ')}\n`)
|
|
77
|
+
);
|
|
78
|
+
process.stdout.write('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ─── Core TUI select ─────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Show an interactive arrow-key selection list.
|
|
85
|
+
*
|
|
86
|
+
* @param {Object} params
|
|
87
|
+
* @param {string} params.question - Prompt label
|
|
88
|
+
* @param {string[]} params.options - List of option values
|
|
89
|
+
* @param {Object} [params.labels] - Optional display label map { value: 'Label' }
|
|
90
|
+
* @param {Object} [params.badges] - Optional badge map { value: '[badge text]' }
|
|
91
|
+
* @param {number} [params.defaultIndex] - Pre-selected index (0-based)
|
|
92
|
+
* @returns {Promise<string>} - The selected option value
|
|
93
|
+
*/
|
|
94
|
+
function tuiSelect({ question, options, labels = {}, badges = {}, defaultIndex = 0 }) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
97
|
+
resolve(options[defaultIndex] || options[0]);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let selected = Math.max(0, Math.min(defaultIndex, options.length - 1));
|
|
102
|
+
|
|
103
|
+
function renderLabel(value) {
|
|
104
|
+
return labels[value] || value;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function renderBadge(value) {
|
|
108
|
+
return badges[value] ? c('green', ` ${badges[value]}`) : '';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function render(firstRender = false) {
|
|
112
|
+
if (firstRender) {
|
|
113
|
+
// Save cursor position BEFORE first render
|
|
114
|
+
process.stdout.write(ansi.saveCursor);
|
|
115
|
+
} else {
|
|
116
|
+
// Restore to saved position, then clear everything below
|
|
117
|
+
process.stdout.write(ansi.restoreCursor + ansi.clearToEnd);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Question line
|
|
121
|
+
process.stdout.write(
|
|
122
|
+
` ${c('cyan', '?')} ${bold(question)}\n`
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Hint line
|
|
126
|
+
process.stdout.write(
|
|
127
|
+
` ${dim('Use ↑↓ arrows · Enter to confirm · Esc to cancel')}\n\n`
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Option lines
|
|
131
|
+
for (let i = 0; i < options.length; i++) {
|
|
132
|
+
const value = options[i];
|
|
133
|
+
const label = renderLabel(value);
|
|
134
|
+
const badge = renderBadge(value);
|
|
135
|
+
const isActive = i === selected;
|
|
136
|
+
|
|
137
|
+
const pointer = isActive ? c('cyan', ' ❯ ') : ' ';
|
|
138
|
+
const dot = isActive ? c('cyan', '●') : c('gray', '○');
|
|
139
|
+
const text = isActive
|
|
140
|
+
? bold(c('white', label))
|
|
141
|
+
: c('gray', label);
|
|
142
|
+
|
|
143
|
+
process.stdout.write(`${pointer}${dot} ${text}${badge}\n`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Hide cursor and switch to raw mode
|
|
148
|
+
process.stdout.write(ansi.hideCursor);
|
|
149
|
+
process.stdin.setRawMode(true);
|
|
150
|
+
process.stdin.resume();
|
|
151
|
+
process.stdin.setEncoding('utf8');
|
|
152
|
+
|
|
153
|
+
render(true);
|
|
154
|
+
|
|
155
|
+
function cleanup() {
|
|
156
|
+
process.stdin.setRawMode(false);
|
|
157
|
+
process.stdin.pause();
|
|
158
|
+
process.stdout.write(ansi.showCursor);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function onKeypress(key) {
|
|
162
|
+
if (key === '\u001b[A' || key === '\u001b[D') {
|
|
163
|
+
// Up / Left
|
|
164
|
+
selected = (selected - 1 + options.length) % options.length;
|
|
165
|
+
render();
|
|
166
|
+
} else if (key === '\u001b[B' || key === '\u001b[C') {
|
|
167
|
+
// Down / Right
|
|
168
|
+
selected = (selected + 1) % options.length;
|
|
169
|
+
render();
|
|
170
|
+
} else if (key === '\r' || key === '\n') {
|
|
171
|
+
// Enter
|
|
172
|
+
cleanup();
|
|
173
|
+
process.stdin.removeListener('data', onKeypress);
|
|
174
|
+
|
|
175
|
+
// Replace the whole rendered block with a single summary line
|
|
176
|
+
const value = options[selected];
|
|
177
|
+
const label = renderLabel(value);
|
|
178
|
+
const badge = renderBadge(value);
|
|
179
|
+
process.stdout.write(
|
|
180
|
+
ansi.restoreCursor + ansi.clearToEnd +
|
|
181
|
+
` ${c('green', '✔')} ${bold(question)} ${c('cyan', label)}${badge}\n\n`
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
resolve(value);
|
|
185
|
+
} else if (key === '\u001b' || key === '\u0003') {
|
|
186
|
+
// Esc / Ctrl+C
|
|
187
|
+
cleanup();
|
|
188
|
+
process.stdin.removeListener('data', onKeypress);
|
|
189
|
+
process.stdout.write(ansi.restoreCursor + ansi.clearToEnd + '\n');
|
|
190
|
+
reject(new Error('Cancelled by user'));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
process.stdin.on('data', onKeypress);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ─── Confirm prompt ──────────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Simple yes/no confirmation.
|
|
202
|
+
* @param {string} question
|
|
203
|
+
* @param {boolean} [defaultYes=true]
|
|
204
|
+
* @returns {Promise<boolean>}
|
|
205
|
+
*/
|
|
206
|
+
function tuiConfirm(question, defaultYes = true) {
|
|
207
|
+
return new Promise((resolve, reject) => {
|
|
208
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
209
|
+
resolve(defaultYes);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const hint = defaultYes ? '(Y/n)' : '(y/N)';
|
|
214
|
+
process.stdout.write(` ${c('cyan', '?')} ${bold(question)} ${c('gray', hint)} `);
|
|
215
|
+
|
|
216
|
+
process.stdin.setRawMode(true);
|
|
217
|
+
process.stdin.resume();
|
|
218
|
+
process.stdin.setEncoding('utf8');
|
|
219
|
+
|
|
220
|
+
function onKey(key) {
|
|
221
|
+
process.stdin.setRawMode(false);
|
|
222
|
+
process.stdin.pause();
|
|
223
|
+
process.stdin.removeListener('data', onKey);
|
|
224
|
+
|
|
225
|
+
if (key === '\u0003') {
|
|
226
|
+
process.stdout.write('\n');
|
|
227
|
+
reject(new Error('Cancelled by user'));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const input = key.toLowerCase().trim();
|
|
232
|
+
let answer;
|
|
233
|
+
if (input === 'y') answer = true;
|
|
234
|
+
else if (input === 'n') answer = false;
|
|
235
|
+
else answer = defaultYes;
|
|
236
|
+
|
|
237
|
+
process.stdout.write(`${answer ? c('green', 'Yes') : c('yellow', 'No')}\n`);
|
|
238
|
+
resolve(answer);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
process.stdin.on('data', onKey);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
module.exports = { printBanner, printDetectionHint, tuiSelect, tuiConfirm };
|
|
@@ -125,8 +125,8 @@ let L001PresentationLayer = (() => {
|
|
|
125
125
|
}
|
|
126
126
|
async customDetection(context) {
|
|
127
127
|
const matches = [];
|
|
128
|
-
let
|
|
129
|
-
let
|
|
128
|
+
let _hasFolders = false;
|
|
129
|
+
let _hasFiles = false;
|
|
130
130
|
let hasCode = false;
|
|
131
131
|
// Check for controller folders
|
|
132
132
|
const controllerFolders = [
|
|
@@ -147,7 +147,7 @@ let L001PresentationLayer = (() => {
|
|
|
147
147
|
path: folder,
|
|
148
148
|
matchedPattern: 'Controller folder',
|
|
149
149
|
});
|
|
150
|
-
|
|
150
|
+
_hasFolders = true;
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
// Check for controller files
|
|
@@ -162,7 +162,7 @@ let L001PresentationLayer = (() => {
|
|
|
162
162
|
matchedPattern: 'Controller file',
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
|
-
|
|
165
|
+
_hasFiles = true;
|
|
166
166
|
}
|
|
167
167
|
// Check for code patterns (sampling first 50 source files)
|
|
168
168
|
const sourceFiles = context.files
|
|
@@ -201,17 +201,9 @@ let L001PresentationLayer = (() => {
|
|
|
201
201
|
// Skip unreadable files
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
score = 1;
|
|
208
|
-
else if (hasFiles)
|
|
209
|
-
score = 0.7;
|
|
210
|
-
else if (hasCode)
|
|
211
|
-
score = 0.6;
|
|
212
|
-
else if (hasFolders)
|
|
213
|
-
score = 0.4;
|
|
214
|
-
return { matches, violations: [], score };
|
|
204
|
+
// Let BaseRule's language-aware scoring handle the score calculation
|
|
205
|
+
// customDetection only contributes additional matches
|
|
206
|
+
return { matches, violations: [] };
|
|
215
207
|
}
|
|
216
208
|
getMetadata(context, matches) {
|
|
217
209
|
const controllerCount = matches.filter((m) => m.matchedPattern === 'Controller file').length;
|
|
@@ -105,8 +105,8 @@ let L002BusinessLayer = (() => {
|
|
|
105
105
|
}
|
|
106
106
|
async customDetection(context) {
|
|
107
107
|
const matches = [];
|
|
108
|
-
let
|
|
109
|
-
let
|
|
108
|
+
let _hasFolders = false;
|
|
109
|
+
let _hasFiles = false;
|
|
110
110
|
let hasCode = false;
|
|
111
111
|
// Check for service folders
|
|
112
112
|
const serviceFolders = [
|
|
@@ -128,7 +128,7 @@ let L002BusinessLayer = (() => {
|
|
|
128
128
|
path: folder,
|
|
129
129
|
matchedPattern: 'Service folder',
|
|
130
130
|
});
|
|
131
|
-
|
|
131
|
+
_hasFolders = true;
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
134
|
// Check for service files
|
|
@@ -144,7 +144,7 @@ let L002BusinessLayer = (() => {
|
|
|
144
144
|
matchedPattern: 'Service file',
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
|
-
|
|
147
|
+
_hasFiles = true;
|
|
148
148
|
}
|
|
149
149
|
// Check for code patterns
|
|
150
150
|
const sourceFiles = context.files
|
|
@@ -179,17 +179,9 @@ let L002BusinessLayer = (() => {
|
|
|
179
179
|
// Skip unreadable files
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
score = 1;
|
|
186
|
-
else if (hasFiles)
|
|
187
|
-
score = 0.7;
|
|
188
|
-
else if (hasCode)
|
|
189
|
-
score = 0.6;
|
|
190
|
-
else if (hasFolders)
|
|
191
|
-
score = 0.4;
|
|
192
|
-
return { matches, violations: [], score };
|
|
182
|
+
// Let BaseRule's language-aware scoring handle the score calculation
|
|
183
|
+
// customDetection only contributes additional matches
|
|
184
|
+
return { matches, violations: [] };
|
|
193
185
|
}
|
|
194
186
|
getMetadata(context, matches) {
|
|
195
187
|
const serviceCount = matches.filter((m) => m.matchedPattern === 'Service file').length;
|
|
@@ -119,8 +119,8 @@ let L003DataLayer = (() => {
|
|
|
119
119
|
}
|
|
120
120
|
async customDetection(context) {
|
|
121
121
|
const matches = [];
|
|
122
|
-
let
|
|
123
|
-
let
|
|
122
|
+
let _hasFolders = false;
|
|
123
|
+
let _hasFiles = false;
|
|
124
124
|
let hasCode = false;
|
|
125
125
|
// Check for repository folders
|
|
126
126
|
const repoFolders = [
|
|
@@ -142,7 +142,7 @@ let L003DataLayer = (() => {
|
|
|
142
142
|
path: folder,
|
|
143
143
|
matchedPattern: 'Repository folder',
|
|
144
144
|
});
|
|
145
|
-
|
|
145
|
+
_hasFolders = true;
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
// Check for repository files
|
|
@@ -158,7 +158,7 @@ let L003DataLayer = (() => {
|
|
|
158
158
|
matchedPattern: 'Repository file',
|
|
159
159
|
});
|
|
160
160
|
}
|
|
161
|
-
|
|
161
|
+
_hasFiles = true;
|
|
162
162
|
}
|
|
163
163
|
// Check for code patterns
|
|
164
164
|
const sourceFiles = context.files
|
|
@@ -193,17 +193,9 @@ let L003DataLayer = (() => {
|
|
|
193
193
|
// Skip unreadable files
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
score = 1;
|
|
200
|
-
else if (hasFiles)
|
|
201
|
-
score = 0.7;
|
|
202
|
-
else if (hasCode)
|
|
203
|
-
score = 0.6;
|
|
204
|
-
else if (hasFolders)
|
|
205
|
-
score = 0.4;
|
|
206
|
-
return { matches, violations: [], score };
|
|
196
|
+
// Let BaseRule's language-aware scoring handle the score calculation
|
|
197
|
+
// customDetection only contributes additional matches
|
|
198
|
+
return { matches, violations: [] };
|
|
207
199
|
}
|
|
208
200
|
getMetadata(context, matches) {
|
|
209
201
|
const repoCount = matches.filter((m) => m.matchedPattern === 'Repository file').length;
|
|
@@ -91,8 +91,8 @@ let L004ModelLayer = (() => {
|
|
|
91
91
|
}
|
|
92
92
|
async customDetection(context) {
|
|
93
93
|
const matches = [];
|
|
94
|
-
let
|
|
95
|
-
let
|
|
94
|
+
let _hasFolders = false;
|
|
95
|
+
let _hasFiles = false;
|
|
96
96
|
let hasCode = false;
|
|
97
97
|
// Check for model folders
|
|
98
98
|
const modelFolders = [
|
|
@@ -114,7 +114,7 @@ let L004ModelLayer = (() => {
|
|
|
114
114
|
path: folder,
|
|
115
115
|
matchedPattern: 'Model folder',
|
|
116
116
|
});
|
|
117
|
-
|
|
117
|
+
_hasFolders = true;
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
// Check for model/entity files
|
|
@@ -132,7 +132,7 @@ let L004ModelLayer = (() => {
|
|
|
132
132
|
matchedPattern: 'Model file',
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
|
-
|
|
135
|
+
_hasFiles = true;
|
|
136
136
|
}
|
|
137
137
|
// Check for code patterns
|
|
138
138
|
const sourceFiles = context.files
|
|
@@ -168,17 +168,9 @@ let L004ModelLayer = (() => {
|
|
|
168
168
|
// Skip unreadable files
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
score = 1;
|
|
175
|
-
else if (hasFiles)
|
|
176
|
-
score = 0.7;
|
|
177
|
-
else if (hasCode)
|
|
178
|
-
score = 0.6;
|
|
179
|
-
else if (hasFolders)
|
|
180
|
-
score = 0.4;
|
|
181
|
-
return { matches, violations: [], score };
|
|
171
|
+
// Let BaseRule's language-aware scoring handle the score calculation
|
|
172
|
+
// customDetection only contributes additional matches
|
|
173
|
+
return { matches, violations: [] };
|
|
182
174
|
}
|
|
183
175
|
getMetadata(context, matches) {
|
|
184
176
|
const modelCount = matches.filter((m) => m.matchedPattern === 'Model file').length;
|
|
@@ -175,8 +175,28 @@ let L005LayerSeparation = (() => {
|
|
|
175
175
|
.filter((f) => !f.isTest && !f.isConfig && f.language)
|
|
176
176
|
.slice(0, 30);
|
|
177
177
|
const layerIndicators = {
|
|
178
|
-
presentation: [
|
|
179
|
-
|
|
178
|
+
presentation: [
|
|
179
|
+
/@Controller\b/, // Java Spring
|
|
180
|
+
/@RestController\b/, // Java Spring
|
|
181
|
+
/@Get\(/, // NestJS
|
|
182
|
+
/@Post\(/, // NestJS
|
|
183
|
+
/router\.(get|post)/, // Express
|
|
184
|
+
/\[ApiController\]/, // C#
|
|
185
|
+
/@app\.route\s*\(/, // Python Flask
|
|
186
|
+
/Route::(get|post)\s*\(/, // PHP Laravel
|
|
187
|
+
/r\.HandleFunc\s*\(/, // Go
|
|
188
|
+
/gin\.Context/, // Go Gin
|
|
189
|
+
],
|
|
190
|
+
data: [
|
|
191
|
+
/@Repository\b/, // Java Spring
|
|
192
|
+
/DbContext/, // C# EF
|
|
193
|
+
/\.query\s*\(/, // Generic
|
|
194
|
+
/DB::table\s*\(/, // PHP Laravel
|
|
195
|
+
/models\.Model/, // Python Django
|
|
196
|
+
/type\s+\w+Repository\s+struct/, // Go
|
|
197
|
+
/extends\s+Model\b/, // PHP Eloquent
|
|
198
|
+
/extends\s+(Jpa|Crud|Mongo)Repository\b/, // Java Spring Data
|
|
199
|
+
],
|
|
180
200
|
};
|
|
181
201
|
for (const file of sourceFiles) {
|
|
182
202
|
try {
|
|
@@ -118,11 +118,14 @@ let L006DependencyDirection = (() => {
|
|
|
118
118
|
.filter((f) => !f.isTest && !f.isConfig && f.language)
|
|
119
119
|
.slice(0, 50);
|
|
120
120
|
const importPatterns = [
|
|
121
|
-
/import\s+.*\s+from\s+['"]([^'"]+)['"]/g,
|
|
122
|
-
/import\s+['"]([^'"]+)['"]/g,
|
|
123
|
-
/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
124
|
-
/from\s+([a-zA-Z_][
|
|
125
|
-
/using\s+([a-zA-Z_.]+);/g,
|
|
121
|
+
/import\s+.*\s+from\s+['"]([^'"]+)['"]/g, // ES6 import
|
|
122
|
+
/import\s+['"]([^'"]+)['"]/g, // ES6 side-effect import
|
|
123
|
+
/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g, // CommonJS / Ruby require
|
|
124
|
+
/from\s+([a-zA-Z_][\w.]*)\s+import/g, // Python from...import
|
|
125
|
+
/using\s+([a-zA-Z_.]+);/g, // C# using
|
|
126
|
+
/import\s+([\w.]+);/g, // Java/Kotlin import
|
|
127
|
+
/use\s+([\w\\]+)/g, // PHP use
|
|
128
|
+
/import\s+"([^"]+)"/g, // Go import
|
|
126
129
|
];
|
|
127
130
|
for (const file of sourceFiles) {
|
|
128
131
|
const fromLayer = this.classifyFileLayer(file);
|
|
@@ -71,43 +71,81 @@ let M005NoDeepImports = (() => {
|
|
|
71
71
|
// Not enough modules to check cross-module imports
|
|
72
72
|
return { matches, violations, score: 0.5 };
|
|
73
73
|
}
|
|
74
|
-
// Check imports in source files
|
|
74
|
+
// Check imports in source files (all recognized languages)
|
|
75
75
|
const sourceFiles = context.files
|
|
76
|
-
.filter((f) => !f.isTest &&
|
|
77
|
-
!f.isConfig &&
|
|
78
|
-
(f.extension === '.ts' || f.extension === '.js' || f.extension === '.tsx'))
|
|
76
|
+
.filter((f) => !f.isTest && !f.isConfig && f.language != null)
|
|
79
77
|
.slice(0, 50);
|
|
80
|
-
|
|
78
|
+
// Language-aware import patterns
|
|
79
|
+
const importPatterns = [
|
|
80
|
+
// TypeScript/JavaScript ES6 imports
|
|
81
|
+
{
|
|
82
|
+
pattern: /import\s+.*\s+from\s+['"]([^'"]+)['"]/g,
|
|
83
|
+
languages: new Set(['typescript', 'javascript']),
|
|
84
|
+
},
|
|
85
|
+
// TypeScript/JavaScript require
|
|
86
|
+
{
|
|
87
|
+
pattern: /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
88
|
+
languages: new Set(['typescript', 'javascript', 'ruby']),
|
|
89
|
+
},
|
|
90
|
+
// Java/Kotlin imports
|
|
91
|
+
{ pattern: /import\s+([\w.]+)/g, languages: new Set(['java', 'kotlin']) },
|
|
92
|
+
// Python imports
|
|
93
|
+
{ pattern: /from\s+([\w.]+)\s+import/g, languages: new Set(['python']) },
|
|
94
|
+
// PHP use statements
|
|
95
|
+
{ pattern: /use\s+([\w\\]+)/g, languages: new Set(['php']) },
|
|
96
|
+
// Go imports
|
|
97
|
+
{ pattern: /import\s+"([^"]+)"/g, languages: new Set(['go']) },
|
|
98
|
+
// C# using
|
|
99
|
+
{ pattern: /using\s+([\w.]+);/g, languages: new Set(['csharp']) },
|
|
100
|
+
];
|
|
81
101
|
for (const file of sourceFiles) {
|
|
82
102
|
try {
|
|
83
103
|
const content = await context.helpers.readFile(file.absolutePath);
|
|
84
104
|
const fileModulePath = this.getModulePath(file.relativePath, modulePaths);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
deepImportCount++;
|
|
101
|
-
violations.push({
|
|
102
|
-
type: 'deep_import',
|
|
103
|
-
severity: enums_1.Severity.WARNING,
|
|
104
|
-
path: file.relativePath,
|
|
105
|
-
message: `Deep import into ${targetModulePath}: ${importPath}`,
|
|
106
|
-
suggestion: `Import from ${targetModulePath}/index instead`,
|
|
107
|
-
});
|
|
105
|
+
const fileLang = file.language || '';
|
|
106
|
+
// Select import patterns relevant to this file's language
|
|
107
|
+
const relevantPatterns = importPatterns.filter((ip) => ip.languages.has(fileLang));
|
|
108
|
+
for (const { pattern } of relevantPatterns) {
|
|
109
|
+
pattern.lastIndex = 0;
|
|
110
|
+
let match;
|
|
111
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
112
|
+
const importPath = match[1];
|
|
113
|
+
// Skip external packages based on language
|
|
114
|
+
if (fileLang === 'typescript' || fileLang === 'javascript') {
|
|
115
|
+
if (!importPath.startsWith('.') &&
|
|
116
|
+
!importPath.startsWith('@app') &&
|
|
117
|
+
!importPath.startsWith('@/')) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
108
120
|
}
|
|
109
121
|
else {
|
|
110
|
-
|
|
122
|
+
// For other languages, use folder-based module path resolution
|
|
123
|
+
// Skip if import path doesn't match any known module path segment
|
|
124
|
+
const matchesModule = modulePaths.some((mp) => {
|
|
125
|
+
const moduleName = mp.split('/').pop()?.toLowerCase() || '';
|
|
126
|
+
return importPath.toLowerCase().includes(moduleName);
|
|
127
|
+
});
|
|
128
|
+
if (!matchesModule)
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
// Check if this is a cross-module import
|
|
132
|
+
const targetModulePath = this.resolveImportToModule(importPath, file.relativePath, modulePaths);
|
|
133
|
+
if (targetModulePath && targetModulePath !== fileModulePath) {
|
|
134
|
+
// Cross-module import detected
|
|
135
|
+
const isDeepImport = this.isDeepImport(importPath, targetModulePath);
|
|
136
|
+
if (isDeepImport) {
|
|
137
|
+
deepImportCount++;
|
|
138
|
+
violations.push({
|
|
139
|
+
type: 'deep_import',
|
|
140
|
+
severity: enums_1.Severity.WARNING,
|
|
141
|
+
path: file.relativePath,
|
|
142
|
+
message: `Deep import into ${targetModulePath}: ${importPath}`,
|
|
143
|
+
suggestion: `Import from ${targetModulePath}/index instead`,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
validImportCount++;
|
|
148
|
+
}
|
|
111
149
|
}
|
|
112
150
|
}
|
|
113
151
|
}
|
|
@@ -144,13 +144,17 @@ let PR001ViewLayer = (() => {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
// Check for view files
|
|
147
|
-
const viewFiles = context.files.filter((f) => /View\.(ts|tsx|js|jsx|swift|kt|dart)$/i.test(f.fileName) ||
|
|
148
|
-
/Screen\.(ts|tsx|js|jsx|swift|kt|dart)$/i.test(f.fileName) ||
|
|
149
|
-
/Page\.(ts|tsx|js|jsx|swift|kt|dart)$/i.test(f.fileName) ||
|
|
147
|
+
const viewFiles = context.files.filter((f) => /View\.(ts|tsx|js|jsx|swift|kt|dart|java|php|py|go|rb|cs)$/i.test(f.fileName) ||
|
|
148
|
+
/Screen\.(ts|tsx|js|jsx|swift|kt|dart|java|php|py|go|rb|cs)$/i.test(f.fileName) ||
|
|
149
|
+
/Page\.(ts|tsx|js|jsx|swift|kt|dart|java|php|py|go|rb|cs)$/i.test(f.fileName) ||
|
|
150
150
|
/Activity\.(kt|java)$/i.test(f.fileName) ||
|
|
151
151
|
/Fragment\.(kt|java)$/i.test(f.fileName) ||
|
|
152
152
|
/ViewController\.swift$/i.test(f.fileName) ||
|
|
153
|
-
/\.vue$/i.test(f.fileName)
|
|
153
|
+
/\.vue$/i.test(f.fileName) ||
|
|
154
|
+
/\.blade\.php$/i.test(f.fileName) ||
|
|
155
|
+
/\.twig$/i.test(f.fileName) ||
|
|
156
|
+
/\.cshtml$/i.test(f.fileName) ||
|
|
157
|
+
/\.html\.erb$/i.test(f.fileName));
|
|
154
158
|
if (viewFiles.length > 0) {
|
|
155
159
|
for (const file of viewFiles.slice(0, 5)) {
|
|
156
160
|
matches.push({
|
|
@@ -166,11 +170,14 @@ let PR001ViewLayer = (() => {
|
|
|
166
170
|
.filter((f) => !f.isTest && !f.isConfig && f.language)
|
|
167
171
|
.slice(0, 50);
|
|
168
172
|
const codePatterns = [
|
|
169
|
-
/@Composable\s+fun/,
|
|
170
|
-
/struct\s+\w+View\s*:\s*View/,
|
|
171
|
-
/class\s+\w+ViewController/,
|
|
172
|
-
/extends\s+(StatefulWidget|StatelessWidget)/,
|
|
173
|
-
/<template>/,
|
|
173
|
+
/@Composable\s+fun/, // Android Kotlin
|
|
174
|
+
/struct\s+\w+View\s*:\s*View/, // SwiftUI
|
|
175
|
+
/class\s+\w+ViewController/, // iOS UIKit
|
|
176
|
+
/extends\s+(StatefulWidget|StatelessWidget)/, // Flutter
|
|
177
|
+
/<template>/, // Vue
|
|
178
|
+
/class\s+\w+View\s*\(\s*APIView\s*\)/, // Python Django REST
|
|
179
|
+
/@app\.route\s*\(/, // Python Flask views
|
|
180
|
+
/class\s+\w+Controller\s*<\s*ApplicationController/, // Ruby Rails
|
|
174
181
|
];
|
|
175
182
|
for (const file of sourceFiles) {
|
|
176
183
|
try {
|