@lbroth/rothunter 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +141 -0
  3. package/dist/adapters/llm.d.ts +68 -0
  4. package/dist/adapters/llm.d.ts.map +1 -0
  5. package/dist/adapters/llm.js +189 -0
  6. package/dist/adapters/llm.js.map +1 -0
  7. package/dist/config.d.ts +37 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +81 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/detector-registry.d.ts +32 -0
  12. package/dist/detector-registry.d.ts.map +1 -0
  13. package/dist/detector-registry.js +74 -0
  14. package/dist/detector-registry.js.map +1 -0
  15. package/dist/detectors/api-race.d.ts +6 -0
  16. package/dist/detectors/api-race.d.ts.map +1 -0
  17. package/dist/detectors/api-race.js +222 -0
  18. package/dist/detectors/api-race.js.map +1 -0
  19. package/dist/detectors/bad-config.d.ts +6 -0
  20. package/dist/detectors/bad-config.d.ts.map +1 -0
  21. package/dist/detectors/bad-config.js +529 -0
  22. package/dist/detectors/bad-config.js.map +1 -0
  23. package/dist/detectors/console-log-prod.d.ts +6 -0
  24. package/dist/detectors/console-log-prod.d.ts.map +1 -0
  25. package/dist/detectors/console-log-prod.js +72 -0
  26. package/dist/detectors/console-log-prod.js.map +1 -0
  27. package/dist/detectors/dead-api.d.ts +10 -0
  28. package/dist/detectors/dead-api.d.ts.map +1 -0
  29. package/dist/detectors/dead-api.js +115 -0
  30. package/dist/detectors/dead-api.js.map +1 -0
  31. package/dist/detectors/dead-export.d.ts +12 -0
  32. package/dist/detectors/dead-export.d.ts.map +1 -0
  33. package/dist/detectors/dead-export.js +140 -0
  34. package/dist/detectors/dead-export.js.map +1 -0
  35. package/dist/detectors/dead-handler.d.ts +12 -0
  36. package/dist/detectors/dead-handler.d.ts.map +1 -0
  37. package/dist/detectors/dead-handler.js +40 -0
  38. package/dist/detectors/dead-handler.js.map +1 -0
  39. package/dist/detectors/dead-module.d.ts +14 -0
  40. package/dist/detectors/dead-module.d.ts.map +1 -0
  41. package/dist/detectors/dead-module.js +50 -0
  42. package/dist/detectors/dead-module.js.map +1 -0
  43. package/dist/detectors/deep-nesting.d.ts +12 -0
  44. package/dist/detectors/deep-nesting.d.ts.map +1 -0
  45. package/dist/detectors/deep-nesting.js +133 -0
  46. package/dist/detectors/deep-nesting.js.map +1 -0
  47. package/dist/detectors/duplicate-function.d.ts +9 -0
  48. package/dist/detectors/duplicate-function.d.ts.map +1 -0
  49. package/dist/detectors/duplicate-function.js +199 -0
  50. package/dist/detectors/duplicate-function.js.map +1 -0
  51. package/dist/detectors/duplicate-type.d.ts +9 -0
  52. package/dist/detectors/duplicate-type.d.ts.map +1 -0
  53. package/dist/detectors/duplicate-type.js +166 -0
  54. package/dist/detectors/duplicate-type.js.map +1 -0
  55. package/dist/detectors/hot-hub-file.d.ts +11 -0
  56. package/dist/detectors/hot-hub-file.d.ts.map +1 -0
  57. package/dist/detectors/hot-hub-file.js +42 -0
  58. package/dist/detectors/hot-hub-file.js.map +1 -0
  59. package/dist/detectors/long-file.d.ts +12 -0
  60. package/dist/detectors/long-file.d.ts.map +1 -0
  61. package/dist/detectors/long-file.js +82 -0
  62. package/dist/detectors/long-file.js.map +1 -0
  63. package/dist/detectors/long-function.d.ts +12 -0
  64. package/dist/detectors/long-function.d.ts.map +1 -0
  65. package/dist/detectors/long-function.js +45 -0
  66. package/dist/detectors/long-function.js.map +1 -0
  67. package/dist/detectors/magic-numbers.d.ts +10 -0
  68. package/dist/detectors/magic-numbers.d.ts.map +1 -0
  69. package/dist/detectors/magic-numbers.js +332 -0
  70. package/dist/detectors/magic-numbers.js.map +1 -0
  71. package/dist/detectors/mutable-globals.d.ts +6 -0
  72. package/dist/detectors/mutable-globals.d.ts.map +1 -0
  73. package/dist/detectors/mutable-globals.js +95 -0
  74. package/dist/detectors/mutable-globals.js.map +1 -0
  75. package/dist/detectors/mutation.d.ts +11 -0
  76. package/dist/detectors/mutation.d.ts.map +1 -0
  77. package/dist/detectors/mutation.js +397 -0
  78. package/dist/detectors/mutation.js.map +1 -0
  79. package/dist/detectors/public-any.d.ts +6 -0
  80. package/dist/detectors/public-any.d.ts.map +1 -0
  81. package/dist/detectors/public-any.js +52 -0
  82. package/dist/detectors/public-any.js.map +1 -0
  83. package/dist/detectors/race-condition.d.ts +6 -0
  84. package/dist/detectors/race-condition.d.ts.map +1 -0
  85. package/dist/detectors/race-condition.js +608 -0
  86. package/dist/detectors/race-condition.js.map +1 -0
  87. package/dist/detectors/shared-db-write.d.ts +6 -0
  88. package/dist/detectors/shared-db-write.d.ts.map +1 -0
  89. package/dist/detectors/shared-db-write.js +656 -0
  90. package/dist/detectors/shared-db-write.js.map +1 -0
  91. package/dist/detectors/silent-catch.d.ts +6 -0
  92. package/dist/detectors/silent-catch.d.ts.map +1 -0
  93. package/dist/detectors/silent-catch.js +167 -0
  94. package/dist/detectors/silent-catch.js.map +1 -0
  95. package/dist/detectors/similar-functions.d.ts +15 -0
  96. package/dist/detectors/similar-functions.d.ts.map +1 -0
  97. package/dist/detectors/similar-functions.js +334 -0
  98. package/dist/detectors/similar-functions.js.map +1 -0
  99. package/dist/detectors/skip-tests.d.ts +6 -0
  100. package/dist/detectors/skip-tests.d.ts.map +1 -0
  101. package/dist/detectors/skip-tests.js +69 -0
  102. package/dist/detectors/skip-tests.js.map +1 -0
  103. package/dist/detectors/todo-comments.d.ts +29 -0
  104. package/dist/detectors/todo-comments.d.ts.map +1 -0
  105. package/dist/detectors/todo-comments.js +154 -0
  106. package/dist/detectors/todo-comments.js.map +1 -0
  107. package/dist/detectors/unused-deps.d.ts +8 -0
  108. package/dist/detectors/unused-deps.d.ts.map +1 -0
  109. package/dist/detectors/unused-deps.js +115 -0
  110. package/dist/detectors/unused-deps.js.map +1 -0
  111. package/dist/extraction/api-race-confirmer.d.ts +31 -0
  112. package/dist/extraction/api-race-confirmer.d.ts.map +1 -0
  113. package/dist/extraction/api-race-confirmer.js +110 -0
  114. package/dist/extraction/api-race-confirmer.js.map +1 -0
  115. package/dist/extraction/llm-confirmer.d.ts +25 -0
  116. package/dist/extraction/llm-confirmer.d.ts.map +1 -0
  117. package/dist/extraction/llm-confirmer.js +118 -0
  118. package/dist/extraction/llm-confirmer.js.map +1 -0
  119. package/dist/extraction/mutation-confirmer.d.ts +30 -0
  120. package/dist/extraction/mutation-confirmer.d.ts.map +1 -0
  121. package/dist/extraction/mutation-confirmer.js +73 -0
  122. package/dist/extraction/mutation-confirmer.js.map +1 -0
  123. package/dist/extraction/prompt-chunking.d.ts +37 -0
  124. package/dist/extraction/prompt-chunking.d.ts.map +1 -0
  125. package/dist/extraction/prompt-chunking.js +61 -0
  126. package/dist/extraction/prompt-chunking.js.map +1 -0
  127. package/dist/extraction/race-confirmer.d.ts +28 -0
  128. package/dist/extraction/race-confirmer.d.ts.map +1 -0
  129. package/dist/extraction/race-confirmer.js +68 -0
  130. package/dist/extraction/race-confirmer.js.map +1 -0
  131. package/dist/extraction/shared-db-write-confirmer.d.ts +31 -0
  132. package/dist/extraction/shared-db-write-confirmer.d.ts.map +1 -0
  133. package/dist/extraction/shared-db-write-confirmer.js +141 -0
  134. package/dist/extraction/shared-db-write-confirmer.js.map +1 -0
  135. package/dist/extraction/triage-confirmer.d.ts +59 -0
  136. package/dist/extraction/triage-confirmer.d.ts.map +1 -0
  137. package/dist/extraction/triage-confirmer.js +104 -0
  138. package/dist/extraction/triage-confirmer.js.map +1 -0
  139. package/dist/graph/cfg.d.ts +45 -0
  140. package/dist/graph/cfg.d.ts.map +1 -0
  141. package/dist/graph/cfg.js +198 -0
  142. package/dist/graph/cfg.js.map +1 -0
  143. package/dist/graph/decorator-entries.d.ts +2 -0
  144. package/dist/graph/decorator-entries.d.ts.map +1 -0
  145. package/dist/graph/decorator-entries.js +89 -0
  146. package/dist/graph/decorator-entries.js.map +1 -0
  147. package/dist/graph/entry-points.d.ts +12 -0
  148. package/dist/graph/entry-points.d.ts.map +1 -0
  149. package/dist/graph/entry-points.js +282 -0
  150. package/dist/graph/entry-points.js.map +1 -0
  151. package/dist/graph/handler-conventions.d.ts +2 -0
  152. package/dist/graph/handler-conventions.d.ts.map +1 -0
  153. package/dist/graph/handler-conventions.js +26 -0
  154. package/dist/graph/handler-conventions.js.map +1 -0
  155. package/dist/graph/iac-entries.d.ts +2 -0
  156. package/dist/graph/iac-entries.d.ts.map +1 -0
  157. package/dist/graph/iac-entries.js +123 -0
  158. package/dist/graph/iac-entries.js.map +1 -0
  159. package/dist/graph/import-graph.d.ts +48 -0
  160. package/dist/graph/import-graph.d.ts.map +1 -0
  161. package/dist/graph/import-graph.js +86 -0
  162. package/dist/graph/import-graph.js.map +1 -0
  163. package/dist/graph/monorepo-detect.d.ts +3 -0
  164. package/dist/graph/monorepo-detect.d.ts.map +1 -0
  165. package/dist/graph/monorepo-detect.js +166 -0
  166. package/dist/graph/monorepo-detect.js.map +1 -0
  167. package/dist/graph/tsconfig-paths.d.ts +23 -0
  168. package/dist/graph/tsconfig-paths.d.ts.map +1 -0
  169. package/dist/graph/tsconfig-paths.js +217 -0
  170. package/dist/graph/tsconfig-paths.js.map +1 -0
  171. package/dist/multi-workspace-scanner.d.ts +13 -0
  172. package/dist/multi-workspace-scanner.d.ts.map +1 -0
  173. package/dist/multi-workspace-scanner.js +130 -0
  174. package/dist/multi-workspace-scanner.js.map +1 -0
  175. package/dist/normalizers/type-normalizer.d.ts +16 -0
  176. package/dist/normalizers/type-normalizer.d.ts.map +1 -0
  177. package/dist/normalizers/type-normalizer.js +189 -0
  178. package/dist/normalizers/type-normalizer.js.map +1 -0
  179. package/dist/parsers/typescript-parser.d.ts +57 -0
  180. package/dist/parsers/typescript-parser.d.ts.map +1 -0
  181. package/dist/parsers/typescript-parser.js +502 -0
  182. package/dist/parsers/typescript-parser.js.map +1 -0
  183. package/dist/reporter/json-reporter.d.ts +12 -0
  184. package/dist/reporter/json-reporter.d.ts.map +1 -0
  185. package/dist/reporter/json-reporter.js +28 -0
  186. package/dist/reporter/json-reporter.js.map +1 -0
  187. package/dist/reporter/markdown-reporter.d.ts +11 -0
  188. package/dist/reporter/markdown-reporter.d.ts.map +1 -0
  189. package/dist/reporter/markdown-reporter.js +77 -0
  190. package/dist/reporter/markdown-reporter.js.map +1 -0
  191. package/dist/rothunter.d.ts +125 -0
  192. package/dist/rothunter.d.ts.map +1 -0
  193. package/dist/rothunter.js +1038 -0
  194. package/dist/rothunter.js.map +1 -0
  195. package/dist/server/false-positives.d.ts +34 -0
  196. package/dist/server/false-positives.d.ts.map +1 -0
  197. package/dist/server/false-positives.js +85 -0
  198. package/dist/server/false-positives.js.map +1 -0
  199. package/dist/server/index.d.ts +2 -0
  200. package/dist/server/index.d.ts.map +1 -0
  201. package/dist/server/index.js +1529 -0
  202. package/dist/server/index.js.map +1 -0
  203. package/dist/server/marked-to-fix.d.ts +16 -0
  204. package/dist/server/marked-to-fix.d.ts.map +1 -0
  205. package/dist/server/marked-to-fix.js +36 -0
  206. package/dist/server/marked-to-fix.js.map +1 -0
  207. package/dist/server/scan-store.d.ts +147 -0
  208. package/dist/server/scan-store.d.ts.map +1 -0
  209. package/dist/server/scan-store.js +291 -0
  210. package/dist/server/scan-store.js.map +1 -0
  211. package/dist/server/settings-store.d.ts +28 -0
  212. package/dist/server/settings-store.d.ts.map +1 -0
  213. package/dist/server/settings-store.js +46 -0
  214. package/dist/server/settings-store.js.map +1 -0
  215. package/dist/server/workspace-store.d.ts +39 -0
  216. package/dist/server/workspace-store.d.ts.map +1 -0
  217. package/dist/server/workspace-store.js +108 -0
  218. package/dist/server/workspace-store.js.map +1 -0
  219. package/dist/types/detector-input.d.ts +37 -0
  220. package/dist/types/detector-input.d.ts.map +1 -0
  221. package/dist/types/detector-input.js +2 -0
  222. package/dist/types/detector-input.js.map +1 -0
  223. package/dist/types.d.ts +110 -0
  224. package/dist/types.d.ts.map +1 -0
  225. package/dist/types.js +2 -0
  226. package/dist/types.js.map +1 -0
  227. package/dist/utils/clustering.d.ts +14 -0
  228. package/dist/utils/clustering.d.ts.map +1 -0
  229. package/dist/utils/clustering.js +56 -0
  230. package/dist/utils/clustering.js.map +1 -0
  231. package/dist/utils/gitignore.d.ts +32 -0
  232. package/dist/utils/gitignore.d.ts.map +1 -0
  233. package/dist/utils/gitignore.js +122 -0
  234. package/dist/utils/gitignore.js.map +1 -0
  235. package/dist/utils/hash.d.ts +11 -0
  236. package/dist/utils/hash.d.ts.map +1 -0
  237. package/dist/utils/hash.js +14 -0
  238. package/dist/utils/hash.js.map +1 -0
  239. package/dist/utils/ignore-annotation.d.ts +28 -0
  240. package/dist/utils/ignore-annotation.d.ts.map +1 -0
  241. package/dist/utils/ignore-annotation.js +46 -0
  242. package/dist/utils/ignore-annotation.js.map +1 -0
  243. package/dist/utils/llm-json.d.ts +2 -0
  244. package/dist/utils/llm-json.d.ts.map +1 -0
  245. package/dist/utils/llm-json.js +53 -0
  246. package/dist/utils/llm-json.js.map +1 -0
  247. package/dist/utils/logger.d.ts +3 -0
  248. package/dist/utils/logger.d.ts.map +1 -0
  249. package/dist/utils/logger.js +4 -0
  250. package/dist/utils/logger.js.map +1 -0
  251. package/dist/utils/project-conventions.d.ts +2 -0
  252. package/dist/utils/project-conventions.d.ts.map +1 -0
  253. package/dist/utils/project-conventions.js +108 -0
  254. package/dist/utils/project-conventions.js.map +1 -0
  255. package/dist/utils/regex.d.ts +9 -0
  256. package/dist/utils/regex.d.ts.map +1 -0
  257. package/dist/utils/regex.js +11 -0
  258. package/dist/utils/regex.js.map +1 -0
  259. package/dist/utils/snippet.d.ts +20 -0
  260. package/dist/utils/snippet.d.ts.map +1 -0
  261. package/dist/utils/snippet.js +28 -0
  262. package/dist/utils/snippet.js.map +1 -0
  263. package/dist/utils/source-reader.d.ts +19 -0
  264. package/dist/utils/source-reader.d.ts.map +1 -0
  265. package/dist/utils/source-reader.js +32 -0
  266. package/dist/utils/source-reader.js.map +1 -0
  267. package/logo.png +0 -0
  268. package/package.json +92 -0
  269. package/scripts/start-llm.mjs +161 -0
