@kernlang/review 3.1.6 → 3.1.8
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/dist/cache.d.ts +1 -1
- package/dist/cache.js +5 -3
- package/dist/cache.js.map +1 -1
- package/dist/call-graph.d.ts +63 -0
- package/dist/call-graph.js +380 -0
- package/dist/call-graph.js.map +1 -0
- package/dist/concept-rules/boundary-mutation.d.ts +1 -1
- package/dist/concept-rules/boundary-mutation.js.map +1 -1
- package/dist/concept-rules/ignored-error.d.ts +1 -1
- package/dist/concept-rules/ignored-error.js.map +1 -1
- package/dist/concept-rules/illegal-dependency.d.ts +1 -1
- package/dist/concept-rules/illegal-dependency.js.map +1 -1
- package/dist/concept-rules/index.js +1 -6
- package/dist/concept-rules/index.js.map +1 -1
- package/dist/concept-rules/unguarded-effect.d.ts +1 -1
- package/dist/concept-rules/unguarded-effect.js.map +1 -1
- package/dist/concept-rules/unrecovered-effect.d.ts +1 -1
- package/dist/concept-rules/unrecovered-effect.js +2 -1
- package/dist/concept-rules/unrecovered-effect.js.map +1 -1
- package/dist/confidence.js +12 -8
- package/dist/confidence.js.map +1 -1
- package/dist/differ.js +3 -7
- package/dist/differ.js.map +1 -1
- package/dist/external-tools.js +5 -6
- package/dist/external-tools.js.map +1 -1
- package/dist/file-context.d.ts +21 -0
- package/dist/file-context.js +234 -0
- package/dist/file-context.js.map +1 -0
- package/dist/file-role.js +14 -7
- package/dist/file-role.js.map +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/graph.js +24 -16
- package/dist/graph.js.map +1 -1
- package/dist/index.d.ts +44 -35
- package/dist/index.js +210 -121
- package/dist/index.js.map +1 -1
- package/dist/inferrer.d.ts +8 -2
- package/dist/inferrer.js +80 -47
- package/dist/inferrer.js.map +1 -1
- package/dist/kern-lint.d.ts +3 -4
- package/dist/kern-lint.js +7 -5
- package/dist/kern-lint.js.map +1 -1
- package/dist/llm-bridge.d.ts +23 -7
- package/dist/llm-bridge.js +267 -31
- package/dist/llm-bridge.js.map +1 -1
- package/dist/llm-review.d.ts +16 -2
- package/dist/llm-review.js +240 -35
- package/dist/llm-review.js.map +1 -1
- package/dist/mappers/ts-concepts.d.ts +1 -1
- package/dist/mappers/ts-concepts.js +303 -32
- package/dist/mappers/ts-concepts.js.map +1 -1
- package/dist/norm-miner.d.ts +31 -0
- package/dist/norm-miner.js +119 -0
- package/dist/norm-miner.js.map +1 -0
- package/dist/obligations.d.ts +63 -0
- package/dist/obligations.js +158 -0
- package/dist/obligations.js.map +1 -0
- package/dist/quality-rules.d.ts +3 -3
- package/dist/quality-rules.js +4 -2
- package/dist/quality-rules.js.map +1 -1
- package/dist/reporter.d.ts +7 -2
- package/dist/reporter.js +82 -51
- package/dist/reporter.js.map +1 -1
- package/dist/rule-eval.d.ts +1 -2
- package/dist/rule-eval.js +5 -9
- package/dist/rule-eval.js.map +1 -1
- package/dist/rule-loader.js +16 -14
- package/dist/rule-loader.js.map +1 -1
- package/dist/rules/base.js +153 -69
- package/dist/rules/base.js.map +1 -1
- package/dist/rules/cli.js +23 -19
- package/dist/rules/cli.js.map +1 -1
- package/dist/rules/confidence.d.ts +1 -1
- package/dist/rules/confidence.js +5 -5
- package/dist/rules/confidence.js.map +1 -1
- package/dist/rules/dead-code.d.ts +10 -0
- package/dist/rules/dead-code.js +75 -0
- package/dist/rules/dead-code.js.map +1 -0
- package/dist/rules/dead-logic.js +35 -31
- package/dist/rules/dead-logic.js.map +1 -1
- package/dist/rules/express.d.ts +2 -1
- package/dist/rules/express.js +380 -126
- package/dist/rules/express.js.map +1 -1
- package/dist/rules/fastapi.js +53 -19
- package/dist/rules/fastapi.js.map +1 -1
- package/dist/rules/ground-layer.js +3 -3
- package/dist/rules/ground-layer.js.map +1 -1
- package/dist/rules/index.js +574 -105
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/ink.js +9 -8
- package/dist/rules/ink.js.map +1 -1
- package/dist/rules/kern-source.js +202 -63
- package/dist/rules/kern-source.js.map +1 -1
- package/dist/rules/nextjs.js +88 -33
- package/dist/rules/nextjs.js.map +1 -1
- package/dist/rules/null-safety.js +52 -26
- package/dist/rules/null-safety.js.map +1 -1
- package/dist/rules/nuxt.js +24 -29
- package/dist/rules/nuxt.js.map +1 -1
- package/dist/rules/react.js +355 -69
- package/dist/rules/react.js.map +1 -1
- package/dist/rules/security-v2.js +71 -57
- package/dist/rules/security-v2.js.map +1 -1
- package/dist/rules/security-v3.js.map +1 -1
- package/dist/rules/security-v4.js +54 -27
- package/dist/rules/security-v4.js.map +1 -1
- package/dist/rules/security.js +35 -5
- package/dist/rules/security.js.map +1 -1
- package/dist/rules/terminal.js +17 -5
- package/dist/rules/terminal.js.map +1 -1
- package/dist/rules/vue.js +162 -107
- package/dist/rules/vue.js.map +1 -1
- package/dist/semantic-diff.d.ts +52 -0
- package/dist/semantic-diff.js +342 -0
- package/dist/semantic-diff.js.map +1 -0
- package/dist/spec-checker.js +11 -10
- package/dist/spec-checker.js.map +1 -1
- package/dist/suppression/apply-suppression.d.ts +2 -3
- package/dist/suppression/apply-suppression.js +3 -3
- package/dist/suppression/apply-suppression.js.map +1 -1
- package/dist/suppression/index.d.ts +2 -2
- package/dist/suppression/index.js +1 -1
- package/dist/suppression/index.js.map +1 -1
- package/dist/suppression/parse-directives.d.ts +1 -1
- package/dist/suppression/parse-directives.js +9 -4
- package/dist/suppression/parse-directives.js.map +1 -1
- package/dist/taint-ast.d.ts +20 -0
- package/dist/taint-ast.js +427 -0
- package/dist/taint-ast.js.map +1 -0
- package/dist/taint-crossfile.d.ts +28 -0
- package/dist/taint-crossfile.js +174 -0
- package/dist/taint-crossfile.js.map +1 -0
- package/dist/taint-findings.d.ts +17 -0
- package/dist/taint-findings.js +131 -0
- package/dist/taint-findings.js.map +1 -0
- package/dist/taint-regex.d.ts +61 -0
- package/dist/taint-regex.js +379 -0
- package/dist/taint-regex.js.map +1 -0
- package/dist/taint-types.d.ts +128 -0
- package/dist/taint-types.js +174 -0
- package/dist/taint-types.js.map +1 -0
- package/dist/taint.d.ts +13 -107
- package/dist/taint.js +16 -1067
- package/dist/taint.js.map +1 -1
- package/dist/template-detector.d.ts +2 -2
- package/dist/template-detector.js +11 -16
- package/dist/template-detector.js.map +1 -1
- package/dist/types.d.ts +35 -0
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
package/dist/rules/index.js
CHANGED
|
@@ -14,39 +14,51 @@
|
|
|
14
14
|
* [nuxt] Active when target = nuxt (on top of vue)
|
|
15
15
|
*/
|
|
16
16
|
import { baseRules } from './base.js';
|
|
17
|
+
import { cliRules } from './cli.js';
|
|
18
|
+
import { deadLogicRules } from './dead-logic.js';
|
|
19
|
+
import { expressRules } from './express.js';
|
|
20
|
+
import { fastapiRules } from './fastapi.js';
|
|
21
|
+
import { inkRules } from './ink.js';
|
|
22
|
+
import { nextjsRules } from './nextjs.js';
|
|
23
|
+
import { nullSafetyRules } from './null-safety.js';
|
|
24
|
+
import { nuxtRules } from './nuxt.js';
|
|
25
|
+
import { reactRules } from './react.js';
|
|
17
26
|
import { securityRules } from './security.js';
|
|
18
27
|
import { securityV2Rules } from './security-v2.js';
|
|
19
28
|
import { securityV3Rules } from './security-v3.js';
|
|
20
29
|
import { securityV4Rules } from './security-v4.js';
|
|
21
|
-
import { deadLogicRules } from './dead-logic.js';
|
|
22
|
-
import { reactRules } from './react.js';
|
|
23
|
-
import { vueRules } from './vue.js';
|
|
24
|
-
import { nextjsRules } from './nextjs.js';
|
|
25
|
-
import { nuxtRules } from './nuxt.js';
|
|
26
|
-
import { expressRules } from './express.js';
|
|
27
|
-
import { cliRules } from './cli.js';
|
|
28
30
|
import { terminalRules } from './terminal.js';
|
|
29
|
-
import {
|
|
30
|
-
import { fastapiRules } from './fastapi.js';
|
|
31
|
-
import { nullSafetyRules } from './null-safety.js';
|
|
31
|
+
import { vueRules } from './vue.js';
|
|
32
32
|
const REACT_TARGETS = new Set(['nextjs', 'tailwind', 'web', 'native', 'ink']);
|
|
33
33
|
const VUE_TARGETS = new Set(['vue', 'nuxt']);
|
|
34
|
+
/** Backend targets — never load frontend-specific rules */
|
|
35
|
+
const BACKEND_TARGETS = new Set(['express', 'fastapi', 'mcp', 'cli', 'terminal']);
|
|
34
36
|
/**
|
|
35
37
|
* Get all active review rules for a given target.
|
|
36
38
|
* Base + security + dead-logic + null-safety are always active; framework rules activate by target.
|
|
37
39
|
*/
|
|
38
40
|
export function getActiveRules(target) {
|
|
39
|
-
const rules = [
|
|
40
|
-
|
|
41
|
+
const rules = [
|
|
42
|
+
...baseRules,
|
|
43
|
+
...securityRules,
|
|
44
|
+
...securityV2Rules,
|
|
45
|
+
...securityV3Rules,
|
|
46
|
+
...securityV4Rules,
|
|
47
|
+
...deadLogicRules,
|
|
48
|
+
...nullSafetyRules,
|
|
49
|
+
];
|
|
50
|
+
// Backend targets never load frontend-specific rules
|
|
51
|
+
const isBackend = target ? BACKEND_TARGETS.has(target) : false;
|
|
52
|
+
if (!isBackend && target && REACT_TARGETS.has(target)) {
|
|
41
53
|
rules.push(...reactRules);
|
|
42
54
|
}
|
|
43
|
-
if (target && VUE_TARGETS.has(target)) {
|
|
55
|
+
if (!isBackend && target && VUE_TARGETS.has(target)) {
|
|
44
56
|
rules.push(...vueRules);
|
|
45
57
|
}
|
|
46
|
-
if (target === 'nextjs') {
|
|
58
|
+
if (!isBackend && target === 'nextjs') {
|
|
47
59
|
rules.push(...nextjsRules);
|
|
48
60
|
}
|
|
49
|
-
if (target === 'nuxt') {
|
|
61
|
+
if (!isBackend && target === 'nuxt') {
|
|
50
62
|
rules.push(...nuxtRules);
|
|
51
63
|
}
|
|
52
64
|
if (target === 'express') {
|
|
@@ -68,122 +80,579 @@ export function getActiveRules(target) {
|
|
|
68
80
|
}
|
|
69
81
|
const REGISTRY = [
|
|
70
82
|
// Base (always active)
|
|
71
|
-
{
|
|
72
|
-
|
|
83
|
+
{
|
|
84
|
+
id: 'floating-promise',
|
|
85
|
+
layer: 'base',
|
|
86
|
+
severity: 'error',
|
|
87
|
+
description: 'Unresolved async operation — missing await/void/return',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'state-mutation',
|
|
91
|
+
layer: 'base',
|
|
92
|
+
severity: 'error',
|
|
93
|
+
description: 'Illegal state mutation outside designated setter',
|
|
94
|
+
},
|
|
73
95
|
{ id: 'empty-catch', layer: 'base', severity: 'warning', description: 'Catch block swallows exception silently' },
|
|
74
|
-
{
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{
|
|
96
|
+
{
|
|
97
|
+
id: 'machine-gap',
|
|
98
|
+
layer: 'base',
|
|
99
|
+
severity: 'warning',
|
|
100
|
+
description: 'Unreachable state or missing transition in state machine',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'config-default-mismatch',
|
|
104
|
+
layer: 'base',
|
|
105
|
+
severity: 'warning',
|
|
106
|
+
description: 'Config schema default does not match type',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'event-map-mismatch',
|
|
110
|
+
layer: 'base',
|
|
111
|
+
severity: 'warning',
|
|
112
|
+
description: 'Event handler type mismatch with event map',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 'non-exhaustive-switch',
|
|
116
|
+
layer: 'base',
|
|
117
|
+
severity: 'warning',
|
|
118
|
+
description: 'Switch/map missing cases for known variants',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'cognitive-complexity',
|
|
122
|
+
layer: 'base',
|
|
123
|
+
severity: 'warning',
|
|
124
|
+
description: 'Function exceeds cognitive complexity threshold',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'template-available',
|
|
128
|
+
layer: 'base',
|
|
129
|
+
severity: 'info',
|
|
130
|
+
description: 'Pattern matches a registered KERN template',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'handler-extraction',
|
|
134
|
+
layer: 'base',
|
|
135
|
+
severity: 'info',
|
|
136
|
+
description: 'Handler-like pattern could be extracted to KERN',
|
|
137
|
+
},
|
|
81
138
|
{ id: 'memory-leak', layer: 'base', severity: 'error', description: 'Event listener added without cleanup' },
|
|
82
139
|
{ id: 'unhandled-async', layer: 'base', severity: 'warning', description: 'Async function without error handling' },
|
|
83
|
-
{
|
|
84
|
-
|
|
140
|
+
{
|
|
141
|
+
id: 'sync-in-async',
|
|
142
|
+
layer: 'base',
|
|
143
|
+
severity: 'warning',
|
|
144
|
+
description: 'Synchronous blocking call inside async function',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'bare-rethrow',
|
|
148
|
+
layer: 'base',
|
|
149
|
+
severity: 'warning',
|
|
150
|
+
description: 'Catch rethrows error without adding context',
|
|
151
|
+
},
|
|
85
152
|
// Security
|
|
86
|
-
{
|
|
87
|
-
|
|
88
|
-
|
|
153
|
+
{
|
|
154
|
+
id: 'xss-unsafe-html',
|
|
155
|
+
layer: 'security',
|
|
156
|
+
severity: 'error',
|
|
157
|
+
description: 'innerHTML/dangerouslySetInnerHTML with untrusted data',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 'hardcoded-secret',
|
|
161
|
+
layer: 'security',
|
|
162
|
+
severity: 'error',
|
|
163
|
+
description: 'API key, password, or secret in source code',
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 'command-injection',
|
|
167
|
+
layer: 'security',
|
|
168
|
+
severity: 'error',
|
|
169
|
+
description: 'exec/spawn with user-controlled input',
|
|
170
|
+
},
|
|
89
171
|
{ id: 'no-eval', layer: 'security', severity: 'error', description: 'eval() or Function() constructor usage' },
|
|
90
|
-
{
|
|
172
|
+
{
|
|
173
|
+
id: 'insecure-random',
|
|
174
|
+
layer: 'security',
|
|
175
|
+
severity: 'warning',
|
|
176
|
+
description: 'Math.random() used for security-sensitive operations',
|
|
177
|
+
},
|
|
91
178
|
{ id: 'cors-wildcard', layer: 'security', severity: 'warning', description: 'CORS wildcard (*) origin allowed' },
|
|
92
|
-
{
|
|
93
|
-
|
|
179
|
+
{
|
|
180
|
+
id: 'helmet-missing',
|
|
181
|
+
layer: 'security',
|
|
182
|
+
severity: 'warning',
|
|
183
|
+
description: 'Express app without helmet security headers',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'open-redirect',
|
|
187
|
+
layer: 'security',
|
|
188
|
+
severity: 'error',
|
|
189
|
+
description: 'Unvalidated redirect target from user input',
|
|
190
|
+
},
|
|
94
191
|
// Security v2
|
|
95
|
-
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
192
|
+
{
|
|
193
|
+
id: 'jwt-weak-verification',
|
|
194
|
+
layer: 'security-v2',
|
|
195
|
+
severity: 'warning',
|
|
196
|
+
description: 'JWT verified without algorithm restriction',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: 'cookie-hardening',
|
|
200
|
+
layer: 'security-v2',
|
|
201
|
+
severity: 'error',
|
|
202
|
+
description: 'Cookie missing secure/httpOnly/sameSite flags',
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: 'csrf-detection',
|
|
206
|
+
layer: 'security-v2',
|
|
207
|
+
severity: 'error',
|
|
208
|
+
description: 'State-changing endpoint without CSRF protection',
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: 'csp-strength',
|
|
212
|
+
layer: 'security-v2',
|
|
213
|
+
severity: 'warning',
|
|
214
|
+
description: 'Weak Content-Security-Policy headers',
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: 'path-traversal',
|
|
218
|
+
layer: 'security-v2',
|
|
219
|
+
severity: 'error',
|
|
220
|
+
description: 'File path from user input without sanitization',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'weak-password-hashing',
|
|
224
|
+
layer: 'security-v2',
|
|
225
|
+
severity: 'error',
|
|
226
|
+
description: 'MD5/SHA1 for password hashing instead of bcrypt/argon2',
|
|
227
|
+
},
|
|
101
228
|
// Security v3 — OWASP gap closure
|
|
102
|
-
{
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
229
|
+
{
|
|
230
|
+
id: 'regex-dos',
|
|
231
|
+
layer: 'security-v3',
|
|
232
|
+
severity: 'warning',
|
|
233
|
+
description: 'Regex vulnerable to catastrophic backtracking (ReDoS)',
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: 'missing-input-validation',
|
|
237
|
+
layer: 'security-v3',
|
|
238
|
+
severity: 'warning',
|
|
239
|
+
description: 'User input used without validation',
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: 'prototype-pollution',
|
|
243
|
+
layer: 'security-v3',
|
|
244
|
+
severity: 'error',
|
|
245
|
+
description: 'Object.prototype mutation via user-controlled keys',
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
id: 'information-exposure',
|
|
249
|
+
layer: 'security-v3',
|
|
250
|
+
severity: 'error',
|
|
251
|
+
description: 'Stack traces or internal details in error responses',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: 'prompt-injection',
|
|
255
|
+
layer: 'security-v3',
|
|
256
|
+
severity: 'warning',
|
|
257
|
+
description: 'User input concatenated into LLM prompts',
|
|
258
|
+
},
|
|
107
259
|
// Security v4 — LLM attack surface
|
|
108
|
-
{
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
{
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
260
|
+
{
|
|
261
|
+
id: 'indirect-prompt-injection',
|
|
262
|
+
layer: 'security-v4',
|
|
263
|
+
severity: 'warning',
|
|
264
|
+
description: 'LLM prompt includes data from external/DB sources',
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
id: 'llm-output-execution',
|
|
268
|
+
layer: 'security-v4',
|
|
269
|
+
severity: 'error',
|
|
270
|
+
description: 'LLM-generated code executed without sandboxing',
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: 'system-prompt-leakage',
|
|
274
|
+
layer: 'security-v4',
|
|
275
|
+
severity: 'warning',
|
|
276
|
+
description: 'System prompt exposed in error paths or responses',
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: 'rag-poisoning',
|
|
280
|
+
layer: 'security-v4',
|
|
281
|
+
severity: 'warning',
|
|
282
|
+
description: 'RAG documents injected without provenance check',
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: 'tool-calling-manipulation',
|
|
286
|
+
layer: 'security-v4',
|
|
287
|
+
severity: 'error',
|
|
288
|
+
description: 'Tool/function call parameters from untrusted LLM output',
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
id: 'encoding-bypass',
|
|
292
|
+
layer: 'security-v4',
|
|
293
|
+
severity: 'warning',
|
|
294
|
+
description: 'Base64/unicode encoding used to bypass prompt filters',
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
id: 'delimiter-injection',
|
|
298
|
+
layer: 'security-v4',
|
|
299
|
+
severity: 'warning',
|
|
300
|
+
description: 'Prompt delimiter breakout via user input',
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
id: 'unsanitized-history',
|
|
304
|
+
layer: 'security-v4',
|
|
305
|
+
severity: 'warning',
|
|
306
|
+
description: 'Chat history concatenated without sanitization',
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
id: 'json-output-manipulation',
|
|
310
|
+
layer: 'security-v4',
|
|
311
|
+
severity: 'warning',
|
|
312
|
+
description: 'LLM JSON output used without schema validation',
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
id: 'missing-output-validation',
|
|
316
|
+
layer: 'security-v4',
|
|
317
|
+
severity: 'warning',
|
|
318
|
+
description: 'LLM output consumed without validation',
|
|
319
|
+
},
|
|
118
320
|
// Dead logic
|
|
119
|
-
{
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
{
|
|
126
|
-
|
|
321
|
+
{
|
|
322
|
+
id: 'identical-conditions',
|
|
323
|
+
layer: 'dead-logic',
|
|
324
|
+
severity: 'error',
|
|
325
|
+
description: 'Duplicate conditions in if/else chain',
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: 'identical-expressions',
|
|
329
|
+
layer: 'dead-logic',
|
|
330
|
+
severity: 'error',
|
|
331
|
+
description: 'Same expression on both sides of operator',
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
id: 'all-identical-branches',
|
|
335
|
+
layer: 'dead-logic',
|
|
336
|
+
severity: 'error',
|
|
337
|
+
description: 'All branches produce identical code',
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
id: 'constant-condition',
|
|
341
|
+
layer: 'dead-logic',
|
|
342
|
+
severity: 'warning',
|
|
343
|
+
description: 'Condition is always true or always false',
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
id: 'one-iteration-loop',
|
|
347
|
+
layer: 'dead-logic',
|
|
348
|
+
severity: 'warning',
|
|
349
|
+
description: 'Loop body always exits on first iteration',
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: 'unused-collection',
|
|
353
|
+
layer: 'dead-logic',
|
|
354
|
+
severity: 'warning',
|
|
355
|
+
description: 'Collection created but never read',
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
id: 'empty-collection-access',
|
|
359
|
+
layer: 'dead-logic',
|
|
360
|
+
severity: 'warning',
|
|
361
|
+
description: 'Accessing elements of provably empty collection',
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
id: 'redundant-jump',
|
|
365
|
+
layer: 'dead-logic',
|
|
366
|
+
severity: 'info',
|
|
367
|
+
description: 'Unreachable code after return/break/continue',
|
|
368
|
+
},
|
|
127
369
|
// Null safety
|
|
128
|
-
{
|
|
129
|
-
|
|
130
|
-
|
|
370
|
+
{
|
|
371
|
+
id: 'unchecked-find',
|
|
372
|
+
layer: 'null-safety',
|
|
373
|
+
severity: 'warning',
|
|
374
|
+
description: 'array.find() result used without null check',
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
id: 'optional-chain-bang',
|
|
378
|
+
layer: 'null-safety',
|
|
379
|
+
severity: 'warning',
|
|
380
|
+
description: 'Optional chain (?) immediately negated by non-null assertion (!)',
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
id: 'unchecked-cast',
|
|
384
|
+
layer: 'null-safety',
|
|
385
|
+
severity: 'warning',
|
|
386
|
+
description: 'Unsafe type assertion without runtime guard',
|
|
387
|
+
},
|
|
131
388
|
// React (target: nextjs, tailwind, web, native, ink)
|
|
132
389
|
{ id: 'async-effect', layer: 'react', severity: 'error', description: 'Async function passed directly to useEffect' },
|
|
133
|
-
{
|
|
134
|
-
|
|
390
|
+
{
|
|
391
|
+
id: 'render-side-effect',
|
|
392
|
+
layer: 'react',
|
|
393
|
+
severity: 'error',
|
|
394
|
+
description: 'Side effect (fetch, mutation) during render',
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
id: 'unstable-key',
|
|
398
|
+
layer: 'react',
|
|
399
|
+
severity: 'warning',
|
|
400
|
+
description: 'Non-stable key prop (index, random, Date.now)',
|
|
401
|
+
},
|
|
135
402
|
{ id: 'stale-closure', layer: 'react', severity: 'warning', description: 'Stale variable captured in hook closure' },
|
|
136
|
-
{
|
|
403
|
+
{
|
|
404
|
+
id: 'state-explosion',
|
|
405
|
+
layer: 'react',
|
|
406
|
+
severity: 'warning',
|
|
407
|
+
description: 'Excessive useState calls — consider useReducer',
|
|
408
|
+
},
|
|
137
409
|
{ id: 'hook-order', layer: 'react', severity: 'error', description: 'React hook called inside condition or loop' },
|
|
138
|
-
{
|
|
410
|
+
{
|
|
411
|
+
id: 'effect-self-update-loop',
|
|
412
|
+
layer: 'react',
|
|
413
|
+
severity: 'error',
|
|
414
|
+
description: 'useEffect updates its own dependency — infinite loop',
|
|
415
|
+
},
|
|
139
416
|
// CLI (target: cli)
|
|
140
|
-
{
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
417
|
+
{
|
|
418
|
+
id: 'cli-missing-shebang',
|
|
419
|
+
layer: 'cli',
|
|
420
|
+
severity: 'warning',
|
|
421
|
+
description: 'Commander CLI entrypoint missing #!/usr/bin/env node',
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
id: 'cli-missing-parse',
|
|
425
|
+
layer: 'cli',
|
|
426
|
+
severity: 'error',
|
|
427
|
+
description: 'Command instance created without parse()/parseAsync()',
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
id: 'cli-async-parse-sync',
|
|
431
|
+
layer: 'cli',
|
|
432
|
+
severity: 'error',
|
|
433
|
+
description: 'Async Commander action paired with parse() instead of parseAsync()',
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
id: 'cli-process-exit-in-action',
|
|
437
|
+
layer: 'cli',
|
|
438
|
+
severity: 'warning',
|
|
439
|
+
description: 'Commander action handler calls process.exit() directly',
|
|
440
|
+
},
|
|
144
441
|
// Vue (target: vue, nuxt)
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
442
|
+
{
|
|
443
|
+
id: 'missing-ref-value',
|
|
444
|
+
layer: 'vue',
|
|
445
|
+
severity: 'warning',
|
|
446
|
+
description: 'ref() used without .value in script setup',
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
id: 'missing-onUnmounted',
|
|
450
|
+
layer: 'vue',
|
|
451
|
+
severity: 'error',
|
|
452
|
+
description: 'watch/addEventListener without onUnmounted cleanup',
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
id: 'setup-side-effect',
|
|
456
|
+
layer: 'vue',
|
|
457
|
+
severity: 'warning',
|
|
458
|
+
description: 'Top-level await in setup without onMounted',
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
id: 'reactive-destructure',
|
|
462
|
+
layer: 'vue',
|
|
463
|
+
severity: 'warning',
|
|
464
|
+
description: 'Destructuring reactive() loses reactivity',
|
|
465
|
+
},
|
|
149
466
|
// Terminal (target: terminal)
|
|
150
|
-
{
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
{
|
|
467
|
+
{
|
|
468
|
+
id: 'terminal-missing-tty-guard',
|
|
469
|
+
layer: 'terminal',
|
|
470
|
+
severity: 'warning',
|
|
471
|
+
description: 'Interactive terminal code runs without TTY guard',
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
id: 'terminal-raw-mode-no-restore',
|
|
475
|
+
layer: 'terminal',
|
|
476
|
+
severity: 'error',
|
|
477
|
+
description: 'stdin raw mode enabled without restore on exit',
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
id: 'terminal-readline-no-close',
|
|
481
|
+
layer: 'terminal',
|
|
482
|
+
severity: 'warning',
|
|
483
|
+
description: 'Readline interface never closed — process can hang',
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
id: 'terminal-alt-screen-no-restore',
|
|
487
|
+
layer: 'terminal',
|
|
488
|
+
severity: 'warning',
|
|
489
|
+
description: 'Alternate screen entered without restore on exit',
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
id: 'terminal-missing-signal-handler',
|
|
493
|
+
layer: 'terminal',
|
|
494
|
+
severity: 'warning',
|
|
495
|
+
description: 'No SIGINT/SIGTERM handler for cleanup',
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
id: 'terminal-cursor-not-restored',
|
|
499
|
+
layer: 'terminal',
|
|
500
|
+
severity: 'warning',
|
|
501
|
+
description: 'Cursor hidden without restore on exit',
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
id: 'terminal-unthrottled-render',
|
|
505
|
+
layer: 'terminal',
|
|
506
|
+
severity: 'warning',
|
|
507
|
+
description: 'Render loop with excessive refresh rate',
|
|
508
|
+
},
|
|
157
509
|
// Ink (target: ink, on top of React)
|
|
158
|
-
{
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
510
|
+
{
|
|
511
|
+
id: 'ink-console-output',
|
|
512
|
+
layer: 'ink',
|
|
513
|
+
severity: 'warning',
|
|
514
|
+
description: 'console.* output corrupts Ink terminal rendering',
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
id: 'ink-direct-stdout',
|
|
518
|
+
layer: 'ink',
|
|
519
|
+
severity: 'error',
|
|
520
|
+
description: 'Direct stdout/stderr writes bypass Ink renderer',
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
id: 'ink-process-exit',
|
|
524
|
+
layer: 'ink',
|
|
525
|
+
severity: 'warning',
|
|
526
|
+
description: 'process.exit() used instead of useApp().exit()',
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
id: 'ink-stdin-bypass',
|
|
530
|
+
layer: 'ink',
|
|
531
|
+
severity: 'warning',
|
|
532
|
+
description: 'Raw stdin/readline listeners bypass Ink useInput()',
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
id: 'ink-uncleared-interval',
|
|
536
|
+
layer: 'ink',
|
|
537
|
+
severity: 'warning',
|
|
538
|
+
description: 'setInterval without cleanup in Ink component',
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
id: 'ink-missing-error-boundary',
|
|
542
|
+
layer: 'ink',
|
|
543
|
+
severity: 'warning',
|
|
544
|
+
description: 'Ink render() without error handling',
|
|
545
|
+
},
|
|
164
546
|
// Next.js (target: nextjs)
|
|
165
547
|
{ id: 'server-hook', layer: 'nextjs', severity: 'error', description: 'React hook used in Server Component' },
|
|
166
|
-
{
|
|
167
|
-
|
|
548
|
+
{
|
|
549
|
+
id: 'hydration-mismatch',
|
|
550
|
+
layer: 'nextjs',
|
|
551
|
+
severity: 'warning',
|
|
552
|
+
description: 'Nondeterministic expression causes SSR/client mismatch',
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
id: 'missing-use-client',
|
|
556
|
+
layer: 'nextjs',
|
|
557
|
+
severity: 'warning',
|
|
558
|
+
description: 'Event handler in Server Component — needs use client',
|
|
559
|
+
},
|
|
168
560
|
// Nuxt (target: nuxt)
|
|
169
|
-
{
|
|
170
|
-
|
|
171
|
-
|
|
561
|
+
{
|
|
562
|
+
id: 'missing-ssr-guard',
|
|
563
|
+
layer: 'nuxt',
|
|
564
|
+
severity: 'error',
|
|
565
|
+
description: 'Browser global accessed without SSR guard',
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
id: 'nuxt-direct-fetch',
|
|
569
|
+
layer: 'nuxt',
|
|
570
|
+
severity: 'warning',
|
|
571
|
+
description: 'Raw fetch() instead of $fetch/useFetch in Nuxt component',
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
id: 'server-route-leak',
|
|
575
|
+
layer: 'nuxt',
|
|
576
|
+
severity: 'error',
|
|
577
|
+
description: 'Server API route may expose sensitive fields',
|
|
578
|
+
},
|
|
172
579
|
// Express (target: express)
|
|
173
|
-
{
|
|
174
|
-
|
|
580
|
+
{
|
|
581
|
+
id: 'unvalidated-input',
|
|
582
|
+
layer: 'express',
|
|
583
|
+
severity: 'error',
|
|
584
|
+
description: 'req.body/params/query used without validation',
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
id: 'missing-error-middleware',
|
|
588
|
+
layer: 'express',
|
|
589
|
+
severity: 'warning',
|
|
590
|
+
description: 'Express app without error-handling middleware',
|
|
591
|
+
},
|
|
175
592
|
{ id: 'sync-in-handler', layer: 'express', severity: 'warning', description: 'Blocking I/O in request handler' },
|
|
176
|
-
{
|
|
593
|
+
{
|
|
594
|
+
id: 'double-response',
|
|
595
|
+
layer: 'express',
|
|
596
|
+
severity: 'error',
|
|
597
|
+
description: 'Response sent twice without early return',
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
id: 'express-missing-next',
|
|
601
|
+
layer: 'express',
|
|
602
|
+
severity: 'error',
|
|
603
|
+
description: 'Middleware accepts next but never calls it — request hangs',
|
|
604
|
+
},
|
|
177
605
|
// FastAPI (target: fastapi, concept-based Python pipeline)
|
|
178
|
-
{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
606
|
+
{
|
|
607
|
+
id: 'fastapi-missing-response-model',
|
|
608
|
+
layer: 'fastapi',
|
|
609
|
+
severity: 'warning',
|
|
610
|
+
description: 'Endpoint without response_model — undocumented response',
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
id: 'fastapi-blocking-sync-route',
|
|
614
|
+
layer: 'fastapi',
|
|
615
|
+
severity: 'warning',
|
|
616
|
+
description: 'Blocking call in async route stalls event loop',
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
id: 'fastapi-shared-state',
|
|
620
|
+
layer: 'fastapi',
|
|
621
|
+
severity: 'error',
|
|
622
|
+
description: 'Route mutates global/module state — race condition',
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
id: 'fastapi-broad-except',
|
|
626
|
+
layer: 'fastapi',
|
|
627
|
+
severity: 'warning',
|
|
628
|
+
description: 'Broad except without re-raising HTTPException',
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
id: 'fastapi-broad-cors',
|
|
632
|
+
layer: 'fastapi',
|
|
633
|
+
severity: 'warning',
|
|
634
|
+
description: 'CORSMiddleware with allow_origins=["*"] — overly permissive',
|
|
635
|
+
},
|
|
182
636
|
// Concept rules (always active, language-agnostic)
|
|
183
|
-
{
|
|
637
|
+
{
|
|
638
|
+
id: 'boundary-mutation',
|
|
639
|
+
layer: 'concept',
|
|
640
|
+
severity: 'warning',
|
|
641
|
+
description: 'Global/shared state mutation across boundaries',
|
|
642
|
+
},
|
|
184
643
|
{ id: 'ignored-error', layer: 'concept', severity: 'warning', description: 'Caught exception silently ignored' },
|
|
185
|
-
{
|
|
186
|
-
|
|
644
|
+
{
|
|
645
|
+
id: 'unguarded-effect',
|
|
646
|
+
layer: 'concept',
|
|
647
|
+
severity: 'warning',
|
|
648
|
+
description: 'Network/DB effect without auth/validation guard',
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
id: 'unrecovered-effect',
|
|
652
|
+
layer: 'concept',
|
|
653
|
+
severity: 'warning',
|
|
654
|
+
description: 'Network/DB effect without error recovery',
|
|
655
|
+
},
|
|
187
656
|
];
|
|
188
657
|
/** Layer → target mapping for filtering */
|
|
189
658
|
const LAYER_TARGET_MAP = {
|
|
@@ -212,7 +681,7 @@ const LAYER_TARGET_MAP = {
|
|
|
212
681
|
export function getRuleRegistry(target) {
|
|
213
682
|
if (!target)
|
|
214
683
|
return [...REGISTRY];
|
|
215
|
-
return REGISTRY.filter(r => {
|
|
684
|
+
return REGISTRY.filter((r) => {
|
|
216
685
|
const targets = LAYER_TARGET_MAP[r.layer];
|
|
217
686
|
return targets === null || targets.includes(target);
|
|
218
687
|
});
|