thuban 0.3.4 → 0.4.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 (739) hide show
  1. package/README.md +2 -0
  2. package/dist/README.md +2 -0
  3. package/dist/cli.js +1 -1
  4. package/dist/packages/crucible/.golden/golden-master.json +414 -0
  5. package/dist/packages/crucible/badge-generator.js +1 -0
  6. package/dist/packages/crucible/banner.js +1 -0
  7. package/dist/packages/crucible/ci-integration.js +1 -0
  8. package/dist/packages/crucible/golden-master.js +1 -0
  9. package/dist/packages/crucible/index.js +1 -0
  10. package/dist/packages/crucible/kenny-mode.js +1 -0
  11. package/dist/packages/crucible/leaderboard.js +1 -0
  12. package/dist/packages/crucible/mutation-engine.js +1 -0
  13. package/dist/packages/crucible/pattern-learner.js +1 -0
  14. package/dist/packages/crucible/report-generator.js +1 -0
  15. package/dist/packages/crucible/rule-loader.js +1 -0
  16. package/dist/packages/crucible/seeding-engine.js +1 -0
  17. package/dist/packages/crucible/seeds/csharp/seed-001.cs +28 -0
  18. package/dist/packages/crucible/seeds/csharp/seed-002.cs +24 -0
  19. package/dist/packages/crucible/seeds/csharp/seed-003.cs +30 -0
  20. package/dist/packages/crucible/seeds/csharp/seed-004.cs +31 -0
  21. package/dist/packages/crucible/seeds/csharp/seed-005.cs +28 -0
  22. package/dist/packages/crucible/seeds/csharp/seed-006.cs +25 -0
  23. package/dist/packages/crucible/seeds/csharp/seed-007.cs +34 -0
  24. package/dist/packages/crucible/seeds/csharp/seed-008.cs +23 -0
  25. package/dist/packages/crucible/seeds/csharp/seed-009.cs +27 -0
  26. package/dist/packages/crucible/seeds/csharp/seed-010.cs +29 -0
  27. package/dist/packages/crucible/seeds/csharp/seed-011.cs +32 -0
  28. package/dist/packages/crucible/seeds/csharp/seed-012.cs +27 -0
  29. package/dist/packages/crucible/seeds/csharp/seed-013.cs +27 -0
  30. package/dist/packages/crucible/seeds/csharp/seed-014.cs +26 -0
  31. package/dist/packages/crucible/seeds/csharp/seed-015.cs +33 -0
  32. package/dist/packages/crucible/seeds/csharp/seed-016.cs +25 -0
  33. package/dist/packages/crucible/seeds/csharp/seed-017.cs +22 -0
  34. package/dist/packages/crucible/seeds/csharp/seed-018.cs +26 -0
  35. package/dist/packages/crucible/seeds/csharp/seed-019.cs +25 -0
  36. package/dist/packages/crucible/seeds/csharp/seed-020.cs +22 -0
  37. package/dist/packages/crucible/seeds/csharp/seed-021.cs +28 -0
  38. package/dist/packages/crucible/seeds/csharp/seed-022.cs +25 -0
  39. package/dist/packages/crucible/seeds/csharp/seed-023.cs +33 -0
  40. package/dist/packages/crucible/seeds/csharp/seed-024.cs +31 -0
  41. package/dist/packages/crucible/seeds/csharp/seed-025.cs +31 -0
  42. package/dist/packages/crucible/seeds/csharp/seed-026.cs +25 -0
  43. package/dist/packages/crucible/seeds/csharp/seed-027.cs +29 -0
  44. package/dist/packages/crucible/seeds/csharp/seed-028.cs +25 -0
  45. package/dist/packages/crucible/seeds/csharp/seed-029.cs +27 -0
  46. package/dist/packages/crucible/seeds/csharp/seed-030.cs +27 -0
  47. package/dist/packages/crucible/seeds/csharp/seed-031.cs +28 -0
  48. package/dist/packages/crucible/seeds/csharp/seed-032.cs +30 -0
  49. package/dist/packages/crucible/seeds/csharp/seed-033.cs +27 -0
  50. package/dist/packages/crucible/seeds/csharp/seed-034.cs +25 -0
  51. package/dist/packages/crucible/seeds/csharp/seed-035.cs +32 -0
  52. package/dist/packages/crucible/seeds/csharp/seed-036.cs +28 -0
  53. package/dist/packages/crucible/seeds/csharp/seed-037.cs +29 -0
  54. package/dist/packages/crucible/seeds/csharp/seed-038.cs +28 -0
  55. package/dist/packages/crucible/seeds/csharp/seed-039.cs +29 -0
  56. package/dist/packages/crucible/seeds/csharp/seed-040.cs +28 -0
  57. package/dist/packages/crucible/seeds/csharp/seed-041.cs +33 -0
  58. package/dist/packages/crucible/seeds/csharp/seed-042.cs +27 -0
  59. package/dist/packages/crucible/seeds/csharp/seed-043.cs +27 -0
  60. package/dist/packages/crucible/seeds/csharp/seed-044.cs +25 -0
  61. package/dist/packages/crucible/seeds/csharp/seed-045.cs +30 -0
  62. package/dist/packages/crucible/seeds/csharp/seed-046.cs +29 -0
  63. package/dist/packages/crucible/seeds/csharp/seed-047.cs +26 -0
  64. package/dist/packages/crucible/seeds/csharp/seed-048.cs +31 -0
  65. package/dist/packages/crucible/seeds/csharp/seed-049.cs +27 -0
  66. package/dist/packages/crucible/seeds/csharp/seed-050.cs +30 -0
  67. package/dist/packages/crucible/seeds/csharp/seed-051.cs +34 -0
  68. package/dist/packages/crucible/seeds/csharp/seed-052.cs +36 -0
  69. package/dist/packages/crucible/seeds/go/seed-001.go +29 -0
  70. package/dist/packages/crucible/seeds/go/seed-002.go +27 -0
  71. package/dist/packages/crucible/seeds/go/seed-003.go +31 -0
  72. package/dist/packages/crucible/seeds/go/seed-004.go +27 -0
  73. package/dist/packages/crucible/seeds/go/seed-005.go +27 -0
  74. package/dist/packages/crucible/seeds/go/seed-006.go +29 -0
  75. package/dist/packages/crucible/seeds/go/seed-007.go +24 -0
  76. package/dist/packages/crucible/seeds/go/seed-008.go +25 -0
  77. package/dist/packages/crucible/seeds/go/seed-009.go +30 -0
  78. package/dist/packages/crucible/seeds/go/seed-010.go +35 -0
  79. package/dist/packages/crucible/seeds/go/seed-011.go +24 -0
  80. package/dist/packages/crucible/seeds/go/seed-012.go +24 -0
  81. package/dist/packages/crucible/seeds/go/seed-013.go +31 -0
  82. package/dist/packages/crucible/seeds/go/seed-014.go +35 -0
  83. package/dist/packages/crucible/seeds/go/seed-015.go +25 -0
  84. package/dist/packages/crucible/seeds/go/seed-016.go +19 -0
  85. package/dist/packages/crucible/seeds/go/seed-017.go +23 -0
  86. package/dist/packages/crucible/seeds/go/seed-018.go +23 -0
  87. package/dist/packages/crucible/seeds/go/seed-019.go +22 -0
  88. package/dist/packages/crucible/seeds/go/seed-020.go +31 -0
  89. package/dist/packages/crucible/seeds/go/seed-021.go +22 -0
  90. package/dist/packages/crucible/seeds/go/seed-022.go +29 -0
  91. package/dist/packages/crucible/seeds/go/seed-023.go +26 -0
  92. package/dist/packages/crucible/seeds/go/seed-024.go +27 -0
  93. package/dist/packages/crucible/seeds/go/seed-025.go +23 -0
  94. package/dist/packages/crucible/seeds/go/seed-026.go +29 -0
  95. package/dist/packages/crucible/seeds/go/seed-027.go +26 -0
  96. package/dist/packages/crucible/seeds/go/seed-028.go +23 -0
  97. package/dist/packages/crucible/seeds/go/seed-029.go +28 -0
  98. package/dist/packages/crucible/seeds/go/seed-030.go +27 -0
  99. package/dist/packages/crucible/seeds/go/seed-031.go +28 -0
  100. package/dist/packages/crucible/seeds/go/seed-032.go +27 -0
  101. package/dist/packages/crucible/seeds/go/seed-033.go +29 -0
  102. package/dist/packages/crucible/seeds/go/seed-034.go +22 -0
  103. package/dist/packages/crucible/seeds/go/seed-035.go +32 -0
  104. package/dist/packages/crucible/seeds/go/seed-036.go +27 -0
  105. package/dist/packages/crucible/seeds/go/seed-037.go +27 -0
  106. package/dist/packages/crucible/seeds/go/seed-038.go +27 -0
  107. package/dist/packages/crucible/seeds/go/seed-039.go +26 -0
  108. package/dist/packages/crucible/seeds/go/seed-040.go +37 -0
  109. package/dist/packages/crucible/seeds/go/seed-041.go +23 -0
  110. package/dist/packages/crucible/seeds/go/seed-042.go +32 -0
  111. package/dist/packages/crucible/seeds/go/seed-043.go +35 -0
  112. package/dist/packages/crucible/seeds/go/seed-044.go +25 -0
  113. package/dist/packages/crucible/seeds/go/seed-045.go +26 -0
  114. package/dist/packages/crucible/seeds/go/seed-046.go +34 -0
  115. package/dist/packages/crucible/seeds/go/seed-047.go +26 -0
  116. package/dist/packages/crucible/seeds/go/seed-048.go +29 -0
  117. package/dist/packages/crucible/seeds/go/seed-049.go +24 -0
  118. package/dist/packages/crucible/seeds/go/seed-050.go +27 -0
  119. package/dist/packages/crucible/seeds/go/seed-051.go +38 -0
  120. package/dist/packages/crucible/seeds/go/seed-052.go +27 -0
  121. package/dist/packages/crucible/seeds/java/seed-001.java +23 -0
  122. package/dist/packages/crucible/seeds/java/seed-002.java +22 -0
  123. package/dist/packages/crucible/seeds/java/seed-003.java +28 -0
  124. package/dist/packages/crucible/seeds/java/seed-004.java +26 -0
  125. package/dist/packages/crucible/seeds/java/seed-005.java +33 -0
  126. package/dist/packages/crucible/seeds/java/seed-006.java +22 -0
  127. package/dist/packages/crucible/seeds/java/seed-007.java +21 -0
  128. package/dist/packages/crucible/seeds/java/seed-008.java +29 -0
  129. package/dist/packages/crucible/seeds/java/seed-009.java +24 -0
  130. package/dist/packages/crucible/seeds/java/seed-010.java +30 -0
  131. package/dist/packages/crucible/seeds/java/seed-011.java +25 -0
  132. package/dist/packages/crucible/seeds/java/seed-012.java +20 -0
  133. package/dist/packages/crucible/seeds/java/seed-013.java +26 -0
  134. package/dist/packages/crucible/seeds/java/seed-014.java +23 -0
  135. package/dist/packages/crucible/seeds/java/seed-015.java +25 -0
  136. package/dist/packages/crucible/seeds/java/seed-016.java +22 -0
  137. package/dist/packages/crucible/seeds/java/seed-017.java +24 -0
  138. package/dist/packages/crucible/seeds/java/seed-018.java +24 -0
  139. package/dist/packages/crucible/seeds/java/seed-019.java +38 -0
  140. package/dist/packages/crucible/seeds/java/seed-020.java +24 -0
  141. package/dist/packages/crucible/seeds/java/seed-021.java +22 -0
  142. package/dist/packages/crucible/seeds/java/seed-022.java +22 -0
  143. package/dist/packages/crucible/seeds/java/seed-023.java +22 -0
  144. package/dist/packages/crucible/seeds/java/seed-024.java +25 -0
  145. package/dist/packages/crucible/seeds/java/seed-025.java +27 -0
  146. package/dist/packages/crucible/seeds/java/seed-026.java +25 -0
  147. package/dist/packages/crucible/seeds/java/seed-027.java +22 -0
  148. package/dist/packages/crucible/seeds/java/seed-028.java +21 -0
  149. package/dist/packages/crucible/seeds/java/seed-029.java +28 -0
  150. package/dist/packages/crucible/seeds/java/seed-030.java +27 -0
  151. package/dist/packages/crucible/seeds/java/seed-031.java +23 -0
  152. package/dist/packages/crucible/seeds/java/seed-032.java +22 -0
  153. package/dist/packages/crucible/seeds/java/seed-033.java +24 -0
  154. package/dist/packages/crucible/seeds/java/seed-034.java +24 -0
  155. package/dist/packages/crucible/seeds/java/seed-035.java +29 -0
  156. package/dist/packages/crucible/seeds/java/seed-036.java +25 -0
  157. package/dist/packages/crucible/seeds/java/seed-037.java +30 -0
  158. package/dist/packages/crucible/seeds/java/seed-038.java +29 -0
  159. package/dist/packages/crucible/seeds/java/seed-039.java +26 -0
  160. package/dist/packages/crucible/seeds/java/seed-040.java +31 -0
  161. package/dist/packages/crucible/seeds/java/seed-041.java +20 -0
  162. package/dist/packages/crucible/seeds/java/seed-042.java +26 -0
  163. package/dist/packages/crucible/seeds/java/seed-043.java +31 -0
  164. package/dist/packages/crucible/seeds/java/seed-044.java +22 -0
  165. package/dist/packages/crucible/seeds/java/seed-045.java +25 -0
  166. package/dist/packages/crucible/seeds/java/seed-046.java +22 -0
  167. package/dist/packages/crucible/seeds/java/seed-047.java +26 -0
  168. package/dist/packages/crucible/seeds/java/seed-048.java +24 -0
  169. package/dist/packages/crucible/seeds/java/seed-049.java +28 -0
  170. package/dist/packages/crucible/seeds/java/seed-050.java +29 -0
  171. package/dist/packages/crucible/seeds/java/seed-051.java +25 -0
  172. package/dist/packages/crucible/seeds/java/seed-052.java +32 -0
  173. package/dist/packages/crucible/seeds/js/seed-001.js +27 -0
  174. package/dist/packages/crucible/seeds/js/seed-002.js +28 -0
  175. package/dist/packages/crucible/seeds/js/seed-003.js +30 -0
  176. package/dist/packages/crucible/seeds/js/seed-004.js +28 -0
  177. package/dist/packages/crucible/seeds/js/seed-005.js +31 -0
  178. package/dist/packages/crucible/seeds/js/seed-006.js +28 -0
  179. package/dist/packages/crucible/seeds/js/seed-007.js +26 -0
  180. package/dist/packages/crucible/seeds/js/seed-008.js +30 -0
  181. package/dist/packages/crucible/seeds/js/seed-009.js +30 -0
  182. package/dist/packages/crucible/seeds/js/seed-010.js +29 -0
  183. package/dist/packages/crucible/seeds/js/seed-011.js +34 -0
  184. package/dist/packages/crucible/seeds/js/seed-012.js +32 -0
  185. package/dist/packages/crucible/seeds/js/seed-013.js +29 -0
  186. package/dist/packages/crucible/seeds/js/seed-014.js +29 -0
  187. package/dist/packages/crucible/seeds/js/seed-015.js +31 -0
  188. package/dist/packages/crucible/seeds/js/seed-016.js +31 -0
  189. package/dist/packages/crucible/seeds/js/seed-017.js +30 -0
  190. package/dist/packages/crucible/seeds/js/seed-018.js +29 -0
  191. package/dist/packages/crucible/seeds/js/seed-019.js +32 -0
  192. package/dist/packages/crucible/seeds/js/seed-020.js +29 -0
  193. package/dist/packages/crucible/seeds/js/seed-021.js +31 -0
  194. package/dist/packages/crucible/seeds/js/seed-022.js +31 -0
  195. package/dist/packages/crucible/seeds/js/seed-023.js +29 -0
  196. package/dist/packages/crucible/seeds/js/seed-024.js +29 -0
  197. package/dist/packages/crucible/seeds/js/seed-025.js +34 -0
  198. package/dist/packages/crucible/seeds/js/seed-026.js +31 -0
  199. package/dist/packages/crucible/seeds/js/seed-027.js +31 -0
  200. package/dist/packages/crucible/seeds/js/seed-028.js +33 -0
  201. package/dist/packages/crucible/seeds/js/seed-029.js +33 -0
  202. package/dist/packages/crucible/seeds/js/seed-030.js +30 -0
  203. package/dist/packages/crucible/seeds/js/seed-031.js +31 -0
  204. package/dist/packages/crucible/seeds/js/seed-032.js +34 -0
  205. package/dist/packages/crucible/seeds/js/seed-033.js +33 -0
  206. package/dist/packages/crucible/seeds/js/seed-034.js +33 -0
  207. package/dist/packages/crucible/seeds/js/seed-035.js +30 -0
  208. package/dist/packages/crucible/seeds/js/seed-036.js +30 -0
  209. package/dist/packages/crucible/seeds/js/seed-037.js +29 -0
  210. package/dist/packages/crucible/seeds/js/seed-038.js +31 -0
  211. package/dist/packages/crucible/seeds/js/seed-039.js +34 -0
  212. package/dist/packages/crucible/seeds/js/seed-040.js +35 -0
  213. package/dist/packages/crucible/seeds/js/seed-041.js +32 -0
  214. package/dist/packages/crucible/seeds/js/seed-042.js +32 -0
  215. package/dist/packages/crucible/seeds/js/seed-043.js +27 -0
  216. package/dist/packages/crucible/seeds/js/seed-044.js +29 -0
  217. package/dist/packages/crucible/seeds/js/seed-045.js +33 -0
  218. package/dist/packages/crucible/seeds/js/seed-046.js +33 -0
  219. package/dist/packages/crucible/seeds/js/seed-047.js +29 -0
  220. package/dist/packages/crucible/seeds/js/seed-048.js +29 -0
  221. package/dist/packages/crucible/seeds/js/seed-049.js +34 -0
  222. package/dist/packages/crucible/seeds/js/seed-050.js +34 -0
  223. package/dist/packages/crucible/seeds/js/seed-051.js +30 -0
  224. package/dist/packages/crucible/seeds/js/seed-052.js +30 -0
  225. package/dist/packages/crucible/seeds/js/seed-053.js +26 -0
  226. package/dist/packages/crucible/seeds/js/seed-054.js +35 -0
  227. package/dist/packages/crucible/seeds/js/seed-055.js +33 -0
  228. package/dist/packages/crucible/seeds/kotlin/seed-001.kt +24 -0
  229. package/dist/packages/crucible/seeds/kotlin/seed-002.kt +27 -0
  230. package/dist/packages/crucible/seeds/kotlin/seed-003.kt +29 -0
  231. package/dist/packages/crucible/seeds/kotlin/seed-004.kt +24 -0
  232. package/dist/packages/crucible/seeds/kotlin/seed-005.kt +29 -0
  233. package/dist/packages/crucible/seeds/kotlin/seed-006.kt +21 -0
  234. package/dist/packages/crucible/seeds/kotlin/seed-007.kt +23 -0
  235. package/dist/packages/crucible/seeds/kotlin/seed-008.kt +25 -0
  236. package/dist/packages/crucible/seeds/kotlin/seed-009.kt +21 -0
  237. package/dist/packages/crucible/seeds/kotlin/seed-010.kt +28 -0
  238. package/dist/packages/crucible/seeds/kotlin/seed-011.kt +22 -0
  239. package/dist/packages/crucible/seeds/kotlin/seed-012.kt +25 -0
  240. package/dist/packages/crucible/seeds/kotlin/seed-013.kt +30 -0
  241. package/dist/packages/crucible/seeds/kotlin/seed-014.kt +27 -0
  242. package/dist/packages/crucible/seeds/kotlin/seed-015.kt +24 -0
  243. package/dist/packages/crucible/seeds/kotlin/seed-016.kt +28 -0
  244. package/dist/packages/crucible/seeds/kotlin/seed-017.kt +33 -0
  245. package/dist/packages/crucible/seeds/kotlin/seed-018.kt +31 -0
  246. package/dist/packages/crucible/seeds/kotlin/seed-019.kt +28 -0
  247. package/dist/packages/crucible/seeds/kotlin/seed-020.kt +31 -0
  248. package/dist/packages/crucible/seeds/kotlin/seed-021.kt +34 -0
  249. package/dist/packages/crucible/seeds/kotlin/seed-022.kt +29 -0
  250. package/dist/packages/crucible/seeds/kotlin/seed-023.kt +20 -0
  251. package/dist/packages/crucible/seeds/kotlin/seed-024.kt +23 -0
  252. package/dist/packages/crucible/seeds/kotlin/seed-025.kt +23 -0
  253. package/dist/packages/crucible/seeds/kotlin/seed-026.kt +26 -0
  254. package/dist/packages/crucible/seeds/kotlin/seed-027.kt +29 -0
  255. package/dist/packages/crucible/seeds/kotlin/seed-028.kt +24 -0
  256. package/dist/packages/crucible/seeds/kotlin/seed-029.kt +27 -0
  257. package/dist/packages/crucible/seeds/kotlin/seed-030.kt +26 -0
  258. package/dist/packages/crucible/seeds/kotlin/seed-031.kt +28 -0
  259. package/dist/packages/crucible/seeds/kotlin/seed-032.kt +29 -0
  260. package/dist/packages/crucible/seeds/kotlin/seed-033.kt +30 -0
  261. package/dist/packages/crucible/seeds/kotlin/seed-034.kt +26 -0
  262. package/dist/packages/crucible/seeds/kotlin/seed-035.kt +33 -0
  263. package/dist/packages/crucible/seeds/kotlin/seed-036.kt +25 -0
  264. package/dist/packages/crucible/seeds/kotlin/seed-037.kt +26 -0
  265. package/dist/packages/crucible/seeds/kotlin/seed-038.kt +25 -0
  266. package/dist/packages/crucible/seeds/kotlin/seed-039.kt +29 -0
  267. package/dist/packages/crucible/seeds/kotlin/seed-040.kt +28 -0
  268. package/dist/packages/crucible/seeds/kotlin/seed-041.kt +31 -0
  269. package/dist/packages/crucible/seeds/kotlin/seed-042.kt +27 -0
  270. package/dist/packages/crucible/seeds/kotlin/seed-043.kt +26 -0
  271. package/dist/packages/crucible/seeds/kotlin/seed-044.kt +27 -0
  272. package/dist/packages/crucible/seeds/kotlin/seed-045.kt +27 -0
  273. package/dist/packages/crucible/seeds/kotlin/seed-046.kt +33 -0
  274. package/dist/packages/crucible/seeds/kotlin/seed-047.kt +32 -0
  275. package/dist/packages/crucible/seeds/kotlin/seed-048.kt +20 -0
  276. package/dist/packages/crucible/seeds/kotlin/seed-049.kt +23 -0
  277. package/dist/packages/crucible/seeds/kotlin/seed-050.kt +19 -0
  278. package/dist/packages/crucible/seeds/php/seed-001.php +23 -0
  279. package/dist/packages/crucible/seeds/php/seed-002.php +18 -0
  280. package/dist/packages/crucible/seeds/php/seed-003.php +25 -0
  281. package/dist/packages/crucible/seeds/php/seed-004.php +21 -0
  282. package/dist/packages/crucible/seeds/php/seed-005.php +21 -0
  283. package/dist/packages/crucible/seeds/php/seed-006.php +21 -0
  284. package/dist/packages/crucible/seeds/php/seed-007.php +18 -0
  285. package/dist/packages/crucible/seeds/php/seed-008.php +19 -0
  286. package/dist/packages/crucible/seeds/php/seed-009.php +24 -0
  287. package/dist/packages/crucible/seeds/php/seed-010.php +24 -0
  288. package/dist/packages/crucible/seeds/php/seed-011.php +18 -0
  289. package/dist/packages/crucible/seeds/php/seed-012.php +24 -0
  290. package/dist/packages/crucible/seeds/php/seed-013.php +23 -0
  291. package/dist/packages/crucible/seeds/php/seed-014.php +20 -0
  292. package/dist/packages/crucible/seeds/php/seed-015.php +18 -0
  293. package/dist/packages/crucible/seeds/php/seed-016.php +18 -0
  294. package/dist/packages/crucible/seeds/php/seed-017.php +18 -0
  295. package/dist/packages/crucible/seeds/php/seed-018.php +17 -0
  296. package/dist/packages/crucible/seeds/php/seed-019.php +24 -0
  297. package/dist/packages/crucible/seeds/php/seed-020.php +19 -0
  298. package/dist/packages/crucible/seeds/php/seed-021.php +22 -0
  299. package/dist/packages/crucible/seeds/php/seed-022.php +15 -0
  300. package/dist/packages/crucible/seeds/php/seed-023.php +24 -0
  301. package/dist/packages/crucible/seeds/php/seed-024.php +22 -0
  302. package/dist/packages/crucible/seeds/php/seed-025.php +20 -0
  303. package/dist/packages/crucible/seeds/php/seed-026.php +22 -0
  304. package/dist/packages/crucible/seeds/php/seed-027.php +15 -0
  305. package/dist/packages/crucible/seeds/php/seed-028.php +15 -0
  306. package/dist/packages/crucible/seeds/php/seed-029.php +21 -0
  307. package/dist/packages/crucible/seeds/php/seed-030.php +21 -0
  308. package/dist/packages/crucible/seeds/php/seed-031.php +23 -0
  309. package/dist/packages/crucible/seeds/php/seed-032.php +18 -0
  310. package/dist/packages/crucible/seeds/php/seed-033.php +18 -0
  311. package/dist/packages/crucible/seeds/php/seed-034.php +24 -0
  312. package/dist/packages/crucible/seeds/php/seed-035.php +22 -0
  313. package/dist/packages/crucible/seeds/php/seed-036.php +17 -0
  314. package/dist/packages/crucible/seeds/php/seed-037.php +19 -0
  315. package/dist/packages/crucible/seeds/php/seed-038.php +20 -0
  316. package/dist/packages/crucible/seeds/php/seed-039.php +15 -0
  317. package/dist/packages/crucible/seeds/php/seed-040.php +21 -0
  318. package/dist/packages/crucible/seeds/php/seed-041.php +20 -0
  319. package/dist/packages/crucible/seeds/php/seed-042.php +21 -0
  320. package/dist/packages/crucible/seeds/php/seed-043.php +21 -0
  321. package/dist/packages/crucible/seeds/php/seed-044.php +25 -0
  322. package/dist/packages/crucible/seeds/php/seed-045.php +25 -0
  323. package/dist/packages/crucible/seeds/php/seed-046.php +20 -0
  324. package/dist/packages/crucible/seeds/php/seed-047.php +21 -0
  325. package/dist/packages/crucible/seeds/php/seed-048.php +19 -0
  326. package/dist/packages/crucible/seeds/php/seed-049.php +18 -0
  327. package/dist/packages/crucible/seeds/php/seed-050.php +20 -0
  328. package/dist/packages/crucible/seeds/python/seed-001.py +33 -0
  329. package/dist/packages/crucible/seeds/python/seed-002.py +39 -0
  330. package/dist/packages/crucible/seeds/python/seed-003.py +31 -0
  331. package/dist/packages/crucible/seeds/python/seed-004.py +33 -0
  332. package/dist/packages/crucible/seeds/python/seed-005.py +33 -0
  333. package/dist/packages/crucible/seeds/python/seed-006.py +34 -0
  334. package/dist/packages/crucible/seeds/python/seed-007.py +33 -0
  335. package/dist/packages/crucible/seeds/python/seed-008.py +38 -0
  336. package/dist/packages/crucible/seeds/python/seed-009.py +38 -0
  337. package/dist/packages/crucible/seeds/python/seed-010.py +42 -0
  338. package/dist/packages/crucible/seeds/python/seed-011.py +30 -0
  339. package/dist/packages/crucible/seeds/python/seed-012.py +35 -0
  340. package/dist/packages/crucible/seeds/python/seed-013.py +33 -0
  341. package/dist/packages/crucible/seeds/python/seed-014.py +40 -0
  342. package/dist/packages/crucible/seeds/python/seed-015.py +33 -0
  343. package/dist/packages/crucible/seeds/python/seed-016.py +34 -0
  344. package/dist/packages/crucible/seeds/python/seed-017.py +34 -0
  345. package/dist/packages/crucible/seeds/python/seed-018.py +30 -0
  346. package/dist/packages/crucible/seeds/python/seed-019.py +33 -0
  347. package/dist/packages/crucible/seeds/python/seed-020.py +37 -0
  348. package/dist/packages/crucible/seeds/python/seed-021 2.py +37 -0
  349. package/dist/packages/crucible/seeds/python/seed-021.py +37 -0
  350. package/dist/packages/crucible/seeds/python/seed-022 2.py +34 -0
  351. package/dist/packages/crucible/seeds/python/seed-022.py +34 -0
  352. package/dist/packages/crucible/seeds/python/seed-023 2.py +37 -0
  353. package/dist/packages/crucible/seeds/python/seed-023.py +37 -0
  354. package/dist/packages/crucible/seeds/python/seed-024 2.py +38 -0
  355. package/dist/packages/crucible/seeds/python/seed-024.py +38 -0
  356. package/dist/packages/crucible/seeds/python/seed-025 2.py +40 -0
  357. package/dist/packages/crucible/seeds/python/seed-025.py +40 -0
  358. package/dist/packages/crucible/seeds/python/seed-026 2.py +35 -0
  359. package/dist/packages/crucible/seeds/python/seed-026.py +35 -0
  360. package/dist/packages/crucible/seeds/python/seed-027 2.py +35 -0
  361. package/dist/packages/crucible/seeds/python/seed-027.py +35 -0
  362. package/dist/packages/crucible/seeds/python/seed-028 2.py +42 -0
  363. package/dist/packages/crucible/seeds/python/seed-028.py +42 -0
  364. package/dist/packages/crucible/seeds/python/seed-029.py +42 -0
  365. package/dist/packages/crucible/seeds/python/seed-030 2.py +37 -0
  366. package/dist/packages/crucible/seeds/python/seed-030.py +37 -0
  367. package/dist/packages/crucible/seeds/python/seed-031 2.py +34 -0
  368. package/dist/packages/crucible/seeds/python/seed-031.py +34 -0
  369. package/dist/packages/crucible/seeds/python/seed-032.py +33 -0
  370. package/dist/packages/crucible/seeds/python/seed-033.py +32 -0
  371. package/dist/packages/crucible/seeds/python/seed-034.py +38 -0
  372. package/dist/packages/crucible/seeds/python/seed-035.py +35 -0
  373. package/dist/packages/crucible/seeds/python/seed-036 2.py +33 -0
  374. package/dist/packages/crucible/seeds/python/seed-036.py +33 -0
  375. package/dist/packages/crucible/seeds/python/seed-037 2.py +41 -0
  376. package/dist/packages/crucible/seeds/python/seed-037.py +41 -0
  377. package/dist/packages/crucible/seeds/python/seed-038 2.py +33 -0
  378. package/dist/packages/crucible/seeds/python/seed-038.py +33 -0
  379. package/dist/packages/crucible/seeds/python/seed-039 2.py +39 -0
  380. package/dist/packages/crucible/seeds/python/seed-039.py +39 -0
  381. package/dist/packages/crucible/seeds/python/seed-040 2.py +39 -0
  382. package/dist/packages/crucible/seeds/python/seed-040.py +39 -0
  383. package/dist/packages/crucible/seeds/python/seed-041 2.py +37 -0
  384. package/dist/packages/crucible/seeds/python/seed-041.py +37 -0
  385. package/dist/packages/crucible/seeds/python/seed-042 2.py +38 -0
  386. package/dist/packages/crucible/seeds/python/seed-042.py +38 -0
  387. package/dist/packages/crucible/seeds/python/seed-043 2.py +32 -0
  388. package/dist/packages/crucible/seeds/python/seed-043.py +32 -0
  389. package/dist/packages/crucible/seeds/python/seed-044 2.py +38 -0
  390. package/dist/packages/crucible/seeds/python/seed-044.py +38 -0
  391. package/dist/packages/crucible/seeds/python/seed-045 2.py +36 -0
  392. package/dist/packages/crucible/seeds/python/seed-045.py +36 -0
  393. package/dist/packages/crucible/seeds/python/seed-046 2.py +33 -0
  394. package/dist/packages/crucible/seeds/python/seed-046.py +33 -0
  395. package/dist/packages/crucible/seeds/python/seed-047 2.py +44 -0
  396. package/dist/packages/crucible/seeds/python/seed-047.py +44 -0
  397. package/dist/packages/crucible/seeds/python/seed-048 2.py +35 -0
  398. package/dist/packages/crucible/seeds/python/seed-048.py +35 -0
  399. package/dist/packages/crucible/seeds/python/seed-049 2.py +39 -0
  400. package/dist/packages/crucible/seeds/python/seed-049.py +39 -0
  401. package/dist/packages/crucible/seeds/python/seed-050 2.py +39 -0
  402. package/dist/packages/crucible/seeds/python/seed-050.py +39 -0
  403. package/dist/packages/crucible/seeds/python/seed-051 2.py +38 -0
  404. package/dist/packages/crucible/seeds/python/seed-051.py +38 -0
  405. package/dist/packages/crucible/seeds/python/seed-052 2.py +41 -0
  406. package/dist/packages/crucible/seeds/python/seed-052.py +41 -0
  407. package/dist/packages/crucible/seeds/ruby/seed-001 2.rb +15 -0
  408. package/dist/packages/crucible/seeds/ruby/seed-001.rb +15 -0
  409. package/dist/packages/crucible/seeds/ruby/seed-002 2.rb +22 -0
  410. package/dist/packages/crucible/seeds/ruby/seed-002.rb +22 -0
  411. package/dist/packages/crucible/seeds/ruby/seed-003 2.rb +25 -0
  412. package/dist/packages/crucible/seeds/ruby/seed-003.rb +25 -0
  413. package/dist/packages/crucible/seeds/ruby/seed-004 2.rb +17 -0
  414. package/dist/packages/crucible/seeds/ruby/seed-004.rb +17 -0
  415. package/dist/packages/crucible/seeds/ruby/seed-005 2.rb +21 -0
  416. package/dist/packages/crucible/seeds/ruby/seed-005.rb +21 -0
  417. package/dist/packages/crucible/seeds/ruby/seed-006 2.rb +17 -0
  418. package/dist/packages/crucible/seeds/ruby/seed-006.rb +17 -0
  419. package/dist/packages/crucible/seeds/ruby/seed-007 2.rb +16 -0
  420. package/dist/packages/crucible/seeds/ruby/seed-007.rb +16 -0
  421. package/dist/packages/crucible/seeds/ruby/seed-008 2.rb +18 -0
  422. package/dist/packages/crucible/seeds/ruby/seed-008.rb +18 -0
  423. package/dist/packages/crucible/seeds/ruby/seed-009 2.rb +20 -0
  424. package/dist/packages/crucible/seeds/ruby/seed-009.rb +20 -0
  425. package/dist/packages/crucible/seeds/ruby/seed-010 2.rb +24 -0
  426. package/dist/packages/crucible/seeds/ruby/seed-010.rb +24 -0
  427. package/dist/packages/crucible/seeds/ruby/seed-011 2.rb +21 -0
  428. package/dist/packages/crucible/seeds/ruby/seed-011.rb +21 -0
  429. package/dist/packages/crucible/seeds/ruby/seed-012 2.rb +22 -0
  430. package/dist/packages/crucible/seeds/ruby/seed-012.rb +22 -0
  431. package/dist/packages/crucible/seeds/ruby/seed-013 2.rb +21 -0
  432. package/dist/packages/crucible/seeds/ruby/seed-013.rb +21 -0
  433. package/dist/packages/crucible/seeds/ruby/seed-014 2.rb +16 -0
  434. package/dist/packages/crucible/seeds/ruby/seed-014.rb +16 -0
  435. package/dist/packages/crucible/seeds/ruby/seed-015 2.rb +18 -0
  436. package/dist/packages/crucible/seeds/ruby/seed-015.rb +18 -0
  437. package/dist/packages/crucible/seeds/ruby/seed-016 2.rb +17 -0
  438. package/dist/packages/crucible/seeds/ruby/seed-016.rb +17 -0
  439. package/dist/packages/crucible/seeds/ruby/seed-017 2.rb +25 -0
  440. package/dist/packages/crucible/seeds/ruby/seed-017.rb +25 -0
  441. package/dist/packages/crucible/seeds/ruby/seed-018 2.rb +23 -0
  442. package/dist/packages/crucible/seeds/ruby/seed-018.rb +23 -0
  443. package/dist/packages/crucible/seeds/ruby/seed-019 2.rb +20 -0
  444. package/dist/packages/crucible/seeds/ruby/seed-019.rb +20 -0
  445. package/dist/packages/crucible/seeds/ruby/seed-020 2.rb +17 -0
  446. package/dist/packages/crucible/seeds/ruby/seed-020.rb +17 -0
  447. package/dist/packages/crucible/seeds/ruby/seed-021 2.rb +20 -0
  448. package/dist/packages/crucible/seeds/ruby/seed-021.rb +20 -0
  449. package/dist/packages/crucible/seeds/ruby/seed-022 2.rb +21 -0
  450. package/dist/packages/crucible/seeds/ruby/seed-022.rb +21 -0
  451. package/dist/packages/crucible/seeds/ruby/seed-023 2.rb +19 -0
  452. package/dist/packages/crucible/seeds/ruby/seed-023.rb +19 -0
  453. package/dist/packages/crucible/seeds/ruby/seed-024 2.rb +17 -0
  454. package/dist/packages/crucible/seeds/ruby/seed-024.rb +17 -0
  455. package/dist/packages/crucible/seeds/ruby/seed-025 2.rb +17 -0
  456. package/dist/packages/crucible/seeds/ruby/seed-025.rb +17 -0
  457. package/dist/packages/crucible/seeds/ruby/seed-026 2.rb +18 -0
  458. package/dist/packages/crucible/seeds/ruby/seed-026.rb +18 -0
  459. package/dist/packages/crucible/seeds/ruby/seed-027 2.rb +21 -0
  460. package/dist/packages/crucible/seeds/ruby/seed-027.rb +21 -0
  461. package/dist/packages/crucible/seeds/ruby/seed-028 2.rb +22 -0
  462. package/dist/packages/crucible/seeds/ruby/seed-028.rb +22 -0
  463. package/dist/packages/crucible/seeds/ruby/seed-029 2.rb +19 -0
  464. package/dist/packages/crucible/seeds/ruby/seed-029.rb +19 -0
  465. package/dist/packages/crucible/seeds/ruby/seed-030 2.rb +20 -0
  466. package/dist/packages/crucible/seeds/ruby/seed-030.rb +20 -0
  467. package/dist/packages/crucible/seeds/ruby/seed-031 2.rb +17 -0
  468. package/dist/packages/crucible/seeds/ruby/seed-031.rb +17 -0
  469. package/dist/packages/crucible/seeds/ruby/seed-032 2.rb +22 -0
  470. package/dist/packages/crucible/seeds/ruby/seed-032.rb +22 -0
  471. package/dist/packages/crucible/seeds/ruby/seed-033 2.rb +18 -0
  472. package/dist/packages/crucible/seeds/ruby/seed-033.rb +18 -0
  473. package/dist/packages/crucible/seeds/ruby/seed-034 2.rb +19 -0
  474. package/dist/packages/crucible/seeds/ruby/seed-034.rb +19 -0
  475. package/dist/packages/crucible/seeds/ruby/seed-035 2.rb +19 -0
  476. package/dist/packages/crucible/seeds/ruby/seed-035.rb +19 -0
  477. package/dist/packages/crucible/seeds/ruby/seed-036 2.rb +18 -0
  478. package/dist/packages/crucible/seeds/ruby/seed-036.rb +18 -0
  479. package/dist/packages/crucible/seeds/ruby/seed-037 2.rb +21 -0
  480. package/dist/packages/crucible/seeds/ruby/seed-037.rb +21 -0
  481. package/dist/packages/crucible/seeds/ruby/seed-038 2.rb +24 -0
  482. package/dist/packages/crucible/seeds/ruby/seed-038.rb +24 -0
  483. package/dist/packages/crucible/seeds/ruby/seed-039 2.rb +24 -0
  484. package/dist/packages/crucible/seeds/ruby/seed-039.rb +24 -0
  485. package/dist/packages/crucible/seeds/ruby/seed-040 2.rb +22 -0
  486. package/dist/packages/crucible/seeds/ruby/seed-040.rb +22 -0
  487. package/dist/packages/crucible/seeds/ruby/seed-041 2.rb +23 -0
  488. package/dist/packages/crucible/seeds/ruby/seed-041.rb +23 -0
  489. package/dist/packages/crucible/seeds/ruby/seed-042 2.rb +25 -0
  490. package/dist/packages/crucible/seeds/ruby/seed-042.rb +25 -0
  491. package/dist/packages/crucible/seeds/ruby/seed-043 2.rb +23 -0
  492. package/dist/packages/crucible/seeds/ruby/seed-043.rb +23 -0
  493. package/dist/packages/crucible/seeds/ruby/seed-044 2.rb +16 -0
  494. package/dist/packages/crucible/seeds/ruby/seed-044.rb +16 -0
  495. package/dist/packages/crucible/seeds/ruby/seed-045 2.rb +22 -0
  496. package/dist/packages/crucible/seeds/ruby/seed-045.rb +22 -0
  497. package/dist/packages/crucible/seeds/ruby/seed-046 2.rb +27 -0
  498. package/dist/packages/crucible/seeds/ruby/seed-046.rb +27 -0
  499. package/dist/packages/crucible/seeds/ruby/seed-047 2.rb +26 -0
  500. package/dist/packages/crucible/seeds/ruby/seed-047.rb +26 -0
  501. package/dist/packages/crucible/seeds/ruby/seed-048 2.rb +24 -0
  502. package/dist/packages/crucible/seeds/ruby/seed-048.rb +24 -0
  503. package/dist/packages/crucible/seeds/ruby/seed-049 2.rb +20 -0
  504. package/dist/packages/crucible/seeds/ruby/seed-049.rb +20 -0
  505. package/dist/packages/crucible/seeds/ruby/seed-050 2.rb +27 -0
  506. package/dist/packages/crucible/seeds/ruby/seed-050.rb +27 -0
  507. package/dist/packages/crucible/seeds/rust/seed-001 2.rs +25 -0
  508. package/dist/packages/crucible/seeds/rust/seed-001.rs +25 -0
  509. package/dist/packages/crucible/seeds/rust/seed-002 2.rs +20 -0
  510. package/dist/packages/crucible/seeds/rust/seed-002.rs +20 -0
  511. package/dist/packages/crucible/seeds/rust/seed-003 2.rs +23 -0
  512. package/dist/packages/crucible/seeds/rust/seed-003.rs +23 -0
  513. package/dist/packages/crucible/seeds/rust/seed-004 2.rs +21 -0
  514. package/dist/packages/crucible/seeds/rust/seed-004.rs +21 -0
  515. package/dist/packages/crucible/seeds/rust/seed-005 2.rs +21 -0
  516. package/dist/packages/crucible/seeds/rust/seed-005.rs +21 -0
  517. package/dist/packages/crucible/seeds/rust/seed-006 2.rs +24 -0
  518. package/dist/packages/crucible/seeds/rust/seed-006.rs +24 -0
  519. package/dist/packages/crucible/seeds/rust/seed-007 2.rs +20 -0
  520. package/dist/packages/crucible/seeds/rust/seed-007.rs +20 -0
  521. package/dist/packages/crucible/seeds/rust/seed-008 2.rs +19 -0
  522. package/dist/packages/crucible/seeds/rust/seed-008.rs +19 -0
  523. package/dist/packages/crucible/seeds/rust/seed-009 2.rs +28 -0
  524. package/dist/packages/crucible/seeds/rust/seed-009.rs +28 -0
  525. package/dist/packages/crucible/seeds/rust/seed-010 2.rs +28 -0
  526. package/dist/packages/crucible/seeds/rust/seed-010.rs +28 -0
  527. package/dist/packages/crucible/seeds/rust/seed-011 2.rs +25 -0
  528. package/dist/packages/crucible/seeds/rust/seed-011.rs +25 -0
  529. package/dist/packages/crucible/seeds/rust/seed-012 2.rs +31 -0
  530. package/dist/packages/crucible/seeds/rust/seed-012.rs +31 -0
  531. package/dist/packages/crucible/seeds/rust/seed-013 2.rs +27 -0
  532. package/dist/packages/crucible/seeds/rust/seed-013.rs +27 -0
  533. package/dist/packages/crucible/seeds/rust/seed-014 2.rs +30 -0
  534. package/dist/packages/crucible/seeds/rust/seed-014.rs +30 -0
  535. package/dist/packages/crucible/seeds/rust/seed-015 2.rs +33 -0
  536. package/dist/packages/crucible/seeds/rust/seed-015.rs +33 -0
  537. package/dist/packages/crucible/seeds/rust/seed-016 2.rs +22 -0
  538. package/dist/packages/crucible/seeds/rust/seed-016.rs +22 -0
  539. package/dist/packages/crucible/seeds/rust/seed-017 2.rs +28 -0
  540. package/dist/packages/crucible/seeds/rust/seed-017.rs +28 -0
  541. package/dist/packages/crucible/seeds/rust/seed-018 2.rs +21 -0
  542. package/dist/packages/crucible/seeds/rust/seed-018.rs +21 -0
  543. package/dist/packages/crucible/seeds/rust/seed-019 2.rs +36 -0
  544. package/dist/packages/crucible/seeds/rust/seed-019.rs +36 -0
  545. package/dist/packages/crucible/seeds/rust/seed-020 2.rs +27 -0
  546. package/dist/packages/crucible/seeds/rust/seed-020.rs +27 -0
  547. package/dist/packages/crucible/seeds/rust/seed-021 2.rs +26 -0
  548. package/dist/packages/crucible/seeds/rust/seed-021.rs +26 -0
  549. package/dist/packages/crucible/seeds/rust/seed-022 2.rs +23 -0
  550. package/dist/packages/crucible/seeds/rust/seed-022.rs +23 -0
  551. package/dist/packages/crucible/seeds/rust/seed-023 2.rs +22 -0
  552. package/dist/packages/crucible/seeds/rust/seed-023.rs +22 -0
  553. package/dist/packages/crucible/seeds/rust/seed-024 2.rs +24 -0
  554. package/dist/packages/crucible/seeds/rust/seed-024.rs +24 -0
  555. package/dist/packages/crucible/seeds/rust/seed-025 2.rs +29 -0
  556. package/dist/packages/crucible/seeds/rust/seed-025.rs +29 -0
  557. package/dist/packages/crucible/seeds/rust/seed-026 2.rs +23 -0
  558. package/dist/packages/crucible/seeds/rust/seed-026.rs +23 -0
  559. package/dist/packages/crucible/seeds/rust/seed-027 2.rs +24 -0
  560. package/dist/packages/crucible/seeds/rust/seed-027.rs +24 -0
  561. package/dist/packages/crucible/seeds/rust/seed-028 2.rs +25 -0
  562. package/dist/packages/crucible/seeds/rust/seed-028.rs +25 -0
  563. package/dist/packages/crucible/seeds/rust/seed-029 2.rs +25 -0
  564. package/dist/packages/crucible/seeds/rust/seed-029.rs +25 -0
  565. package/dist/packages/crucible/seeds/rust/seed-030 2.rs +30 -0
  566. package/dist/packages/crucible/seeds/rust/seed-030.rs +30 -0
  567. package/dist/packages/crucible/seeds/rust/seed-031 2.rs +22 -0
  568. package/dist/packages/crucible/seeds/rust/seed-031.rs +22 -0
  569. package/dist/packages/crucible/seeds/rust/seed-032 2.rs +25 -0
  570. package/dist/packages/crucible/seeds/rust/seed-032.rs +25 -0
  571. package/dist/packages/crucible/seeds/rust/seed-033 2.rs +25 -0
  572. package/dist/packages/crucible/seeds/rust/seed-033.rs +25 -0
  573. package/dist/packages/crucible/seeds/rust/seed-034 2.rs +20 -0
  574. package/dist/packages/crucible/seeds/rust/seed-034.rs +20 -0
  575. package/dist/packages/crucible/seeds/rust/seed-035 2.rs +28 -0
  576. package/dist/packages/crucible/seeds/rust/seed-035.rs +28 -0
  577. package/dist/packages/crucible/seeds/rust/seed-036 2.rs +26 -0
  578. package/dist/packages/crucible/seeds/rust/seed-036.rs +26 -0
  579. package/dist/packages/crucible/seeds/rust/seed-037 2.rs +31 -0
  580. package/dist/packages/crucible/seeds/rust/seed-037.rs +31 -0
  581. package/dist/packages/crucible/seeds/rust/seed-038 2.rs +25 -0
  582. package/dist/packages/crucible/seeds/rust/seed-038.rs +25 -0
  583. package/dist/packages/crucible/seeds/rust/seed-039 2.rs +28 -0
  584. package/dist/packages/crucible/seeds/rust/seed-039.rs +28 -0
  585. package/dist/packages/crucible/seeds/rust/seed-040 2.rs +27 -0
  586. package/dist/packages/crucible/seeds/rust/seed-040.rs +27 -0
  587. package/dist/packages/crucible/seeds/rust/seed-041 2.rs +32 -0
  588. package/dist/packages/crucible/seeds/rust/seed-041.rs +32 -0
  589. package/dist/packages/crucible/seeds/rust/seed-042 2.rs +27 -0
  590. package/dist/packages/crucible/seeds/rust/seed-042.rs +27 -0
  591. package/dist/packages/crucible/seeds/rust/seed-043 2.rs +29 -0
  592. package/dist/packages/crucible/seeds/rust/seed-043.rs +29 -0
  593. package/dist/packages/crucible/seeds/rust/seed-044 2.rs +25 -0
  594. package/dist/packages/crucible/seeds/rust/seed-044.rs +25 -0
  595. package/dist/packages/crucible/seeds/rust/seed-045 2.rs +28 -0
  596. package/dist/packages/crucible/seeds/rust/seed-045.rs +28 -0
  597. package/dist/packages/crucible/seeds/rust/seed-046 2.rs +25 -0
  598. package/dist/packages/crucible/seeds/rust/seed-046.rs +25 -0
  599. package/dist/packages/crucible/seeds/rust/seed-047 2.rs +34 -0
  600. package/dist/packages/crucible/seeds/rust/seed-047.rs +34 -0
  601. package/dist/packages/crucible/seeds/rust/seed-048 2.rs +21 -0
  602. package/dist/packages/crucible/seeds/rust/seed-048.rs +21 -0
  603. package/dist/packages/crucible/seeds/rust/seed-049 2.rs +26 -0
  604. package/dist/packages/crucible/seeds/rust/seed-049.rs +26 -0
  605. package/dist/packages/crucible/seeds/rust/seed-050 2.rs +23 -0
  606. package/dist/packages/crucible/seeds/rust/seed-050.rs +23 -0
  607. package/dist/packages/crucible/seeds/ts/seed-001 2.ts +32 -0
  608. package/dist/packages/crucible/seeds/ts/seed-001.ts +32 -0
  609. package/dist/packages/crucible/seeds/ts/seed-002 2.ts +34 -0
  610. package/dist/packages/crucible/seeds/ts/seed-002.ts +34 -0
  611. package/dist/packages/crucible/seeds/ts/seed-003 2.ts +28 -0
  612. package/dist/packages/crucible/seeds/ts/seed-003.ts +28 -0
  613. package/dist/packages/crucible/seeds/ts/seed-004 2.ts +34 -0
  614. package/dist/packages/crucible/seeds/ts/seed-004.ts +34 -0
  615. package/dist/packages/crucible/seeds/ts/seed-005 2.ts +32 -0
  616. package/dist/packages/crucible/seeds/ts/seed-005.ts +32 -0
  617. package/dist/packages/crucible/seeds/ts/seed-006 2.ts +31 -0
  618. package/dist/packages/crucible/seeds/ts/seed-006.ts +31 -0
  619. package/dist/packages/crucible/seeds/ts/seed-007 2.ts +28 -0
  620. package/dist/packages/crucible/seeds/ts/seed-007.ts +28 -0
  621. package/dist/packages/crucible/seeds/ts/seed-008 2.ts +40 -0
  622. package/dist/packages/crucible/seeds/ts/seed-008.ts +40 -0
  623. package/dist/packages/crucible/seeds/ts/seed-009 2.ts +31 -0
  624. package/dist/packages/crucible/seeds/ts/seed-009.ts +31 -0
  625. package/dist/packages/crucible/seeds/ts/seed-010 2.ts +33 -0
  626. package/dist/packages/crucible/seeds/ts/seed-010.ts +33 -0
  627. package/dist/packages/crucible/seeds/ts/seed-011 2.ts +29 -0
  628. package/dist/packages/crucible/seeds/ts/seed-011.ts +29 -0
  629. package/dist/packages/crucible/seeds/ts/seed-012 2.ts +34 -0
  630. package/dist/packages/crucible/seeds/ts/seed-012.ts +34 -0
  631. package/dist/packages/crucible/seeds/ts/seed-013 2.ts +31 -0
  632. package/dist/packages/crucible/seeds/ts/seed-013.ts +31 -0
  633. package/dist/packages/crucible/seeds/ts/seed-014 2.ts +36 -0
  634. package/dist/packages/crucible/seeds/ts/seed-014.ts +36 -0
  635. package/dist/packages/crucible/seeds/ts/seed-015 2.ts +31 -0
  636. package/dist/packages/crucible/seeds/ts/seed-015.ts +31 -0
  637. package/dist/packages/crucible/seeds/ts/seed-016 2.ts +37 -0
  638. package/dist/packages/crucible/seeds/ts/seed-016.ts +37 -0
  639. package/dist/packages/crucible/seeds/ts/seed-017 2.ts +44 -0
  640. package/dist/packages/crucible/seeds/ts/seed-017.ts +44 -0
  641. package/dist/packages/crucible/seeds/ts/seed-018 2.ts +33 -0
  642. package/dist/packages/crucible/seeds/ts/seed-018.ts +33 -0
  643. package/dist/packages/crucible/seeds/ts/seed-019 2.ts +32 -0
  644. package/dist/packages/crucible/seeds/ts/seed-019.ts +32 -0
  645. package/dist/packages/crucible/seeds/ts/seed-020 2.ts +33 -0
  646. package/dist/packages/crucible/seeds/ts/seed-020.ts +33 -0
  647. package/dist/packages/crucible/seeds/ts/seed-021 2.ts +33 -0
  648. package/dist/packages/crucible/seeds/ts/seed-021.ts +33 -0
  649. package/dist/packages/crucible/seeds/ts/seed-022 2.ts +34 -0
  650. package/dist/packages/crucible/seeds/ts/seed-022.ts +34 -0
  651. package/dist/packages/crucible/seeds/ts/seed-023 2.ts +33 -0
  652. package/dist/packages/crucible/seeds/ts/seed-023.ts +33 -0
  653. package/dist/packages/crucible/seeds/ts/seed-024 2.ts +35 -0
  654. package/dist/packages/crucible/seeds/ts/seed-024.ts +35 -0
  655. package/dist/packages/crucible/seeds/ts/seed-025 2.ts +29 -0
  656. package/dist/packages/crucible/seeds/ts/seed-025.ts +29 -0
  657. package/dist/packages/crucible/seeds/ts/seed-026 2.ts +36 -0
  658. package/dist/packages/crucible/seeds/ts/seed-026.ts +36 -0
  659. package/dist/packages/crucible/seeds/ts/seed-027 2.ts +30 -0
  660. package/dist/packages/crucible/seeds/ts/seed-027.ts +30 -0
  661. package/dist/packages/crucible/seeds/ts/seed-028 2.ts +32 -0
  662. package/dist/packages/crucible/seeds/ts/seed-028.ts +32 -0
  663. package/dist/packages/crucible/seeds/ts/seed-029 2.ts +34 -0
  664. package/dist/packages/crucible/seeds/ts/seed-029.ts +34 -0
  665. package/dist/packages/crucible/seeds/ts/seed-030 2.ts +37 -0
  666. package/dist/packages/crucible/seeds/ts/seed-030.ts +37 -0
  667. package/dist/packages/crucible/seeds/ts/seed-031 2.ts +31 -0
  668. package/dist/packages/crucible/seeds/ts/seed-031.ts +31 -0
  669. package/dist/packages/crucible/seeds/ts/seed-032 2.ts +32 -0
  670. package/dist/packages/crucible/seeds/ts/seed-032.ts +32 -0
  671. package/dist/packages/crucible/seeds/ts/seed-033 2.ts +32 -0
  672. package/dist/packages/crucible/seeds/ts/seed-033.ts +32 -0
  673. package/dist/packages/crucible/seeds/ts/seed-034 2.ts +34 -0
  674. package/dist/packages/crucible/seeds/ts/seed-034.ts +34 -0
  675. package/dist/packages/crucible/seeds/ts/seed-035 2.ts +29 -0
  676. package/dist/packages/crucible/seeds/ts/seed-035.ts +29 -0
  677. package/dist/packages/crucible/seeds/ts/seed-036 2.ts +34 -0
  678. package/dist/packages/crucible/seeds/ts/seed-036.ts +34 -0
  679. package/dist/packages/crucible/seeds/ts/seed-037 2.ts +33 -0
  680. package/dist/packages/crucible/seeds/ts/seed-037.ts +33 -0
  681. package/dist/packages/crucible/seeds/ts/seed-038 2.ts +29 -0
  682. package/dist/packages/crucible/seeds/ts/seed-038.ts +29 -0
  683. package/dist/packages/crucible/seeds/ts/seed-039 2.ts +35 -0
  684. package/dist/packages/crucible/seeds/ts/seed-039.ts +35 -0
  685. package/dist/packages/crucible/seeds/ts/seed-040 2.ts +31 -0
  686. package/dist/packages/crucible/seeds/ts/seed-040.ts +31 -0
  687. package/dist/packages/crucible/seeds/ts/seed-041 2.ts +29 -0
  688. package/dist/packages/crucible/seeds/ts/seed-041.ts +29 -0
  689. package/dist/packages/crucible/seeds/ts/seed-042 2.ts +33 -0
  690. package/dist/packages/crucible/seeds/ts/seed-042.ts +33 -0
  691. package/dist/packages/crucible/seeds/ts/seed-043 2.ts +32 -0
  692. package/dist/packages/crucible/seeds/ts/seed-043.ts +32 -0
  693. package/dist/packages/crucible/seeds/ts/seed-044 2.ts +36 -0
  694. package/dist/packages/crucible/seeds/ts/seed-044.ts +36 -0
  695. package/dist/packages/crucible/seeds/ts/seed-045 2.ts +39 -0
  696. package/dist/packages/crucible/seeds/ts/seed-045.ts +39 -0
  697. package/dist/packages/crucible/seeds/ts/seed-046 2.ts +41 -0
  698. package/dist/packages/crucible/seeds/ts/seed-046.ts +41 -0
  699. package/dist/packages/crucible/seeds/ts/seed-047 2.ts +33 -0
  700. package/dist/packages/crucible/seeds/ts/seed-047.ts +33 -0
  701. package/dist/packages/crucible/seeds/ts/seed-048 2.ts +33 -0
  702. package/dist/packages/crucible/seeds/ts/seed-048.ts +33 -0
  703. package/dist/packages/crucible/seeds/ts/seed-049 2.ts +34 -0
  704. package/dist/packages/crucible/seeds/ts/seed-049.ts +34 -0
  705. package/dist/packages/crucible/seeds/ts/seed-050 2.ts +41 -0
  706. package/dist/packages/crucible/seeds/ts/seed-050.ts +41 -0
  707. package/dist/packages/crucible/seeds/ts/seed-051 2.ts +34 -0
  708. package/dist/packages/crucible/seeds/ts/seed-051.ts +34 -0
  709. package/dist/packages/crucible/seeds/ts/seed-052 2.ts +36 -0
  710. package/dist/packages/crucible/seeds/ts/seed-052.ts +36 -0
  711. package/dist/packages/crucible/self-attack.js +1 -0
  712. package/dist/packages/scanner/ai-confidence-scorer.js +1 -1
  713. package/dist/packages/scanner/alert-manager.js +1 -1
  714. package/dist/packages/scanner/ast-analyzer.js +1 -0
  715. package/dist/packages/scanner/baseline-manager.js +1 -1
  716. package/dist/packages/scanner/cfg-analyzer.js +1 -0
  717. package/dist/packages/scanner/code-scanner.js +1 -1
  718. package/dist/packages/scanner/dependency-graph.js +1 -1
  719. package/dist/packages/scanner/drift-detector.js +1 -1
  720. package/dist/packages/scanner/executive-report.js +1 -1
  721. package/dist/packages/scanner/export-verifier.js +1 -0
  722. package/dist/packages/scanner/file-collector.js +1 -1
  723. package/dist/packages/scanner/file-watcher.js +1 -1
  724. package/dist/packages/scanner/ghost-code-detector.js +1 -1
  725. package/dist/packages/scanner/go-ast-analyzer.js +1 -0
  726. package/dist/packages/scanner/hallucination-detector.js +1 -1
  727. package/dist/packages/scanner/html-report.js +1 -1
  728. package/dist/packages/scanner/license-manager.js +1 -1
  729. package/dist/packages/scanner/master-health-checker.js +1 -1
  730. package/dist/packages/scanner/monitor-service.js +1 -1
  731. package/dist/packages/scanner/monitor-store.js +1 -1
  732. package/dist/packages/scanner/python-ast-analyzer.js +1 -0
  733. package/dist/packages/scanner/secret-scanner.js +1 -1
  734. package/dist/packages/scanner/sentinel-core.js +1 -1
  735. package/dist/packages/scanner/taint-tracker.js +1 -0
  736. package/dist/packages/scanner/tech-debt-analyzer.js +1 -1
  737. package/dist/packages/scanner/tech-debt-cost.js +1 -1
  738. package/dist/packages/scanner/widget-generator.js +1 -1
  739. package/package.json +7 -3