@@ -0,0 +1,45 @@
1
+ import { stableHash } from '../utils/hash.js';
2
+ // Flag function symbols over 60 (low) / 120 (med) / 200 (high) lines.
3
+ // Test bodies (describe/it/test/suite) skipped.
4
+ export function detectLongFunctions(input) {
5
+ const low = input.lowThreshold ?? 60;
6
+ const med = input.medThreshold ?? 120;
7
+ const high = input.highThreshold ?? 200;
8
+ const findings = [];
9
+ for (const sym of input.symbols) {
10
+ if (sym.kind !== 'function')
11
+ continue;
12
+ if (isTestHarnessName(sym.name))
13
+ continue;
14
+ const lines = sym.range.endLine - sym.range.startLine + 1;
15
+ if (lines < low)
16
+ continue;
17
+ const severity = lines >= high ? 'high' : lines >= med ? 'medium' : 'low';
18
+ findings.push({
19
+ detectorId: 'long-function',
20
+ severity,
21
+ confidence: 0.98,
22
+ layer: 1,
23
+ title: `Long function: \`${sym.name}\` (${lines} lines) in ${sym.file}`,
24
+ description: `\`${sym.name}\` spans ${lines} lines (${sym.range.startLine}–${sym.range.endLine}). Functions this long are hard to test, hard to reason about, and accumulate branching that begs to be flattened (polymorphism, lookup tables, early returns).`,
25
+ evidence: [
26
+ {
27
+ file: sym.file,
28
+ range: { startLine: sym.range.startLine, endLine: sym.range.endLine },
29
+ snippet: firstLines(sym.source, 4),
30
+ },
31
+ ],
32
+ suggestion: 'Extract one cohesive chunk at a time into a well-named helper. If branching dominates, replace the switch/if chain with a dispatch table keyed by the discriminator.',
33
+ fingerprint: `long-function:${stableHash(`${sym.file}:${sym.name}:${sym.range.startLine}`)}`,
34
+ });
35
+ }
36
+ return findings;
37
+ }
38
+ const TEST_HARNESS_NAMES = new Set(['describe', 'it', 'test', 'suite', 'context']);
39
+ function isTestHarnessName(name) {
40
+ return TEST_HARNESS_NAMES.has(name);
41
+ }
42
+ function firstLines(source, n) {
43
+ return source.split('\n').slice(0, n).join('\n');
44
+ }
45
+ //# sourceMappingURL=long-function.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"long-function.js","sourceRoot":"","sources":["../../src/detectors/long-function.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAY9C,sEAAsE;AACtE,gDAAgD;AAChD,MAAM,UAAU,mBAAmB,CAAC,KAAgC;IAClE,MAAM,GAAG,GAAG,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC;IACrC,MAAM,GAAG,GAAG,KAAK,CAAC,YAAY,IAAI,GAAG,CAAC;IACtC,MAAM,IAAI,GAAG,KAAK,CAAC,aAAa,IAAI,GAAG,CAAC;IACxC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAChC,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU;YAAE,SAAS;QACtC,IAAI,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;QAC1D,IAAI,KAAK,GAAG,GAAG;YAAE,SAAS;QAC1B,MAAM,QAAQ,GAA8B,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;QACrG,QAAQ,CAAC,IAAI,CAAC;YACZ,UAAU,EAAE,eAAe;YAC3B,QAAQ;YACR,UAAU,EAAE,IAAI;YAChB,KAAK,EAAE,CAAC;YACR,KAAK,EAAE,oBAAoB,GAAG,CAAC,IAAI,OAAO,KAAK,cAAc,GAAG,CAAC,IAAI,EAAE;YACvE,WAAW,EACT,KAAK,GAAG,CAAC,IAAI,YAAY,KAAK,WAAW,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,iKAAiK;YACpP,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,KAAK,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE;oBACrE,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;iBACnC;aACF;YACD,UAAU,EACR,sKAAsK;YACxK,WAAW,EAAE,iBAAiB,UAAU,CAAC,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,EAAE;SAC7F,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;AAEnF,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,UAAU,CAAC,MAAc,EAAE,CAAS;IAC3C,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { Finding } from '../types.js';
2
+ import type { FileWalkingDetectorInput } from '../types/detector-input.js';
3
+ export interface MagicNumbersDetectorInput extends FileWalkingDetectorInput {
4
+ /** Numbers considered "obvious" and not magic. Default `{0, 1, -1, 2, 10, 100, 1000}`. */
5
+ whitelist?: ReadonlySet<number>;
6
+ /** Per-file finding cap so a single noisy file doesn't dominate the report. Default 5. */
7
+ perFileCap?: number;
8
+ }
9
+ export declare function detectMagicNumbers(input: MagicNumbersDetectorInput): Finding[];
10
+ //# sourceMappingURL=magic-numbers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"magic-numbers.d.ts","sourceRoot":"","sources":["../../src/detectors/magic-numbers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAI3C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAE3E,MAAM,WAAW,yBAA0B,SAAQ,wBAAwB;IAC3E,0FAA0F;IACxF,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAChC,0FAA0F;IAC1F,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAID,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,yBAAyB,GAAG,OAAO,EAAE,CAY9E"}
@@ -0,0 +1,332 @@
1
+ import { makeSourceReader } from '../utils/source-reader.js';
2
+ import { stableHash } from '../utils/hash.js';
3
+ import { hasIgnoreAnnotation } from '../utils/ignore-annotation.js';
4
+ // Numeric literals outside the whitelist {0,1,-1,2,10,100,1000}. Skip
5
+ // array indices, for-loop bounds, named-const declarations. LOW, per-file cap.
6
+ export function detectMagicNumbers(input) {
7
+ const whitelist = input.whitelist ?? DEFAULT_WHITELIST;
8
+ const cap = input.perFileCap ?? 5;
9
+ const read = makeSourceReader(input.workspaceRoot, input.project);
10
+ const findings = [];
11
+ for (const rel of input.files) {
12
+ if (!isAnalysable(rel))
13
+ continue;
14
+ const raw = read(rel);
15
+ if (raw == null)
16
+ continue;
17
+ findings.push(...analyseFile(rel, raw, whitelist, cap));
18
+ }
19
+ return findings;
20
+ }
21
+ // Default whitelist. Includes the obvious math constants (0/1/-1/2),
22
+ // common round numbers (10/100/1000), AND bit-width / byte-size
23
+ // constants (8/16/24/32/64/128/256). Encoder/decoder code (base64,
24
+ // chunking, hashing, network parsing) is otherwise flooded with FPs:
25
+ // `Math.floor(byteLength / 9 * 8)` and friends are domain math, not
26
+ // magic numbers. Add 512/1024/4096 too — they're page/buffer sizes
27
+ // every reader recognises at a glance.
28
+ const DEFAULT_WHITELIST = new Set([
29
+ 0, 1, -1, 2,
30
+ 10, 100, 1000,
31
+ 8, 16, 24, 32, 64, 128, 256, 512, 1024, 4096,
32
+ ]);
33
+ // Match positive integer literals (we treat negatives via the previous char).
34
+ const NUM_RE = /\b(\d+(?:\.\d+)?)\b/g;
35
+ function analyseFile(file, raw, whitelist, cap) {
36
+ const out = [];
37
+ // Pre-strip strings + comments AND regex literals so literals inside
38
+ // them aren't flagged. Regex masking matters a lot: `[A-Za-z0-9]` flags
39
+ // 9, `\d{15}` flags 15, `\d{1,3}` flags 3, etc. Those are charset/
40
+ // quantifier internals, not magic numbers.
41
+ const masked = maskStringsAndComments(raw);
42
+ for (const m of masked.matchAll(NUM_RE)) {
43
+ if (out.length >= cap)
44
+ break;
45
+ const positive = parseFloat(m[1]);
46
+ const before = masked.slice(Math.max(0, m.index - 30), m.index);
47
+ const after = masked.slice(m.index + m[0].length, m.index + m[0].length + 30);
48
+ // Distinguish UNARY minus (`-3`, `[-3]`, `f(-3)`, `return -3`) from
49
+ // BINARY subtraction (`x - 3`). Unary triggers when the char before
50
+ // the `-` is start-of-string, opening punctuation, a comma, an
51
+ // operator/comparison, or a `:=` assignment. Identifier or closing-
52
+ // bracket before `-` means subtraction → keep the value positive.
53
+ const unaryMinus = /(?:^|[=([{,;:?+\-*/%&|^!<>~]|\b(?:return|typeof|in|of|case|delete|void|throw|yield|await|new)\b)\s*-\s*$/.test(before);
54
+ const value = unaryMinus ? -positive : positive;
55
+ if (whitelist.has(value))
56
+ continue;
57
+ // Skip if the literal is being assigned to a constant (declaration site).
58
+ if (/\b(?:const|let|var|enum|readonly)\s+[A-Z_][A-Z0-9_]*\s*[:=]\s*-?\s*$/.test(before))
59
+ continue;
60
+ if (/\b(?:const|let|var)\s+\w+\s*[:=]\s*-?\s*$/.test(before))
61
+ continue;
62
+ // Skip array indices (preceded by `[`).
63
+ if (/\[\s*-?\s*$/.test(before))
64
+ continue;
65
+ // Skip enum members.
66
+ if (/=\s*-?\s*$/.test(before) && /\benum\b/.test(masked.slice(Math.max(0, m.index - 200), m.index)))
67
+ continue;
68
+ // Skip exponents (e.g. 1e-3 or 1e+3 — the `3` should not be flagged).
69
+ if (/e[+-]?$/i.test(before))
70
+ continue;
71
+ // Skip HTTP status code idioms — `reply.code(502)`, `res.status(404)`,
72
+ // `.code(401).send(...)`, `throw new HttpError(500, ...)`. The method
73
+ // name IS the named constant in framework code; introducing a
74
+ // `HTTP_BAD_GATEWAY = 502` const adds friction without clarity.
75
+ if (/\.(?:code|status|statusCode|sendStatus)\s*\(\s*-?\s*$/.test(before) && value >= 100 && value < 600)
76
+ continue;
77
+ // Skip HTTP success/error-tier range checks: `if (status < 200 ||
78
+ // status >= 300)` and friends. The 200/300/400/500 boundaries are
79
+ // textbook HTTP semantics; flagging them invites the reader to
80
+ // invent names that read worse than the literal.
81
+ if (value >= 100 && value < 600 && value % 100 === 0 &&
82
+ /(?:<=?|>=?|===?|!==?)\s*$/.test(before) &&
83
+ /\b(?:status|statusCode|httpStatus|http_status|code)\b/.test(lineAround(masked, m.index)))
84
+ continue;
85
+ // Skip elements of `new Set([...])` / `new Map([...])` / array literal
86
+ // bound to a named const. The CONST NAME documents what each value
87
+ // represents (`RETRYABLE_HTTP`, `ALLOWED_PORTS`, …) — naming each
88
+ // element individually is busywork.
89
+ if (insideNamedConstCollection(masked, m.index))
90
+ continue;
91
+ // Skip `key: NUMBER,` inside object literals when the value is a
92
+ // bare literal (no expression). `confidence: 0.95,` / `timeoutMs:
93
+ // 1500,` style — the KEY names the magic. Same effective semantics
94
+ // as `const KEY = NUMBER`. Tight bounds avoid catching subtraction.
95
+ if (/[:=]\s*-?\s*$/.test(before) &&
96
+ /^\s*-?\s*[,;)}\]\n]/.test(after) &&
97
+ !/\b(?:return|throw|case|new)\b\s*-?\s*$/.test(before)) {
98
+ continue;
99
+ }
100
+ const line = lineOf(raw, m.index);
101
+ if (hasIgnoreAnnotation(raw, line, 'magic-numbers'))
102
+ continue;
103
+ out.push({
104
+ detectorId: 'magic-numbers',
105
+ severity: 'low',
106
+ confidence: 0.7,
107
+ layer: 1,
108
+ title: `Magic number \`${m[1]}\` in ${file}:${line}`,
109
+ description: `Numeric literal \`${m[1]}\` appears in business logic without a named constant. Re-readers must guess what it represents (a timeout? a port? a column count?).`,
110
+ evidence: [
111
+ {
112
+ file,
113
+ range: { startLine: line, endLine: line },
114
+ snippet: snippetAround(raw, line),
115
+ },
116
+ ],
117
+ suggestion: 'Extract to a named const (`const RETRY_LIMIT = 3;`). If it derives from a real-world unit, encode the unit in the name (`TIMEOUT_MS`, `MAX_AGE_DAYS`).',
118
+ fingerprint: `magic-numbers:${stableHash(`${file}:${line}:${m[1]}`)}`,
119
+ });
120
+ }
121
+ return out;
122
+ }
123
+ function maskStringsAndComments(raw) {
124
+ let out = '';
125
+ let inString = null;
126
+ let inLineComment = false;
127
+ let inBlockComment = false;
128
+ let inRegex = false;
129
+ let inRegexClass = false;
130
+ // Track the previous non-whitespace masked-output character so we can
131
+ // disambiguate `/` as regex-start vs division. After punctuation /
132
+ // keywords a `/` opens a regex; after an identifier or closing bracket
133
+ // it is division.
134
+ let prevSig = '';
135
+ for (let i = 0; i < raw.length; i++) {
136
+ const c = raw[i];
137
+ const next = raw[i + 1];
138
+ if (inLineComment) {
139
+ if (c === '\n') {
140
+ inLineComment = false;
141
+ out += '\n';
142
+ }
143
+ else {
144
+ out += ' ';
145
+ }
146
+ continue;
147
+ }
148
+ if (inBlockComment) {
149
+ if (c === '*' && next === '/') {
150
+ inBlockComment = false;
151
+ out += ' ';
152
+ i++;
153
+ }
154
+ else {
155
+ out += c === '\n' ? '\n' : ' ';
156
+ }
157
+ continue;
158
+ }
159
+ if (inString) {
160
+ if (c === '\\') {
161
+ out += ' ';
162
+ i++;
163
+ continue;
164
+ }
165
+ if (c === inString) {
166
+ inString = null;
167
+ out += c;
168
+ prevSig = c;
169
+ continue;
170
+ }
171
+ out += c === '\n' ? '\n' : ' ';
172
+ continue;
173
+ }
174
+ if (inRegex) {
175
+ // Mask everything inside the regex body so the numeric scanner sees
176
+ // no digits. Track `[…]` character classes only to know when the
177
+ // regex actually ends (slash inside class does not terminate).
178
+ if (c === '\\') {
179
+ out += ' ';
180
+ i++;
181
+ continue;
182
+ }
183
+ if (c === '[' && !inRegexClass)
184
+ inRegexClass = true;
185
+ else if (c === ']' && inRegexClass)
186
+ inRegexClass = false;
187
+ if (c === '/' && !inRegexClass) {
188
+ inRegex = false;
189
+ out += c;
190
+ prevSig = c;
191
+ // Consume trailing flags (g, i, m, s, u, y, d) so they cannot be
192
+ // misread as identifiers in lookback.
193
+ while (i + 1 < raw.length && /[gimsuyd]/.test(raw[i + 1] ?? '')) {
194
+ out += ' ';
195
+ i++;
196
+ }
197
+ continue;
198
+ }
199
+ out += c === '\n' ? '\n' : ' ';
200
+ continue;
201
+ }
202
+ if (c === '/' && next === '/') {
203
+ inLineComment = true;
204
+ out += ' ';
205
+ i++;
206
+ continue;
207
+ }
208
+ if (c === '/' && next === '*') {
209
+ inBlockComment = true;
210
+ out += ' ';
211
+ i++;
212
+ continue;
213
+ }
214
+ if (c === '/' && canStartRegexAfter(prevSig)) {
215
+ inRegex = true;
216
+ inRegexClass = false;
217
+ out += c;
218
+ prevSig = c;
219
+ continue;
220
+ }
221
+ if (c === '"' || c === "'" || c === '`') {
222
+ inString = c;
223
+ out += c;
224
+ prevSig = c;
225
+ continue;
226
+ }
227
+ out += c;
228
+ if (c !== ' ' && c !== '\t' && c !== '\n' && c !== '\r')
229
+ prevSig = c;
230
+ }
231
+ return out;
232
+ }
233
+ /**
234
+ * Decide whether a `/` is the start of a regex literal (vs division)
235
+ * based on the previous non-whitespace character. After punctuation,
236
+ * an opening bracket / paren / brace, a comma, a return-like keyword,
237
+ * or an operator a slash starts a regex. After an identifier char or
238
+ * closing bracket it's division.
239
+ */
240
+ function canStartRegexAfter(prev) {
241
+ if (prev === '')
242
+ return true;
243
+ return /[=(,;:?{[+\-*/%&|^!<>~]/.test(prev);
244
+ }
245
+ /**
246
+ * Detect whether a numeric literal at `idx` sits inside a collection
247
+ * literal (`new Set([…])`, `new Map([…])`, plain `[…]`) bound to a
248
+ * named const declared earlier on the same logical statement. The
249
+ * collection name documents each element — `RETRYABLE_HTTP = new Set
250
+ * ([408, 425, 429, …])` does not need every literal extracted to its
251
+ * own const.
252
+ *
253
+ * Walks backwards from `idx` counting `[` / `]` / `(` / `)` to find the
254
+ * enclosing bracket open, then checks whether the chunk between the
255
+ * preceding `=` and the bracket looks like a collection constructor or
256
+ * a bare array literal.
257
+ */
258
+ function insideNamedConstCollection(masked, idx) {
259
+ let depthSq = 0;
260
+ let depthPar = 0;
261
+ for (let i = idx - 1; i >= 0; i--) {
262
+ const c = masked[i];
263
+ if (c === ']')
264
+ depthSq++;
265
+ else if (c === '[') {
266
+ if (depthSq === 0) {
267
+ // Found the enclosing array open. Look back for `=` to find the
268
+ // assignment target.
269
+ const lhs = masked.slice(Math.max(0, i - 200), i);
270
+ const eq = lhs.lastIndexOf('=');
271
+ if (eq === -1)
272
+ return false;
273
+ const declHead = lhs.slice(Math.max(0, eq - 80), eq);
274
+ if (!/\b(?:const|let|var|readonly)\s+\w+/.test(declHead))
275
+ return false;
276
+ const between = lhs.slice(eq + 1).trim();
277
+ // Bracket directly follows `=` (bare array) OR follows
278
+ // `new Set(` / `new Map(` / `new Array(` / `new Uint8Array(`
279
+ // etc. The trailing `(` is part of the constructor call —
280
+ // `new Set([...])` has the array inside the call parens.
281
+ if (between === '' || /\bnew\s+\w+\s*\(?\s*$/.test(between))
282
+ return true;
283
+ return false;
284
+ }
285
+ depthSq--;
286
+ }
287
+ else if (c === ')')
288
+ depthPar++;
289
+ else if (c === '(') {
290
+ if (depthPar === 0)
291
+ return false;
292
+ depthPar--;
293
+ }
294
+ else if (c === ';' && depthSq === 0 && depthPar === 0) {
295
+ return false;
296
+ }
297
+ }
298
+ return false;
299
+ }
300
+ function isAnalysable(file) {
301
+ const posix = file.replace(/\\/g, '/');
302
+ return /\.(?:ts|tsx|mts|cts)$/.test(posix)
303
+ && !/\.d\.ts$/.test(posix)
304
+ && !/(^|\/)node_modules\//.test(posix)
305
+ && !/(?:^|\/)__tests__\//.test(posix)
306
+ && !/(?:^|\/)tests?\//.test(posix)
307
+ && !/(?:^|\/)scripts?\//.test(posix)
308
+ && !/\.test\.(?:ts|tsx)$/.test(posix)
309
+ && !/\.spec\.(?:ts|tsx)$/.test(posix)
310
+ // Tool config files are mostly threshold / timeout / port numbers
311
+ // by design — flagging every coverage threshold + chunk-size limit
312
+ // as a magic number is noise. Skip the common tooling configs.
313
+ && !/(?:^|\/)(?:vite|vitest|jest|rollup|webpack|esbuild|tsup|playwright|cypress|drizzle|next|nuxt|astro|svelte|remix|tailwind|postcss|prettier|eslint|biome|babel|rome|tsdown|tsconfig\.[^/]*)\.config\.(?:ts|tsx|mts|cts|js|mjs|cjs)$/.test(posix)
314
+ && !/(?:^|\/)\.?(?:eslint|prettier|stylelint)rc(?:\.[^/]+)?$/.test(posix);
315
+ }
316
+ function lineOf(raw, idx) {
317
+ return raw.slice(0, idx).split('\n').length;
318
+ }
319
+ function lineAround(raw, idx) {
320
+ const start = raw.lastIndexOf('\n', idx) + 1;
321
+ let end = raw.indexOf('\n', idx);
322
+ if (end === -1)
323
+ end = raw.length;
324
+ return raw.slice(start, end);
325
+ }
326
+ function snippetAround(raw, line) {
327
+ const lines = raw.split('\n');
328
+ const from = Math.max(0, line - 1);
329
+ const to = Math.min(lines.length, line + 1);
330
+ return lines.slice(from, to).join('\n');
331
+ }
332
+ //# sourceMappingURL=magic-numbers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"magic-numbers.js","sourceRoot":"","sources":["../../src/detectors/magic-numbers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { Finding } from '../types.js';
2
+ import type { FileWalkingDetectorInput } from '../types/detector-input.js';
3
+ export interface MutableGlobalsDetectorInput extends FileWalkingDetectorInput {
4
+ }
5
+ export declare function detectMutableGlobals(input: MutableGlobalsDetectorInput): Finding[];
6
+ //# sourceMappingURL=mutable-globals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutable-globals.d.ts","sourceRoot":"","sources":["../../src/detectors/mutable-globals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAK3C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAE3E,MAAM,WAAW,2BAA4B,SAAQ,wBAAwB;CAAG;AAIhF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,2BAA2B,GAAG,OAAO,EAAE,CAUlF"}
@@ -0,0 +1,95 @@
1
+ import { makeSourceReader } from '../utils/source-reader.js';
2
+ import { stableHash } from '../utils/hash.js';
3
+ import { hasIgnoreAnnotation } from '../utils/ignore-annotation.js';
4
+ import { escapeForRegex } from '../utils/regex.js';
5
+ // Top-level `let`/`var` reassigned in the same file → shared mutable
6
+ // state across importers. MED. One-shot assignment at decl is skipped.
7
+ export function detectMutableGlobals(input) {
8
+ const read = makeSourceReader(input.workspaceRoot, input.project);
9
+ const findings = [];
10
+ for (const rel of input.files) {
11
+ if (!isAnalysable(rel))
12
+ continue;
13
+ const raw = read(rel);
14
+ if (raw == null)
15
+ continue;
16
+ findings.push(...analyseFile(rel, raw));
17
+ }
18
+ return findings;
19
+ }
20
+ function analyseFile(file, raw) {
21
+ const lines = raw.split('\n');
22
+ const out = [];
23
+ // Locate top-level let/var declarations: line starts with `let `/`var `
24
+ // (allow `export let`).
25
+ const declRe = /^(?:export\s+)?(?:let|var)\s+(\w+)\b/;
26
+ const declarations = [];
27
+ let braceDepth = 0;
28
+ let parenDepth = 0;
29
+ let lineNo = 0;
30
+ for (const line of lines) {
31
+ lineNo++;
32
+ // crude tracking — strings/comments are NOT masked here, so multiline
33
+ // template-literal-heavy files may misalign; the heuristic still works
34
+ // in practice because we only care about indentation-0 declarations.
35
+ const m = declRe.exec(line);
36
+ if (m && braceDepth === 0 && parenDepth === 0) {
37
+ declarations.push({ name: m[1], line: lineNo });
38
+ }
39
+ for (const c of line) {
40
+ if (c === '{')
41
+ braceDepth++;
42
+ else if (c === '}')
43
+ braceDepth = Math.max(0, braceDepth - 1);
44
+ else if (c === '(')
45
+ parenDepth++;
46
+ else if (c === ')')
47
+ parenDepth = Math.max(0, parenDepth - 1);
48
+ }
49
+ }
50
+ for (const d of declarations) {
51
+ // Look for reassignments anywhere after the declaration line.
52
+ const after = lines.slice(d.line).join('\n');
53
+ const re = new RegExp(`(?<![\\.\\w])${escapeForRegex(d.name)}\\s*(?:=(?!=)|\\+=|-=|\\*=|/=|\\?\\?=|\\|\\|=|&&=)`, 'g');
54
+ const matches = [...after.matchAll(re)];
55
+ if (matches.length === 0)
56
+ continue;
57
+ if (hasIgnoreAnnotation(raw, d.line, 'mutable-globals'))
58
+ continue;
59
+ out.push({
60
+ detectorId: 'mutable-globals',
61
+ severity: 'medium',
62
+ confidence: 0.8,
63
+ layer: 1,
64
+ title: `Mutable top-level binding: \`${d.name}\` in ${file}:${d.line}`,
65
+ description: `\`${d.name}\` is declared with \`let\`/\`var\` at module scope and reassigned later. Module-scope mutation is shared by every importer — common cause of cross-test pollution, hidden state in SSR, and bugs that only appear after the second request.`,
66
+ evidence: [
67
+ {
68
+ file,
69
+ range: { startLine: d.line, endLine: d.line },
70
+ snippet: snippetAround(raw, d.line),
71
+ },
72
+ ],
73
+ suggestion: 'Encapsulate the state in a class / closure / module factory so each consumer gets its own copy, or move to `const` if no mutation is actually needed.',
74
+ fingerprint: `mutable-globals:${stableHash(`${file}:${d.name}`)}`,
75
+ });
76
+ }
77
+ return out;
78
+ }
79
+ function isAnalysable(file) {
80
+ const posix = file.replace(/\\/g, '/');
81
+ return /\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(posix)
82
+ && !/\.d\.ts$/.test(posix)
83
+ && !/(^|\/)node_modules\//.test(posix)
84
+ && !/(?:^|\/)__tests__\//.test(posix)
85
+ && !/(?:^|\/)tests?\//.test(posix)
86
+ && !/\.test\.(?:ts|tsx|js|jsx)$/.test(posix)
87
+ && !/\.spec\.(?:ts|tsx|js|jsx)$/.test(posix);
88
+ }
89
+ function snippetAround(raw, line) {
90
+ const lines = raw.split('\n');
91
+ const from = Math.max(0, line - 1);
92
+ const to = Math.min(lines.length, line + 1);
93
+ return lines.slice(from, to).join('\n');
94
+ }
95
+ //# sourceMappingURL=mutable-globals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutable-globals.js","sourceRoot":"","sources":["../../src/detectors/mutable-globals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Finding } from '../types.js';
2
+ import type { FileWalkingDetectorInput } from '../types/detector-input.js';
3
+ export interface MutationDetectorInput extends FileWalkingDetectorInput {
4
+ }
5
+ export declare function detectMutations(input: MutationDetectorInput): Finding[];
6
+ /**
7
+ * Compress the enclosing function source so the LLM prompt stays in budget.
8
+ * We keep the full signature line plus up to ~40 lines of body — enough to
9
+ * judge intent without paying for the model to read a 200-line method.
10
+ */
11
+ //# sourceMappingURL=mutation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation.d.ts","sourceRoot":"","sources":["../../src/detectors/mutation.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,OAAO,EAAY,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAE3E,MAAM,WAAW,qBAAsB,SAAQ,wBAAwB;CAAG;AAiE1E,wBAAgB,eAAe,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,EAAE,CAsCvE;AAkUD;;;;GAIG"}