devkit-quality 0.1.0

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.
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runTypeScriptRules = runTypeScriptRules;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const typescript_1 = __importDefault(require("typescript"));
9
+ const finding_1 = require("../finding");
10
+ const config_1 = require("../config");
11
+ const walk_1 = require("../ast/walk");
12
+ const IMPLICIT_ANY_CODES = new Set([7005, 7006, 7008, 7010, 7019, 7031, 7034]);
13
+ function runAstChecks(context) {
14
+ const findings = [];
15
+ for (const file of context.files) {
16
+ const sourceFile = context.program.getSourceFile(file);
17
+ if (!sourceFile || !/\.tsx?$/i.test(file))
18
+ continue;
19
+ const relativePath = node_path_1.default.relative(context.projectRoot, file).replace(/\\/g, '/');
20
+ const lines = sourceFile.text.split(/\r\n|\r|\n/);
21
+ if ((0, config_1.isRuleEnabled)(context.config, 'TS002')) {
22
+ for (let i = 0; i < lines.length; i += 1) {
23
+ const trimmed = lines[i].trim();
24
+ if ((trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*')) &&
25
+ (trimmed.includes('@ts-ignore') || trimmed.includes('@ts-nocheck') || trimmed.includes('@ts-expect-error'))) {
26
+ findings.push((0, finding_1.buildFinding)({
27
+ ruleId: 'TS002',
28
+ category: 'typescript',
29
+ severity: 'MEDIUM',
30
+ confidence: 'CERTAIN',
31
+ file: relativePath,
32
+ line: i + 1,
33
+ column: lines[i].indexOf(trimmed) + 1,
34
+ message: 'Suppressed type checking',
35
+ description: 'A directive disables TypeScript checking for the following line or file.',
36
+ evidence: trimmed,
37
+ suggestion: 'Fix the underlying type error instead of suppressing it.',
38
+ fixAvailable: false
39
+ }));
40
+ }
41
+ }
42
+ }
43
+ const visit = (node) => {
44
+ if ((0, config_1.isRuleEnabled)(context.config, 'TS001') && node.kind === typescript_1.default.SyntaxKind.AnyKeyword) {
45
+ const { line, column } = (0, walk_1.lineAndColumn)(sourceFile, node.getStart());
46
+ findings.push((0, finding_1.buildFinding)({
47
+ ruleId: 'TS001',
48
+ category: 'typescript',
49
+ severity: 'MEDIUM',
50
+ confidence: 'CERTAIN',
51
+ file: relativePath,
52
+ line,
53
+ column,
54
+ message: 'Unsafe any usage',
55
+ description: 'The code explicitly disables TypeScript safety with any.',
56
+ evidence: node.parent?.getText().slice(0, 160) ?? 'any',
57
+ suggestion: 'Replace any with a concrete type or a safer abstraction.',
58
+ fixAvailable: false
59
+ }));
60
+ }
61
+ if ((0, config_1.isRuleEnabled)(context.config, 'TS003') && typescript_1.default.isNonNullExpression(node)) {
62
+ const { line, column } = (0, walk_1.lineAndColumn)(sourceFile, node.getStart());
63
+ findings.push((0, finding_1.buildFinding)({
64
+ ruleId: 'TS003',
65
+ category: 'typescript',
66
+ severity: 'LOW',
67
+ confidence: 'CERTAIN',
68
+ file: relativePath,
69
+ line,
70
+ column,
71
+ message: 'Non-null assertion',
72
+ description: 'The non-null assertion operator bypasses null/undefined checking.',
73
+ evidence: node.getText().slice(0, 160),
74
+ suggestion: 'Narrow the type with a real check instead of asserting non-null.',
75
+ fixAvailable: false
76
+ }));
77
+ }
78
+ typescript_1.default.forEachChild(node, visit);
79
+ };
80
+ visit(sourceFile);
81
+ }
82
+ return findings;
83
+ }
84
+ function runImplicitAnyDiagnostics(context) {
85
+ if (!(0, config_1.isRuleEnabled)(context.config, 'TS001'))
86
+ return [];
87
+ const findings = [];
88
+ for (const file of context.files) {
89
+ const sourceFile = context.program.getSourceFile(file);
90
+ if (!sourceFile)
91
+ continue;
92
+ let diagnostics = [];
93
+ try {
94
+ diagnostics = context.program.getSemanticDiagnostics(sourceFile);
95
+ }
96
+ catch {
97
+ continue;
98
+ }
99
+ const relativePath = node_path_1.default.relative(context.projectRoot, file).replace(/\\/g, '/');
100
+ for (const diagnostic of diagnostics) {
101
+ if (diagnostic.start === undefined || !IMPLICIT_ANY_CODES.has(diagnostic.code))
102
+ continue;
103
+ const { line, column } = (0, walk_1.lineAndColumn)(sourceFile, diagnostic.start);
104
+ findings.push((0, finding_1.buildFinding)({
105
+ ruleId: 'TS001',
106
+ category: 'typescript',
107
+ severity: 'MEDIUM',
108
+ confidence: 'CERTAIN',
109
+ file: relativePath,
110
+ line,
111
+ column,
112
+ message: 'Implicit any',
113
+ description: typescript_1.default.flattenDiagnosticMessageText(diagnostic.messageText, ' '),
114
+ evidence: `TS${diagnostic.code}`,
115
+ suggestion: 'Add an explicit type annotation.',
116
+ fixAvailable: false
117
+ }));
118
+ }
119
+ }
120
+ return findings;
121
+ }
122
+ function runTypeScriptRules(context) {
123
+ return [...runAstChecks(context), ...runImplicitAnyDiagnostics(context)];
124
+ }
package/dist/rules.js ADDED
@@ -0,0 +1,546 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rules = void 0;
4
+ exports.getRuleById = getRuleById;
5
+ exports.listRules = listRules;
6
+ exports.rules = [
7
+ // Dead code
8
+ {
9
+ id: 'DEAD002',
10
+ title: 'Unused import',
11
+ category: 'deadCode',
12
+ severity: 'MEDIUM',
13
+ confidence: 'CERTAIN',
14
+ description: 'An imported binding is never referenced in the file.',
15
+ explanation: 'Verified by the TypeScript compiler (noUnusedLocals/noUnusedParameters).',
16
+ example: "import { unused } from './util';",
17
+ why: 'Unused imports add noise and can hide typos in what should have been imported.',
18
+ fixClassification: 'safe'
19
+ },
20
+ {
21
+ id: 'DEAD003',
22
+ title: 'Unused variable',
23
+ category: 'deadCode',
24
+ severity: 'MEDIUM',
25
+ confidence: 'CERTAIN',
26
+ description: 'A local variable (or destructured binding) is declared but never read.',
27
+ explanation: 'Verified by the TypeScript compiler.',
28
+ example: 'const result = compute(); // result is never used',
29
+ why: 'Dead locals are maintenance noise and often indicate an incomplete refactor.',
30
+ fixClassification: 'safe'
31
+ },
32
+ {
33
+ id: 'DEAD004',
34
+ title: 'Unused function',
35
+ category: 'deadCode',
36
+ severity: 'MEDIUM',
37
+ confidence: 'CERTAIN',
38
+ description: 'A non-exported function is declared but never called anywhere in the file.',
39
+ explanation: 'Verified by the TypeScript compiler.',
40
+ example: 'function helper() { /* never called */ }',
41
+ why: 'Unused functions are dead weight and a common artifact of incomplete refactors.',
42
+ fixClassification: 'unsafe'
43
+ },
44
+ {
45
+ id: 'DEAD005',
46
+ title: 'Unused class',
47
+ category: 'deadCode',
48
+ severity: 'MEDIUM',
49
+ confidence: 'CERTAIN',
50
+ description: 'A non-exported class is declared but never instantiated or referenced.',
51
+ explanation: 'Verified by the TypeScript compiler.',
52
+ example: 'class Helper { /* never used */ }',
53
+ why: 'Unused classes increase surface area with no runtime benefit.',
54
+ fixClassification: 'unsafe'
55
+ },
56
+ {
57
+ id: 'DEAD006',
58
+ title: 'Unused parameter',
59
+ category: 'deadCode',
60
+ severity: 'LOW',
61
+ confidence: 'CERTAIN',
62
+ description: 'A function parameter is declared but never read in the function body.',
63
+ explanation: 'Verified by the TypeScript compiler.',
64
+ example: 'function handler(req, res) { return res.end(); } // req unused',
65
+ why: 'Often fine, but can indicate a forgotten argument or stale interface.',
66
+ fixClassification: 'safe'
67
+ },
68
+ {
69
+ id: 'DEAD007',
70
+ title: 'Unused type',
71
+ category: 'deadCode',
72
+ severity: 'MEDIUM',
73
+ confidence: 'CERTAIN',
74
+ description: 'A type alias or interface is declared but never referenced.',
75
+ explanation: 'Verified by the TypeScript compiler.',
76
+ example: 'interface Options { } // never used',
77
+ why: 'Unused types drift out of sync with real usage and mislead readers.',
78
+ fixClassification: 'unsafe'
79
+ },
80
+ {
81
+ id: 'DEAD009',
82
+ title: 'Unused export',
83
+ category: 'deadCode',
84
+ severity: 'MEDIUM',
85
+ confidence: 'MEDIUM',
86
+ description: 'An exported symbol is not imported by any other file in the project.',
87
+ explanation: 'Computed from the internal import/export graph; cannot see consumers outside this repository.',
88
+ example: 'export function unusedHelper() { }',
89
+ why: 'Unused exports often mean the export can be made private or removed entirely.',
90
+ fixClassification: 'unsafe'
91
+ },
92
+ {
93
+ id: 'DEAD010',
94
+ title: 'Unused file',
95
+ category: 'deadCode',
96
+ severity: 'MEDIUM',
97
+ confidence: 'MEDIUM',
98
+ description: 'A file is never imported by any other file and is not a configured entry point.',
99
+ explanation: 'Computed from the internal import graph plus package.json main/bin/scripts.',
100
+ example: 'src/legacy/oldHelper.ts (nothing imports it)',
101
+ why: 'Unreachable files are dead weight and slow down navigation and builds.',
102
+ fixClassification: 'unsafe'
103
+ },
104
+ {
105
+ id: 'DEAD011',
106
+ title: 'Unreachable statement',
107
+ category: 'deadCode',
108
+ severity: 'MEDIUM',
109
+ confidence: 'CERTAIN',
110
+ description: 'Code appears after a return, throw, break, or continue in the same block.',
111
+ explanation: 'Detected via AST control-flow analysis within each statement block.',
112
+ example: 'return value;\nconsole.log("never runs");',
113
+ why: 'Unreachable code cannot execute and misleads readers about program behavior.',
114
+ fixClassification: 'safe'
115
+ },
116
+ // Dependencies
117
+ {
118
+ id: 'DEP001',
119
+ title: 'Unused dependency',
120
+ category: 'dependencies',
121
+ severity: 'MEDIUM',
122
+ confidence: 'CERTAIN',
123
+ description: 'Dependency is declared but never referenced from the source tree, require/import calls, or scripts.',
124
+ explanation: 'Reduces repository health and can hide package drift.',
125
+ example: 'import { z } from "zod"; // package installed but not used',
126
+ why: 'Unused packages increase install time, maintenance burden, and supply-chain risk.',
127
+ fixClassification: 'safe'
128
+ },
129
+ {
130
+ id: 'DEP002',
131
+ title: 'Unlisted dependency',
132
+ category: 'dependencies',
133
+ severity: 'HIGH',
134
+ confidence: 'HIGH',
135
+ description: 'A module is imported but not declared anywhere in package.json.',
136
+ explanation: 'The import resolves today only because a transitive dependency happens to provide it.',
137
+ example: "import { pick } from 'lodash'; // lodash not in package.json",
138
+ why: 'Relying on undeclared transitive dependencies breaks unpredictably on lockfile changes.',
139
+ fixClassification: 'manual'
140
+ },
141
+ {
142
+ id: 'DEP003',
143
+ title: 'Circular import',
144
+ category: 'dependencies',
145
+ severity: 'MEDIUM',
146
+ confidence: 'HIGH',
147
+ description: 'A cycle exists in the internal module import graph.',
148
+ explanation: 'Detected via depth-first traversal of the internal import graph.',
149
+ example: 'a.ts imports b.ts, which imports a.ts',
150
+ why: 'Import cycles cause initialization-order bugs and make modules hard to reason about independently.',
151
+ fixClassification: 'manual'
152
+ },
153
+ // Complexity
154
+ {
155
+ id: 'COMPLEX001',
156
+ title: 'High cyclomatic complexity function',
157
+ category: 'complexity',
158
+ severity: 'MEDIUM',
159
+ confidence: 'HIGH',
160
+ description: 'A function has more independent branches than the configured maximum.',
161
+ explanation: 'Counted from if/for/while/case/catch/ternary/&&/|| nodes in the function body.',
162
+ example: 'if (a && b || c) { ... }',
163
+ why: 'Complex logic is harder to test, review, and safely refactor.',
164
+ fixClassification: 'manual',
165
+ defaultThreshold: 10
166
+ },
167
+ {
168
+ id: 'COMPLEX002',
169
+ title: 'High cognitive complexity function',
170
+ category: 'complexity',
171
+ severity: 'MEDIUM',
172
+ confidence: 'HIGH',
173
+ description: 'A function has more nesting-weighted branching than the configured maximum.',
174
+ explanation: 'A simplified cognitive-complexity score that penalizes deep nesting more than flat branching.',
175
+ example: 'deeply nested if/for/while blocks',
176
+ why: 'Nested logic is disproportionately harder to hold in your head than flat logic of the same size.',
177
+ fixClassification: 'manual',
178
+ defaultThreshold: 15
179
+ },
180
+ {
181
+ id: 'COMPLEX003',
182
+ title: 'Function too long',
183
+ category: 'complexity',
184
+ severity: 'MEDIUM',
185
+ confidence: 'HIGH',
186
+ description: 'A function spans more lines than the configured maximum.',
187
+ explanation: 'Measured from the function declaration start to its closing brace.',
188
+ example: 'a 300-line function',
189
+ why: 'Long functions usually do more than one job and are harder to review and test.',
190
+ fixClassification: 'manual',
191
+ defaultThreshold: 100
192
+ },
193
+ {
194
+ id: 'COMPLEX004',
195
+ title: 'Too many parameters',
196
+ category: 'complexity',
197
+ severity: 'MEDIUM',
198
+ confidence: 'HIGH',
199
+ description: 'A function declares more parameters than the configured maximum.',
200
+ explanation: 'Counted directly from the function signature.',
201
+ example: 'function create(a, b, c, d, e, f) { }',
202
+ why: 'Long parameter lists are error-prone to call correctly and hard to extend.',
203
+ fixClassification: 'manual',
204
+ defaultThreshold: 5
205
+ },
206
+ {
207
+ id: 'COMPLEX005',
208
+ title: 'Excessive nesting depth',
209
+ category: 'complexity',
210
+ severity: 'MEDIUM',
211
+ confidence: 'HIGH',
212
+ description: 'A function nests control structures deeper than the configured maximum.',
213
+ explanation: 'Measured as the deepest if/for/while/switch/catch nesting level reached in the function.',
214
+ example: 'if { for { while { if { ... } } } }',
215
+ why: 'Deep nesting is a strong readability and testability smell.',
216
+ fixClassification: 'manual',
217
+ defaultThreshold: 4
218
+ },
219
+ // Duplication
220
+ {
221
+ id: 'DUP001',
222
+ title: 'Duplicate code block',
223
+ category: 'duplication',
224
+ severity: 'LOW',
225
+ confidence: 'MEDIUM',
226
+ description: 'An identical token sequence of at least the configured length appears in more than one location.',
227
+ explanation: 'Detected via sliding-window token hashing (exact match, not semantic).',
228
+ example: 'the same validation logic copy-pasted in two functions',
229
+ why: 'Duplication causes divergence during fixes and makes review harder.',
230
+ fixClassification: 'manual',
231
+ defaultThreshold: 40
232
+ },
233
+ // Error handling
234
+ {
235
+ id: 'ERR001',
236
+ title: 'Empty catch block',
237
+ category: 'errorHandling',
238
+ severity: 'MEDIUM',
239
+ confidence: 'CERTAIN',
240
+ description: 'An exception is caught and silently discarded.',
241
+ explanation: 'Detected via AST inspection of catch block bodies.',
242
+ example: 'try { risky(); } catch (e) { }',
243
+ why: 'Swallowed exceptions hide real failures and make debugging production issues far harder.',
244
+ fixClassification: 'manual'
245
+ },
246
+ {
247
+ id: 'ERR002',
248
+ title: 'Catch-and-rethrow without modification',
249
+ category: 'errorHandling',
250
+ severity: 'LOW',
251
+ confidence: 'HIGH',
252
+ description: 'A catch block does nothing but rethrow the original error unchanged.',
253
+ explanation: 'Detected via AST inspection of catch block bodies.',
254
+ example: 'try { risky(); } catch (e) { throw e; }',
255
+ why: 'The try/catch adds no value and can be removed entirely.',
256
+ fixClassification: 'manual'
257
+ },
258
+ {
259
+ id: 'ERR003',
260
+ title: 'Floating promise',
261
+ category: 'errorHandling',
262
+ severity: 'MEDIUM',
263
+ confidence: 'MEDIUM',
264
+ description: 'A Promise-like result is neither awaited nor handled.',
265
+ explanation: 'Uses the TypeScript type checker to recognize Promise-returning calls, including methods and imported functions.',
266
+ example: 'async function save() { }\nsave(); // not awaited',
267
+ why: 'Unhandled promise rejections can crash the process or silently swallow errors.',
268
+ fixClassification: 'manual'
269
+ },
270
+ {
271
+ id: 'ERR004',
272
+ title: 'Promise chain without rejection handler',
273
+ category: 'errorHandling',
274
+ severity: 'MEDIUM',
275
+ confidence: 'MEDIUM',
276
+ description: 'A .then() chain is not followed by a .catch() handler.',
277
+ explanation: 'A rejected Promise can become an unhandled rejection when the chain has no error handler.',
278
+ example: 'loadData().then(render);',
279
+ why: 'Unhandled asynchronous errors can fail requests or terminate processes.',
280
+ fixClassification: 'manual'
281
+ },
282
+ // Redundant logic
283
+ {
284
+ id: 'REDUNDANT001',
285
+ title: 'Redundant boolean comparison',
286
+ category: 'redundantLogic',
287
+ severity: 'LOW',
288
+ confidence: 'CERTAIN',
289
+ description: 'A value is compared directly against the literal true or false.',
290
+ explanation: 'Detected via AST inspection of equality expressions.',
291
+ example: 'if (isValid === true) { }',
292
+ why: 'The comparison is redundant; the expression can be used directly.',
293
+ fixClassification: 'safe'
294
+ },
295
+ {
296
+ id: 'REDUNDANT002',
297
+ title: 'Impossible condition',
298
+ category: 'redundantLogic',
299
+ severity: 'MEDIUM',
300
+ confidence: 'CERTAIN',
301
+ description: 'An if-statement condition is a literal true or false.',
302
+ explanation: 'Detected via AST inspection of if-statement conditions.',
303
+ example: 'if (false) { neverRuns(); }',
304
+ why: 'One branch can never execute, which is almost always an oversight.',
305
+ fixClassification: 'unsafe'
306
+ },
307
+ // TypeScript quality
308
+ {
309
+ id: 'TS001',
310
+ title: 'Unsafe any',
311
+ category: 'typescript',
312
+ severity: 'MEDIUM',
313
+ confidence: 'CERTAIN',
314
+ description: 'The code uses explicit or implicit any patterns that bypass TypeScript safety.',
315
+ explanation: 'Any weakens type-checking and allows incorrect values to flow through an application.',
316
+ example: 'const value: any = payload',
317
+ why: 'It reduces compile-time guarantees and increases runtime failures.',
318
+ fixClassification: 'unsafe'
319
+ },
320
+ {
321
+ id: 'TS002',
322
+ title: 'Suppressed type checking',
323
+ category: 'typescript',
324
+ severity: 'MEDIUM',
325
+ confidence: 'CERTAIN',
326
+ description: '@ts-ignore, @ts-nocheck, or @ts-expect-error disables checking.',
327
+ explanation: 'Detected via comment scanning.',
328
+ example: '// @ts-ignore\nconst x: number = "oops";',
329
+ why: 'Suppressing errors hides real type problems instead of fixing them.',
330
+ fixClassification: 'manual'
331
+ },
332
+ {
333
+ id: 'TS003',
334
+ title: 'Non-null assertion',
335
+ category: 'typescript',
336
+ severity: 'LOW',
337
+ confidence: 'CERTAIN',
338
+ description: 'The ! operator asserts a value is non-null without verifying it.',
339
+ explanation: 'Detected via AST inspection for non-null assertion expressions.',
340
+ example: 'const name = user!.name;',
341
+ why: 'A wrong assertion becomes a runtime crash instead of a compile-time error.',
342
+ fixClassification: 'manual'
343
+ },
344
+ // JavaScript quality (scored under hygiene per the PRD weighting table)
345
+ {
346
+ id: 'JS001',
347
+ title: "'var' declaration",
348
+ category: 'hygiene',
349
+ severity: 'LOW',
350
+ confidence: 'CERTAIN',
351
+ description: 'var is used instead of let or const.',
352
+ explanation: 'Detected via AST inspection of variable declaration lists.',
353
+ example: 'var count = 0;',
354
+ why: 'var is function-scoped and hoisted, which commonly causes bugs block-scoped declarations avoid.',
355
+ fixClassification: 'unsafe'
356
+ },
357
+ {
358
+ id: 'JS002',
359
+ title: 'Loose equality',
360
+ category: 'hygiene',
361
+ severity: 'LOW',
362
+ confidence: 'CERTAIN',
363
+ description: '== or != is used where strict equality is expected.',
364
+ explanation: '== null / != null are exempted as an intentional, common idiom.',
365
+ example: 'if (value == 0) { }',
366
+ why: 'Loose equality performs implicit type coercion, which can hide bugs.',
367
+ fixClassification: 'unsafe'
368
+ },
369
+ // Security
370
+ {
371
+ id: 'SEC001',
372
+ title: 'Hardcoded secret',
373
+ category: 'security',
374
+ severity: 'HIGH',
375
+ confidence: 'HIGH',
376
+ description: 'Potential secret material appears directly in source code.',
377
+ explanation: 'Recognizes common provider token formats and high-entropy values assigned to credential-like variable names.',
378
+ example: 'const apiKey = "sk_live_123"',
379
+ why: 'Secrets in code create immediate security and operational risk.',
380
+ fixClassification: 'manual'
381
+ },
382
+ {
383
+ id: 'SEC002',
384
+ title: 'Use of eval',
385
+ category: 'security',
386
+ severity: 'HIGH',
387
+ confidence: 'CERTAIN',
388
+ description: 'eval executes arbitrary strings as code.',
389
+ explanation: 'Detected via AST inspection of call expressions.',
390
+ example: "eval(userInput)",
391
+ why: 'eval is a common injection vector and defeats static analysis and bundling.',
392
+ fixClassification: 'manual'
393
+ },
394
+ {
395
+ id: 'SEC003',
396
+ title: 'Dangerous command execution',
397
+ category: 'security',
398
+ severity: 'HIGH',
399
+ confidence: 'MEDIUM',
400
+ description: 'A shell command is dynamically built or shell execution is explicitly enabled.',
401
+ explanation: 'Checks dynamic command expressions, a one-hop local variable initializer, and shell: true on spawn-family calls.',
402
+ example: 'exec(`rm -rf ${userPath}`)',
403
+ why: 'Unsanitized dynamic shell commands are a classic command-injection vector.',
404
+ fixClassification: 'manual'
405
+ },
406
+ {
407
+ id: 'SEC004',
408
+ title: 'Unsafe HTML assignment',
409
+ category: 'security',
410
+ severity: 'MEDIUM',
411
+ confidence: 'MEDIUM',
412
+ description: 'innerHTML/outerHTML is assigned to, or dangerouslySetInnerHTML is used.',
413
+ explanation: 'Detected via AST, insertAdjacentHTML calls, and raw HTML bindings in Vue/HTML templates.',
414
+ example: 'el.innerHTML = userContent;',
415
+ why: 'Rendering unsanitized content as HTML is a common cross-site-scripting vector.',
416
+ fixClassification: 'manual'
417
+ },
418
+ {
419
+ id: 'SEC005',
420
+ title: 'Weak cryptographic hash',
421
+ category: 'security',
422
+ severity: 'MEDIUM',
423
+ confidence: 'HIGH',
424
+ description: 'crypto.createHash is called with a weak algorithm (md5, sha1, des, rc4).',
425
+ explanation: 'Detected via AST inspection of createHash call arguments.',
426
+ example: "crypto.createHash('md5')",
427
+ why: 'Weak hash algorithms are broken for integrity and security-sensitive use cases.',
428
+ fixClassification: 'manual'
429
+ },
430
+ {
431
+ id: 'SEC007',
432
+ title: 'TLS certificate verification disabled',
433
+ category: 'security',
434
+ severity: 'HIGH',
435
+ confidence: 'HIGH',
436
+ description: 'NODE_TLS_REJECT_UNAUTHORIZED is set to 0.',
437
+ explanation: 'Detected via text scanning for the environment variable assignment.',
438
+ example: 'process.env.NODE_TLS' + '_REJECT_UNAUTHORIZED = "0"',
439
+ why: 'Disabling TLS verification exposes the process to man-in-the-middle attacks.',
440
+ fixClassification: 'manual'
441
+ },
442
+ {
443
+ id: 'SEC008',
444
+ title: 'Potential SQL injection',
445
+ category: 'security',
446
+ severity: 'HIGH',
447
+ confidence: 'MEDIUM',
448
+ description: 'A SQL query string is dynamically constructed.',
449
+ explanation: 'Concatenating input into query syntax can allow an attacker to alter the query.',
450
+ example: 'db.query(`SELECT * FROM users WHERE id = ${id}`);',
451
+ why: 'Parameterized queries keep data separate from executable SQL.',
452
+ fixClassification: 'manual'
453
+ },
454
+ {
455
+ id: 'SEC009',
456
+ title: 'Potential path traversal',
457
+ category: 'security',
458
+ severity: 'HIGH',
459
+ confidence: 'LOW',
460
+ description: 'A filesystem path may include untrusted request input.',
461
+ explanation: 'Unvalidated path segments can escape an intended directory.',
462
+ example: "readFile(path.join(root, req.params.file));",
463
+ why: 'Path traversal can expose or overwrite files outside the intended area.',
464
+ fixClassification: 'manual'
465
+ },
466
+ {
467
+ id: 'SEC010',
468
+ title: 'Unsafe deserialization or dynamic execution',
469
+ category: 'security',
470
+ severity: 'HIGH',
471
+ confidence: 'MEDIUM',
472
+ description: 'An unsafe deserializer or dynamic execution API is used.',
473
+ explanation: 'Untrusted serialized values or code can trigger object construction or execution.',
474
+ example: 'unserialize(input);',
475
+ why: 'Unsafe deserialization can lead to code execution or unexpected object behavior.',
476
+ fixClassification: 'manual'
477
+ },
478
+ // Architecture
479
+ {
480
+ id: 'ARCH001',
481
+ title: 'Forbidden layer import',
482
+ category: 'architecture',
483
+ severity: 'HIGH',
484
+ confidence: 'CERTAIN',
485
+ description: 'A file in one architecture layer imports from a layer it is configured to never depend on.',
486
+ explanation: 'Requires architecture.layers to be configured in .devkitrc.json; no findings without configuration.',
487
+ example: 'a controller importing directly from the database layer',
488
+ why: 'Layer violations erode the architecture boundaries a codebase relies on to stay maintainable.',
489
+ fixClassification: 'manual'
490
+ },
491
+ // Hygiene
492
+ {
493
+ id: 'HYGIENE001',
494
+ title: 'Debug statement',
495
+ category: 'hygiene',
496
+ severity: 'LOW',
497
+ confidence: 'CERTAIN',
498
+ description: 'Debugging output or test instrumentation remains in production code.',
499
+ explanation: 'Console logging and debugger statements are noisy and can leak secrets.',
500
+ example: 'console.log("debug")',
501
+ why: 'Keeps logs noisy and can expose sensitive information.',
502
+ fixClassification: 'safe'
503
+ },
504
+ {
505
+ id: 'HYGIENE002',
506
+ title: 'Temporary marker',
507
+ category: 'hygiene',
508
+ severity: 'LOW',
509
+ confidence: 'MEDIUM',
510
+ description: 'A TODO, FIXME, HACK, or XXX marker remains in the code.',
511
+ explanation: 'Detected via text scanning.',
512
+ example: '// TODO: handle edge case',
513
+ why: 'Unresolved markers accumulate and signal untracked technical debt.',
514
+ fixClassification: 'safe'
515
+ },
516
+ {
517
+ id: 'HYGIENE003',
518
+ title: 'Backup or temporary file committed',
519
+ category: 'hygiene',
520
+ severity: 'LOW',
521
+ confidence: 'HIGH',
522
+ description: 'A file with a backup/temp extension (.bak, .orig, .tmp, ~) is present.',
523
+ explanation: 'Detected via filename pattern matching.',
524
+ example: 'config.json.bak',
525
+ why: 'Stray backup files clutter the repository and can contain stale secrets.',
526
+ fixClassification: 'safe'
527
+ },
528
+ {
529
+ id: 'HYGIENE004',
530
+ title: 'Committed .env file',
531
+ category: 'hygiene',
532
+ severity: 'HIGH',
533
+ confidence: 'MEDIUM',
534
+ description: 'A .env file (not a .example/.sample/.template variant) is present in the repository.',
535
+ explanation: 'Detected via filename pattern matching.',
536
+ example: '.env',
537
+ why: 'Environment files frequently contain live secrets and should never be committed.',
538
+ fixClassification: 'manual'
539
+ }
540
+ ];
541
+ function getRuleById(ruleId) {
542
+ return exports.rules.find((rule) => rule.id === ruleId);
543
+ }
544
+ function listRules() {
545
+ return [...exports.rules];
546
+ }