@aarock1234/slopscan 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.
Files changed (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -0
  3. package/SKILL.md +149 -0
  4. package/dist/analyzer.d.ts +5 -0
  5. package/dist/analyzer.js +13 -0
  6. package/dist/analyzer.js.map +1 -0
  7. package/dist/analyzers/ast.d.ts +12 -0
  8. package/dist/analyzers/ast.js +73 -0
  9. package/dist/analyzers/ast.js.map +1 -0
  10. package/dist/analyzers/judge-chunks.d.ts +13 -0
  11. package/dist/analyzers/judge-chunks.js +87 -0
  12. package/dist/analyzers/judge-chunks.js.map +1 -0
  13. package/dist/analyzers/judge-validate.d.ts +19 -0
  14. package/dist/analyzers/judge-validate.js +81 -0
  15. package/dist/analyzers/judge-validate.js.map +1 -0
  16. package/dist/analyzers/judge.d.ts +44 -0
  17. package/dist/analyzers/judge.js +177 -0
  18. package/dist/analyzers/judge.js.map +1 -0
  19. package/dist/change.d.ts +19 -0
  20. package/dist/change.js +87 -0
  21. package/dist/change.js.map +1 -0
  22. package/dist/cli.d.ts +2 -0
  23. package/dist/cli.js +125 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/config.d.ts +91 -0
  26. package/dist/config.js +61 -0
  27. package/dist/config.js.map +1 -0
  28. package/dist/confirm.d.ts +11 -0
  29. package/dist/confirm.js +100 -0
  30. package/dist/confirm.js.map +1 -0
  31. package/dist/finding.d.ts +13 -0
  32. package/dist/finding.js +18 -0
  33. package/dist/finding.js.map +1 -0
  34. package/dist/lang.d.ts +16 -0
  35. package/dist/lang.js +37 -0
  36. package/dist/lang.js.map +1 -0
  37. package/dist/model.d.ts +2 -0
  38. package/dist/model.js +34 -0
  39. package/dist/model.js.map +1 -0
  40. package/dist/report.d.ts +124 -0
  41. package/dist/report.js +112 -0
  42. package/dist/report.js.map +1 -0
  43. package/dist/rule.d.ts +66 -0
  44. package/dist/rule.js +259 -0
  45. package/dist/rule.js.map +1 -0
  46. package/dist/scan.d.ts +14 -0
  47. package/dist/scan.js +33 -0
  48. package/dist/scan.js.map +1 -0
  49. package/dist/score.d.ts +147 -0
  50. package/dist/score.js +142 -0
  51. package/dist/score.js.map +1 -0
  52. package/dist/shared/concurrency.d.ts +5 -0
  53. package/dist/shared/concurrency.js +17 -0
  54. package/dist/shared/concurrency.js.map +1 -0
  55. package/dist/shared/env.d.ts +27 -0
  56. package/dist/shared/env.js +29 -0
  57. package/dist/shared/env.js.map +1 -0
  58. package/dist/shared/errors.d.ts +18 -0
  59. package/dist/shared/errors.js +26 -0
  60. package/dist/shared/errors.js.map +1 -0
  61. package/dist/shared/log.d.ts +2 -0
  62. package/dist/shared/log.js +18 -0
  63. package/dist/shared/log.js.map +1 -0
  64. package/dist/shared/paths.d.ts +3 -0
  65. package/dist/shared/paths.js +6 -0
  66. package/dist/shared/paths.js.map +1 -0
  67. package/dist/shared/prompts.d.ts +3 -0
  68. package/dist/shared/prompts.js +34 -0
  69. package/dist/shared/prompts.js.map +1 -0
  70. package/package.json +79 -0
  71. package/prompts/judge.md +37 -0
  72. package/rules/any/futureproof/abstraction/pass-through-wrapper.md +70 -0
  73. package/rules/any/futureproof/abstraction/single-caller-helper.md +38 -0
  74. package/rules/any/futureproof/abstraction/single-impl-interface.md +83 -0
  75. package/rules/any/futureproof/exports/dead-export.md +62 -0
  76. package/rules/any/futureproof/layering/framework-type-in-domain.md +83 -0
  77. package/rules/any/futureproof/layering/logic-in-handler.md +94 -0
  78. package/rules/any/futureproof/params/boolean-positional-param.md +128 -0
  79. package/rules/any/futureproof/params/positional-config-args.md +135 -0
  80. package/rules/any/futureproof/structure/wide-function.md +138 -0
  81. package/rules/any/futureproof/testing/test-asserts-implementation.md +70 -0
  82. package/rules/any/hacky/comments/comment-restates-code.md +50 -0
  83. package/rules/any/hacky/comments/shipped-todo-comment.md +55 -0
  84. package/rules/any/hacky/concurrency/sleep-based-sync.md +95 -0
  85. package/rules/any/hacky/config/hardcoded-url.md +88 -0
  86. package/rules/any/hacky/constants/magic-number.md +94 -0
  87. package/rules/any/hacky/debugging/debug-print.md +73 -0
  88. package/rules/any/hacky/duplication/copy-paste-block.md +117 -0
  89. package/rules/any/hacky/types/stringly-typed-enum.md +86 -0
  90. package/rules/any/idiom/comments/no-section-banners.md +47 -0
  91. package/rules/go/futureproof/constants/string-enums-when-serialized.md +47 -0
  92. package/rules/go/futureproof/constants/typed-constants-for-enums.md +45 -0
  93. package/rules/go/futureproof/interfaces/compile-time-impl-assertion.md +43 -0
  94. package/rules/go/futureproof/interfaces/define-interfaces-at-consumer.md +47 -0
  95. package/rules/go/futureproof/interfaces/keep-interfaces-small.md +53 -0
  96. package/rules/go/futureproof/naming/no-utils-helpers-common-package.md +34 -0
  97. package/rules/go/futureproof/structs/zero-value-usable.md +50 -0
  98. package/rules/go/futureproof/types/generics-over-any.md +55 -0
  99. package/rules/go/hacky/concurrency/no-goroutine-without-wait.md +44 -0
  100. package/rules/go/hacky/context/never-pass-nil-context.md +64 -0
  101. package/rules/go/hacky/context/no-context-in-struct.md +44 -0
  102. package/rules/go/hacky/errors/check-close-error-on-writable.md +53 -0
  103. package/rules/go/hacky/errors/custom-error-type-errors-as.md +54 -0
  104. package/rules/go/hacky/errors/ignored-error-blank.md +84 -0
  105. package/rules/go/hacky/errors/panic-only-unrecoverable.md +77 -0
  106. package/rules/go/hacky/errors/sentinel-errors-with-errors-is.md +63 -0
  107. package/rules/go/hacky/errors/wrap-errors-with-w.md +71 -0
  108. package/rules/go/hacky/state/package-level-mutable-var.md +95 -0
  109. package/rules/go/hacky/structure/os-exit-only-in-main.md +61 -0
  110. package/rules/go/hacky/type-safety/no-map-string-any.md +49 -0
  111. package/rules/go/hacky/types/json-into-struct-not-map.md +48 -0
  112. package/rules/go/hacky/types/no-any.md +71 -0
  113. package/rules/go/hacky/types/no-map-any-any.md +35 -0
  114. package/rules/go/idiom/comments/doc-comment-on-exported.md +81 -0
  115. package/rules/go/idiom/comments/doc-comment-starts-with-name.md +36 -0
  116. package/rules/go/idiom/concurrency/mutex-over-channels-for-state.md +47 -0
  117. package/rules/go/idiom/constructors/constructor-named-new.md +37 -0
  118. package/rules/go/idiom/context/context-is-first-param.md +47 -0
  119. package/rules/go/idiom/context/io-takes-context.md +50 -0
  120. package/rules/go/idiom/errors/capitalized-error-message.md +52 -0
  121. package/rules/go/idiom/errors/check-error-immediately.md +45 -0
  122. package/rules/go/idiom/errors/defer-close-explicit-discard.md +65 -0
  123. package/rules/go/idiom/errors/error-is-last-return-value.md +53 -0
  124. package/rules/go/idiom/errors/no-log-and-return.md +45 -0
  125. package/rules/go/idiom/formatting/blank-line-before-return.md +48 -0
  126. package/rules/go/idiom/formatting/multiline-struct-literals.md +56 -0
  127. package/rules/go/idiom/imports/import-order-groups.md +42 -0
  128. package/rules/go/idiom/imports/side-effect-imports-own-group.md +51 -0
  129. package/rules/go/idiom/interfaces/accept-interfaces-return-concrete.md +39 -0
  130. package/rules/go/idiom/iterators/prefer-iter-seq.md +47 -0
  131. package/rules/go/idiom/logging/lowercase-log-messages.md +80 -0
  132. package/rules/go/idiom/logging/use-slog.md +46 -0
  133. package/rules/go/idiom/naming/acronyms-consistent-case.md +97 -0
  134. package/rules/go/idiom/naming/interface-er-suffix.md +33 -0
  135. package/rules/go/idiom/naming/no-package-name-stutter.md +37 -0
  136. package/rules/go/idiom/naming/package-name-single-lowercase-word.md +40 -0
  137. package/rules/go/idiom/receivers/consistent-receiver-kind-per-type.md +40 -0
  138. package/rules/go/idiom/receivers/no-this-receiver.md +37 -0
  139. package/rules/go/idiom/receivers/pointer-vs-value-receiver-choice.md +49 -0
  140. package/rules/go/idiom/receivers/short-receiver-names.md +43 -0
  141. package/rules/go/idiom/structs/new-expr-for-pointer-fields.md +40 -0
  142. package/rules/go/idiom/structure/main-delegates-to-run.md +54 -0
  143. package/rules/ts/futureproof/classes/composition-over-abstract-base.md +73 -0
  144. package/rules/ts/futureproof/functions/explicit-return-type-on-exports.md +50 -0
  145. package/rules/ts/futureproof/structure/colocate-zod-schemas.md +46 -0
  146. package/rules/ts/futureproof/types/exhaustive-switch-never-check.md +52 -0
  147. package/rules/ts/futureproof/types/readonly-for-immutable-data.md +46 -0
  148. package/rules/ts/futureproof/zod/enum-values-from-const-object.md +37 -0
  149. package/rules/ts/futureproof/zod/no-z-native-enum.md +36 -0
  150. package/rules/ts/futureproof/zod/schema-first-infer-type.md +46 -0
  151. package/rules/ts/hacky/async/no-floating-promises.md +52 -0
  152. package/rules/ts/hacky/config/env-read-outside-config.md +56 -0
  153. package/rules/ts/hacky/errors/catch-param-typed-unknown.md +67 -0
  154. package/rules/ts/hacky/errors/custom-error-class-instanceof.md +66 -0
  155. package/rules/ts/hacky/errors/empty-catch.md +52 -0
  156. package/rules/ts/hacky/errors/log-and-swallow.md +65 -0
  157. package/rules/ts/hacky/errors/throw-typed-error-with-context.md +64 -0
  158. package/rules/ts/hacky/operators/nullish-coalescing-over-or.md +50 -0
  159. package/rules/ts/hacky/state/exported-let.md +60 -0
  160. package/rules/ts/hacky/type-safety/no-double-assertion.md +34 -0
  161. package/rules/ts/hacky/type-safety/no-explicit-any.md +48 -0
  162. package/rules/ts/hacky/type-safety/no-non-null-assertion.md +43 -0
  163. package/rules/ts/hacky/type-safety/no-unchecked-type-assertion.md +54 -0
  164. package/rules/ts/hacky/type-safety/validate-parsed-json.md +48 -0
  165. package/rules/ts/idiom/async/no-promise-chains.md +45 -0
  166. package/rules/ts/idiom/async/parallelize-independent-awaits.md +46 -0
  167. package/rules/ts/idiom/comments/doc-comment-above-declaration.md +43 -0
  168. package/rules/ts/idiom/constants/as-const-for-literal-config.md +65 -0
  169. package/rules/ts/idiom/constants/no-enum.md +37 -0
  170. package/rules/ts/idiom/control-flow/always-brace-if.md +56 -0
  171. package/rules/ts/idiom/control-flow/guard-clauses-early-return.md +52 -0
  172. package/rules/ts/idiom/control-flow/no-nested-ternary.md +54 -0
  173. package/rules/ts/idiom/errors/error-message-lowercase.md +56 -0
  174. package/rules/ts/idiom/errors/log-at-boundary-not-every-layer.md +66 -0
  175. package/rules/ts/idiom/exports/inline-export-at-declaration.md +49 -0
  176. package/rules/ts/idiom/exports/no-default-export.md +37 -0
  177. package/rules/ts/idiom/formatting/multiline-object-literals.md +46 -0
  178. package/rules/ts/idiom/functions/arrow-for-callbacks.md +44 -0
  179. package/rules/ts/idiom/functions/function-declaration-for-top-level.md +56 -0
  180. package/rules/ts/idiom/imports/export-type-for-types.md +34 -0
  181. package/rules/ts/idiom/imports/import-order.md +66 -0
  182. package/rules/ts/idiom/imports/import-type-for-types.md +50 -0
  183. package/rules/ts/idiom/imports/side-effect-imports-last.md +42 -0
  184. package/rules/ts/idiom/logging/structured-logging-lowercase.md +45 -0
  185. package/rules/ts/idiom/naming/boolean-name-question-prefix.md +42 -0
  186. package/rules/ts/idiom/naming/descriptive-identifier-quality.md +48 -0
  187. package/rules/ts/idiom/naming/screaming-snake-module-constants.md +58 -0
  188. package/rules/ts/idiom/strings/template-literals-over-concat.md +51 -0
  189. package/rules/ts/idiom/types/interface-extends-over-intersection.md +44 -0
  190. package/rules/ts/idiom/types/null-vs-undefined-convention.md +43 -0
  191. package/rules/ts/idiom/types/prefer-type-over-interface.md +64 -0
  192. package/rules/ts/idiom/types/satisfies-over-annotation.md +43 -0
  193. package/rules/ts/idiom/types/type-predicates-for-narrowing.md +53 -0
  194. package/rules/ts/idiom/variables/const-by-default.md +41 -0
  195. package/rules/ts/idiom/variables/no-var.md +36 -0
@@ -0,0 +1,37 @@
1
+ ---
2
+ severity: minor
3
+ detect: ast
4
+ ignore:
5
+ - '**/*.config.ts'
6
+ - '**/app/**'
7
+ - '**/pages/**'
8
+ ast:
9
+ rule:
10
+ pattern: export default $X
11
+ ---
12
+
13
+ ## Why
14
+
15
+ A default export has no name of its own, so every importer invents one and the same function ends up with three names across the codebase. Named exports keep one name, refactor safely, and autocomplete. Framework files that require a default export, such as config files and route modules, are excluded.
16
+
17
+ ## Message
18
+
19
+ default export has no name of its own; use a named export
20
+
21
+ ## Bad
22
+
23
+ ```ts
24
+ // BAD: importers will call this anything they like
25
+ export default function createUser(input: CreateUserInput): Promise<User> {
26
+ return repository.insert(input);
27
+ }
28
+ ```
29
+
30
+ ## Good
31
+
32
+ ```ts
33
+ // GOOD: one name everywhere
34
+ export function createUser(input: CreateUserInput): Promise<User> {
35
+ return repository.insert(input);
36
+ }
37
+ ```
@@ -0,0 +1,46 @@
1
+ ---
2
+ severity: info
3
+ detect: ast
4
+ ast:
5
+ rule:
6
+ kind: object
7
+ has:
8
+ nthChild: 2
9
+ regex: '^[^\n]*$'
10
+ ---
11
+
12
+ ## Why
13
+
14
+ An object literal with two or more properties squeezed onto one line hides the key/value pairs in a wall of punctuation and turns every added property into a diff on the same line. One property per line lets the reader scan keys down the left edge and makes each change a one-line diff. Single-property objects such as `{ id }` are fine inline.
15
+
16
+ ## Message
17
+
18
+ object with several properties on one line; put one property per line
19
+
20
+ ## Bad
21
+
22
+ ```ts
23
+ // BAD: two properties squeezed onto one line
24
+ const options = { timeout: 5000, retries: 3 };
25
+ ```
26
+
27
+ ```ts
28
+ // BAD: inline argument object with several properties
29
+ await db.user.update({ where: { id }, data: input });
30
+ ```
31
+
32
+ ## Good
33
+
34
+ ```ts
35
+ const options = {
36
+ timeout: 5000,
37
+ retries: 3,
38
+ };
39
+ ```
40
+
41
+ ```ts
42
+ await db.user.update({
43
+ where: { id },
44
+ data: input,
45
+ });
46
+ ```
@@ -0,0 +1,44 @@
1
+ ---
2
+ severity: minor
3
+ detect: ast
4
+ ast:
5
+ rule:
6
+ kind: function_expression
7
+ inside:
8
+ kind: arguments
9
+ not:
10
+ has:
11
+ kind: this
12
+ stopBy: end
13
+ ---
14
+
15
+ ## Why
16
+
17
+ A `function` expression passed as a callback carries its own `this`, which is almost never what the surrounding code wants, and the keyword adds noise to what is usually a one-line transform. An arrow keeps the outer `this`, reads as an expression, and is the form every reader expects inside `map`, `filter`, event handlers, and route definitions. Callbacks that deliberately use their own `this`, such as some test framework hooks, are left alone.
18
+
19
+ ## Message
20
+
21
+ `function` expression as a callback; use an arrow function
22
+
23
+ ## Bad
24
+
25
+ ```ts
26
+ // BAD: function expression where an arrow is expected
27
+ const activeUsers = users.filter(function (user) {
28
+ return user.isActive;
29
+ });
30
+ ```
31
+
32
+ ## Good
33
+
34
+ ```ts
35
+ const activeUsers = users.filter(user => user.isActive);
36
+
37
+ app.get('/health', (req, res) => res.json({ status: 'ok' }));
38
+ ```
39
+
40
+ ```ts
41
+ describe('parser', function () {
42
+ this.timeout(10_000);
43
+ });
44
+ ```
@@ -0,0 +1,56 @@
1
+ ---
2
+ severity: minor
3
+ detect: ast
4
+ ast:
5
+ rule:
6
+ kind: lexical_declaration
7
+ has:
8
+ kind: variable_declarator
9
+ has:
10
+ kind: arrow_function
11
+ field: value
12
+ inside:
13
+ any:
14
+ - kind: program
15
+ - kind: export_statement
16
+ ---
17
+
18
+ ## Why
19
+
20
+ A top-level function written as `const f = () => {}` is not hoisted, so callers above it in the file break, and it shows up in stack traces and debuggers as an anonymous arrow bound to a variable. A `function` declaration is hoisted, carries its name, and stands out visually as a unit of the module. Arrows are for callbacks and inline expressions, where lexical `this` and brevity actually help.
21
+
22
+ ## Message
23
+
24
+ top-level arrow function; use a `function` declaration
25
+
26
+ ## Bad
27
+
28
+ ```ts
29
+ // BAD: exported arrow is not hoisted and is anonymous in stack traces
30
+ export const createUser = async (input: CreateUserInput): Promise<User> => {
31
+ return repository.insert(input);
32
+ };
33
+ ```
34
+
35
+ ```ts
36
+ // BAD: module-level helper written as an arrow
37
+ const toSlug = (title: string) => title.toLowerCase().replaceAll(' ', '-');
38
+ ```
39
+
40
+ ## Good
41
+
42
+ ```ts
43
+ export async function createUser(input: CreateUserInput): Promise<User> {
44
+ return repository.insert(input);
45
+ }
46
+ ```
47
+
48
+ ```ts
49
+ function toSlug(title: string): string {
50
+ return title.toLowerCase().replaceAll(' ', '-');
51
+ }
52
+
53
+ export function slugs(titles: readonly string[]): string[] {
54
+ return titles.map(title => toSlug(title));
55
+ }
56
+ ```
@@ -0,0 +1,34 @@
1
+ ---
2
+ severity: minor
3
+ detect: judge
4
+ falsePositives:
5
+ - re-exports of values such as functions, classes, const objects, or schemas
6
+ - a `type` or `interface` declared inline with `export type` or `export interface`
7
+ - re-exports that mix types and values and already mark the types with an inline `type` modifier
8
+ ---
9
+
10
+ ## Why
11
+
12
+ Re-exporting a type through a plain `export { X } from` keeps a runtime edge to the module even though nothing from it survives compilation. `export type { X } from` is erased, keeps `isolatedModules` builds happy, and tells the reader that the module contributes only types. It mirrors `import type` so the two read the same way.
13
+
14
+ ## Message
15
+
16
+ re-export is a type; use `export type`
17
+
18
+ ## Bad
19
+
20
+ ```ts
21
+ // BAD: User is a type, but the re-export keeps a runtime edge to ./user
22
+ export { User } from './user';
23
+ ```
24
+
25
+ ## Good
26
+
27
+ ```ts
28
+ export type { User } from './user';
29
+ export { createUser } from './user';
30
+ ```
31
+
32
+ ```ts
33
+ export { type User, createUser } from './user';
34
+ ```
@@ -0,0 +1,66 @@
1
+ ---
2
+ severity: minor
3
+ detect: ast
4
+ ast:
5
+ rule:
6
+ kind: import_statement
7
+ any:
8
+ - has:
9
+ field: source
10
+ regex: '^.\.'
11
+ precedes:
12
+ kind: import_statement
13
+ stopBy: end
14
+ has:
15
+ field: source
16
+ regex: '^.[^.]'
17
+ - has:
18
+ field: source
19
+ regex: '^.@/'
20
+ precedes:
21
+ kind: import_statement
22
+ stopBy: end
23
+ has:
24
+ field: source
25
+ regex: '^.[^.]'
26
+ not:
27
+ has:
28
+ field: source
29
+ regex: '^.@/'
30
+ ---
31
+
32
+ ## Why
33
+
34
+ Imports come in three groups, external packages, then internal `@/` modules, then relative paths, with a blank line between groups. A reader scans the top of a file to learn what it depends on, and a fixed order makes external dependencies and local coupling visible at a glance. Mixed groups force a line-by-line read and produce noisy diffs when imports are added.
35
+
36
+ ## Message
37
+
38
+ imports out of order; external packages, then `@/` modules, then relative paths
39
+
40
+ ## Bad
41
+
42
+ ```ts
43
+ // BAD: relative import placed before an external package
44
+ import { formatDate } from './format';
45
+ import { z } from 'zod';
46
+ ```
47
+
48
+ ```ts
49
+ import { z } from 'zod';
50
+ // BAD: internal module placed before an external package
51
+ import { config } from '@/config';
52
+ import { PrismaClient } from '@prisma/client';
53
+ ```
54
+
55
+ ## Good
56
+
57
+ ```ts
58
+ import { PrismaClient } from '@prisma/client';
59
+ import { z } from 'zod';
60
+
61
+ import { config } from '@/config';
62
+ import { logger } from '@/lib/logger';
63
+
64
+ import { formatDate } from './format';
65
+ import type { User } from './user';
66
+ ```
@@ -0,0 +1,50 @@
1
+ ---
2
+ severity: minor
3
+ detect: judge
4
+ falsePositives:
5
+ - imports whose binding is used as a runtime value anywhere in the file, including in `instanceof`, `typeof`, decorators, or `satisfies`
6
+ - imports of a const object that doubles as a type through `typeof`
7
+ - files where every import in the statement is already marked with an inline `type` modifier
8
+ ---
9
+
10
+ ## Why
11
+
12
+ A binding that is only ever used in type positions should be imported with `import type`, which is erased at compile time. A plain import of a type keeps a runtime dependency on the module, can drag a circular import into existence, and hides from the reader that nothing from that module runs. The distinction is cheap to write and tells the next person exactly what the file needs at runtime.
13
+
14
+ ## Message
15
+
16
+ import is only used as a type; use `import type`
17
+
18
+ ## Bad
19
+
20
+ ```ts
21
+ // BAD: User is only used in a type annotation
22
+ import { User } from './user';
23
+
24
+ export function displayName(user: User): string {
25
+ return user.name;
26
+ }
27
+ ```
28
+
29
+ ## Good
30
+
31
+ ```ts
32
+ import type { User } from './user';
33
+
34
+ export function displayName(user: User): string {
35
+ return user.name;
36
+ }
37
+ ```
38
+
39
+ ```ts
40
+ import { UserService } from './user.service';
41
+ import type { User } from './user';
42
+
43
+ export function build(): UserService {
44
+ return new UserService();
45
+ }
46
+
47
+ export function displayName(user: User): string {
48
+ return user.name;
49
+ }
50
+ ```
@@ -0,0 +1,42 @@
1
+ ---
2
+ severity: minor
3
+ detect: ast
4
+ ast:
5
+ rule:
6
+ kind: import_statement
7
+ not:
8
+ has:
9
+ kind: import_clause
10
+ precedes:
11
+ kind: import_statement
12
+ stopBy: end
13
+ ---
14
+
15
+ ## Why
16
+
17
+ A side-effect import such as `import './polyfills'` runs code for its effect alone, so it stands apart from the imports that bind names. Placing it last, after a blank line, makes the effect visible instead of burying it among ordinary imports where a reader assumes nothing happens. A comment saying what the effect is helps the next person decide whether it can be removed.
18
+
19
+ ## Message
20
+
21
+ side-effect import belongs after all named imports
22
+
23
+ ## Bad
24
+
25
+ ```ts
26
+ // BAD: side-effect import hidden at the top of the list
27
+ import './polyfills';
28
+ import { z } from 'zod';
29
+
30
+ import { config } from '@/config';
31
+ ```
32
+
33
+ ## Good
34
+
35
+ ```ts
36
+ import { z } from 'zod';
37
+
38
+ import { config } from '@/config';
39
+
40
+ // registers the fetch polyfill for node 18
41
+ import './polyfills';
42
+ ```
@@ -0,0 +1,45 @@
1
+ ---
2
+ severity: minor
3
+ detect: ast
4
+ ast:
5
+ rule:
6
+ any:
7
+ - pattern: $LOGGER.$METHOD($MSG)
8
+ - pattern: $LOGGER.$METHOD($MSG, $$$REST)
9
+ constraints:
10
+ METHOD:
11
+ regex: '^(log|info|warn|error|debug|trace|fatal)$'
12
+ MSG:
13
+ regex: '^.[A-Z][a-z]'
14
+ ---
15
+
16
+ ## Why
17
+
18
+ Log lines are grepped and read in bulk, and a mix of `Starting server` and `connected to database` makes the stream look like it came from two systems. Messages are lowercase fragments, with proper nouns and acronyms kept as they are, and variable data goes in the structured fields rather than the sentence. A consistent shape makes the output scannable and the fields queryable.
19
+
20
+ ## Message
21
+
22
+ log message starts with a capital letter; write it as a lowercase fragment
23
+
24
+ ## Bad
25
+
26
+ ```ts
27
+ // BAD: capitalized sentence in a log line
28
+ logger.info('Starting server on port 3000');
29
+ ```
30
+
31
+ ```ts
32
+ // BAD: console output follows the same convention
33
+ console.error('Failed to connect', error);
34
+ ```
35
+
36
+ ## Good
37
+
38
+ ```ts
39
+ logger.info('starting server', { port: 3000 });
40
+ logger.error('failed to connect', { error });
41
+ ```
42
+
43
+ ```ts
44
+ logger.info('connecting to PostgreSQL');
45
+ ```
@@ -0,0 +1,42 @@
1
+ ---
2
+ severity: minor
3
+ detect: judge
4
+ falsePositives:
5
+ - names that already read as a yes/no question with a prefix like `is`, `has`, `should`, `can`, `was`, `needs`, or `allows`
6
+ - boolean properties whose name is dictated by an external schema, framework prop, or API response
7
+ - loop or callback parameters of one or two letters in a very short scope
8
+ ---
9
+
10
+ ## Why
11
+
12
+ A boolean whose name is a bare noun or verb, such as `valid`, `children`, or `retry`, forces the reader to look up its type before an `if` on it makes sense. Names that read as a yes/no question, such as `isValid`, `hasChildren`, or `shouldRetry`, carry the type in the name and make conditions read as prose. This applies to variables, properties, parameters, and functions that return a boolean.
13
+
14
+ ## Message
15
+
16
+ boolean name does not read as a question; prefix with is, has, should, or can
17
+
18
+ ## Bad
19
+
20
+ ```ts
21
+ // BAD: valid could be a noun, a verb, or an adjective
22
+ const valid = schema.safeParse(data).success;
23
+
24
+ // BAD: children sounds like a collection, not a flag
25
+ const children = node.children.length > 0;
26
+
27
+ // BAD: retry reads as an action, but it returns a yes/no answer
28
+ function retry(attempt: number, error: Error): boolean {
29
+ return attempt < 3 && error instanceof TimeoutError;
30
+ }
31
+ ```
32
+
33
+ ## Good
34
+
35
+ ```ts
36
+ const isValid = schema.safeParse(data).success;
37
+ const hasChildren = node.children.length > 0;
38
+
39
+ function shouldRetry(attempt: number, error: Error): boolean {
40
+ return attempt < 3 && error instanceof TimeoutError;
41
+ }
42
+ ```
@@ -0,0 +1,48 @@
1
+ ---
2
+ severity: minor
3
+ detect: judge
4
+ falsePositives:
5
+ - conventional short names in tiny scopes, such as `i` in a counting loop, `e` in a catch, `x` in a one-line arrow, or `T` as a type parameter
6
+ - domain abbreviations that are standard in the codebase, such as `db`, `req`, `res`, `ctx`, `id`, or `url`
7
+ - names that match an external API or schema field the code has to mirror
8
+ ---
9
+
10
+ ## Why
11
+
12
+ An identifier is read far more often than it is written, and a name like `data`, `tmp`, `res2`, or `handleStuff` tells the reader nothing they did not already know from the type. Good names say what the value is or what the function does, so the surrounding code needs fewer comments and a wrong assumption is caught on sight. Length is not the goal; precision is, and a short name in a short scope is fine.
13
+
14
+ ## Message
15
+
16
+ identifier does not describe what it holds or does; use a descriptive name
17
+
18
+ ## Bad
19
+
20
+ ```ts
21
+ // BAD: data, res, and tmp say nothing about what they hold
22
+ async function process(data: string): Promise<string[]> {
23
+ const res = await fetchRows(data);
24
+ const tmp = res.filter(r => r.active);
25
+
26
+ return tmp.map(t => t.id);
27
+ }
28
+ ```
29
+
30
+ ```ts
31
+ // BAD: a numbered copy of a name is a sign the first name was wrong
32
+ const user2 = await repository.find(managerId);
33
+ ```
34
+
35
+ ## Good
36
+
37
+ ```ts
38
+ async function activeRowIds(tableName: string): Promise<string[]> {
39
+ const rows = await fetchRows(tableName);
40
+ const activeRows = rows.filter(row => row.active);
41
+
42
+ return activeRows.map(row => row.id);
43
+ }
44
+ ```
45
+
46
+ ```ts
47
+ const manager = await repository.find(managerId);
48
+ ```
@@ -0,0 +1,58 @@
1
+ ---
2
+ severity: minor
3
+ detect: ast
4
+ ast:
5
+ rule:
6
+ pattern: const $NAME = $VALUE
7
+ inside:
8
+ any:
9
+ - kind: program
10
+ - kind: export_statement
11
+ constraints:
12
+ NAME:
13
+ regex: '^[a-z][a-zA-Z0-9]*$'
14
+ VALUE:
15
+ any:
16
+ - kind: number
17
+ - kind: string
18
+ - kind: 'true'
19
+ - kind: 'false'
20
+ ---
21
+
22
+ ## Why
23
+
24
+ A module-level constant holding a literal value is a configuration knob, and SCREAMING_SNAKE_CASE marks it as one at every use site. A camelCase name such as `maxRetries` looks like a local variable, so a reader inside a function cannot tell whether it is a fixed limit or something computed nearby. Local constants stay camelCase; only the module-level literals get the loud name.
25
+
26
+ ## Message
27
+
28
+ module-level literal constant is camelCase; use SCREAMING_SNAKE_CASE
29
+
30
+ ## Bad
31
+
32
+ ```ts
33
+ // BAD: a fixed module-level limit dressed as a local variable
34
+ const maxRetries = 3;
35
+ ```
36
+
37
+ ```ts
38
+ // BAD: exported literal config in camelCase
39
+ export const defaultTimeout = 10_000;
40
+ ```
41
+
42
+ ## Good
43
+
44
+ ```ts
45
+ const MAX_RETRIES = 3;
46
+
47
+ export const DEFAULT_TIMEOUT = 10_000;
48
+ ```
49
+
50
+ ```ts
51
+ export const userSchema = z.object({
52
+ id: z.string(),
53
+ });
54
+
55
+ function retry(): void {
56
+ const attempts = 3;
57
+ }
58
+ ```
@@ -0,0 +1,51 @@
1
+ ---
2
+ severity: minor
3
+ detect: ast
4
+ ast:
5
+ rule:
6
+ kind: binary_expression
7
+ all:
8
+ - has:
9
+ field: operator
10
+ regex: '^\+$'
11
+ - has:
12
+ kind: string
13
+ not:
14
+ inside:
15
+ kind: augmented_assignment_expression
16
+ ---
17
+
18
+ ## Why
19
+
20
+ Building a string with `+` scatters the literal parts and the values across quotes and operators, so the reader has to reassemble the final shape in their head and a missing space or quote is easy to miss. A template literal shows the string as it will appear with the values slotted in place. Incremental building in a loop with `+=` is a different pattern and is left alone.
21
+
22
+ ## Message
23
+
24
+ string built with `+`; use a template literal
25
+
26
+ ## Bad
27
+
28
+ ```ts
29
+ // BAD: the final shape is spread across three fragments and two operators
30
+ const greeting = 'hello, ' + name + '!';
31
+ ```
32
+
33
+ ```ts
34
+ // BAD: path assembled by concatenation
35
+ const url = baseUrl + '/users/' + userId + '/posts';
36
+ ```
37
+
38
+ ## Good
39
+
40
+ ```ts
41
+ const greeting = `hello, ${name}!`;
42
+ const url = `${baseUrl}/users/${userId}/posts`;
43
+ ```
44
+
45
+ ```ts
46
+ let csv = '';
47
+
48
+ for (const row of rows) {
49
+ csv += row.join(',') + '\n';
50
+ }
51
+ ```
@@ -0,0 +1,44 @@
1
+ ---
2
+ severity: info
3
+ detect: judge
4
+ falsePositives:
5
+ - intersections in generic constraints such as `T extends Identifiable & Timestamped`
6
+ - 'branded types of the form `T & { readonly __brand: B }`'
7
+ - intersections with a union or a mapped type, which `extends` cannot express
8
+ - an intersection of two object literals that is used once and never extended again
9
+ ---
10
+
11
+ ## Why
12
+
13
+ Extending a named object shape with `type Admin = User & { role: 'admin' }` is checked lazily on every use, and when two members conflict the intersection silently becomes `never` for that property instead of an error at the declaration. `interface Admin extends User` is checked once, rejects conflicting members where they are declared, and gives shorter error messages that name the interface rather than expanding the whole intersection. Intersections are for unions, brands, and generic constraints.
14
+
15
+ ## Message
16
+
17
+ object shape extended with an intersection; use `interface extends`
18
+
19
+ ## Bad
20
+
21
+ ```ts
22
+ // BAD: extending a named object shape through an intersection
23
+ type Admin = User & {
24
+ role: 'admin';
25
+ permissions: readonly string[];
26
+ };
27
+ ```
28
+
29
+ ## Good
30
+
31
+ ```ts
32
+ interface Admin extends User {
33
+ role: 'admin';
34
+ permissions: readonly string[];
35
+ }
36
+ ```
37
+
38
+ ```ts
39
+ type UserId = string & { readonly __brand: 'UserId' };
40
+
41
+ function sortByCreation<T extends Identifiable & Timestamped>(items: readonly T[]): T[] {
42
+ return [...items].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
43
+ }
44
+ ```