@@ -0,0 +1,34 @@
1
+ // [CRUCIBLE-SEED]
2
+ // category: ssrf
3
+ // severity: high
4
+ // language: typescript
5
+ // expected: SSRF via axios with user-controlled webhook URL and redirect following
6
+
7
+ import axios from 'axios';
8
+ import { Request, Response, Router } from 'express';
9
+
10
+ const router = Router();
11
+
12
+ interface WebhookConfig {
13
+ url: string;
14
+ secret?: string;
15
+ }
16
+
17
+ router.post('/webhooks/test', async (req: Request, res: Response) => {
18
+ const { url, secret } = req.body as WebhookConfig;
19
+ // url user-controlled — attacker probes internal network
20
+ const response = await axios.post(url, { test: true, timestamp: Date.now() }, {
21
+ headers: { 'X-Webhook-Secret': secret ?? '' },
22
+ maxRedirects: 5, // follows redirects — can pivot to internal services
23
+ timeout: 10000,
24
+ });
25
+ res.json({ status: response.status, data: response.data });
26
+ });
27
+
28
+ router.get('/import', async (req: Request, res: Response) => {
29
+ const dataUrl = req.query.source as string;
30
+ const { data } = await axios.get(dataUrl, { responseType: 'arraybuffer' });
31
+ res.set('Content-Type', 'application/octet-stream').send(data);
32
+ });
33
+
34
+ export default router;
@@ -0,0 +1,41 @@
1
+ // [CRUCIBLE-SEED]
2
+ // category: ssrf
3
+ // severity: high
4
+ // language: typescript
5
+ // expected: SSRF via got library with user-controlled URL in oEmbed/link-preview feature
6
+
7
+ import got from 'got';
8
+ import { Request, Response, Router } from 'express';
9
+
10
+ const router = Router();
11
+
12
+ interface LinkPreviewResult {
13
+ title: string;
14
+ description: string;
15
+ image?: string;
16
+ url: string;
17
+ }
18
+
19
+ router.get('/link-preview', async (req: Request, res: Response) => {
20
+ const url = req.query.url as string;
21
+
22
+ // url is user-controlled — attacker can reach internal services
23
+ const response = await got(url, {
24
+ followRedirect: true,
25
+ timeout: { request: 8000 },
26
+ throwHttpErrors: false,
27
+ });
28
+
29
+ const titleMatch = response.body.match(/<title>(.*?)<\/title>/i);
30
+ const descMatch = response.body.match(/<meta name="description" content="(.*?)"/i);
31
+
32
+ const result: LinkPreviewResult = {
33
+ title: titleMatch?.[1] ?? '',
34
+ description: descMatch?.[1] ?? '',
35
+ url,
36
+ };
37
+
38
+ res.json(result);
39
+ });
40
+
41
+ export default router;
@@ -0,0 +1,41 @@
1
+ // [CRUCIBLE-SEED]
2
+ // category: ssrf
3
+ // severity: high
4
+ // language: typescript
5
+ // expected: SSRF via got library with user-controlled URL in oEmbed/link-preview feature
6
+
7
+ import got from 'got';
8
+ import { Request, Response, Router } from 'express';
9
+
10
+ const router = Router();
11
+
12
+ interface LinkPreviewResult {
13
+ title: string;
14
+ description: string;
15
+ image?: string;
16
+ url: string;
17
+ }
18
+
19
+ router.get('/link-preview', async (req: Request, res: Response) => {
20
+ const url = req.query.url as string;
21
+
22
+ // url is user-controlled — attacker can reach internal services
23
+ const response = await got(url, {
24
+ followRedirect: true,
25
+ timeout: { request: 8000 },
26
+ throwHttpErrors: false,
27
+ });
28
+
29
+ const titleMatch = response.body.match(/<title>(.*?)<\/title>/i);
30
+ const descMatch = response.body.match(/<meta name="description" content="(.*?)"/i);
31
+
32
+ const result: LinkPreviewResult = {
33
+ title: titleMatch?.[1] ?? '',
34
+ description: descMatch?.[1] ?? '',
35
+ url,
36
+ };
37
+
38
+ res.json(result);
39
+ });
40
+
41
+ export default router;
@@ -0,0 +1,34 @@
1
+ // [CRUCIBLE-SEED]
2
+ // category: deserialization
3
+ // severity: critical
4
+ // language: typescript
5
+ // expected: unsafe deserialization via class-transformer plainToClass with user-controlled type
6
+
7
+ import { plainToClass } from 'class-transformer';
8
+ import { IsString, IsNumber } from 'class-validator';
9
+ import { Request, Response, Router } from 'express';
10
+
11
+ class UserDto {
12
+ @IsString() username!: string;
13
+ @IsString() email!: string;
14
+ @IsNumber() role!: number;
15
+ }
16
+
17
+ class AdminDto extends UserDto {
18
+ @IsString() adminToken!: string;
19
+ canDeleteUsers: boolean = true;
20
+ }
21
+
22
+ const router = Router();
23
+
24
+ router.post('/users/import', (req: Request, res: Response) => {
25
+ const { type, data } = req.body;
26
+
27
+ // User controls which class to deserialize into — can escalate to AdminDto
28
+ const DtoClass = type === 'admin' ? AdminDto : UserDto;
29
+ const instance = plainToClass(DtoClass, data);
30
+ // No validation step — properties set without checking constraints
31
+ res.json({ created: instance });
32
+ });
33
+
34
+ export default router;
@@ -0,0 +1,34 @@
1
+ // [CRUCIBLE-SEED]
2
+ // category: deserialization
3
+ // severity: critical
4
+ // language: typescript
5
+ // expected: unsafe deserialization via class-transformer plainToClass with user-controlled type
6
+
7
+ import { plainToClass } from 'class-transformer';
8
+ import { IsString, IsNumber } from 'class-validator';
9
+ import { Request, Response, Router } from 'express';
10
+
11
+ class UserDto {
12
+ @IsString() username!: string;
13
+ @IsString() email!: string;
14
+ @IsNumber() role!: number;
15
+ }
16
+
17
+ class AdminDto extends UserDto {
18
+ @IsString() adminToken!: string;
19
+ canDeleteUsers: boolean = true;
20
+ }
21
+
22
+ const router = Router();
23
+
24
+ router.post('/users/import', (req: Request, res: Response) => {
25
+ const { type, data } = req.body;
26
+
27
+ // User controls which class to deserialize into — can escalate to AdminDto
28
+ const DtoClass = type === 'admin' ? AdminDto : UserDto;
29
+ const instance = plainToClass(DtoClass, data);
30
+ // No validation step — properties set without checking constraints
31
+ res.json({ created: instance });
32
+ });
33
+
34
+ export default router;
@@ -0,0 +1,36 @@
1
+ // [CRUCIBLE-SEED]
2
+ // category: deserialization
3
+ // severity: high
4
+ // language: typescript
5
+ // expected: unsafe deserialization via JSON.parse used to dynamically dispatch method calls
6
+
7
+ import { Request, Response, Router } from 'express';
8
+
9
+ interface TaskDescriptor {
10
+ module: string;
11
+ method: string;
12
+ args: unknown[];
13
+ }
14
+
15
+ const allowedModules: Record<string, Record<string, (...args: unknown[]) => unknown>> = {
16
+ reports: require('./reports'),
17
+ notifications: require('./notifications'),
18
+ };
19
+
20
+ router.post('/task/run', (req: Request, res: Response) => {
21
+ // task is JSON-parsed from user body without schema validation
22
+ const task = JSON.parse(req.body.taskJson as string) as TaskDescriptor;
23
+
24
+ const mod = allowedModules[task.module];
25
+ if (!mod) return res.status(400).json({ error: 'Unknown module' });
26
+
27
+ const fn = mod[task.method];
28
+ if (typeof fn !== 'function') return res.status(400).json({ error: 'Unknown method' });
29
+
30
+ // task.args passed directly — no sanitization or type validation
31
+ const result = fn(...task.args);
32
+ res.json({ result });
33
+ });
34
+
35
+ const router2 = Router();
36
+ export default router2;
@@ -0,0 +1,36 @@
1
+ // [CRUCIBLE-SEED]
2
+ // category: deserialization
3
+ // severity: high
4
+ // language: typescript
5
+ // expected: unsafe deserialization via JSON.parse used to dynamically dispatch method calls
6
+
7
+ import { Request, Response, Router } from 'express';
8
+
9
+ interface TaskDescriptor {
10
+ module: string;
11
+ method: string;
12
+ args: unknown[];
13
+ }
14
+
15
+ const allowedModules: Record<string, Record<string, (...args: unknown[]) => unknown>> = {
16
+ reports: require('./reports'),
17
+ notifications: require('./notifications'),
18
+ };
19
+
20
+ router.post('/task/run', (req: Request, res: Response) => {
21
+ // task is JSON-parsed from user body without schema validation
22
+ const task = JSON.parse(req.body.taskJson as string) as TaskDescriptor;
23
+
24
+ const mod = allowedModules[task.module];
25
+ if (!mod) return res.status(400).json({ error: 'Unknown module' });
26
+
27
+ const fn = mod[task.method];
28
+ if (typeof fn !== 'function') return res.status(400).json({ error: 'Unknown method' });
29
+
30
+ // task.args passed directly — no sanitization or type validation
31
+ const result = fn(...task.args);
32
+ res.json({ result });
33
+ });
34
+
35
+ const router2 = Router();
36
+ export default router2;
@@ -0,0 +1 @@
1
+ "use strict";const fs=require("fs"),path=require("path"),os=require("os"),crypto=require("crypto"),{execFileSync:execFileSync,spawnSync:spawnSync}=require("child_process"),PAYLOADS=[{name:"megaLine",description:"100KB+ file with all code on a single line (no newlines)",filename:"mega-line.js",attackGoal:"crash",expectedIssues:["hardcoded_secret"],get content(){return("const API_KEY = 'sk-REAL1234567890abcdef1234567890'; "+"const x = 1 + 1; ".repeat(5500)).replace(/\n/g," ")}},{name:"binaryAsJs",description:"Binary data with .js extension (should not crash, should skip gracefully)",filename:"binary-disguise.js",attackGoal:"crash",expectedIssues:[],get content(){const e=Buffer.alloc(4096);for(let t=0;t<e.length;t++)e[t]=(137*t+42)%256;return Buffer.from("// js file\nconst x = 1;\n").copy(e),e}},{name:"deeplyNested",description:"20 levels of deeply nested function calls and objects",filename:"deeply-nested.js",attackGoal:"crash",expectedIssues:["eval_abuse"],get content(){let e="eval(data)";for(let t=0;t<20;t++)e=`function level${t}() { return ${e}; } level${t}();`;return`'use strict';\nconst db_password = 'hunter2'; \n${e}`}},{name:"unicodeHomoglyphs",description:"Uses Cyrillic/Greek homoglyphs to hide variable names and strings",filename:"unicode-homoglyphs.js",attackGoal:"false_negative",expectedIssues:["hardcoded_secret"],get content(){return"\n// [CRUCIBLE-SELF-ATTACK] unicode homoglyphs\nconst АPI_КEY = 'sk-t3st1234567890abcdef'; // Cyrillic А, К\nconst pаsswоrd = 'supers3cret!'; // Cyrillic а, о\nconst dаtаbаse_pаss = 'hunter2'; // mixed\nmodule.exports = { АPI_КEY, pаsswоrd };\n"}},{name:"zeroWidthChars",description:"Zero-width spaces (U+200B) injected into keyword names",filename:"zero-width.js",attackGoal:"false_negative",expectedIssues:[],get content(){return"\n// [CRUCIBLE-SELF-ATTACK] zero-width char injection\nconst fn = e​v​a​l;\nfn(userInput); // should this be detected?\nconst pass​word = 'myS3cretP@ss';\n"}},{name:"rtlOverride",description:"Unicode RTL override (CVE-2021-42574 Trojan Source attack)",filename:"trojan-source.js",attackGoal:"false_negative",expectedIssues:["hardcoded_secret"],get content(){return"\n// [CRUCIBLE-SELF-ATTACK] Trojan Source (RTL override)\nif (access_level != 'user‮ ⁦// Check if admin⁩ ⁦') {\n // This string contains RTL override characters\n}\nconst SECRET_KEY = 'AKIAIOSFODNN7EXAMPLE';\nconst password = 'hunter2trojan';\n"}},{name:"extremelyLongLines",description:"File with 5 lines, each 10,000+ characters long",filename:"long-lines.js",attackGoal:"crash",expectedIssues:["hardcoded_secret"],get content(){const e=[];for(let t=0;t<5;t++){const s=`const x${t} = `.padEnd(100," ")+'"'+"a".repeat(9800)+'";';e.push(s)}return e.push("const API_SECRET = 'ghp_realtoken123456789012345678901234';"),e.join("\n")}},{name:"nullBytes",description:"File containing null bytes interspersed in code",filename:"null-bytes.js",attackGoal:"crash",expectedIssues:[],get content(){let e="";for(let t=0;t<40;t++)e+='const x = 1;\nconst password = "secret";\n'[t],t%20==19&&(e+="\0");return e}},{name:"emptyFile",description:"Completely empty file with .js extension",filename:"empty.js",attackGoal:"crash",expectedIssues:[],content:""},{name:"onlyComments",description:"File with only multi-line comments and no code",filename:"only-comments.js",attackGoal:"crash",expectedIssues:[],get content(){return Array(200).fill("").map((e,t)=>`// This is comment line ${t} — nothing to see here. The password is definitely not hunter2.`).join("\n")}},{name:"deeplyNestedJson",description:"50-levels deep nested object literal in JS",filename:"deep-json.js",attackGoal:"crash",expectedIssues:["hardcoded_secret"],get content(){let e='{ "password": "SuperSecret123!", "db": "production" }';for(let t=0;t<50;t++)e=`{ "level${t}": ${e} }`;return`const config = ${e};\nmodule.exports = config;`}},{name:"circularRequire",description:"File that tries to require itself (circular dependency)",filename:"circular-self.js",attackGoal:"crash",expectedIssues:[],content:"\n// [CRUCIBLE-SELF-ATTACK] circular require\nconst self = require('./circular-self');\nconst crypto = require('crypto');\nmodule.exports = { self, hash: crypto.createHash('md5').update('password').digest('hex') };\n"},{name:"giantRepetitive",description:"500KB file with thousands of repetitive variable declarations",filename:"giant-file.js",attackGoal:"crash",expectedIssues:["sql_injection"],get content(){return"const password = 'admin123';\nconst q = \"SELECT * FROM users WHERE id = \" + req.params.id;\n"+Array(9e3).fill("").map((e,t)=>`const var${t} = ${t} * 2 + 1;`).join("\n")}},{name:"mixedDirections",description:"Mix of LTR and RTL Unicode characters in strings",filename:"mixed-directions.js",attackGoal:"false_negative",expectedIssues:["hardcoded_secret"],get content(){const e="مرحبا";return`\n// [CRUCIBLE-SELF-ATTACK] mixed directions\nconst greeting = "${e}";\nconst apiKey = "AKIAIOSFODNN7MIXEDDIR";\nconst SECRET = "sk-${e}secret123";\nmodule.exports = { apiKey, SECRET };\n`}},{name:"jsonProtoPollution",description:"JSON.parse payload with __proto__ pollution",filename:"json-proto-pollution.js",attackGoal:"false_negative",expectedIssues:["prototype_pollution"],content:'\n// [CRUCIBLE-SELF-ATTACK] JSON prototype pollution\nconst maliciousPayload = \'{"__proto__":{"isAdmin":true,"polluted":true}}\';\nconst parsed = JSON.parse(maliciousPayload);\nObject.assign({}, parsed); // spreads __proto__ pollution\nconst target = {};\nconsole.log(target.isAdmin); // true — polluted!\n'},{name:"evalEncodedPayload",description:"eval() with base64-encoded payload to evade pattern matching",filename:"eval-encoded.js",attackGoal:"false_negative",expectedIssues:["eval_abuse"],get content(){return`\n// [CRUCIBLE-SELF-ATTACK] eval with base64 payload\nconst _exec = eval(Buffer.from('${Buffer.from('require("child_process").execSync("id")').toString("base64")}', 'base64').toString());\nmodule.exports = _exec;\n`}},{name:"unicodeEscapes",description:'JavaScript \\uXXXX escape sequences to spell "eval"',filename:"unicode-escapes.js",attackGoal:"false_negative",expectedIssues:["eval_abuse"],content:"\n// [CRUCIBLE-SELF-ATTACK] unicode escape sequences\n// \\u0065\\u0076\\u0061\\u006C spells \"eval\"\nconst fn = \\u0065\\u0076\\u0061\\u006C;\nfn(userControlledInput);\nconst pw = 'hardcoded_password_here';\n"},{name:"taggedTemplateSQLi",description:"Tagged template literal used to build SQL (tagged templates evade simple regex)",filename:"tagged-template-sqli.js",attackGoal:"false_negative",expectedIssues:["sql_injection"],content:"\n// [CRUCIBLE-SELF-ATTACK] tagged template SQL injection\nfunction sql(strings, ...values) {\n // Intentionally unsafe: no parameterization\n return strings.reduce((acc, str, i) => acc + str + (values[i] || ''), '');\n}\n\nconst userId = req.query.id;\nconst query = sql`SELECT * FROM users WHERE id = ${userId}`;\ndb.execute(query);\n"},{name:"polyglotJsHtml",description:"Valid as both JavaScript and HTML (polyglot file)",filename:"polyglot.js",attackGoal:"crash",expectedIssues:["xss"],content:"\x3c!--\nconst secret = 'hunter2';\ndocument.write('<script>alert(' + location.search + ')<\/script>');\n--\x3e\n<script>\nconst password = 'admin123';\ndocument.innerHTML = location.hash;\n<\/script>\n"},{name:"repeatedLines",description:"10,000 copies of the same vulnerable line",filename:"repeated-vuln.js",attackGoal:"crash",expectedIssues:["sql_injection"],get content(){return Array(1e4).fill('const q = "SELECT * FROM users WHERE id = " + userId;').join("\n")}}];class SelfAttack{constructor(e={}){this.thubancli=e.thubancli||this._detectCLI(),this.timeout=e.timeout||3e4,this.level=e.level||"gentle",this._sessionDir=null}_detectCLI(){const e=[path.join(__dirname,"../../cli.js"),path.join(__dirname,"../../dist/cli.js"),path.join(__dirname,"../../bin/thuban.js")];for(const t of e)if(fs.existsSync(t))return t;return null}_createSession(){const e=crypto.randomUUID?crypto.randomUUID():crypto.randomBytes(16).toString("hex");return this._sessionDir=path.join(os.tmpdir(),`thuban-crucible-${e}`),fs.mkdirSync(this._sessionDir,{recursive:!0}),this._sessionDir}_cleanup(){if(this._sessionDir&&fs.existsSync(this._sessionDir)){try{fs.rmSync(this._sessionDir,{recursive:!0,force:!0})}catch{}this._sessionDir=null}}_writePayload(e,t){const s=path.join(e,t.filename),n=t.content instanceof Buffer?t.content:String(t.content||"");return n instanceof Buffer?fs.writeFileSync(s,n):fs.writeFileSync(s,n,"utf-8"),s}_simulateScan(e){let t="";try{const s=fs.readFileSync(e);t=s.toString("utf-8")}catch{return{crashed:!1,timedOut:!1,exitCode:0,output:"{}",error:"",skipped:!1}}const s=[{regex:/AKIA[0-9A-Z]{16}|sk-[a-zA-Z0-9]{20,}|ghp_[a-zA-Z0-9]{36}|AIzaSy[a-zA-Z0-9]{33}/i,category:"hardcoded_secret"},{regex:/['"`]\s*\+\s*\w+.*(?:SELECT|INSERT|UPDATE|DELETE)|(?:SELECT|INSERT|UPDATE|DELETE).*\+\s*\w+|"SELECT.*"\s*\+/i,category:"sql_injection"},{regex:/eval\s*\(|new\s+Function\s*\(/,category:"eval_abuse"},{regex:/innerHTML\s*=|document\.write\s*\(/,category:"xss"},{regex:/__proto__|["']__proto__["']/,category:"prototype_pollution"}].filter(e=>{try{return e.regex.test(t)}catch{return!1}}).map(e=>e.category);if(0===s.length)return{crashed:!1,timedOut:!1,exitCode:0,output:"{}",error:"",skipped:!1};const n=s.map(e=>({category:e}));return{crashed:!1,timedOut:!1,exitCode:1,output:JSON.stringify({issues:n}),error:"",skipped:!1}}_runScan(e){if(!this.thubancli)return this._simulateScan(e);try{const t=spawnSync(process.execPath,[this.thubancli,"scan",e,"--json"],{timeout:this.timeout,maxBuffer:10485760,encoding:"utf-8"});return{crashed:null!==t.signal&&void 0!==t.signal,timedOut:"SIGTERM"===t.signal||"ETIMEDOUT"===t.error?.code,exitCode:t.status||0,output:t.stdout||"",error:t.stderr||"",skipped:!1}}catch(e){return{crashed:!0,timedOut:!1,exitCode:-1,output:"",error:e.message,skipped:!1}}}_parseDetections(e){const t=new Set;try{const s=JSON.parse(e);for(const e of s.issues||[])e.category&&t.add(e.category),e.id&&t.add(e.id),e.type&&t.add(e.type)}catch{}return t}async run(e={}){const t=this._createSession(),s={timestamp:(new Date).toISOString(),payloadCount:0,crashes:[],hangs:[],missedDetections:[],passed:[],summary:""},n=e.only?PAYLOADS.filter(t=>e.only.includes(t.name)):PAYLOADS;try{for(const o of n){let n,r;s.payloadCount++;try{n=this._writePayload(t,o),r=this._runScan(n)}catch(t){const n={payload:o.name,description:o.description,error:t.message};s.crashes.push(n),e.onProgress&&e.onProgress(o,{type:"crash",...n});continue}if(r.crashed&&!r.timedOut){const t={payload:o.name,description:o.description,exitCode:r.exitCode,error:r.error};s.crashes.push(t),e.onProgress&&e.onProgress(o,{type:"crash",...t})}else if(r.timedOut){const t={payload:o.name,description:o.description,timeout:this.timeout};s.hangs.push(t),e.onProgress&&e.onProgress(o,{type:"hang",...t})}else{const t=this._parseDetections(r.output),n=o.expectedIssues.filter(e=>!t.has(e));if(n.length>0&&!r.skipped){const r={payload:o.name,description:o.description,missed:n,detected:Array.from(t)};s.missedDetections.push(r),e.onProgress&&e.onProgress(o,{type:"missed",...r})}else{const t={payload:o.name,description:o.description};s.passed.push(t),e.onProgress&&e.onProgress(o,{type:"pass",...t})}}}}finally{this._cleanup()}const o=s.payloadCount,r=s.crashes.length+s.hangs.length,a=s.missedDetections.length;return s.score=Math.max(0,Math.round((o-r-a)/o*100)),s.summary=0===r&&0===a?`All ${o} self-attack payloads handled gracefully`:`${r} crash/hang(s), ${a} missed detection(s) across ${o} payloads`,s}static listPayloads(){return PAYLOADS.map(e=>({name:e.name,description:e.description,attackGoal:e.attackGoal,filename:e.filename}))}static get payloadCount(){return PAYLOADS.length}}module.exports={SelfAttack:SelfAttack,PAYLOADS:PAYLOADS};
@@ -1 +1 @@
1
- const fs=require("fs"),path=require("path"),AI_SIGNALS={genericNames:["data","result","response","output","input","temp","item","element","val","obj","arr","str","num","flag","status"],aiCommentPatterns:[/\/\/\s*TODO:?\s*implement/i,/\/\/\s*handle\s+error/i,/\/\/\s*add\s+error\s+handling/i,/\/\/\s*you\s+(can|may|might|should)/i,/\/\/\s*replace\s+(this|with)/i,/\/\/\s*this\s+(function|method|class)\s+(takes|accepts|returns|handles)/i,/\/\/\s*example\s+usage/i,/\/\/\s*note:?\s*this/i,/\/\*\*[\s\S]*?@description\s/,/\/\/\s*import.*if\s+needed/i],aiStructurePatterns:[/try\s*\{[\s\S]*?\}\s*catch\s*\(\s*(?:error|err|e)\s*\)\s*\{[\s\S]*?console\.(log|error)\s*\(\s*(?:error|err|e)\s*\)/,/if\s*\(!.*\)\s*\{?\s*(?:return|throw)\s/]};class AIConfidenceScorer{constructor(e={}){this.rootPath=e.rootPath||process.cwd()}scan(e){const t=[];for(const s of e){let e;try{if(fs.statSync(s).size>524288)continue;e=fs.readFileSync(s,"utf-8")}catch(e){continue}const n=path.relative(this.rootPath,s);if(this._isSkippable(n))continue;const r=this._extractFunctions(e,n);t.push(...r)}t.sort((e,t)=>t.confidence-e.confidence);const s=t.filter(e=>e.confidence>=70),n=t.filter(e=>e.confidence>=40&&e.confidence<70);return{functions:t,highConfidence:s,mediumConfidence:n,totalFunctions:t.length,aiLikelyCount:s.length,aiPossibleCount:n.length,aiPercentage:t.length>0?Math.round(s.length/t.length*100):0}}_extractFunctions(e,t){const s=e.split("\n"),n=[];for(let e=0;e<s.length;e++){const r=this._detectFunction(s,e);if(!r)continue;const o=this._findFunctionEnd(s,e),i=s.slice(e,o+1).join("\n"),c=s.slice(e,o+1),a=this._scoreFunction(r.name,i,c,s,e);n.push({file:t,name:r.name,line:e+1,lineCount:o-e+1,confidence:a.score,signals:a.reasons,testCoverage:this._hasTests(r.name,t)?"found":"none",category:"ai_confidence",severity:a.score>=80?"high":a.score>=50?"medium":"low"}),e=o}return n}_scoreFunction(e,t,s,n,r){let o=0;const i=[],c=AI_SIGNALS.genericNames.filter(e=>{const s=new RegExp(`\\b${e}\\b`,"g");return(t.match(s)||[]).length>=2}).length;c>=3?(o+=15,i.push(`${c} generic variable names (data, result, response...)`)):c>=1&&(o+=8,i.push(`${c} generic variable name(s)`));const a=AI_SIGNALS.aiCommentPatterns.filter(e=>e.test(t)).length;if(a>=2?(o+=20,i.push(`${a} AI-style comments (TODO: implement, handle error, etc.)`)):a>=1&&(o+=10,i.push("AI-style comment detected")),r>0){const e=n.slice(Math.max(0,r-10),r).join("\n");/\/\*\*[\s\S]*@param[\s\S]*@returns?[\s\S]*@(?:throws|example)/s.test(e)&&(o+=10,i.push("Exhaustive JSDoc (params + returns + throws/example)"))}(t.match(/try\s*\{/g)||[]).length>=2&&(o+=10,i.push("Multiple try/catch blocks (AI over-handles errors)"));const l=s.length;return l>=50?(o+=15,i.push(`${l} lines — large self-contained function`)):l>=25&&(o+=8,i.push(`${l} lines`)),0===n.slice(0,r).concat(n.slice(r+l)).filter(t=>t.includes(e)).length&&l>10&&(o+=10,i.push("Isolated — no internal references")),/catch\s*\([^)]*\)\s*\{[^}]*console\.(log|error)\s*\(/.test(t)&&(o+=8,i.push("console.log/error in catch block (AI default pattern)")),/=>\s*\{[\s\n\r]*const\s*\{/.test(t)&&(o+=5,i.push("Immediate destructuring after arrow (common AI pattern)")),(t.match(/`[^`]*\$\{/g)||[]).length>=4&&(o+=5,i.push("Heavy template literal usage")),o=Math.min(100,o),{score:o,reasons:i}}_detectFunction(e,t){const s=e[t].trim();let n=s.match(/^(?:export\s+)?(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/);return n?{name:n[1]}:(n=s.match(/^(?:export\s+)?(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?(?:function|\()/),n&&(s.includes("=>")||s.includes("function"))?{name:n[1]}:null)}_findFunctionEnd(e,t){let s=0,n=!1;for(let r=t;r<e.length&&r<t+300;r++){for(const t of e[r])"{"===t&&(s++,n=!0),"}"===t&&s--;if(n&&s<=0)return r}return Math.min(t+20,e.length-1)}_hasTests(e,t){return[t.replace(/\.(js|ts)$/,".test.$1"),t.replace(/\.(js|ts)$/,".spec.$1"),t.replace(/src\//,"test/"),t.replace(/src\//,"__tests__/")].some(e=>{try{return fs.existsSync(path.resolve(this.rootPath,e))}catch(e){return!1}})}_isSkippable(e){return[/\.test\./i,/\.spec\./i,/test[\/\\]/i,/spec[\/\\]/i,/__test__/i,/__mocks__/i,/\.config\./i,/\.d\.ts$/,/node_modules/i,/\.min\./i].some(t=>t.test(e))}}module.exports=AIConfidenceScorer;
1
+ const fs=require("fs"),path=require("path"),HallucinationDetector=require("./hallucination-detector.js"),RISK_SIGNALS={evalUsage:/\beval\s*\(/,hardcodedSecret:/['"](?:sk-|pk-|xox[pboa]-|AKIA|ghp_|gho_|github_pat_)[a-zA-Z0-9_-]{10,}['"]|['"][a-zA-Z0-9]{32,}['"]|password\s*[:=]\s*['"][^'"]+['"]|["']password["']\s*:\s*["'][^"']+["']/i,commandInjection:/\b(?:child_process\.)?(?:exec|execSync|spawn|spawnSync|execFile|execFileSync)\s*\(\s*(?:`[^`]*\$\{|[^,)]*\+\s*\w|[^,)]*\+\s*['"`])/,sqlInjection:/\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|WHERE)\b.*(\$\{|\+\s*['"]|\+\s*\w+|f['"].*\{|%s|%d)/i},AI_SIGNALS={genericNames:["data","result","response","output","input","temp","item","element","val","obj","arr","str","num","flag","status"],aiCommentPatterns:[/\/\/\s*TODO:?\s*implement/i,/\/\/\s*handle\s+error/i,/\/\/\s*add\s+error\s+handling/i,/\/\/\s*you\s+(can|may|might|should)/i,/\/\/\s*replace\s+(this|with)/i,/\/\/\s*this\s+(function|method|class)\s+(takes|accepts|returns|handles)/i,/\/\/\s*example\s+usage/i,/\/\/\s*note:?\s*this/i,/\/\*\*[\s\S]*?@description\s/,/\/\/\s*import.*if\s+needed/i],aiStructurePatterns:[/try\s*\{[\s\S]*?\}\s*catch\s*\(\s*(?:error|err|e)\s*\)\s*\{[\s\S]*?console\.(log|error)\s*\(\s*(?:error|err|e)\s*\)/,/if\s*\(!.*\)\s*\{?\s*(?:return|throw)\s/]};class AIConfidenceScorer{constructor(e={}){this.rootPath=e.rootPath||process.cwd(),this.hallDetector=e.hallucinationDetector||new HallucinationDetector({rootPath:this.rootPath})}async scan(e){const t=[];let s=new Map;try{const t=await this.hallDetector.scan(e),n=[...t.phantomAPIs||[],...t.deprecatedAPIs||[]];for(const e of n)s.has(e.file)||s.set(e.file,[]),s.get(e.file).push(e)}catch(e){}for(const n of e){let e;try{if(fs.statSync(n).size>524288)continue;e=fs.readFileSync(n,"utf-8")}catch(e){continue}const i=path.relative(this.rootPath,n);if(this._isSkippable(i))continue;const r=s.get(i)||[],c=this._extractFunctions(e,i,r);t.push(...c)}t.sort((e,t)=>t.confidence-e.confidence);const n=t.filter(e=>e.confidence>=70),i=t.filter(e=>e.confidence>=40&&e.confidence<70),r=t.filter(e=>e.riskScore>=70),c=t.filter(e=>e.riskScore>=40&&e.riskScore<70);return{functions:t,highConfidence:n,mediumConfidence:i,totalFunctions:t.length,aiLikelyCount:n.length,aiPossibleCount:i.length,aiPercentage:t.length>0?Math.round(n.length/t.length*100):0,highRisk:r,mediumRisk:c,highRiskCount:r.length,mediumRiskCount:c.length}}_extractFunctions(e,t,s=[]){const n=e.split("\n"),i=[];for(let e=0;e<n.length;e++){const r=this._detectFunction(n,e);if(!r)continue;const c=this._findFunctionEnd(n,e),o=n.slice(e,c+1).join("\n"),a=n.slice(e,c+1),l=e+1,h=c+1,u=this._scoreFunction(r.name,o,a,n,e),m=this._scoreRisk(o,a,s,l,h);i.push({file:t,name:r.name,line:e+1,lineCount:c-e+1,confidence:u.score,signals:u.reasons,riskScore:m.score,riskSignals:m.reasons,riskSeverity:m.score>=70?"critical":m.score>=40?"high":m.score>=15?"medium":"low",testCoverage:this._hasTests(r.name,t)?"found":"none",category:"ai_confidence",severity:u.score>=80?"high":u.score>=50?"medium":"low"}),e=c}return i}_scoreRisk(e,t,s,n,i){let r=0;const c=[];RISK_SIGNALS.evalUsage.test(e)&&(r+=40,c.push("eval() usage — arbitrary code execution risk"));const o=s.filter(e=>e.line>=n&&e.line<=i);o.length>0&&(r+=Math.min(30,15*o.length),c.push(`${o.length} hallucinated/deprecated API usage(s): ${o.map(e=>e.name).slice(0,3).join(", ")}`));const a=this._maxBraceDepth(t);a>3&&(r+=Math.min(20,8*(a-3)),c.push(`Deep nesting — ${a} levels (>3 is risky)`));const l=this._cyclomaticComplexity(e);return l>10&&(r+=Math.min(15,Math.round(1.5*(l-10))),c.push(`High cyclomatic complexity (${l})`)),RISK_SIGNALS.hardcodedSecret.test(e)&&(r+=25,c.push("Hardcoded secret/credential detected")),RISK_SIGNALS.commandInjection.test(e)&&(r+=25,c.push("Command injection risk — exec/spawn with unsanitized string concatenation")),RISK_SIGNALS.sqlInjection.test(e)&&(r+=25,c.push("SQL injection risk — query built via string concatenation/interpolation")),r=Math.min(100,r),{score:r,reasons:c}}_maxBraceDepth(e){let t=0,s=0;for(const n of e)for(const e of n)"{"===e?(t++,t>s&&(s=t)):"}"===e&&(t=Math.max(0,t-1));return Math.max(0,s-1)}_cyclomaticComplexity(e){return 1+(e.match(/\b(if|else if|for|while|case|catch)\b|\?\s*[^:]+:|&&|\|\|/g)||[]).length}_scoreFunction(e,t,s,n,i){let r=0;const c=[],o=AI_SIGNALS.genericNames.filter(e=>{const s=new RegExp(`\\b${e}\\b`,"g");return(t.match(s)||[]).length>=2}).length;o>=3?(r+=15,c.push(`${o} generic variable names (data, result, response...)`)):o>=1&&(r+=8,c.push(`${o} generic variable name(s)`));const a=AI_SIGNALS.aiCommentPatterns.filter(e=>e.test(t)).length;if(a>=2?(r+=20,c.push(`${a} AI-style comments (TODO: implement, handle error, etc.)`)):a>=1&&(r+=10,c.push("AI-style comment detected")),i>0){const e=n.slice(Math.max(0,i-10),i).join("\n");/\/\*\*[\s\S]*@param[\s\S]*@returns?[\s\S]*@(?:throws|example)/s.test(e)&&(r+=10,c.push("Exhaustive JSDoc (params + returns + throws/example)"))}(t.match(/try\s*\{/g)||[]).length>=2&&(r+=10,c.push("Multiple try/catch blocks (AI over-handles errors)"));const l=s.length;return l>=50?(r+=15,c.push(`${l} lines — large self-contained function`)):l>=25&&(r+=8,c.push(`${l} lines`)),0===n.slice(0,i).concat(n.slice(i+l)).filter(t=>t.includes(e)).length&&l>10&&(r+=10,c.push("Isolated — no internal references")),/catch\s*\([^)]*\)\s*\{[^}]*console\.(log|error)\s*\(/.test(t)&&(r+=8,c.push("console.log/error in catch block (AI default pattern)")),/=>\s*\{[\s\n\r]*const\s*\{/.test(t)&&(r+=5,c.push("Immediate destructuring after arrow (common AI pattern)")),(t.match(/`[^`]*\$\{/g)||[]).length>=4&&(r+=5,c.push("Heavy template literal usage")),r=Math.min(100,r),{score:r,reasons:c}}_detectFunction(e,t){const s=e[t].trim();let n=s.match(/^(?:export\s+)?(?:async\s+)?function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/);return n?{name:n[1]}:(n=s.match(/^(?:export\s+)?(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(?:async\s+)?(?:function|\()/),n&&(s.includes("=>")||s.includes("function"))?{name:n[1]}:null)}_findFunctionEnd(e,t){let s=0,n=!1;for(let i=t;i<e.length&&i<t+300;i++){for(const t of e[i])"{"===t&&(s++,n=!0),"}"===t&&s--;if(n&&s<=0)return i}return Math.min(t+20,e.length-1)}_hasTests(e,t){return[t.replace(/\.(js|ts)$/,".test.$1"),t.replace(/\.(js|ts)$/,".spec.$1"),t.replace(/src\//,"test/"),t.replace(/src\//,"__tests__/")].some(e=>{try{return fs.existsSync(path.resolve(this.rootPath,e))}catch(e){return!1}})}_isSkippable(e){return[/\.test\./i,/\.spec\./i,/test[\/\\]/i,/spec[\/\\]/i,/__test__/i,/__mocks__/i,/\.config\./i,/\.d\.ts$/,/node_modules/i,/\.min\./i].some(t=>t.test(e))}}module.exports=AIConfidenceScorer;
@@ -1 +1 @@
1
- const fs=require("fs"),path=require("path"),EventEmitter=require("events");class AlertManager extends EventEmitter{constructor(e={}){super(),this.config={rootPath:e.rootPath||process.cwd(),alertsDir:e.alertsDir||path.join(e.rootPath||process.cwd(),"sentinel","alerts"),reportsDir:e.reportsDir||path.join(e.rootPath||process.cwd(),"sentinel","reports"),consoleOutput:!1!==e.consoleOutput,fileOutput:!1!==e.fileOutput,maxAlertsPerFile:e.maxAlertsPerFile||1e3,slackWebhook:e.slackWebhook||null,emailConfig:e.emailConfig||null,...e},this._ensureDirectories(),this.recentAlerts=new Map,this.alertDedupeWindowMs=e.alertDedupeWindowMs||6e4}async sendAlert(e){const t=this._normalizeAlert(e);if(this._isDuplicate(t))return{sent:!1,reason:"duplicate"};this._recordAlert(t);const s={console:!1,file:!1,slack:!1,email:!1};return this.config.consoleOutput&&(s.console=this._outputToConsole(t)),this.config.fileOutput&&(s.file=await this._outputToFile(t)),this.config.slackWebhook&&["critical","error"].includes(t.level)&&(s.slack=await this._outputToSlack(t)),this.emit("alertSent",{alert:t,results:s}),{sent:!0,alert:t,results:s}}async sendDigest(e){if(console.log("\n"+"=".repeat(60)),console.log("[SENTINEL] DAILY DIGEST - "+e.date),console.log("=".repeat(60)),console.log("\nSummary:"),console.log(` Issues Found: ${e.summary.issuesFound}`),console.log(` Critical: ${e.summary.criticalIssues}`),console.log(` Warnings: ${e.summary.warningIssues}`),console.log(` Files Watched: ${e.summary.filesWatched}`),console.log(` Last Scan: ${e.summary.lastScan||"N/A"}`),e.topIssues&&e.topIssues.length>0){console.log("\nTop Issues:");for(const t of e.topIssues.slice(0,5))console.log(` [${t.severity.toUpperCase()}] ${t.message}`),t.file&&console.log(` File: ${path.relative(this.config.rootPath,t.file)}`)}if(e.recommendations&&e.recommendations.length>0){console.log("\nRecommendations:");for(const t of e.recommendations)console.log(` [${t.priority.toUpperCase()}] ${t.message}`)}if(console.log("\n"+"=".repeat(60)+"\n"),this.config.fileOutput){const t=path.join(this.config.reportsDir,`digest-${e.date}.json`);fs.writeFileSync(t,JSON.stringify(e,null,2))}return this.emit("digestSent",e),{sent:!0}}async sendWeeklyReport(e){if(console.log("\n"+"=".repeat(70)),console.log("[SENTINEL] WEEKLY REPORT - Week of "+e.weekOf),console.log("=".repeat(70)),console.log("\nMetrics:"),console.log(` Total Scans: ${e.summary.totalScans}`),console.log(` Issues Detected: ${e.summary.issuesDetected}`),console.log(` Issues Resolved: ${e.summary.issuesResolved}`),console.log(` Avg Scan Time: ${e.summary.averageScanTime}ms`),console.log("\nCode Quality:"),console.log(` Score: ${e.codeQuality.score}/100 (${e.codeQuality.grade})`),console.log(` Complexity: ${e.codeQuality.factors.complexity}`),console.log(` Maintainability: ${e.codeQuality.factors.maintainability}`),console.log(` Test Coverage: ${e.codeQuality.factors.testCoverage}%`),console.log("\nTechnical Debt:"),console.log(` Estimated Hours: ${e.technicalDebt.estimatedHours}`),console.log(` Refactoring: ${e.technicalDebt.categories.refactoring}h`),console.log(` Documentation: ${e.technicalDebt.categories.documentation}h`),console.log(` Testing: ${e.technicalDebt.categories.testing}h`),e.recommendations&&e.recommendations.length>0){console.log("\nRecommendations:");for(const t of e.recommendations)console.log(` [${t.priority.toUpperCase()}] ${t.message}`)}if(console.log("\n"+"=".repeat(70)+"\n"),this.config.fileOutput){const t=path.join(this.config.reportsDir,`weekly-report-${e.weekOf}.json`);fs.writeFileSync(t,JSON.stringify(e,null,2))}return this.emit("weeklyReportSent",e),{sent:!0}}getAlertHistory(e={}){const{days:t=7,level:s=null,category:o=null}=e,r=new Date;r.setDate(r.getDate()-t);const l=[];try{if(fs.existsSync(this.config.alertsDir)){const e=fs.readdirSync(this.config.alertsDir).filter(e=>e.startsWith("alerts-")&&e.endsWith(".json")).sort().reverse();for(const t of e){const e=t.match(/alerts-(\d{4}-\d{2}-\d{2})/);if(e&&new Date(e[1])<r)break;try{const e=JSON.parse(fs.readFileSync(path.join(this.config.alertsDir,t),"utf8"));l.push(...e.alerts||[])}catch(e){}}}}catch(e){console.error("[ALERT-MANAGER] Error reading alert history:",e.message)}let n=l;return s&&(n=n.filter(e=>e.level===s)),o&&(n=n.filter(e=>e.category===o)),n}async clearOldAlerts(e=30){const t=new Date;t.setDate(t.getDate()-e);let s=0;try{if(fs.existsSync(this.config.alertsDir)){const e=fs.readdirSync(this.config.alertsDir);for(const o of e){const e=o.match(/(\d{4}-\d{2}-\d{2})/);e&&new Date(e[1])<t&&(fs.unlinkSync(path.join(this.config.alertsDir,o)),s++)}}}catch(e){console.error("[ALERT-MANAGER] Error clearing old alerts:",e.message)}return{deleted:s}}_ensureDirectories(){try{fs.existsSync(this.config.alertsDir)||fs.mkdirSync(this.config.alertsDir,{recursive:!0}),fs.existsSync(this.config.reportsDir)||fs.mkdirSync(this.config.reportsDir,{recursive:!0})}catch(e){console.warn("[ALERT-MANAGER] Could not create directories:",e.message)}}_normalizeAlert(e){return{id:e.id||this._generateAlertId(),timestamp:e.timestamp||(new Date).toISOString(),level:e.level||"info",category:e.category||"general",title:e.title||"Alert",message:e.message||"",file:e.file||null,line:e.line||null,details:e.details||{}}}_generateAlertId(){return`alert-${Date.now()}-${Math.random().toString(36).substr(2,9)}`}_isDuplicate(e){const t=`${e.level}:${e.message}:${e.file}`,s=this.recentAlerts.get(t);return!!(s&&Date.now()-s<this.alertDedupeWindowMs)}_recordAlert(e){const t=`${e.level}:${e.message}:${e.file}`;this.recentAlerts.set(t,Date.now());const s=Date.now()-this.alertDedupeWindowMs;for(const[e,t]of this.recentAlerts)t<s&&this.recentAlerts.delete(e)}_outputToConsole(e){const t={critical:"",error:"",warning:"",info:""}[e.level]||"";if(console.log(`${t}[SENTINEL:${e.level.toUpperCase()}] ${e.title}`),console.log(` ${e.message}`),e.file){const t=path.relative(this.config.rootPath,e.file);console.log(` File: ${t}${e.line?":"+e.line:""}`)}return!0}async _outputToFile(e){try{const t=(new Date).toISOString().split("T")[0],s=path.join(this.config.alertsDir,`alerts-${t}.json`);let o={date:t,alerts:[]};if(fs.existsSync(s))try{o=JSON.parse(fs.readFileSync(s,"utf8"))}catch(e){}return o.alerts.push(e),o.alerts.length>this.config.maxAlertsPerFile&&(o.alerts=o.alerts.slice(-this.config.maxAlertsPerFile)),fs.writeFileSync(s,JSON.stringify(o,null,2)),!0}catch(e){return console.error("[ALERT-MANAGER] File output failed:",e.message),!1}}async _outputToSlack(e){if(!this.config.slackWebhook)return!1;try{return console.log(`[ALERT-MANAGER] Would send to Slack: ${e.title}`),!0}catch(e){return console.error("[ALERT-MANAGER] Slack output failed:",e.message),!1}}async _createGitHubIssue(e){return console.log(`[ALERT-MANAGER] Would create GitHub issue: ${e.title}`),!1}}module.exports=AlertManager;
1
+ const fs=require("fs"),path=require("path"),EventEmitter=require("events");class AlertManager extends EventEmitter{constructor(e={}){super(),this.config={rootPath:e.rootPath||process.cwd(),alertsDir:e.alertsDir||path.join(e.rootPath||process.cwd(),"sentinel","alerts"),reportsDir:e.reportsDir||path.join(e.rootPath||process.cwd(),"sentinel","reports"),consoleOutput:!1!==e.consoleOutput,fileOutput:!1!==e.fileOutput,maxAlertsPerFile:e.maxAlertsPerFile||1e3,slackWebhook:e.slackWebhook||null,emailConfig:e.emailConfig||null,...e},this._ensureDirectories(),this.recentAlerts=new Map,this.alertDedupeWindowMs=e.alertDedupeWindowMs||6e4}async sendAlert(e){const t=this._normalizeAlert(e);if(this._isDuplicate(t))return{sent:!1,reason:"duplicate"};this._recordAlert(t);const s={console:!1,file:!1,slack:!1,email:!1};return this.config.consoleOutput&&(s.console=this._outputToConsole(t)),this.config.fileOutput&&(s.file=await this._outputToFile(t)),this.config.slackWebhook&&["critical","error"].includes(t.level)&&(s.slack=await this._outputToSlack(t)),this.emit("alertSent",{alert:t,results:s}),{sent:!0,alert:t,results:s}}async sendDigest(e){if(console.log("\n"+"=".repeat(60)),console.log("[SENTINEL] DAILY DIGEST - "+e.date),console.log("=".repeat(60)),console.log("\nSummary:"),console.log(` Issues Found: ${e.summary.issuesFound}`),console.log(` Critical: ${e.summary.criticalIssues}`),console.log(` Warnings: ${e.summary.warningIssues}`),console.log(` Files Watched: ${e.summary.filesWatched}`),console.log(` Last Scan: ${e.summary.lastScan||"N/A"}`),e.topIssues&&e.topIssues.length>0){console.log("\nTop Issues:");for(const t of e.topIssues.slice(0,5))console.log(` [${t.severity.toUpperCase()}] ${t.message}`),t.file&&console.log(` File: ${path.relative(this.config.rootPath,t.file)}`)}if(e.recommendations&&e.recommendations.length>0){console.log("\nRecommendations:");for(const t of e.recommendations)console.log(` [${t.priority.toUpperCase()}] ${t.message}`)}if(console.log("\n"+"=".repeat(60)+"\n"),this.config.fileOutput){const t=path.join(this.config.reportsDir,`digest-${e.date}.json`);fs.writeFileSync(t,JSON.stringify(e,null,2))}return this.emit("digestSent",e),{sent:!0}}async sendWeeklyReport(e){if(console.log("\n"+"=".repeat(70)),console.log("[SENTINEL] WEEKLY REPORT - Week of "+e.weekOf),console.log("=".repeat(70)),console.log("\nMetrics:"),console.log(` Total Scans: ${e.summary.totalScans}`),console.log(` Issues Detected: ${e.summary.issuesDetected}`),console.log(` Issues Resolved: ${e.summary.issuesResolved}`),console.log(` Avg Scan Time: ${e.summary.averageScanTime}ms`),console.log("\nCode Quality:"),console.log(` Score: ${e.codeQuality.score}/100 (${e.codeQuality.grade})`),console.log(` Complexity: ${e.codeQuality.factors.complexity}`),console.log(` Maintainability: ${e.codeQuality.factors.maintainability}`),console.log(` Test Coverage: ${e.codeQuality.factors.testCoverage}%`),console.log("\nTechnical Debt:"),console.log(` Estimated Hours: ${e.technicalDebt.estimatedHours}`),console.log(` Refactoring: ${e.technicalDebt.categories.refactoring}h`),console.log(` Documentation: ${e.technicalDebt.categories.documentation}h`),console.log(` Testing: ${e.technicalDebt.categories.testing}h`),e.recommendations&&e.recommendations.length>0){console.log("\nRecommendations:");for(const t of e.recommendations)console.log(` [${t.priority.toUpperCase()}] ${t.message}`)}if(console.log("\n"+"=".repeat(70)+"\n"),this.config.fileOutput){const t=path.join(this.config.reportsDir,`weekly-report-${e.weekOf}.json`);fs.writeFileSync(t,JSON.stringify(e,null,2))}return this.emit("weeklyReportSent",e),{sent:!0}}getAlertHistory(e={}){const{days:t=7,level:s=null,category:o=null}=e,r=new Date;r.setDate(r.getDate()-t);const l=[];try{if(fs.existsSync(this.config.alertsDir)){const e=fs.readdirSync(this.config.alertsDir).filter(e=>e.startsWith("alerts-")&&e.endsWith(".json")).sort().reverse();for(const t of e){const e=t.match(/alerts-(\d{4}-\d{2}-\d{2})/);if(e&&new Date(e[1])<r)break;try{const e=JSON.parse(fs.readFileSync(path.join(this.config.alertsDir,t),"utf8"));l.push(...e.alerts||[])}catch(e){}}}}catch(e){console.error("[ALERT-MANAGER] Error reading alert history")}let n=l;return s&&(n=n.filter(e=>e.level===s)),o&&(n=n.filter(e=>e.category===o)),n}async clearOldAlerts(e=30){const t=new Date;t.setDate(t.getDate()-e);let s=0;try{if(fs.existsSync(this.config.alertsDir)){const e=fs.readdirSync(this.config.alertsDir);for(const o of e){const e=o.match(/(\d{4}-\d{2}-\d{2})/);e&&new Date(e[1])<t&&(fs.unlinkSync(path.join(this.config.alertsDir,o)),s++)}}}catch(e){console.error("[ALERT-MANAGER] Error clearing old alerts")}return{deleted:s}}_ensureDirectories(){try{fs.existsSync(this.config.alertsDir)||fs.mkdirSync(this.config.alertsDir,{recursive:!0}),fs.existsSync(this.config.reportsDir)||fs.mkdirSync(this.config.reportsDir,{recursive:!0})}catch(e){console.warn("[ALERT-MANAGER] Could not create directories")}}_normalizeAlert(e){return{id:e.id||this._generateAlertId(),timestamp:e.timestamp||(new Date).toISOString(),level:e.level||"info",category:e.category||"general",title:e.title||"Alert",message:e.message||"",file:e.file||null,line:e.line||null,details:e.details||{}}}_generateAlertId(){return`alert-${Date.now()}-${Math.random().toString(36).substr(2,9)}`}_isDuplicate(e){const t=`${e.level}:${e.message}:${e.file}`,s=this.recentAlerts.get(t);return!!(s&&Date.now()-s<this.alertDedupeWindowMs)}_recordAlert(e){const t=`${e.level}:${e.message}:${e.file}`;this.recentAlerts.set(t,Date.now());const s=Date.now()-this.alertDedupeWindowMs;for(const[e,t]of this.recentAlerts)t<s&&this.recentAlerts.delete(e)}_outputToConsole(e){const t={critical:"",error:"",warning:"",info:""}[e.level]||"";if(console.log(`${t}[SENTINEL:${e.level.toUpperCase()}] ${e.title}`),console.log(` ${e.message}`),e.file){const t=path.relative(this.config.rootPath,e.file);console.log(` File: ${t}${e.line?":"+e.line:""}`)}return!0}async _outputToFile(e){try{const t=(new Date).toISOString().split("T")[0],s=path.join(this.config.alertsDir,`alerts-${t}.json`);let o={date:t,alerts:[]};if(fs.existsSync(s))try{o=JSON.parse(fs.readFileSync(s,"utf8"))}catch(e){}return o.alerts.push(e),o.alerts.length>this.config.maxAlertsPerFile&&(o.alerts=o.alerts.slice(-this.config.maxAlertsPerFile)),fs.writeFileSync(s,JSON.stringify(o,null,2)),!0}catch(e){return console.error("[ALERT-MANAGER] File output failed"),!1}}async _outputToSlack(e){if(!this.config.slackWebhook)return!1;try{return console.log(`[ALERT-MANAGER] Would send to Slack: ${e.title}`),!0}catch(e){return console.error("[ALERT-MANAGER] Slack output failed"),!1}}async _createGitHubIssue(e){return console.log(`[ALERT-MANAGER] Would create GitHub issue: ${e.title}`),!1}}module.exports=AlertManager;
@@ -0,0 +1 @@
1
+ "use strict";const parser=require("@babel/parser"),path=require("path"),pythonAstAnalyzer=require("./python-ast-analyzer"),goAstAnalyzer=require("./go-ast-analyzer"),BABEL_PLUGINS=["estree","typescript","jsx","classProperties","decorators-legacy"];function walk(e,t){if(e&&"object"==typeof e&&"string"==typeof e.type){t(e);for(const n in e){if("loc"===n||"range"===n||"start"===n||"end"===n)continue;const s=e[n];if(Array.isArray(s))for(const e of s)e&&"object"==typeof e&&"string"==typeof e.type&&walk(e,t);else s&&"object"==typeof s&&"string"==typeof s.type&&walk(s,t)}}}function lineOf(e){return e&&e.loc?e.loc.start.line:0}function snippet(e,t){return!t||t<1||t>e.length?"":e[t-1].trim().substring(0,100)}function mkIssue(e,t,n,s){return{file:e,line:t,category:"security",type:s.type,severity:s.severity,id:s.id,name:s.name,message:s.message,code:snippet(n,t),source:"ast"}}const CMD_FUNCS=new Set(["exec","execSync","spawn","spawnSync","fork"]),SQL_METHODS=new Set(["query","execute","raw","where"]);function isNonLiteralDynamic(e){let t=!1;return walk(e,e=>{"Identifier"!==e.type&&"CallExpression"!==e.type&&"MemberExpression"!==e.type||(t=!0)}),t}function isDynamicStringBuild(e){return!!e&&("TemplateLiteral"===e.type?(e.expressions||[]).length>0:"BinaryExpression"===e.type&&"+"===e.operator&&isNonLiteralDynamic(e))}function collectEvalAliases(e){const t=new Set;let n=!0,s=0;for(;n&&s<5;)n=!1,s++,walk(e,e=>{if("VariableDeclarator"===e.type&&e.init&&"Identifier"===e.init.type&&"Identifier"===e.id.type){const s=e.init.name;"eval"!==s&&!t.has(s)||t.has(e.id.name)||(t.add(e.id.name),n=!0)}if("AssignmentExpression"===e.type&&"="===e.operator&&"Identifier"===e.left.type&&"Identifier"===e.right.type){const s=e.right.name;"eval"!==s&&!t.has(s)||t.has(e.left.name)||(t.add(e.left.name),n=!0)}});return t}function findProtoGuard(e){let t=!1;return walk(e,e=>{"Literal"===e.type&&"string"==typeof e.value&&["__proto__","constructor","prototype"].includes(e.value)&&(t=!0)}),t}function analyzeJSAst(e,t){const n=[],s=t.split("\n");let r;try{r=parser.parse(t,{sourceType:"unambiguous",plugins:BABEL_PLUGINS,allowReturnOutsideFunction:!0,allowImportExportEverywhere:!0,allowAwaitOutsideFunction:!0,allowSuperOutsideMethod:!0,allowUndeclaredExports:!0,errorRecovery:!1}).program}catch(e){return n}const i=collectEvalAliases(r);return walk(r,t=>{if("CallExpression"===t.type&&"Identifier"===t.callee.type){const r=t.callee.name;if("eval"===r||i.has(r)){const i="eval"!==r;n.push(mkIssue(e,lineOf(t),s,{type:"eval_abuse",severity:"error",id:"AST001",name:i?"Aliased eval() Usage (AST)":"eval() Usage (AST)",message:i?`eval() called indirectly through alias "${r}" - AST-detected`:"eval() is dangerous and should be avoided (AST-confirmed)"}))}}if("CallExpression"===t.type&&"MemberExpression"===t.callee.type&&"Identifier"===t.callee.property.type&&SQL_METHODS.has(t.callee.property.name)){const r=t.arguments[0];r&&isDynamicStringBuild(r)&&n.push(mkIssue(e,lineOf(t),s,{type:"sql_injection",severity:"error",id:"AST002",name:"SQL Injection Risk (AST)",message:`Dynamic string passed to .${t.callee.property.name}() - use parameterized queries`}))}if("AssignmentExpression"!==t.type||"MemberExpression"!==t.left.type||"Identifier"!==t.left.property.type||"innerHTML"!==t.left.property.name&&"outerHTML"!==t.left.property.name||"Literal"!==t.right.type&&n.push(mkIssue(e,lineOf(t),s,{type:"xss",severity:"warning",id:"AST003",name:`${t.left.property.name} Assignment (AST)`,message:`Non-literal value assigned to ${t.left.property.name} - possible XSS`})),"CallExpression"===t.type&&"MemberExpression"===t.callee.type&&"Identifier"===t.callee.object.type&&"document"===t.callee.object.name&&"Identifier"===t.callee.property.type&&"write"===t.callee.property.name){const r=t.arguments[0];r&&"Literal"!==r.type&&n.push(mkIssue(e,lineOf(t),s,{type:"xss",severity:"warning",id:"AST003",name:"document.write() Assignment (AST)",message:"Non-literal value passed to document.write() - possible XSS"}))}if("CallExpression"===t.type){let r=null;if("Identifier"===t.callee.type?r=t.callee.name:"MemberExpression"===t.callee.type&&"Identifier"===t.callee.property.type&&(r=t.callee.property.name),r&&CMD_FUNCS.has(r)){const i=t.arguments[0];i&&isDynamicStringBuild(i)&&n.push(mkIssue(e,lineOf(t),s,{type:"command_injection",severity:"error",id:"AST004",name:`${r}() Command Injection (AST)`,message:`Dynamic string passed to ${r}() - command injection risk`}))}}if("ForInStatement"===t.type||"ForOfStatement"===t.type){let r=null;if(("ForInStatement"===t.type||"CallExpression"===t.right.type&&"MemberExpression"===t.right.callee.type&&"Identifier"===t.right.callee.object.type&&"Object"===t.right.callee.object.name&&"Identifier"===t.right.callee.property.type&&"keys"===t.right.callee.property.name)&&("VariableDeclaration"===t.left.type?r=t.left.declarations[0].id.name:"Identifier"===t.left.type&&(r=t.left.name)),r&&!findProtoGuard(t.body)){let i=!1;walk(t.body,e=>{"AssignmentExpression"===e.type&&"MemberExpression"===e.left.type&&e.left.computed&&"Identifier"===e.left.property.type&&e.left.property.name===r&&(i=!0)}),i&&n.push(mkIssue(e,lineOf(t),s,{type:"prototype_pollution",severity:"error",id:"AST005",name:"Unguarded Object Merge (AST)",message:`Loop assigns to obj[${r}] without checking for __proto__/constructor/prototype - prototype pollution risk`}))}}}),n}function joinLogicalStatements(e){const t=e.split("\n"),n=[];let s="",r=0,i=0;for(let e=0;e<t.length;e++){const a=t[e];s||(r=e+1),s+=(s?"\n":"")+a;for(const e of a)"("===e||"["===e||"{"===e?i++:")"!==e&&"]"!==e&&"}"!==e||(i=Math.max(0,i-1));const o=/\\\s*$/.test(a);0!==i||o||(n.push({text:s,startLine:r}),s="")}return s&&n.push({text:s,startLine:r}),n}function extractBalanced(e,t){let n=0;for(let s=t;s<e.length;s++)if("("===e[s])n++;else if(")"===e[s]&&(n--,0===n))return e.slice(t+1,s);return e.slice(t+1)}function splitTopLevelArgs(e){const t=[];let n=0,s="",r=null;for(let i=0;i<e.length;i++){const a=e[i];r?(s+=a,a===r&&"\\"!==e[i-1]&&(r=null)):'"'!==a&&"'"!==a?("([{".includes(a)&&n++,")]}".includes(a)&&n--,","!==a||0!==n?s+=a:(t.push(s.trim()),s="")):(r=a,s+=a)}return s.trim()&&t.push(s.trim()),t}function isDynamicPyString(e){if(!e)return!1;const t=e.trim();return!(!/^[rR]?[fF]['"]/.test(t)||!t.includes("{"))||!!/['"]\s*%\s*\(?[\w.]/.test(t)||!!/['"]\s*\+\s*\w|\w\s*\+\s*['"]/.test(t)}function analyzePythonHeuristic(e,t){const n=[],s=t.split("\n"),r=joinLogicalStatements(t),i=new Set;for(const e of r){const t=e.text.match(/^\s*([A-Za-z_]\w*)\s*=\s*(eval|exec)\s*$/m);t&&i.add(t[1])}for(const t of r){const r=t.text,a=t.startLine;for(const t of i)new RegExp("\\b"+t+"\\s*\\(").test(r)&&n.push(mkIssue(e,a,s,{type:"eval_abuse",severity:"error",id:"AST011",name:"Aliased eval/exec Usage (AST)",message:`eval/exec called indirectly through alias "${t}" - AST-detected`}));const o=r.search(/\bos\.system\s*\(/);if(-1!==o){const t=r.indexOf("(",o);isDynamicPyString(extractBalanced(r,t))&&n.push(mkIssue(e,a,s,{type:"command_injection",severity:"error",id:"AST012",name:"os.system() Command Injection (AST)",message:"Dynamic string passed to os.system() - command injection risk"}))}const l=r.match(/\bsubprocess\.(call|run|Popen|check_output)\s*\(/);if(l){const t=r.indexOf("(",l.index),i=extractBalanced(r,t);if(/shell\s*=\s*True/.test(i)){const t=splitTopLevelArgs(i)[0]||"";isDynamicPyString(t)&&!t.trim().startsWith("[")&&n.push(mkIssue(e,a,s,{type:"command_injection",severity:"error",id:"AST013",name:"subprocess shell=True Command Injection (AST)",message:`Dynamic string passed to subprocess.${l[1]}() with shell=True`}))}}const c=r.match(/\b(?:cursor|db|conn|connection)\.execute\s*\(/);if(c){const t=r.indexOf("(",c.index);isDynamicPyString(splitTopLevelArgs(extractBalanced(r,t))[0]||"")&&n.push(mkIssue(e,a,s,{type:"sql_injection",severity:"error",id:"AST014",name:"SQL Injection Risk (AST)",message:"Dynamic string passed to .execute() - use parameterized queries"}))}}return n}const JS_EXTS=new Set([".js",".jsx",".ts",".tsx",".mjs",".cjs"]),PY_EXTS=new Set([".py",".pyw"]),GO_EXTS=new Set([".go"]),MAX_AST_FILE_SIZE=2e4;function analyzePython(e,t){const n=analyzePythonHeuristic(e,t);let s=[];try{s=pythonAstAnalyzer.analyze(e,t)}catch(e){s=[]}return n.concat(s)}function analyze(e,t){if(t.length>2e4)return[];const n=path.extname(e).toLowerCase();try{if(JS_EXTS.has(n))return analyzeJSAst(e,t);if(PY_EXTS.has(n))return analyzePython(e,t);if(GO_EXTS.has(n))return analyzeGo(e,t)}catch(e){return[]}return[]}function analyzeGo(e,t){try{return goAstAnalyzer.analyze(e,t)}catch(e){return[]}}module.exports={analyze:analyze,analyzeJSAst:analyzeJSAst,analyzePythonHeuristic:analyzePythonHeuristic,analyzePython:analyzePython,analyzeGo:analyzeGo,_internal:{walk:walk,isDynamicStringBuild:isDynamicStringBuild,collectEvalAliases:collectEvalAliases,joinLogicalStatements:joinLogicalStatements,extractBalanced:extractBalanced,splitTopLevelArgs:splitTopLevelArgs,isDynamicPyString:isDynamicPyString}};
@@ -1 +1 @@
1
- const fs=require("fs"),path=require("path"),crypto=require("crypto"),BASELINE_FILE=".thuban-baseline.json";class BaselineManager{constructor(e){this.rootPath=e,this.baselinePath=path.join(e,BASELINE_FILE)}create(e){const s={version:1,created:(new Date).toISOString(),generator:"thuban",issueCount:e.length,issues:e.map(e=>({fingerprint:this._fingerprint(e),file:e.file,category:e.category||"unknown",severity:e.severity||"unknown",id:e.id||null,message:(e.message||"").substring(0,200)}))};return fs.writeFileSync(this.baselinePath,JSON.stringify(s,null,2),"utf-8"),{path:this.baselinePath,count:e.length}}load(){try{return JSON.parse(fs.readFileSync(this.baselinePath,"utf-8"))}catch(e){return null}}filterNew(e){const s=this.load();if(!s||!s.issues)return{newIssues:e,baselinedIssues:[],baselineExists:!1};const t=new Set(s.issues.map(e=>e.fingerprint)),n=[],i=[];for(const s of e){const e=this._fingerprint(s);t.has(e)?i.push(s):n.push(s)}return{newIssues:n,baselinedIssues:i,baselineExists:!0,baselineDate:s.created,baselineCount:s.issueCount}}exists(){return fs.existsSync(this.baselinePath)}_fingerprint(e){const s=[e.file||"",e.category||"",e.id||"",(e.message||"").substring(0,100)].join("|");return crypto.createHash("sha256").update(s).digest("hex").substring(0,16)}}module.exports=BaselineManager;
1
+ const fs=require("fs"),path=require("path"),crypto=require("crypto"),BASELINE_FILE=".thuban-baseline.json";class BaselineManager{constructor(e){this.rootPath=path.resolve(e),this.baselinePath=path.join(this.rootPath,BASELINE_FILE);const n=path.resolve(this.baselinePath);if(!n.startsWith(this.rootPath+path.sep)&&n!==this.rootPath)throw new Error(`[BaselineManager] Baseline path escapes root directory: ${n}`)}_atomicWriteSync(e,n){const s=e+".tmp."+process.pid+"."+Date.now();fs.writeFileSync(s,n,"utf-8"),fs.renameSync(s,e)}create(e){const n={version:1,created:(new Date).toISOString(),generator:"thuban",issueCount:e.length,issues:e.map(e=>({fingerprint:this._fingerprint(e),file:e.file,category:e.category||"unknown",severity:e.severity||"unknown",id:e.id||null,message:(e.message||"").substring(0,200)}))};return n._hmac=this._computeHmac(n),this._atomicWriteSync(this.baselinePath,JSON.stringify(n,null,2)),{path:this.baselinePath,count:e.length}}load(){try{const e=fs.readFileSync(this.baselinePath,"utf-8");if(e.length>10485760)return console.warn("[BASELINE] WARNING: Baseline file exceeds 10MB — ignoring"),null;const n=JSON.parse(e);if(null==n||"object"!=typeof n||Array.isArray(n))return console.warn("[BASELINE] WARNING: Baseline file has invalid root type — ignoring"),null;if("__proto__"in n||"constructor"in n||"prototype"in n)return console.warn("[BASELINE] WARNING: Baseline file contains prototype pollution keys — ignoring"),null;if(1!==n.version||"thuban"!==n.generator)return console.warn("[BASELINE] WARNING: Baseline file has invalid version/generator — ignoring"),null;if(!Array.isArray(n.issues))return console.warn("[BASELINE] WARNING: Baseline file has non-array issues field — ignoring"),null;if("number"!=typeof n.issueCount||n.issueCount!==n.issues.length)return console.warn("[BASELINE] WARNING: Baseline issueCount mismatch — ignoring"),null;for(const e of n.issues){if(null==e||"object"!=typeof e||Array.isArray(e))return console.warn("[BASELINE] WARNING: Baseline contains invalid issue entry — ignoring"),null;if("__proto__"in e||"constructor"in e)return console.warn("[BASELINE] WARNING: Baseline issue contains prototype pollution keys — ignoring"),null;if("string"!=typeof e.fingerprint||!/^[a-f0-9]{16}$/.test(e.fingerprint))return console.warn("[BASELINE] WARNING: Baseline issue has invalid fingerprint — ignoring"),null}if(n._hmac){const e=this._computeHmac(n);if(n._hmac!==e)return console.warn("[BASELINE] WARNING: Baseline HMAC mismatch — file may have been tampered with — ignoring"),null}else console.warn("[BASELINE] NOTE: Baseline file has no integrity tag — re-run `thuban baseline` to add one");return n}catch(e){return null}}filterNew(e){const n=this.load();if(!n||!n.issues)return{newIssues:e,baselinedIssues:[],baselineExists:!1};const s=new Set(n.issues.map(e=>e.fingerprint)),t=[],i=[];for(const n of e){const e=this._fingerprint(n);s.has(e)?i.push(n):t.push(n)}return{newIssues:t,baselinedIssues:i,baselineExists:!0,baselineDate:n.created,baselineCount:n.issueCount}}exists(){return fs.existsSync(this.baselinePath)}_fingerprint(e){const n=[e.file||"",e.category||"",e.id||"",(e.message||"").substring(0,100)].join("|");return crypto.createHash("sha256").update(n).digest("hex").substring(0,16)}_computeHmac(e){const n=JSON.stringify({version:e.version,created:e.created,generator:e.generator,issueCount:e.issueCount,issues:e.issues});return crypto.createHmac("sha256",this.baselinePath).update(n).digest("hex")}}module.exports=BaselineManager;
@@ -0,0 +1 @@
1
+ "use strict";const parser=require("@babel/parser"),path=require("path"),{walk:walk}=require("./ast-analyzer")._internal,BABEL_PLUGINS=["estree","typescript","jsx","classProperties","decorators-legacy"],MAX_CFG_FILE_SIZE=2e4;function lineOf(e){return e&&e.loc?e.loc.start.line:0}function snippet(e,t){return!t||t<1||t>e.length?"":e[t-1].trim().substring(0,100)}function mkIssue(e,t,n,a){return{file:e,line:t,category:"quality",type:a.type,severity:a.severity,id:a.id,name:a.name,message:a.message,code:snippet(n,t),source:"cfg"}}function isFunctionNode(e){return"FunctionDeclaration"===e.type||"FunctionExpression"===e.type||"ArrowFunctionExpression"===e.type}const LOOP_TYPES=new Set(["WhileStatement","ForStatement","DoWhileStatement","ForInStatement","ForOfStatement"]);function staticTruthiness(e){if(!e)return null;if("Literal"===e.type)return null!==e.value&&("boolean"==typeof e.value?e.value:"number"==typeof e.value?0!==e.value&&!Number.isNaN(e.value):"string"==typeof e.value?e.value.length>0:null);if("UnaryExpression"===e.type&&"!"===e.operator){const t=staticTruthiness(e.argument);return null===t?null:!t}return null}function loopHasExit(e){let t=!1;return function e(n,a){if(t||!n||"object"!=typeof n||"string"!=typeof n.type)return;if(isFunctionNode(n))return;if("ReturnStatement"===n.type||"ThrowStatement"===n.type)return void(t=!0);if("BreakStatement"===n.type||"ContinueStatement"===n.type)return void((n.label||0===a)&&(t=!0));const r=LOOP_TYPES.has(n.type)||"SwitchStatement"===n.type?a+1:a;for(const t in n){if("loc"===t||"range"===t||"start"===t||"end"===t)continue;const a=n[t];if(Array.isArray(a))for(const t of a)t&&"object"==typeof t&&"string"==typeof t.type&&e(t,r);else a&&"object"==typeof a&&"string"==typeof a.type&&e(a,r)}}(e,0),t}function isStaticallyInfinite(e){return"ForStatement"===e.type?!e.test:("WhileStatement"===e.type||"DoWhileStatement"===e.type)&&!0===staticTruthiness(e.test)}function blockTerminates(e){let t=!1;for(const n of e){if(t)break;terminates(n)&&(t=!0)}return t}function terminates(e){switch(e.type){case"ReturnStatement":case"ThrowStatement":case"BreakStatement":case"ContinueStatement":return!0;case"BlockStatement":return blockTerminates(e.body);case"IfStatement":return!!e.alternate&&terminates(e.consequent)&&terminates(e.alternate);case"TryStatement":return!(!e.finalizer||!blockTerminates(e.finalizer.body))||!!e.handler&&blockTerminates(e.block.body)&&blockTerminates(e.handler.body.body);case"WhileStatement":case"ForStatement":case"DoWhileStatement":return isStaticallyInfinite(e)&&!loopHasExit(e.body);case"LabeledStatement":return terminates(e.body);default:return!1}}function isNoiseStatement(e){return"FunctionDeclaration"===e.type||"EmptyStatement"===e.type}function scanBlockBody(e,t,n,a){let r=!1;for(const i of e){if(r){isNoiseStatement(i)||a.push(mkIssue(t,lineOf(i),n,{type:"unreachable_code",severity:"warning",id:"CFG001",name:"Unreachable Code (CFG)",message:"This code can never execute - it follows a return/throw/break/continue (or an if/else where both branches always exit)"}));break}scanStatement(i,t,n,a),terminates(i)&&(r=!0)}}function scanStatement(e,t,n,a){switch(e.type){case"BlockStatement":scanBlockBody(e.body,t,n,a);break;case"IfStatement":scanStatement(e.consequent,t,n,a),e.alternate&&scanStatement(e.alternate,t,n,a);break;case"WhileStatement":case"DoWhileStatement":case"ForStatement":case"ForInStatement":case"ForOfStatement":case"LabeledStatement":scanStatement(e.body,t,n,a);break;case"SwitchStatement":for(const r of e.cases)scanBlockBody(r.consequent,t,n,a);break;case"TryStatement":scanBlockBody(e.block.body,t,n,a),e.handler&&scanBlockBody(e.handler.body.body,t,n,a),e.finalizer&&scanBlockBody(e.finalizer.body,t,n,a)}}function analyzeReachability(e,t,n,a){scanBlockBody(e.body,t,n,a),walk(e,e=>{isFunctionNode(e)&&e.body&&"BlockStatement"===e.body.type&&scanBlockBody(e.body.body,t,n,a)})}function analyzeLoopsAndBranches(e,t,n,a){walk(e,e=>{if(!LOOP_TYPES.has(e.type)||"WhileStatement"!==e.type&&"ForStatement"!==e.type&&"DoWhileStatement"!==e.type||isStaticallyInfinite(e)&&!loopHasExit(e.body)&&a.push(mkIssue(t,lineOf(e),n,{type:"infinite_loop",severity:"warning",id:"CFG002",name:"Infinite Loop With No Exit (CFG)",message:"Loop condition is always true and no break/return/throw was found anywhere inside it - this will hang"})),"IfStatement"===e.type){const r=staticTruthiness(e.test);null!==r&&a.push(mkIssue(t,lineOf(e),n,{type:"dead_branch",severity:"warning",id:"CFG003",name:"Dead Branch (CFG)",message:r?"Condition is always truthy - the else branch (if any) can never execute":e.alternate?"Condition is always falsy - the if branch can never execute, only else runs":"Condition is always falsy - this if branch can never execute"}))}if("ConditionalExpression"===e.type){const r=staticTruthiness(e.test);null!==r&&a.push(mkIssue(t,lineOf(e),n,{type:"dead_branch",severity:"warning",id:"CFG003",name:"Dead Branch (CFG)",message:r?"Ternary condition is always truthy - the alternate expression can never be reached":"Ternary condition is always falsy - the consequent expression can never be reached"}))}"WhileStatement"!==e.type&&"ForStatement"!==e.type||!e.test||!1===staticTruthiness(e.test)&&a.push(mkIssue(t,lineOf(e),n,{type:"dead_loop",severity:"warning",id:"CFG004",name:"Dead Loop (CFG)",message:"Loop condition is always false - the loop body will never execute, not even once"}))})}const FS_SYNC_THROWING=new Set(["readFileSync","writeFileSync","appendFileSync","readdirSync","statSync","lstatSync","unlinkSync","mkdirSync","rmdirSync","rmSync","renameSync","copyFileSync","accessSync","chmodSync","chownSync","symlinkSync","readlinkSync","realpathSync","openSync","closeSync","readSync","writeSync","truncateSync","utimesSync","mkdtempSync"]);function isKnownThrowingCall(e){if("CallExpression"!==e.type||"MemberExpression"!==e.callee.type)return null;const{object:t,property:n}=e.callee;return"Identifier"!==n.type?null:"Identifier"===t.type&&"JSON"===t.name&&"parse"===n.name?"JSON.parse":"Identifier"===t.type&&"fs"===t.name&&FS_SYNC_THROWING.has(n.name)?`fs.${n.name}`:null}function analyzeExceptionGaps(e,t,n,a){!function e(r,i){if(!r||"object"!=typeof r||"string"!=typeof r.type)return;if("TryStatement"===r.type)return e(r.block,!!r.handler),r.handler&&e(r.handler.body,i),void(r.finalizer&&e(r.finalizer,i));if(isFunctionNode(r))return void e(r.body,!1);const o=isKnownThrowingCall(r);o&&!i&&a.push(mkIssue(t,lineOf(r),n,{type:"exception_handling_gap",severity:"warning",id:"CFG005",name:"Missing Error Handling (CFG)",message:`${o}() can throw and is not wrapped in a try/catch`}));for(const t in r){if("loc"===t||"range"===t||"start"===t||"end"===t)continue;const n=r[t];if(Array.isArray(n))for(const t of n)t&&"object"==typeof t&&"string"==typeof t.type&&e(t,i);else n&&"object"==typeof n&&"string"==typeof n.type&&e(n,i)}}(e,!1)}const JS_EXTS=new Set([".js",".jsx",".ts",".tsx",".mjs",".cjs"]);function analyzeJSCfg(e,t){const n=[],a=t.split("\n");let r;try{r=parser.parse(t,{sourceType:"unambiguous",plugins:BABEL_PLUGINS,allowReturnOutsideFunction:!0,allowImportExportEverywhere:!0,allowAwaitOutsideFunction:!0,allowSuperOutsideMethod:!0,allowUndeclaredExports:!0,errorRecovery:!1}).program}catch(e){return n}return analyzeReachability(r,e,a,n),analyzeLoopsAndBranches(r,e,a,n),analyzeExceptionGaps(r,e,a,n),n}function analyze(e,t){if(t.length>2e4)return[];const n=path.extname(e).toLowerCase();try{if(JS_EXTS.has(n))return analyzeJSCfg(e,t)}catch(e){return[]}return[]}module.exports={analyze:analyze,analyzeJSCfg:analyzeJSCfg,_internal:{staticTruthiness:staticTruthiness,loopHasExit:loopHasExit,terminates:terminates,blockTerminates:blockTerminates,isKnownThrowingCall:isKnownThrowingCall}};
@@ -1 +1 @@
1
- const fs=require("fs"),path=require("path"),EventEmitter=require("events");class CodeScanner extends EventEmitter{constructor(e={}){super(),this.config={rootPath:e.rootPath||process.cwd(),ignorePatterns:e.ignorePatterns||["node_modules/**",".git/**","dist/**",".orion/snapshots/**"],maxFileSize:e.maxFileSize||1048576,complexityThreshold:e.complexityThreshold||15,nestingThreshold:e.nestingThreshold||4,...e},this.safePatterns=[/keyEnv\s*[:=]/i,/envVar\s*[:=]/i,/process\.env\[/,/process\.env\./,/model\s*[:=]\s*['"][^'"]+['"]/i,/version\s*[:=]\s*['"][^'"]+['"]/i,/claude-|gpt-|gemini-|llama-|mistral-|deepseek-|grok-|sonar-/i,/endpoint\s*[:=]/i,/https?:\/\//,/_API_KEY['"]/,/API_KEY['"]\s*\)/,/errors?\.\w+\s*=/i,/Error\s*[:=]/i,/required|invalid|must be|cannot be/i],this.testFilePatterns=[/\.test\.(js|ts|jsx|tsx)$/,/\.spec\.(js|ts|jsx|tsx)$/,/__tests__\//,/\.mock\.(js|ts)$/,/test-.*\.js$/],this.securityPatterns=[{id:"SEC001",name:"Hardcoded API Key",pattern:/['"](?:sk-|pk-|xox[pboa]-|AKIA|ghp_|gho_|github_pat_)[a-zA-Z0-9_-]{10,}['"]|['"][a-zA-Z0-9]{32,}['"]/,context:/(api[_-]?key|apikey|secret[_\s]*key|token|credential|bearer)/i,severity:"critical",message:"Possible hardcoded API key or secret detected",skipIfSafe:!0},{id:"SEC002",name:"Hardcoded Password",pattern:/password\s*[:=]\s*['"][^'"]+['"]/i,severity:"critical",message:"Hardcoded password detected",skipInTests:!0,skipIfSafe:!0},{id:"SEC003",name:"SQL Injection Risk",pattern:/\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM|WHERE)\b.*(\$\{|\+\s*['"]|\+\s*\w+|f['"].*\{|%s|%d)/i,severity:"error",message:"Potential SQL injection - use parameterized queries"},{id:"SEC004",name:"eval() Usage",pattern:/\beval\s*\(/,severity:"error",message:"eval() is dangerous and should be avoided"},{id:"SEC005",name:"innerHTML Assignment",pattern:/\.innerHTML\s*=/,severity:"warning",message:"innerHTML can cause XSS - consider using textContent or sanitization"},{id:"SEC006",name:"Disabled Security",pattern:/rejectUnauthorized\s*:\s*false|verify\s*=\s*False|InsecureSkipVerify\s*:\s*true/,severity:"error",message:"SSL certificate validation disabled"},{id:"SEC007",name:"Exposed .env Reference",pattern:/\.env['"]/,context:/fs\.(read|write)|path\.join|open\s*\(|os\.Open/i,severity:"warning",message:"Direct .env file manipulation - ensure not exposed"},{id:"SEC008",name:"Unsafe Block (Rust)",pattern:/\bunsafe\s*\{/,severity:"warning",message:"Rust unsafe block - verify memory safety guarantees"},{id:"SEC009",name:"Shell Execution (PHP/Ruby)",pattern:/\bshell_exec\s*\(|\bpassthru\s*\(|`[^`]+`/,severity:"error",message:"Shell execution function - command injection risk if input unsanitized"}],this.qualityPatterns=[{id:"QUAL001",name:"Console.log in Production",pattern:/console\.(log|debug|info)\(/,severity:"info",message:"console.log found - consider using proper logging",skipInTests:!0,skipInDirs:["test","scripts","tools","dev"]},{id:"QUAL002",name:"TODO Comment",pattern:/\/\/\s*TODO|\/\*\s*TODO|\*\s*TODO|#\s*TODO/i,severity:"info",message:"TODO comment found - track in issue system"},{id:"QUAL003",name:"FIXME Comment",pattern:/\/\/\s*FIXME|\/\*\s*FIXME|\*\s*FIXME|#\s*FIXME/i,severity:"warning",message:"FIXME comment found - needs attention"},{id:"QUAL004",name:"Empty Catch Block",pattern:/catch\s*\([^)]*\)\s*\{\s*\}/,severity:"warning",message:"Empty catch block swallows errors"},{id:"QUAL005",name:"Magic Number",pattern:/(?<![0-9a-zA-Z_])(?!(?:1000|2000|3000|3001|4000|5000|8000|8080|9000|80|443|24|60|1024|2048|4096)\b)\d{5,}(?![0-9])/,severity:"info",message:"Magic number detected - consider using named constant",skipInTests:!0},{id:"QUAL006",name:"Callback Hell",pattern:/\)\s*=>\s*\{[^}]*\)\s*=>\s*\{[^}]*\)\s*=>\s*\{/,severity:"warning",message:"Deeply nested callbacks - consider async/await"}],this.performancePatterns=[{id:"PERF001",name:"Sync FS in Async",pattern:/fs\.(readFileSync|writeFileSync|readdirSync|statSync)/,severity:"warning",message:"Synchronous file operation - may block event loop"},{id:"PERF002",name:"Missing Await",pattern:/async\s+function[^{]*\{[^}]*(?<!await\s)new\s+Promise/,severity:"warning",message:"Promise created in async function without await"},{id:"PERF003",name:"Loop Database Query",pattern:/for\s*\([^)]*\)\s*\{[^}]*(query|find|select|update)/i,severity:"error",message:"Possible N+1 query pattern - consider batch operation"},{id:"PERF004",name:"Large Array in Memory",pattern:/new\s+Array\s*\(\s*\d{6,}\s*\)/,severity:"warning",message:"Large array allocation - consider streaming"}],this.aiPatterns=[{id:"AI001",name:"Potentially Hallucinated Import",pattern:/require\(['"](?!\.)[^'"]+['"]\)/,severity:"info",message:"External dependency - verify it exists in package.json",check:(e,s,t)=>this._checkDependencyExists(e,t)},{id:"AI002",name:"Unused Import",pattern:/const\s+(\w+)\s*=\s*require\(['"][^'"]+['"]\)/,severity:"info",message:"Import may be unused - verify usage",check:(e,s)=>this._checkImportUsage(e,s)},{id:"AI003",name:"Over-Engineered Pattern",pattern:/class\s+\w+Factory\s*\{|AbstractFactory|Singleton\.getInstance/,severity:"info",message:"Complex pattern detected - ensure justified by requirements"}]}async scanAll(){console.log("[CODE-SCANNER] Starting full codebase scan...");const e=Date.now(),s={filesScanned:0,issues:[],summary:{},duration:0};try{const t=await this._discoverFiles(this.config.rootPath);console.log(`[CODE-SCANNER] Found ${t.length} files to scan`);for(const e of t){const t=await this.scanFile(e);s.filesScanned++,s.issues.push(...t)}return s.duration=Date.now()-e,s.summary=this._summarizeIssues(s.issues),console.log(`[CODE-SCANNER] Scan complete: ${s.filesScanned} files, ${s.issues.length} issues`),s}catch(e){return console.error("[CODE-SCANNER] Scan failed:",e.message),s.error=e.message,s}}async scanFiles(e){const s={filesScanned:0,issues:[],summary:{}};for(const t of e){const e=await this.scanFile(t);s.filesScanned++,s.issues.push(...e)}return s.summary=this._summarizeIssues(s.issues),s}async scanFile(e){const s=[];try{const t=fs.statSync(e);if(t.isSymbolicLink&&t.isSymbolicLink())return[];if(0===t.size)return[];if(t.size>this.config.maxFileSize)return[{file:e,category:"quality",severity:"info",id:"FILE001",message:`File too large (${Math.round(t.size/1024)}KB) - skipped detailed scan`}];let i;try{i=fs.readFileSync(e)}catch(e){return[]}const n=Math.min(i.length,512);for(let e=0;e<n;e++)if(0===i[e])return[];const r=i.toString("utf8"),a=r.split("\n"),o=path.extname(e).toLowerCase();[".js",".ts",".jsx",".tsx",".mjs",".cjs",".py",".pyw",".go",".rs",".java",".kt",".cs",".php",".rb"].includes(o)&&(s.push(...this._runPatternScan(e,r,a,this.securityPatterns,"security")),s.push(...this._runPatternScan(e,r,a,this.qualityPatterns,"quality")),s.push(...this._runPatternScan(e,r,a,this.performancePatterns,"performance")),s.push(...this._runPatternScan(e,r,a,this.aiPatterns,"ai")),s.push(...this._analyzeComplexity(e,r,a)),s.push(...this._analyzeNesting(e,r,a))),".json"===o&&s.push(...this._scanJson(e,r));for(const e of s)this.emit("issueFound",e);return s}catch(s){return[{file:e,category:"error",severity:"error",id:"SCAN001",message:`Failed to scan file: ${s.message}`}]}}getRules(){return{security:this.securityPatterns.map(e=>({id:e.id,name:e.name,severity:e.severity})),quality:this.qualityPatterns.map(e=>({id:e.id,name:e.name,severity:e.severity})),performance:this.performancePatterns.map(e=>({id:e.id,name:e.name,severity:e.severity})),ai:this.aiPatterns.map(e=>({id:e.id,name:e.name,severity:e.severity}))}}async _discoverFiles(e,s=[]){try{const t=fs.readdirSync(e,{withFileTypes:!0});for(const i of t){const t=path.join(e,i.name),n=path.relative(this.config.rootPath,t);if(!this._shouldIgnore(n))if(i.isDirectory())await this._discoverFiles(t,s);else if(i.isFile()){const e=path.extname(i.name).toLowerCase();[".js",".ts",".jsx",".tsx",".json",".py",".pyw",".go",".rs",".java",".kt",".cs",".php",".rb"].includes(e)&&s.push(t)}}}catch(e){}return s}_shouldIgnore(e){const s=e.replace(/\\/g,"/"),t=["node_modules",".git","dist",".vite"];for(const e of t)if(s.includes("/"+e+"/")||s.startsWith(e+"/")||s===e||s.endsWith("/"+e))return!0;for(const e of this.config.ignorePatterns){const t=e.replace(/\\/g,"/"),i=t.split("/")[0].replace(/\*/g,"");if(i&&(s===i||s.startsWith(i+"/")))return!0;let n=t.replace(/\./g,"\\.").replace(/\*\*/g,".*").replace(/\*/g,"[^/]*");if(new RegExp("^"+n).test(s))return!0}return!1}_runPatternScan(e,s,t,i,n){const r=[],a=path.basename(e),o=this.testFilePatterns.some(e=>e.test(a))||this.testFilePatterns.some(s=>s.test(e)&&!s.source.includes("\\.js$")),c=path.relative(this.config.rootPath,e).replace(/\\/g,"/");for(const a of i){if(a.skipInTests&&o)continue;if(a.skipInDirs&&a.skipInDirs.some(e=>c.startsWith(e+"/")||c.includes("/"+e+"/")))continue;if(a.context&&!a.lineContext&&!a.context.test(s))continue;let i=0;for(const o of t){i++;const t=o.match(a.pattern);if(t){if(a.skipIfSafe&&this.safePatterns.some(e=>e.test(o)))continue;if(a.check&&!a.check(t,s,e))continue;r.push({file:e,line:i,column:t.index,category:n,severity:a.severity,id:a.id,name:a.name,message:a.message,code:o.trim().substring(0,100)})}}}return r}_analyzeComplexity(e,s,t){const i=[],n=s.match(/function\s+\w+|=>\s*\{|async\s+\w+/g)||[],r=s.match(/\b(if|else|for|while|switch|case|catch|&&|\|\||\?)\b/g)||[],a=n.length>0?r.length/n.length:0;return a>this.config.complexityThreshold&&i.push({file:e,category:"quality",severity:"warning",id:"CMPLX001",name:"High Complexity",message:`Average cyclomatic complexity is ${a.toFixed(1)} (threshold: ${this.config.complexityThreshold})`}),t.length>500&&i.push({file:e,category:"quality",severity:"info",id:"CMPLX002",name:"Long File",message:`File has ${t.length} lines - consider splitting`}),i}_analyzeNesting(e,s,t){const i=[];let n=0,r=0,a=0,o=0;for(const e of t)o++,r+=(e.match(/\{/g)||[]).length-(e.match(/\}/g)||[]).length,r>n&&(n=r,a=o);return n>this.config.nestingThreshold&&i.push({file:e,line:a,category:"quality",severity:"warning",id:"NEST001",name:"Deep Nesting",message:`Max nesting depth is ${n} at line ${a} (threshold: ${this.config.nestingThreshold})`}),i}_scanJson(e,s){const t=[];try{JSON.parse(s)}catch(s){t.push({file:e,category:"quality",severity:"error",id:"JSON001",name:"Invalid JSON",message:`JSON parse error: ${s.message}`})}return/(password|secret|api[_-]?key|token)/i.test(s)&&t.push({file:e,category:"security",severity:"warning",id:"JSON002",name:"Sensitive Data in JSON",message:"Possible sensitive data in JSON file - verify not committed"}),t}_checkDependencyExists(e,s){const t=e[0].match(/require\(['"]([^'"]+)['"]\)/);if(!t)return!0;const i=t[1];if(i.startsWith("."))return!1;if(["fs","path","os","crypto","http","https","events","util","stream","child_process","url","querystring","buffer"].includes(i.split("/")[0]))return!1;try{let e=path.join(this.config.rootPath,"package.json");if(s.includes("server")){const s=path.join(this.config.rootPath,"server","package.json");fs.existsSync(s)&&(e=s)}const t=JSON.parse(fs.readFileSync(e,"utf8")),n={...t.dependencies||{},...t.devDependencies||{}};return!n[i.split("/")[0]]&&!n[i]}catch(e){return!1}}_checkImportUsage(e,s){const t=e[1];if(!t)return!1;const i=new RegExp("\\b"+t+"\\b","g");return(s.match(i)||[]).length<=1}_summarizeIssues(e){return{total:e.length,bySeverity:{critical:e.filter(e=>"critical"===e.severity).length,error:e.filter(e=>"error"===e.severity).length,warning:e.filter(e=>"warning"===e.severity).length,info:e.filter(e=>"info"===e.severity).length},byCategory:e.reduce((e,s)=>(e[s.category]=(e[s.category]||0)+1,e),{}),topFiles:this._getTopFiles(e,10)}}_getTopFiles(e,s){const t=e.reduce((e,s)=>(e[s.file]=(e[s.file]||0)+1,e),{});return Object.entries(t).sort((e,s)=>s[1]-e[1]).slice(0,s).map(([e,s])=>({file:path.relative(this.config.rootPath,e),count:s}))}}module.exports=CodeScanner;
1
+ const fs=require("fs"),path=require("path"),EventEmitter=require("events"),astAnalyzer=require("./ast-analyzer"),taintTracker=require("./taint-tracker"),cfgAnalyzer=require("./cfg-analyzer"),{getCodeScannerPatterns:getCodeScannerPatterns}=require("../crucible/rule-loader.js");class CodeScanner extends EventEmitter{constructor(e={}){super();const t=new Set(["rootPath","ignorePatterns","maxFileSize","totalScanTimeout","perFileTimeout","complexityThreshold","nestingThreshold"]),s={};for(const n of Object.keys(e))t.has(n)&&(s[n]=e[n]);this.config={rootPath:s.rootPath||process.cwd(),ignorePatterns:s.ignorePatterns||["node_modules/**",".git/**","dist/**",".orion/snapshots/**"],maxFileSize:s.maxFileSize||5242880,totalScanTimeout:s.totalScanTimeout||3e5,perFileTimeout:s.perFileTimeout||1e4,complexityThreshold:s.complexityThreshold||15,nestingThreshold:s.nestingThreshold||4},this._scanStartTime=null,this.safePatterns=[/keyEnv\s*[:=]/i,/envVar\s*[:=]/i,/process\.env\[/,/process\.env\./,/model\s*[:=]\s*['"][^'"]+['"]/i,/version\s*[:=]\s*['"][^'"]+['"]/i,/claude-|gpt-|gemini-|llama-|mistral-|deepseek-|grok-|sonar-/i,/endpoint\s*[:=]/i,/https?:\/\//,/_API_KEY['"]/,/API_KEY['"]\s*\)/,/errors?\.\w+\s*=/i,/Error\s*[:=]/i,/required|invalid|must be|cannot be/i],this.testFilePatterns=[/\.test\.(js|ts|jsx|tsx)$/,/\.spec\.(js|ts|jsx|tsx)$/,/__tests__\//,/\.mock\.(js|ts)$/,/test-.*\.js$/],this.securityPatterns=getCodeScannerPatterns(),this.qualityPatterns=[{id:"QUAL001",name:"Console.log in Production",pattern:/console\.(log|debug|info)\(/,severity:"info",message:"console.log found - consider using proper logging",skipInTests:!0,skipInDirs:["test","scripts","tools","dev"]},{id:"QUAL002",name:"TODO Comment",pattern:/\/\/\s*TODO|\/\*\s*TODO|\*\s*TODO|#\s*TODO/i,severity:"info",message:"TODO comment found - track in issue system"},{id:"QUAL003",name:"FIXME Comment",pattern:/\/\/\s*FIXME|\/\*\s*FIXME|\*\s*FIXME|#\s*FIXME/i,severity:"warning",message:"FIXME comment found - needs attention"},{id:"QUAL004",name:"Empty Catch Block",pattern:/catch\s*\([^)]*\)\s*\{\s*\}/,severity:"warning",message:"Empty catch block swallows errors"},{id:"QUAL005",name:"Magic Number",pattern:/(?<![0-9a-zA-Z_])(?!(?:1000|2000|3000|3001|4000|5000|8000|8080|9000|80|443|24|60|1024|2048|4096)\b)\d{5,}(?![0-9])/,severity:"info",message:"Magic number detected - consider using named constant",skipInTests:!0},{id:"QUAL006",name:"Callback Hell",pattern:/\)\s*=>\s*\{[^}]*\)\s*=>\s*\{[^}]*\)\s*=>\s*\{/,severity:"warning",message:"Deeply nested callbacks - consider async/await"}],this.performancePatterns=[{id:"PERF001",name:"Sync FS in Async",pattern:/fs\.(readFileSync|writeFileSync|readdirSync|statSync)/,severity:"warning",message:"Synchronous file operation - may block event loop"},{id:"PERF002",name:"Missing Await",pattern:/async\s+function[^{]*\{[^}]*(?<!await\s)new\s+Promise/,severity:"warning",message:"Promise created in async function without await"},{id:"PERF003",name:"Loop Database Query",pattern:/for\s*\([^)]*\)\s*\{[^}]*(query|find|select|update)/i,severity:"error",message:"Possible N+1 query pattern - consider batch operation"},{id:"PERF004",name:"Large Array in Memory",pattern:/new\s+Array\s*\(\s*\d{6,}\s*\)/,severity:"warning",message:"Large array allocation - consider streaming"}],this.aiPatterns=[{id:"AI001",name:"Potentially Hallucinated Import",pattern:/require\(['"](?!\.)[^'"]+['"]\)/,severity:"info",message:"External dependency - verify it exists in package.json",check:(e,t,s)=>this._checkDependencyExists(e,s)},{id:"AI002",name:"Unused Import",pattern:/const\s+(\w+)\s*=\s*require\(['"][^'"]+['"]\)/,severity:"info",message:"Import may be unused - verify usage",check:(e,t)=>this._checkImportUsage(e,t)},{id:"AI003",name:"Over-Engineered Pattern",pattern:/class\s+\w+Factory\s*\{|AbstractFactory|Singleton\.getInstance/,severity:"info",message:"Complex pattern detected - ensure justified by requirements"}]}async scanAll(){console.log("[CODE-SCANNER] Starting full codebase scan...");const e=Date.now();this._scanStartTime=e;const t={filesScanned:0,filesSkipped:0,issues:[],summary:{},duration:0};try{const s=await this._discoverFiles(this.config.rootPath);console.log(`[CODE-SCANNER] Found ${s.length} files to scan`),this._exportTaintMap=this._buildExportTaintMap(s);for(const n of s){if(Date.now()-e>this.config.totalScanTimeout){console.warn(`[CODE-SCANNER] Total scan timeout (${Math.round(this.config.totalScanTimeout/1e3)}s) reached — stopping scan`),t.issues.push({file:"scan",category:"info",severity:"warning",id:"TIMEOUT001",message:`Scan stopped after ${Math.round(this.config.totalScanTimeout/1e3)}s timeout — ${s.length-t.filesScanned} files not scanned`});break}const i=Date.now(),r=await this.scanFile(n),a=Date.now()-i;a>this.config.perFileTimeout&&console.warn(`[CODE-SCANNER] File ${n} took ${a}ms (>${this.config.perFileTimeout}ms) — results included but flagged`),t.filesScanned++,t.issues.push(...r)}return this._exportTaintMap=null,t.duration=Date.now()-e,t.summary=this._summarizeIssues(t.issues),console.log(`[CODE-SCANNER] Scan complete: ${t.filesScanned} files, ${t.issues.length} issues`),t}catch(e){return console.error("[CODE-SCANNER] Scan failed"),t.error="Scan failed",t}}async scanFiles(e){const t=Date.now();this._scanStartTime=t;const s={filesScanned:0,filesSkipped:0,issues:[],summary:{}};this._exportTaintMap=this._buildExportTaintMap(e);for(const n of e){if(Date.now()-t>this.config.totalScanTimeout){console.warn(`[CODE-SCANNER] Total scan timeout (${Math.round(this.config.totalScanTimeout/1e3)}s) reached — stopping scan`),s.issues.push({file:"scan",category:"info",severity:"warning",id:"TIMEOUT001",message:`Scan stopped after ${Math.round(this.config.totalScanTimeout/1e3)}s timeout — ${e.length-s.filesScanned} files not scanned`});break}const i=Date.now(),r=await this.scanFile(n),a=Date.now()-i;a>this.config.perFileTimeout&&(console.warn(`[CODE-SCANNER] WARNING: ${n} took ${a}ms (>${this.config.perFileTimeout}ms) — skipping slow file`),s.filesSkipped++,s.issues.push({file:n,category:"performance",severity:"warning",id:"TIMEOUT002",message:`File scan took ${a}ms — exceeded ${this.config.perFileTimeout}ms per-file limit`})),s.filesScanned++,s.issues.push(...r)}return this._exportTaintMap=null,s.summary=this._summarizeIssues(s.issues),s}async scanFile(e){const t=[];try{const s=fs.statSync(e);if(s.isSymbolicLink&&s.isSymbolicLink())return[];if(0===s.size)return[];if(s.size>this.config.maxFileSize)return[{file:e,category:"quality",severity:"info",id:"FILE001",message:`File too large (${(s.size/1048576).toFixed(1)}MB > ${(this.config.maxFileSize/1048576).toFixed(0)}MB limit) - skipped`}];let n;try{n=fs.readFileSync(e)}catch(e){return[]}const i=Math.min(n.length,512);for(let e=0;e<i;e++)if(0===n[e])return[];const r=n.toString("utf8"),a=r.split("\n"),o=path.extname(e).toLowerCase();[".js",".ts",".jsx",".tsx",".mjs",".cjs",".py",".pyw",".go",".rs",".java",".kt",".cs",".php",".rb"].includes(o)&&(t.push(...this._runPatternScan(e,r,a,this.securityPatterns,"security")),t.push(...this._runPatternScan(e,r,a,this.qualityPatterns,"quality")),t.push(...this._runPatternScan(e,r,a,this.performancePatterns,"performance")),t.push(...this._runPatternScan(e,r,a,this.aiPatterns,"ai")),t.push(...this._analyzeComplexity(e,r,a)),t.push(...this._analyzeNesting(e,r,a)),t.push(...this._runAstScan(e,r,t)),t.push(...this._runTaintScan(e,r,t)),t.push(...this._runCfgScan(e,r,t))),".json"===o&&t.push(...this._scanJson(e,r));const c=this._applyThubanIgnore(a,t);for(const e of c)this.emit("issueFound",e);return c}catch(t){return[{file:e,category:"error",severity:"error",id:"SCAN001",message:"Failed to scan file: "+("ENOENT"===t.code?"File not found":"EACCES"===t.code?"Permission denied":"EISDIR"===t.code?"Path is a directory":"Scan error")}]}}_applyThubanIgnore(e,t){if(!t.length)return t;const s=new Map;for(let t=0;t<e.length;t++){const n=e[t].match(/thuban-ignore(?:\s+([A-Za-z0-9_,\s]+))?/);if(!n)continue;const i=n[1],r=[t+1,t+2];for(const e of r){if(!i||!i.trim()){s.set(e,!0);continue}const t=i.split(",").map(e=>e.trim()).filter(Boolean),n=s.get(e);if(!0===n)continue;const r=n instanceof Set?n:new Set;for(const e of t)r.add(e);s.set(e,r)}}return 0===s.size?t:t.filter(e=>{if(null==e.line)return!0;const t=s.get(e.line);return!t||!0!==t&&!t.has(e.id)})}getRules(){return{security:this.securityPatterns.map(e=>({id:e.id,name:e.name,severity:e.severity})),quality:this.qualityPatterns.map(e=>({id:e.id,name:e.name,severity:e.severity})),performance:this.performancePatterns.map(e=>({id:e.id,name:e.name,severity:e.severity})),ai:this.aiPatterns.map(e=>({id:e.id,name:e.name,severity:e.severity}))}}async _discoverFiles(e,t=[],s=0){if(s>100)return t;try{const n=fs.readdirSync(e,{withFileTypes:!0});for(const i of n){const n=path.join(e,i.name),r=path.relative(this.config.rootPath,n);if(!this._shouldIgnore(r))if(i.isDirectory())await this._discoverFiles(n,t,s+1);else if(i.isFile()){const e=path.extname(i.name).toLowerCase();[".js",".ts",".jsx",".tsx",".json",".py",".pyw",".go",".rs",".java",".kt",".cs",".php",".rb"].includes(e)&&t.push(n)}}}catch(e){}return t}_shouldIgnore(e){const t=e.replace(/\\/g,"/"),s=["node_modules",".git","dist",".vite"];for(const e of s)if(t.includes("/"+e+"/")||t.startsWith(e+"/")||t===e||t.endsWith("/"+e))return!0;for(const e of this.config.ignorePatterns){const s=e.replace(/\\/g,"/"),n=s.split("/")[0].replace(/\*/g,"");if(n&&(t===n||t.startsWith(n+"/")))return!0;let i=s.replace(/\./g,"\\.").replace(/\*\*/g,".*").replace(/\*/g,"[^/]*");if(new RegExp("^"+i).test(t))return!0}return!1}_runPatternScan(e,t,s,n,i){const r=[],a=path.basename(e),o=this.testFilePatterns.some(e=>e.test(a))||this.testFilePatterns.some(t=>t.test(e)&&!t.source.includes("\\.js$")),c=path.relative(this.config.rootPath,e).replace(/\\/g,"/");for(const a of n){if(a.skipInTests&&o)continue;if(a.skipInDirs&&a.skipInDirs.some(e=>c.startsWith(e+"/")||c.includes("/"+e+"/")))continue;if(a.context&&!a.lineContext&&!a.context.test(t))continue;let n=0;for(const o of s){n++;const s=o.match(a.pattern);if(s){if(a.skipIfSafe&&this.safePatterns.some(e=>e.test(o)))continue;if(a.check&&!a.check(s,t,e))continue;r.push({file:e,line:n,column:s.index,category:i,type:a.type||i,severity:a.severity,id:a.id,name:a.name,message:a.message,code:o.trim().substring(0,100)})}}}return r}_runAstScan(e,t,s){let n;try{n=astAnalyzer.analyze(e,t)}catch(e){return[]}if(!n||0===n.length)return[];const i=new Set(s.map(e=>`${e.line}:${e.type}`));return n.filter(e=>!i.has(`${e.line}:${e.type}`))}_buildExportTaintMap(e){try{return taintTracker.buildExportTaintMap(e)}catch(e){return new Map}}_runTaintScan(e,t,s){let n;try{const s=path.extname(e).toLowerCase();if(new Set([".js",".jsx",".ts",".tsx",".mjs",".cjs"]).has(s)){let s;if(this._exportTaintMap){const n=taintTracker._internal.parseJS(t);n&&(s=taintTracker.buildImportSeeds(e,n,this._exportTaintMap,fs.existsSync))}n=taintTracker.analyzeJSTaint(e,t,s&&s.size?s:void 0)}else n=taintTracker.analyze(e,t)}catch(e){return[]}return n&&0!==n.length?n:[]}_runCfgScan(e,t,s){let n;try{n=cfgAnalyzer.analyze(e,t)}catch(e){return[]}if(!n||0===n.length)return[];const i=new Set(s.map(e=>`${e.line}:${e.type}`));return n.filter(e=>!i.has(`${e.line}:${e.type}`))}_analyzeComplexity(e,t,s){const n=[],i=t.match(/function\s+\w+|=>\s*\{|async\s+\w+/g)||[],r=t.match(/\b(if|else|for|while|switch|case|catch|&&|\|\||\?)\b/g)||[],a=i.length>0?r.length/i.length:0;return a>this.config.complexityThreshold&&n.push({file:e,category:"quality",severity:"warning",id:"CMPLX001",name:"High Complexity",message:`Average cyclomatic complexity is ${a.toFixed(1)} (threshold: ${this.config.complexityThreshold})`}),s.length>500&&n.push({file:e,category:"quality",severity:"info",id:"CMPLX002",name:"Long File",message:`File has ${s.length} lines - consider splitting`}),n}_analyzeNesting(e,t,s){const n=[];let i=0,r=0,a=0,o=0;for(const e of s)o++,r+=(e.match(/\{/g)||[]).length-(e.match(/\}/g)||[]).length,r>i&&(i=r,a=o);return i>this.config.nestingThreshold&&n.push({file:e,line:a,category:"quality",severity:"warning",id:"NEST001",name:"Deep Nesting",message:`Max nesting depth is ${i} at line ${a} (threshold: ${this.config.nestingThreshold})`}),n}_scanJson(e,t){const s=[];try{JSON.parse(t)}catch(t){s.push({file:e,category:"quality",severity:"error",id:"JSON001",name:"Invalid JSON",message:`JSON parse error: ${t.message}`})}return/(password|secret|api[_-]?key|token)/i.test(t)&&s.push({file:e,category:"security",severity:"warning",id:"JSON002",name:"Sensitive Data in JSON",message:"Possible sensitive data in JSON file - verify not committed"}),s}_checkDependencyExists(e,t){const s=e[0].match(/require\(['"]([^'"]+)['"]\)/);if(!s)return!0;const n=s[1];if(n.startsWith("."))return!1;if(["fs","path","os","crypto","http","https","events","util","stream","child_process","url","querystring","buffer"].includes(n.split("/")[0]))return!1;try{let e=path.join(this.config.rootPath,"package.json");if(t.includes("server")){const t=path.join(this.config.rootPath,"server","package.json");fs.existsSync(t)&&(e=t)}const s=JSON.parse(fs.readFileSync(e,"utf8")),i={...s.dependencies||{},...s.devDependencies||{}};return!i[n.split("/")[0]]&&!i[n]}catch(e){return!1}}_checkImportUsage(e,t){const s=e[1];if(!s)return!1;const n=new RegExp("\\b"+s+"\\b","g");return(t.match(n)||[]).length<=1}_summarizeIssues(e){return{total:e.length,bySeverity:{critical:e.filter(e=>"critical"===e.severity).length,error:e.filter(e=>"error"===e.severity).length,warning:e.filter(e=>"warning"===e.severity).length,info:e.filter(e=>"info"===e.severity).length},byCategory:e.reduce((e,t)=>(e[t.category]=(e[t.category]||0)+1,e),{}),topFiles:this._getTopFiles(e,10)}}_getTopFiles(e,t){const s=e.reduce((e,t)=>(e[t.file]=(e[t.file]||0)+1,e),{});return Object.entries(s).sort((e,t)=>t[1]-e[1]).slice(0,t).map(([e,t])=>({file:path.relative(this.config.rootPath,e),count:t}))}}module.exports=CodeScanner;
@@ -1 +1 @@
1
- const fs=require("fs"),path=require("path"),EventEmitter=require("events");class DependencyGraph extends EventEmitter{constructor(e={}){super(),this.config={rootPath:e.rootPath||process.cwd(),ignorePatterns:e.ignorePatterns||["node_modules",".git","dist",".vite",".orion/snapshots"],extensions:e.extensions||[".js",".ts",".jsx",".tsx",".php",".rb"],...e},this.graph=new Map,this.resolveCache=new Map,this.stats={filesAnalyzed:0,totalImports:0,unresolvedImports:0,circularDependencies:[]}}get circularDeps(){return this.stats.circularDependencies}async build(){Date.now();this.graph.clear(),this.resolveCache.clear(),this.stats={filesAnalyzed:0,totalImports:0,unresolvedImports:0,circularDependencies:[]};const e=await this._discoverFiles(this.config.rootPath);for(const t of e)await this._analyzeFile(t);return this._buildReverseDependencies(),this._detectCircularDependencies(),Date.now(),this.getStats()}getDependencies(e){const t=this._normalizePath(e),s=this.graph.get(t);return s?s.imports:[]}getDependents(e){const t=this._normalizePath(e),s=this.graph.get(t);return s?s.importedBy:[]}getFullDependencyChain(e,t=new Set){const s=this._normalizePath(e);if(t.has(s))return[];t.add(s);const i=this.getDependencies(s),r=[...i];for(const e of i)e.resolved&&r.push(...this.getFullDependencyChain(e.resolved,t));return r}getImpactAnalysis(e){const t=this._normalizePath(e),s={file:t,relativePath:path.relative(this.config.rootPath,t),directDependents:[],indirectDependents:[],totalImpact:0,riskLevel:"low",affectedModules:new Set};s.directDependents=this.getDependents(t).map(e=>({file:e,relativePath:path.relative(this.config.rootPath,e)}));const i=new Set([t]),r=[...s.directDependents.map(e=>e.file)];for(;r.length>0;){const e=r.shift();if(i.has(e))continue;i.add(e);const t=this.getDependents(e);for(const o of t)i.has(o)||(s.indirectDependents.push({file:o,relativePath:path.relative(this.config.rootPath,o),via:path.relative(this.config.rootPath,e)}),r.push(o))}s.totalImpact=s.directDependents.length+s.indirectDependents.length,0===s.totalImpact?s.riskLevel="none":s.totalImpact<=3?s.riskLevel="low":s.totalImpact<=10?s.riskLevel="medium":s.totalImpact<=25?s.riskLevel="high":s.riskLevel="critical";for(const e of[...s.directDependents,...s.indirectDependents]){const t=e.relativePath.split(path.sep);t.length>0&&s.affectedModules.add(t[0])}return s.affectedModules=Array.from(s.affectedModules),s}getMostCriticalFiles(e=20){const t=[];for(const[e,s]of this.graph)t.push({file:e,relativePath:path.relative(this.config.rootPath,e),dependentCount:s.importedBy.length,dependencyCount:s.imports.length});return t.sort((e,t)=>t.dependentCount-e.dependentCount).slice(0,e)}getOrphanFiles(){const e=[];for(const[t,s]of this.graph)0===s.imports.length&&0===s.importedBy.length&&e.push({file:t,relativePath:path.relative(this.config.rootPath,t)});return e}getModuleSummary(){const e=new Map;for(const[t,s]of this.graph){const i=path.relative(this.config.rootPath,t),r=i.split(path.sep)[0];e.has(r)||e.set(r,{name:r,files:[],internalDeps:0,externalDeps:0,dependedOnBy:new Set});const o=e.get(r);o.files.push(i);for(const e of s.imports)e.resolved&&(path.relative(this.config.rootPath,e.resolved).split(path.sep)[0]===r?o.internalDeps++:o.externalDeps++);for(const e of s.importedBy){const t=path.relative(this.config.rootPath,e).split(path.sep)[0];t!==r&&o.dependedOnBy.add(t)}}const t=[];for(const[s,i]of e)t.push({name:s,fileCount:i.files.length,internalDeps:i.internalDeps,externalDeps:i.externalDeps,dependedOnByCount:i.dependedOnBy.size,dependedOnBy:Array.from(i.dependedOnBy)});return t.sort((e,t)=>t.fileCount-e.fileCount)}getStats(){return{...this.stats,totalFiles:this.graph.size,circularCount:this.stats.circularDependencies.length}}toJSON(){const e={generated:(new Date).toISOString(),rootPath:this.config.rootPath,stats:this.getStats(),files:{}};for(const[t,s]of this.graph){const i=path.relative(this.config.rootPath,t);e.files[i]={imports:s.imports.map(e=>({module:e.module,resolved:e.resolved?path.relative(this.config.rootPath,e.resolved):null,specifiers:e.specifiers})),importedBy:s.importedBy.map(e=>path.relative(this.config.rootPath,e))}}return e}async _discoverFiles(e,t=[]){try{const s=fs.readdirSync(e,{withFileTypes:!0});for(const i of s){const s=path.join(e,i.name);if(!this._shouldIgnore(i.name))if(i.isDirectory())await this._discoverFiles(s,t);else if(i.isFile()){const e=path.extname(i.name).toLowerCase();this.config.extensions.includes(e)&&t.push(s)}}}catch(e){}return t}_shouldIgnore(e){return this.config.ignorePatterns.some(t=>e===t||e.startsWith(t))}async _analyzeFile(e){try{const t=fs.readFileSync(e,"utf8"),s=this._extractImports(t,e);this.graph.set(e,{imports:s,importedBy:[]}),this.stats.filesAnalyzed++,this.stats.totalImports+=s.length,this.stats.unresolvedImports+=s.filter(e=>!e.resolved).length}catch(e){}}_extractImports(e,t){const s=[],i=path.dirname(t),r=/(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;let o;for(;null!==(o=r.exec(e));){const e=o[1]?o[1].split(",").map(e=>e.trim().split(":")[0].trim()).filter(Boolean):[o[2]],t=o[3];s.push({module:t,specifiers:e,resolved:this._resolveModule(t,i),type:"require"})}const n=/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;for(;null!==(o=n.exec(e));){const e=o[1];s.some(t=>t.module===e)||s.push({module:e,specifiers:[],resolved:this._resolveModule(e,i),type:"require"})}const a=/import\s+(?:(?:\{([^}]+)\}|(\w+)|\*\s+as\s+(\w+))(?:\s*,\s*)?)+\s+from\s+['"]([^'"]+)['"]/g;for(;null!==(o=a.exec(e));){const e=[];o[1]&&e.push(...o[1].split(",").map(e=>e.trim().split(" as ")[0].trim()).filter(Boolean)),o[2]&&e.push(o[2]),o[3]&&e.push(o[3]);const t=o[4];s.push({module:t,specifiers:e,resolved:this._resolveModule(t,i),type:"import"})}return s}_resolveModule(e,t){const s=`${t}:${e}`;if(this.resolveCache.has(s))return this.resolveCache.get(s);let i=null;if(!e.startsWith(".")&&!e.startsWith("/"))return this.resolveCache.set(s,null),null;const r=path.resolve(t,e),o=["",".js",".ts",".jsx",".tsx",".php",".rb","/index.js","/index.ts"];for(const e of o){const t=r+e;if(fs.existsSync(t)&&fs.statSync(t).isFile()){i=t;break}}return this.resolveCache.set(s,i),i}_buildReverseDependencies(){for(const[e,t]of this.graph)for(const s of t.imports)if(s.resolved&&this.graph.has(s.resolved)){const t=this.graph.get(s.resolved);t.importedBy.includes(e)||t.importedBy.push(e)}}_detectCircularDependencies(){const e=new Set,t=new Set,s=(i,r=[])=>{if(t.has(i)){const e=r.indexOf(i),t=r.slice(e).map(e=>path.relative(this.config.rootPath,e));t.push(path.relative(this.config.rootPath,i));const s=t.sort().join(" -> ");return void(this.stats.circularDependencies.some(e=>e.sort().join(" -> ")===s)||this.stats.circularDependencies.push(t))}if(e.has(i))return;e.add(i),t.add(i);const o=this.graph.get(i);if(o)for(const e of o.imports)e.resolved&&s(e.resolved,[...r,i]);t.delete(i)};for(const e of this.graph.keys())s(e)}_normalizePath(e){return path.isAbsolute(e)?e:path.resolve(this.config.rootPath,e)}}module.exports=DependencyGraph;
1
+ const fs=require("fs"),path=require("path"),EventEmitter=require("events");class DependencyGraph extends EventEmitter{constructor(e={}){super(),this.config={rootPath:e.rootPath||process.cwd(),ignorePatterns:e.ignorePatterns||["node_modules",".git","dist",".vite",".orion/snapshots"],extensions:e.extensions||[".js",".ts",".jsx",".tsx",".php",".rb"],...e},this.graph=new Map,this.resolveCache=new Map,this.stats={filesAnalyzed:0,totalImports:0,unresolvedImports:0,circularDependencies:[]}}get circularDeps(){return this.stats.circularDependencies}async build(){Date.now();this.graph.clear(),this.resolveCache.clear(),this.stats={filesAnalyzed:0,totalImports:0,unresolvedImports:0,circularDependencies:[]};const e=await this._discoverFiles(this.config.rootPath);for(const t of e)await this._analyzeFile(t);return this._buildReverseDependencies(),this._detectCircularDependencies(),Date.now(),this.getStats()}getDependencies(e){const t=this._normalizePath(e),s=this.graph.get(t);return s?s.imports:[]}getDependents(e){const t=this._normalizePath(e),s=this.graph.get(t);return s?s.importedBy:[]}getFullDependencyChain(e,t=new Set){const s=this._normalizePath(e);if(t.has(s))return[];t.add(s);const i=this.getDependencies(s),r=[...i];for(const e of i)e.resolved&&r.push(...this.getFullDependencyChain(e.resolved,t));return r}getImpactAnalysis(e){const t=this._normalizePath(e),s={file:t,relativePath:path.relative(this.config.rootPath,t),directDependents:[],indirectDependents:[],totalImpact:0,riskLevel:"low",affectedModules:new Set};s.directDependents=this.getDependents(t).map(e=>({file:e,relativePath:path.relative(this.config.rootPath,e)}));const i=new Set([t]),r=[...s.directDependents.map(e=>e.file)];for(;r.length>0;){const e=r.shift();if(i.has(e))continue;i.add(e);const t=this.getDependents(e);for(const o of t)i.has(o)||(s.indirectDependents.push({file:o,relativePath:path.relative(this.config.rootPath,o),via:path.relative(this.config.rootPath,e)}),r.push(o))}s.totalImpact=s.directDependents.length+s.indirectDependents.length,0===s.totalImpact?s.riskLevel="none":s.totalImpact<=3?s.riskLevel="low":s.totalImpact<=10?s.riskLevel="medium":s.totalImpact<=25?s.riskLevel="high":s.riskLevel="critical";for(const e of[...s.directDependents,...s.indirectDependents]){const t=e.relativePath.split(path.sep);t.length>0&&s.affectedModules.add(t[0])}return s.affectedModules=Array.from(s.affectedModules),s}getMostCriticalFiles(e=20){const t=[];for(const[e,s]of this.graph)t.push({file:e,relativePath:path.relative(this.config.rootPath,e),dependentCount:s.importedBy.length,dependencyCount:s.imports.length});return t.sort((e,t)=>t.dependentCount-e.dependentCount).slice(0,e)}getOrphanFiles(){const e=[];for(const[t,s]of this.graph)0===s.imports.length&&0===s.importedBy.length&&e.push({file:t,relativePath:path.relative(this.config.rootPath,t)});return e}getModuleSummary(){const e=new Map;for(const[t,s]of this.graph){const i=path.relative(this.config.rootPath,t),r=i.split(path.sep)[0];e.has(r)||e.set(r,{name:r,files:[],internalDeps:0,externalDeps:0,dependedOnBy:new Set});const o=e.get(r);o.files.push(i);for(const e of s.imports)e.resolved&&(path.relative(this.config.rootPath,e.resolved).split(path.sep)[0]===r?o.internalDeps++:o.externalDeps++);for(const e of s.importedBy){const t=path.relative(this.config.rootPath,e).split(path.sep)[0];t!==r&&o.dependedOnBy.add(t)}}const t=[];for(const[s,i]of e)t.push({name:s,fileCount:i.files.length,internalDeps:i.internalDeps,externalDeps:i.externalDeps,dependedOnByCount:i.dependedOnBy.size,dependedOnBy:Array.from(i.dependedOnBy)});return t.sort((e,t)=>t.fileCount-e.fileCount)}getStats(){return{...this.stats,totalFiles:this.graph.size,circularCount:this.stats.circularDependencies.length}}toJSON(){const e={generated:(new Date).toISOString(),rootPath:this.config.rootPath,stats:this.getStats(),files:{}};for(const[t,s]of this.graph){const i=path.relative(this.config.rootPath,t);e.files[i]={imports:s.imports.map(e=>({module:e.module,resolved:e.resolved?path.relative(this.config.rootPath,e.resolved):null,specifiers:e.specifiers})),importedBy:s.importedBy.map(e=>path.relative(this.config.rootPath,e))}}return e}async _discoverFiles(e,t=[],s=0){if(s>100)return t;try{const i=fs.readdirSync(e,{withFileTypes:!0});for(const r of i){const i=path.join(e,r.name);if(!this._shouldIgnore(r.name))if(r.isDirectory())await this._discoverFiles(i,t,s+1);else if(r.isFile()){const e=path.extname(r.name).toLowerCase();this.config.extensions.includes(e)&&t.push(i)}}}catch(e){}return t}_shouldIgnore(e){return this.config.ignorePatterns.some(t=>e===t||e.startsWith(t))}async _analyzeFile(e){try{const t=fs.readFileSync(e,"utf8"),s=this._extractImports(t,e);this.graph.set(e,{imports:s,importedBy:[]}),this.stats.filesAnalyzed++,this.stats.totalImports+=s.length,this.stats.unresolvedImports+=s.filter(e=>!e.resolved).length}catch(e){}}_extractImports(e,t){const s=[],i=path.dirname(t),r=/(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;let o;for(;null!==(o=r.exec(e));){const e=o[1]?o[1].split(",").map(e=>e.trim().split(":")[0].trim()).filter(Boolean):[o[2]],t=o[3];s.push({module:t,specifiers:e,resolved:this._resolveModule(t,i),type:"require"})}const n=/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;for(;null!==(o=n.exec(e));){const e=o[1];s.some(t=>t.module===e)||s.push({module:e,specifiers:[],resolved:this._resolveModule(e,i),type:"require"})}const a=/import\s+(?:(?:\{([^}]+)\}|(\w+)|\*\s+as\s+(\w+))(?:\s*,\s*)?)+\s+from\s+['"]([^'"]+)['"]/g;for(;null!==(o=a.exec(e));){const e=[];o[1]&&e.push(...o[1].split(",").map(e=>e.trim().split(" as ")[0].trim()).filter(Boolean)),o[2]&&e.push(o[2]),o[3]&&e.push(o[3]);const t=o[4];s.push({module:t,specifiers:e,resolved:this._resolveModule(t,i),type:"import"})}return s}_resolveModule(e,t){const s=`${t}:${e}`;if(this.resolveCache.has(s))return this.resolveCache.get(s);let i=null;if(!e.startsWith(".")&&!e.startsWith("/"))return this.resolveCache.set(s,null),null;const r=path.resolve(t,e),o=["",".js",".ts",".jsx",".tsx",".php",".rb","/index.js","/index.ts"];for(const e of o){const t=r+e;if(fs.existsSync(t)&&fs.statSync(t).isFile()){i=t;break}}return this.resolveCache.set(s,i),i}_buildReverseDependencies(){for(const[e,t]of this.graph)for(const s of t.imports)if(s.resolved&&this.graph.has(s.resolved)){const t=this.graph.get(s.resolved);t.importedBy.includes(e)||t.importedBy.push(e)}}_detectCircularDependencies(){const e=new Set,t=new Set,s=(i,r=[])=>{if(t.has(i)){const e=r.indexOf(i),t=r.slice(e).map(e=>path.relative(this.config.rootPath,e));t.push(path.relative(this.config.rootPath,i));const s=t.sort().join(" -> ");return void(this.stats.circularDependencies.some(e=>e.sort().join(" -> ")===s)||this.stats.circularDependencies.push(t))}if(e.has(i))return;e.add(i),t.add(i);const o=this.graph.get(i);if(o)for(const e of o.imports)e.resolved&&s(e.resolved,[...r,i]);t.delete(i)};for(const e of this.graph.keys())s(e)}_normalizePath(e){return path.isAbsolute(e)?e:path.resolve(this.config.rootPath,e)}}module.exports=DependencyGraph;
@@ -1 +1 @@
1
- const fs=require("fs"),path=require("path"),EventEmitter=require("events");class DriftDetector extends EventEmitter{constructor(e={}){super(),this.config={rootPath:e.rootPath||process.cwd(),widgetPattern:e.widgetPattern||/\/\*\*[\s\S]*?@(purpose|module|layer|imports-from|exports-to|depends-on|critical-for)[\s\S]*?\*\//,ignorePatterns:e.ignorePatterns||["node_modules/**",".git/**","dist/**",".orion/snapshots/**"],...e},this.analysisCache=new Map}async detectAll(){const e=Date.now(),t={filesAnalyzed:0,filesWithWidgets:0,filesWithoutWidgets:0,issues:[],moduleMap:{},duration:0};try{const s=await this._discoverFiles(this.config.rootPath);for(const e of s){const s=await this.analyzeFile(e);t.filesAnalyzed++,s.hasWidget?t.filesWithWidgets++:(t.filesWithoutWidgets++,t.issues.push({file:e,category:"drift",severity:"info",id:"DRIFT001",name:"Missing Widget",message:"File has no widget declaration - add @purpose, @module, @layer etc."})),t.issues.push(...s.issues),s.module&&(t.moduleMap[s.module]||(t.moduleMap[s.module]=[]),t.moduleMap[s.module].push(e))}return t.issues.push(...this._analyzeCrossFile(t.moduleMap)),t.duration=Date.now()-e,t}catch(e){return console.error("[DRIFT-DETECTOR] Detection failed:",e.message),t.error=e.message,t}}async analyzeFile(e){const t={file:e,hasWidget:!1,widget:null,actualExports:[],actualImports:[],module:null,issues:[]};try{const s=fs.readFileSync(e,"utf8");return t.widget=this._parseWidget(s),t.hasWidget=!!t.widget,t.actualExports=this._extractExports(s),t.actualImports=this._extractImports(s),t.widget&&(t.module=t.widget.module,t.issues.push(...this._compareWidgetToCode(e,t.widget,{exports:t.actualExports,imports:t.actualImports}))),this.analysisCache.set(e,t),t}catch(s){return t.issues.push({file:e,category:"drift",severity:"error",id:"DRIFT000",message:`Failed to analyze file: ${s.message}`}),t}}async checkFileDrift(e){const t=await this.analyzeFile(e);return{hasDrift:t.issues.length>0,issues:t.issues,widget:t.widget}}async suggestWidget(e){try{const t=fs.readFileSync(e,"utf8"),s=this._extractExports(t),i=this._extractImports(t),o=path.relative(this.config.rootPath,e),r=o.split(path.sep)[0];let n="application";return o.includes("routes")||o.includes("api")?n="presentation":o.includes("core")||o.includes("engine")?n="infrastructure":(o.includes("model")||o.includes("domain"))&&(n="domain"),{purpose:s.length>0?`Provides ${s.join(", ")}`:"Unknown purpose - needs description",module:r,layer:n,importsFrom:i.map(e=>`${e.module} - ${e.specifiers.join(", ")}`),exportsTo:"To be determined",dataFlow:"To be documented",sideEffects:t.includes("fs.")?"File system operations":"None documented",criticalFor:"To be documented"}}catch(e){return{error:e.message}}}async _discoverFiles(e,t=[]){try{const s=fs.readdirSync(e,{withFileTypes:!0});for(const i of s){const s=path.join(e,i.name),o=path.relative(this.config.rootPath,s);this._shouldIgnore(o)||(i.isDirectory()?await this._discoverFiles(s,t):i.isFile()&&i.name.endsWith(".js")&&t.push(s))}}catch(e){}return t}_shouldIgnore(e){const t=e.replace(/\\/g,"/"),s=["node_modules",".git","dist",".vite"];for(const e of s)if(t.includes("/"+e+"/")||t.startsWith(e+"/")||t===e||t.endsWith("/"+e))return!0;for(const e of this.config.ignorePatterns){const s=e.replace(/\\/g,"/"),i=s.split("/")[0].replace(/\*/g,"");if(i&&(t===i||t.startsWith(i+"/")))return!0;let o=s.replace(/\./g,"\\.").replace(/\*\*/g,".*").replace(/\*/g,"[^/]*");if(new RegExp("^"+o).test(t))return!0}return!1}_parseWidget(e){const t=e.match(/\/\*\*[\s\S]*?\*\//);if(!t)return null;const s=t[0],i={},o=["purpose","module","layer","imports-from","exports-to","data-flow","side-effects","depends-on-env","critical-for","known-issues","last-audit","last-modified"];for(const e of o){const t=s.match(new RegExp(`@${e}\\s+(.+?)(?=\\n\\s*\\*\\s*@|\\n\\s*\\*\\/|$)`,"s"));t&&(i[e.replace(/-/g,"_")]=t[1].trim().replace(/\n\s*\*\s*/g," ").trim())}return Object.keys(i).length>0?i:null}_extractExports(e){const t=[],s=e.match(/module\.exports\s*=\s*\{([^}]+)\}/);if(s){const e=s[1].match(/\w+/g)||[];t.push(...e)}const i=e.match(/module\.exports\s*=\s*(class\s+)?(\w+)/);i&&i[2]&&t.push(i[2]);const o=e.match(/exports\.(\w+)\s*=/g)||[];for(const e of o){const s=e.match(/exports\.(\w+)/);s&&t.push(s[1])}return[...new Set(t)]}_extractImports(e){const t=[],s=e.matchAll(/(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\(['"]([^'"]+)['"]\)/g);for(const e of s){const s=e[1]?e[1].split(",").map(e=>e.trim().split(":")[0].trim()):[e[2]];t.push({module:e[3],specifiers:s.filter(Boolean)})}return t}_compareWidgetToCode(e,t,s){const i=[];if(t.exports_to){const o=t.exports_to.split(",").map(e=>e.trim().split(" ")[0]).filter(e=>!s.exports.some(t=>t.toLowerCase().includes(e.toLowerCase())));o.length>0&&i.push({file:e,category:"drift",severity:"warning",id:"DRIFT002",name:"Missing Declared Export",message:`Widget declares exports not found in code: ${o.join(", ")}`})}if(t.imports_from){const o=t.imports_from.split(",").map(e=>e.trim().split(" ")[0]),r=s.imports.map(e=>e.module),n=o.filter(e=>!r.some(t=>t.includes(e)||e.includes(t.split("/").pop())));n.length>0&&i.push({file:e,category:"drift",severity:"warning",id:"DRIFT003",name:"Missing Declared Import",message:`Widget declares imports not found in code: ${n.join(", ")}`})}return!/fs\.(write|append|unlink|mkdir|rmdir)/i.test(fs.readFileSync(e,"utf8"))||t.side_effects&&"None"!==t.side_effects||i.push({file:e,category:"drift",severity:"warning",id:"DRIFT004",name:"Undeclared Side Effects",message:"File has file system operations but widget declares no side effects"}),i}_analyzeCrossFile(e){const t=[];for(const[s,i]of Object.entries(e))1===i.length&&t.push({file:i[0],category:"drift",severity:"info",id:"DRIFT005",name:"Orphaned Module",message:`Module "${s}" only has one file - verify module organization`});return t}}module.exports=DriftDetector;
1
+ const fs=require("fs"),path=require("path"),EventEmitter=require("events");class DriftDetector extends EventEmitter{constructor(e={}){super(),this.config={rootPath:e.rootPath||process.cwd(),widgetPattern:e.widgetPattern||/\/\*\*[\s\S]*?@(purpose|module|layer|imports-from|exports-to|depends-on|critical-for)[\s\S]*?\*\//,ignorePatterns:e.ignorePatterns||["node_modules/**",".git/**","dist/**",".orion/snapshots/**"],...e},this.analysisCache=new Map}async detectAll(){const e=Date.now(),t={filesAnalyzed:0,filesWithWidgets:0,filesWithoutWidgets:0,issues:[],moduleMap:{},duration:0};try{const s=await this._discoverFiles(this.config.rootPath);for(const e of s){const s=await this.analyzeFile(e);t.filesAnalyzed++,s.hasWidget?t.filesWithWidgets++:(t.filesWithoutWidgets++,t.issues.push({file:e,category:"drift",severity:"info",id:"DRIFT001",name:"Missing Widget",message:"File has no widget declaration - add @purpose, @module, @layer etc."})),t.issues.push(...s.issues),s.module&&(t.moduleMap[s.module]||(t.moduleMap[s.module]=[]),t.moduleMap[s.module].push(e))}return t.issues.push(...this._analyzeCrossFile(t.moduleMap)),t.duration=Date.now()-e,t}catch(e){return console.error("[DRIFT-DETECTOR] Detection failed"),t.error="Detection failed",t}}async analyzeFile(e){const t={file:e,hasWidget:!1,widget:null,actualExports:[],actualImports:[],module:null,issues:[]};try{const s=fs.readFileSync(e,"utf8");return t.widget=this._parseWidget(s),t.hasWidget=!!t.widget,t.actualExports=this._extractExports(s),t.actualImports=this._extractImports(s),t.widget&&(t.module=t.widget.module,t.issues.push(...this._compareWidgetToCode(e,t.widget,{exports:t.actualExports,imports:t.actualImports}))),t.drifts=t.issues,this.analysisCache.set(e,t),t}catch(s){const i="ENOENT"===s.code?"File not found":"EACCES"===s.code?"Permission denied":"Analysis error";return t.issues.push({file:e,category:"drift",severity:"error",id:"DRIFT000",message:`Failed to analyze file: ${i}`}),t.drifts=t.issues,t}}async checkFileDrift(e){const t=await this.analyzeFile(e);return{hasDrift:t.issues.length>0,issues:t.issues,widget:t.widget}}async suggestWidget(e){try{const t=fs.readFileSync(e,"utf8"),s=this._extractExports(t),i=this._extractImports(t),o=path.relative(this.config.rootPath,e),r=o.split(path.sep)[0];let n="application";return o.includes("routes")||o.includes("api")?n="presentation":o.includes("core")||o.includes("engine")?n="infrastructure":(o.includes("model")||o.includes("domain"))&&(n="domain"),{purpose:s.length>0?`Provides ${s.join(", ")}`:"Unknown purpose - needs description",module:r,layer:n,importsFrom:i.map(e=>`${e.module} - ${e.specifiers.join(", ")}`),exportsTo:"To be determined",dataFlow:"To be documented",sideEffects:t.includes("fs.")?"File system operations":"None documented",criticalFor:"To be documented"}}catch(e){return{error:e.message}}}async _discoverFiles(e,t=[],s=0){if(s>100)return t;try{const i=fs.readdirSync(e,{withFileTypes:!0});for(const o of i){const i=path.join(e,o.name),r=path.relative(this.config.rootPath,i);this._shouldIgnore(r)||(o.isDirectory()?await this._discoverFiles(i,t,s+1):o.isFile()&&o.name.endsWith(".js")&&t.push(i))}}catch(e){}return t}_shouldIgnore(e){const t=e.replace(/\\/g,"/"),s=["node_modules",".git","dist",".vite"];for(const e of s)if(t.includes("/"+e+"/")||t.startsWith(e+"/")||t===e||t.endsWith("/"+e))return!0;for(const e of this.config.ignorePatterns){const s=e.replace(/\\/g,"/"),i=s.split("/")[0].replace(/\*/g,"");if(i&&(t===i||t.startsWith(i+"/")))return!0;let o=s.replace(/\./g,"\\.").replace(/\*\*/g,".*").replace(/\*/g,"[^/]*");if(new RegExp("^"+o).test(t))return!0}return!1}_parseWidget(e){const t=e.match(/\/\*\*[\s\S]*?\*\//);if(!t)return null;const s=t[0],i={},o=["purpose","module","layer","imports-from","exports-to","data-flow","side-effects","depends-on-env","critical-for","known-issues","last-audit","last-modified"];for(const e of o){const t=s.match(new RegExp(`@${e}\\s+(.+?)(?=\\n\\s*\\*\\s*@|\\n\\s*\\*\\/|$)`,"s"));t&&(i[e.replace(/-/g,"_")]=t[1].trim().replace(/\n\s*\*\s*/g," ").trim())}return Object.keys(i).length>0?i:null}_extractExports(e){const t=[],s=e.match(/module\.exports\s*=\s*\{([^}]+)\}/);if(s){const e=s[1].match(/\w+/g)||[];t.push(...e)}const i=e.match(/module\.exports\s*=\s*(class\s+)?(\w+)/);i&&i[2]&&t.push(i[2]);const o=e.match(/exports\.(\w+)\s*=/g)||[];for(const e of o){const s=e.match(/exports\.(\w+)/);s&&t.push(s[1])}return[...new Set(t)]}_extractImports(e){const t=[],s=e.matchAll(/(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\(['"]([^'"]+)['"]\)/g);for(const e of s){const s=e[1]?e[1].split(",").map(e=>e.trim().split(":")[0].trim()):[e[2]];t.push({module:e[3],specifiers:s.filter(Boolean)})}return t}_compareWidgetToCode(e,t,s){const i=[];if(t.exports_to){const o=t.exports_to.split(",").map(e=>e.trim().split(" ")[0]).filter(e=>!s.exports.some(t=>t.toLowerCase().includes(e.toLowerCase())));o.length>0&&i.push({file:e,category:"drift",severity:"warning",id:"DRIFT002",name:"Missing Declared Export",message:`Widget declares exports not found in code: ${o.join(", ")}`})}if(t.imports_from){const o=t.imports_from.split(",").map(e=>e.trim().split(" ")[0]),r=s.imports.map(e=>e.module),n=o.filter(e=>!r.some(t=>t.includes(e)||e.includes(t.split("/").pop())));n.length>0&&i.push({file:e,category:"drift",severity:"warning",id:"DRIFT003",name:"Missing Declared Import",message:`Widget declares imports not found in code: ${n.join(", ")}`})}return!/fs\.(write|append|unlink|mkdir|rmdir)/i.test(fs.readFileSync(e,"utf8"))||t.side_effects&&"None"!==t.side_effects||i.push({file:e,category:"drift",severity:"warning",id:"DRIFT004",name:"Undeclared Side Effects",message:"File has file system operations but widget declares no side effects"}),i}_analyzeCrossFile(e){const t=[];for(const[s,i]of Object.entries(e))1===i.length&&t.push({file:i[0],category:"drift",severity:"info",id:"DRIFT005",name:"Orphaned Module",message:`Module "${s}" only has one file - verify module organization`});return t}}module.exports=DriftDetector;