onbuzz 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/LICENSE +267 -0
  2. package/README.md +425 -0
  3. package/bin/cli.js +556 -0
  4. package/bin/loxia-terminal-v2.js +162 -0
  5. package/bin/loxia-terminal.js +90 -0
  6. package/bin/start-with-terminal.js +200 -0
  7. package/node_modules/@isaacs/balanced-match/LICENSE.md +23 -0
  8. package/node_modules/@isaacs/balanced-match/README.md +60 -0
  9. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.d.ts +9 -0
  10. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.d.ts.map +1 -0
  11. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.js +59 -0
  12. package/node_modules/@isaacs/balanced-match/dist/commonjs/index.js.map +1 -0
  13. package/node_modules/@isaacs/balanced-match/dist/commonjs/package.json +3 -0
  14. package/node_modules/@isaacs/balanced-match/dist/esm/index.d.ts +9 -0
  15. package/node_modules/@isaacs/balanced-match/dist/esm/index.d.ts.map +1 -0
  16. package/node_modules/@isaacs/balanced-match/dist/esm/index.js +54 -0
  17. package/node_modules/@isaacs/balanced-match/dist/esm/index.js.map +1 -0
  18. package/node_modules/@isaacs/balanced-match/dist/esm/package.json +3 -0
  19. package/node_modules/@isaacs/balanced-match/package.json +79 -0
  20. package/node_modules/@isaacs/brace-expansion/LICENSE +23 -0
  21. package/node_modules/@isaacs/brace-expansion/README.md +97 -0
  22. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.d.ts +6 -0
  23. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.d.ts.map +1 -0
  24. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js +199 -0
  25. package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +1 -0
  26. package/node_modules/@isaacs/brace-expansion/dist/commonjs/package.json +3 -0
  27. package/node_modules/@isaacs/brace-expansion/dist/esm/index.d.ts +6 -0
  28. package/node_modules/@isaacs/brace-expansion/dist/esm/index.d.ts.map +1 -0
  29. package/node_modules/@isaacs/brace-expansion/dist/esm/index.js +195 -0
  30. package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +1 -0
  31. package/node_modules/@isaacs/brace-expansion/dist/esm/package.json +3 -0
  32. package/node_modules/@isaacs/brace-expansion/package.json +60 -0
  33. package/node_modules/glob/LICENSE.md +63 -0
  34. package/node_modules/glob/README.md +1177 -0
  35. package/node_modules/glob/dist/commonjs/glob.d.ts +388 -0
  36. package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -0
  37. package/node_modules/glob/dist/commonjs/glob.js +247 -0
  38. package/node_modules/glob/dist/commonjs/glob.js.map +1 -0
  39. package/node_modules/glob/dist/commonjs/has-magic.d.ts +14 -0
  40. package/node_modules/glob/dist/commonjs/has-magic.d.ts.map +1 -0
  41. package/node_modules/glob/dist/commonjs/has-magic.js +27 -0
  42. package/node_modules/glob/dist/commonjs/has-magic.js.map +1 -0
  43. package/node_modules/glob/dist/commonjs/ignore.d.ts +24 -0
  44. package/node_modules/glob/dist/commonjs/ignore.d.ts.map +1 -0
  45. package/node_modules/glob/dist/commonjs/ignore.js +119 -0
  46. package/node_modules/glob/dist/commonjs/ignore.js.map +1 -0
  47. package/node_modules/glob/dist/commonjs/index.d.ts +97 -0
  48. package/node_modules/glob/dist/commonjs/index.d.ts.map +1 -0
  49. package/node_modules/glob/dist/commonjs/index.js +68 -0
  50. package/node_modules/glob/dist/commonjs/index.js.map +1 -0
  51. package/node_modules/glob/dist/commonjs/index.min.js +4 -0
  52. package/node_modules/glob/dist/commonjs/index.min.js.map +7 -0
  53. package/node_modules/glob/dist/commonjs/package.json +3 -0
  54. package/node_modules/glob/dist/commonjs/pattern.d.ts +76 -0
  55. package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -0
  56. package/node_modules/glob/dist/commonjs/pattern.js +219 -0
  57. package/node_modules/glob/dist/commonjs/pattern.js.map +1 -0
  58. package/node_modules/glob/dist/commonjs/processor.d.ts +59 -0
  59. package/node_modules/glob/dist/commonjs/processor.d.ts.map +1 -0
  60. package/node_modules/glob/dist/commonjs/processor.js +301 -0
  61. package/node_modules/glob/dist/commonjs/processor.js.map +1 -0
  62. package/node_modules/glob/dist/commonjs/walker.d.ts +97 -0
  63. package/node_modules/glob/dist/commonjs/walker.d.ts.map +1 -0
  64. package/node_modules/glob/dist/commonjs/walker.js +387 -0
  65. package/node_modules/glob/dist/commonjs/walker.js.map +1 -0
  66. package/node_modules/glob/dist/esm/glob.d.ts +388 -0
  67. package/node_modules/glob/dist/esm/glob.d.ts.map +1 -0
  68. package/node_modules/glob/dist/esm/glob.js +243 -0
  69. package/node_modules/glob/dist/esm/glob.js.map +1 -0
  70. package/node_modules/glob/dist/esm/has-magic.d.ts +14 -0
  71. package/node_modules/glob/dist/esm/has-magic.d.ts.map +1 -0
  72. package/node_modules/glob/dist/esm/has-magic.js +23 -0
  73. package/node_modules/glob/dist/esm/has-magic.js.map +1 -0
  74. package/node_modules/glob/dist/esm/ignore.d.ts +24 -0
  75. package/node_modules/glob/dist/esm/ignore.d.ts.map +1 -0
  76. package/node_modules/glob/dist/esm/ignore.js +115 -0
  77. package/node_modules/glob/dist/esm/ignore.js.map +1 -0
  78. package/node_modules/glob/dist/esm/index.d.ts +97 -0
  79. package/node_modules/glob/dist/esm/index.d.ts.map +1 -0
  80. package/node_modules/glob/dist/esm/index.js +55 -0
  81. package/node_modules/glob/dist/esm/index.js.map +1 -0
  82. package/node_modules/glob/dist/esm/index.min.js +4 -0
  83. package/node_modules/glob/dist/esm/index.min.js.map +7 -0
  84. package/node_modules/glob/dist/esm/package.json +3 -0
  85. package/node_modules/glob/dist/esm/pattern.d.ts +76 -0
  86. package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -0
  87. package/node_modules/glob/dist/esm/pattern.js +215 -0
  88. package/node_modules/glob/dist/esm/pattern.js.map +1 -0
  89. package/node_modules/glob/dist/esm/processor.d.ts +59 -0
  90. package/node_modules/glob/dist/esm/processor.d.ts.map +1 -0
  91. package/node_modules/glob/dist/esm/processor.js +294 -0
  92. package/node_modules/glob/dist/esm/processor.js.map +1 -0
  93. package/node_modules/glob/dist/esm/walker.d.ts +97 -0
  94. package/node_modules/glob/dist/esm/walker.d.ts.map +1 -0
  95. package/node_modules/glob/dist/esm/walker.js +381 -0
  96. package/node_modules/glob/dist/esm/walker.js.map +1 -0
  97. package/node_modules/glob/node_modules/minimatch/LICENSE.md +55 -0
  98. package/node_modules/glob/node_modules/minimatch/README.md +453 -0
  99. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +2 -0
  100. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -0
  101. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js +14 -0
  102. package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -0
  103. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +20 -0
  104. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -0
  105. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +591 -0
  106. package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -0
  107. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts +8 -0
  108. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -0
  109. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +152 -0
  110. package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -0
  111. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +15 -0
  112. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -0
  113. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +30 -0
  114. package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -0
  115. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +94 -0
  116. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -0
  117. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +1029 -0
  118. package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -0
  119. package/node_modules/glob/node_modules/minimatch/dist/commonjs/package.json +3 -0
  120. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +22 -0
  121. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -0
  122. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +38 -0
  123. package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -0
  124. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +2 -0
  125. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -0
  126. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js +10 -0
  127. package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -0
  128. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +20 -0
  129. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -0
  130. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +587 -0
  131. package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -0
  132. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts +8 -0
  133. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -0
  134. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +148 -0
  135. package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -0
  136. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +15 -0
  137. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -0
  138. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +26 -0
  139. package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -0
  140. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +94 -0
  141. package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -0
  142. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +1016 -0
  143. package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -0
  144. package/node_modules/glob/node_modules/minimatch/dist/esm/package.json +3 -0
  145. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +22 -0
  146. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -0
  147. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +34 -0
  148. package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -0
  149. package/node_modules/glob/node_modules/minimatch/package.json +67 -0
  150. package/node_modules/glob/package.json +101 -0
  151. package/node_modules/minipass/LICENSE +15 -0
  152. package/node_modules/minipass/README.md +825 -0
  153. package/node_modules/minipass/dist/commonjs/index.d.ts +549 -0
  154. package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -0
  155. package/node_modules/minipass/dist/commonjs/index.js +1028 -0
  156. package/node_modules/minipass/dist/commonjs/index.js.map +1 -0
  157. package/node_modules/minipass/dist/commonjs/package.json +3 -0
  158. package/node_modules/minipass/dist/esm/index.d.ts +549 -0
  159. package/node_modules/minipass/dist/esm/index.d.ts.map +1 -0
  160. package/node_modules/minipass/dist/esm/index.js +1018 -0
  161. package/node_modules/minipass/dist/esm/index.js.map +1 -0
  162. package/node_modules/minipass/dist/esm/package.json +3 -0
  163. package/node_modules/minipass/package.json +82 -0
  164. package/node_modules/package-json-from-dist/LICENSE.md +63 -0
  165. package/node_modules/package-json-from-dist/README.md +110 -0
  166. package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts +89 -0
  167. package/node_modules/package-json-from-dist/dist/commonjs/index.d.ts.map +1 -0
  168. package/node_modules/package-json-from-dist/dist/commonjs/index.js +134 -0
  169. package/node_modules/package-json-from-dist/dist/commonjs/index.js.map +1 -0
  170. package/node_modules/package-json-from-dist/dist/commonjs/package.json +3 -0
  171. package/node_modules/package-json-from-dist/dist/esm/index.d.ts +89 -0
  172. package/node_modules/package-json-from-dist/dist/esm/index.d.ts.map +1 -0
  173. package/node_modules/package-json-from-dist/dist/esm/index.js +129 -0
  174. package/node_modules/package-json-from-dist/dist/esm/index.js.map +1 -0
  175. package/node_modules/package-json-from-dist/dist/esm/package.json +3 -0
  176. package/node_modules/package-json-from-dist/package.json +68 -0
  177. package/node_modules/path-scurry/LICENSE.md +55 -0
  178. package/node_modules/path-scurry/README.md +636 -0
  179. package/node_modules/path-scurry/dist/commonjs/index.d.ts +1115 -0
  180. package/node_modules/path-scurry/dist/commonjs/index.d.ts.map +1 -0
  181. package/node_modules/path-scurry/dist/commonjs/index.js +2018 -0
  182. package/node_modules/path-scurry/dist/commonjs/index.js.map +1 -0
  183. package/node_modules/path-scurry/dist/commonjs/package.json +3 -0
  184. package/node_modules/path-scurry/dist/esm/index.d.ts +1115 -0
  185. package/node_modules/path-scurry/dist/esm/index.d.ts.map +1 -0
  186. package/node_modules/path-scurry/dist/esm/index.js +1983 -0
  187. package/node_modules/path-scurry/dist/esm/index.js.map +1 -0
  188. package/node_modules/path-scurry/dist/esm/package.json +3 -0
  189. package/node_modules/path-scurry/node_modules/lru-cache/LICENSE.md +55 -0
  190. package/node_modules/path-scurry/node_modules/lru-cache/README.md +383 -0
  191. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +1323 -0
  192. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -0
  193. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +1589 -0
  194. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -0
  195. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +2 -0
  196. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +7 -0
  197. package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/package.json +3 -0
  198. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +1323 -0
  199. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -0
  200. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +1585 -0
  201. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -0
  202. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +2 -0
  203. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +7 -0
  204. package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/package.json +3 -0
  205. package/node_modules/path-scurry/node_modules/lru-cache/package.json +101 -0
  206. package/node_modules/path-scurry/package.json +88 -0
  207. package/node_modules/rimraf/LICENSE.md +55 -0
  208. package/node_modules/rimraf/README.md +226 -0
  209. package/node_modules/rimraf/dist/commonjs/default-tmp.d.ts +3 -0
  210. package/node_modules/rimraf/dist/commonjs/default-tmp.d.ts.map +1 -0
  211. package/node_modules/rimraf/dist/commonjs/default-tmp.js +58 -0
  212. package/node_modules/rimraf/dist/commonjs/default-tmp.js.map +1 -0
  213. package/node_modules/rimraf/dist/commonjs/error.d.ts +6 -0
  214. package/node_modules/rimraf/dist/commonjs/error.d.ts.map +1 -0
  215. package/node_modules/rimraf/dist/commonjs/error.js +10 -0
  216. package/node_modules/rimraf/dist/commonjs/error.js.map +1 -0
  217. package/node_modules/rimraf/dist/commonjs/fix-eperm.d.ts +3 -0
  218. package/node_modules/rimraf/dist/commonjs/fix-eperm.d.ts.map +1 -0
  219. package/node_modules/rimraf/dist/commonjs/fix-eperm.js +38 -0
  220. package/node_modules/rimraf/dist/commonjs/fix-eperm.js.map +1 -0
  221. package/node_modules/rimraf/dist/commonjs/fs.d.ts +15 -0
  222. package/node_modules/rimraf/dist/commonjs/fs.d.ts.map +1 -0
  223. package/node_modules/rimraf/dist/commonjs/fs.js +33 -0
  224. package/node_modules/rimraf/dist/commonjs/fs.js.map +1 -0
  225. package/node_modules/rimraf/dist/commonjs/ignore-enoent.d.ts +3 -0
  226. package/node_modules/rimraf/dist/commonjs/ignore-enoent.d.ts.map +1 -0
  227. package/node_modules/rimraf/dist/commonjs/ignore-enoent.js +24 -0
  228. package/node_modules/rimraf/dist/commonjs/ignore-enoent.js.map +1 -0
  229. package/node_modules/rimraf/dist/commonjs/index.d.ts +50 -0
  230. package/node_modules/rimraf/dist/commonjs/index.d.ts.map +1 -0
  231. package/node_modules/rimraf/dist/commonjs/index.js +78 -0
  232. package/node_modules/rimraf/dist/commonjs/index.js.map +1 -0
  233. package/node_modules/rimraf/dist/commonjs/opt-arg.d.ts +34 -0
  234. package/node_modules/rimraf/dist/commonjs/opt-arg.d.ts.map +1 -0
  235. package/node_modules/rimraf/dist/commonjs/opt-arg.js +53 -0
  236. package/node_modules/rimraf/dist/commonjs/opt-arg.js.map +1 -0
  237. package/node_modules/rimraf/dist/commonjs/package.json +3 -0
  238. package/node_modules/rimraf/dist/commonjs/path-arg.d.ts +4 -0
  239. package/node_modules/rimraf/dist/commonjs/path-arg.d.ts.map +1 -0
  240. package/node_modules/rimraf/dist/commonjs/path-arg.js +48 -0
  241. package/node_modules/rimraf/dist/commonjs/path-arg.js.map +1 -0
  242. package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts +3 -0
  243. package/node_modules/rimraf/dist/commonjs/readdir-or-error.d.ts.map +1 -0
  244. package/node_modules/rimraf/dist/commonjs/readdir-or-error.js +19 -0
  245. package/node_modules/rimraf/dist/commonjs/readdir-or-error.js.map +1 -0
  246. package/node_modules/rimraf/dist/commonjs/retry-busy.d.ts +8 -0
  247. package/node_modules/rimraf/dist/commonjs/retry-busy.d.ts.map +1 -0
  248. package/node_modules/rimraf/dist/commonjs/retry-busy.js +65 -0
  249. package/node_modules/rimraf/dist/commonjs/retry-busy.js.map +1 -0
  250. package/node_modules/rimraf/dist/commonjs/rimraf-manual.d.ts +3 -0
  251. package/node_modules/rimraf/dist/commonjs/rimraf-manual.d.ts.map +1 -0
  252. package/node_modules/rimraf/dist/commonjs/rimraf-manual.js +8 -0
  253. package/node_modules/rimraf/dist/commonjs/rimraf-manual.js.map +1 -0
  254. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.d.ts +4 -0
  255. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.d.ts.map +1 -0
  256. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.js +138 -0
  257. package/node_modules/rimraf/dist/commonjs/rimraf-move-remove.js.map +1 -0
  258. package/node_modules/rimraf/dist/commonjs/rimraf-native.d.ts +4 -0
  259. package/node_modules/rimraf/dist/commonjs/rimraf-native.d.ts.map +1 -0
  260. package/node_modules/rimraf/dist/commonjs/rimraf-native.js +24 -0
  261. package/node_modules/rimraf/dist/commonjs/rimraf-native.js.map +1 -0
  262. package/node_modules/rimraf/dist/commonjs/rimraf-posix.d.ts +4 -0
  263. package/node_modules/rimraf/dist/commonjs/rimraf-posix.d.ts.map +1 -0
  264. package/node_modules/rimraf/dist/commonjs/rimraf-posix.js +103 -0
  265. package/node_modules/rimraf/dist/commonjs/rimraf-posix.js.map +1 -0
  266. package/node_modules/rimraf/dist/commonjs/rimraf-windows.d.ts +4 -0
  267. package/node_modules/rimraf/dist/commonjs/rimraf-windows.d.ts.map +1 -0
  268. package/node_modules/rimraf/dist/commonjs/rimraf-windows.js +159 -0
  269. package/node_modules/rimraf/dist/commonjs/rimraf-windows.js.map +1 -0
  270. package/node_modules/rimraf/dist/commonjs/use-native.d.ts +4 -0
  271. package/node_modules/rimraf/dist/commonjs/use-native.d.ts.map +1 -0
  272. package/node_modules/rimraf/dist/commonjs/use-native.js +18 -0
  273. package/node_modules/rimraf/dist/commonjs/use-native.js.map +1 -0
  274. package/node_modules/rimraf/dist/esm/bin.d.mts +3 -0
  275. package/node_modules/rimraf/dist/esm/bin.d.mts.map +1 -0
  276. package/node_modules/rimraf/dist/esm/bin.mjs +250 -0
  277. package/node_modules/rimraf/dist/esm/bin.mjs.map +1 -0
  278. package/node_modules/rimraf/dist/esm/default-tmp.d.ts +3 -0
  279. package/node_modules/rimraf/dist/esm/default-tmp.d.ts.map +1 -0
  280. package/node_modules/rimraf/dist/esm/default-tmp.js +55 -0
  281. package/node_modules/rimraf/dist/esm/default-tmp.js.map +1 -0
  282. package/node_modules/rimraf/dist/esm/error.d.ts +6 -0
  283. package/node_modules/rimraf/dist/esm/error.d.ts.map +1 -0
  284. package/node_modules/rimraf/dist/esm/error.js +5 -0
  285. package/node_modules/rimraf/dist/esm/error.js.map +1 -0
  286. package/node_modules/rimraf/dist/esm/fix-eperm.d.ts +3 -0
  287. package/node_modules/rimraf/dist/esm/fix-eperm.d.ts.map +1 -0
  288. package/node_modules/rimraf/dist/esm/fix-eperm.js +33 -0
  289. package/node_modules/rimraf/dist/esm/fix-eperm.js.map +1 -0
  290. package/node_modules/rimraf/dist/esm/fs.d.ts +15 -0
  291. package/node_modules/rimraf/dist/esm/fs.d.ts.map +1 -0
  292. package/node_modules/rimraf/dist/esm/fs.js +18 -0
  293. package/node_modules/rimraf/dist/esm/fs.js.map +1 -0
  294. package/node_modules/rimraf/dist/esm/ignore-enoent.d.ts +3 -0
  295. package/node_modules/rimraf/dist/esm/ignore-enoent.d.ts.map +1 -0
  296. package/node_modules/rimraf/dist/esm/ignore-enoent.js +19 -0
  297. package/node_modules/rimraf/dist/esm/ignore-enoent.js.map +1 -0
  298. package/node_modules/rimraf/dist/esm/index.d.ts +50 -0
  299. package/node_modules/rimraf/dist/esm/index.d.ts.map +1 -0
  300. package/node_modules/rimraf/dist/esm/index.js +70 -0
  301. package/node_modules/rimraf/dist/esm/index.js.map +1 -0
  302. package/node_modules/rimraf/dist/esm/opt-arg.d.ts +34 -0
  303. package/node_modules/rimraf/dist/esm/opt-arg.d.ts.map +1 -0
  304. package/node_modules/rimraf/dist/esm/opt-arg.js +46 -0
  305. package/node_modules/rimraf/dist/esm/opt-arg.js.map +1 -0
  306. package/node_modules/rimraf/dist/esm/package.json +3 -0
  307. package/node_modules/rimraf/dist/esm/path-arg.d.ts +4 -0
  308. package/node_modules/rimraf/dist/esm/path-arg.d.ts.map +1 -0
  309. package/node_modules/rimraf/dist/esm/path-arg.js +46 -0
  310. package/node_modules/rimraf/dist/esm/path-arg.js.map +1 -0
  311. package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts +3 -0
  312. package/node_modules/rimraf/dist/esm/readdir-or-error.d.ts.map +1 -0
  313. package/node_modules/rimraf/dist/esm/readdir-or-error.js +14 -0
  314. package/node_modules/rimraf/dist/esm/readdir-or-error.js.map +1 -0
  315. package/node_modules/rimraf/dist/esm/retry-busy.d.ts +8 -0
  316. package/node_modules/rimraf/dist/esm/retry-busy.d.ts.map +1 -0
  317. package/node_modules/rimraf/dist/esm/retry-busy.js +60 -0
  318. package/node_modules/rimraf/dist/esm/retry-busy.js.map +1 -0
  319. package/node_modules/rimraf/dist/esm/rimraf-manual.d.ts +3 -0
  320. package/node_modules/rimraf/dist/esm/rimraf-manual.d.ts.map +1 -0
  321. package/node_modules/rimraf/dist/esm/rimraf-manual.js +5 -0
  322. package/node_modules/rimraf/dist/esm/rimraf-manual.js.map +1 -0
  323. package/node_modules/rimraf/dist/esm/rimraf-move-remove.d.ts +4 -0
  324. package/node_modules/rimraf/dist/esm/rimraf-move-remove.d.ts.map +1 -0
  325. package/node_modules/rimraf/dist/esm/rimraf-move-remove.js +133 -0
  326. package/node_modules/rimraf/dist/esm/rimraf-move-remove.js.map +1 -0
  327. package/node_modules/rimraf/dist/esm/rimraf-native.d.ts +4 -0
  328. package/node_modules/rimraf/dist/esm/rimraf-native.d.ts.map +1 -0
  329. package/node_modules/rimraf/dist/esm/rimraf-native.js +19 -0
  330. package/node_modules/rimraf/dist/esm/rimraf-native.js.map +1 -0
  331. package/node_modules/rimraf/dist/esm/rimraf-posix.d.ts +4 -0
  332. package/node_modules/rimraf/dist/esm/rimraf-posix.d.ts.map +1 -0
  333. package/node_modules/rimraf/dist/esm/rimraf-posix.js +98 -0
  334. package/node_modules/rimraf/dist/esm/rimraf-posix.js.map +1 -0
  335. package/node_modules/rimraf/dist/esm/rimraf-windows.d.ts +4 -0
  336. package/node_modules/rimraf/dist/esm/rimraf-windows.d.ts.map +1 -0
  337. package/node_modules/rimraf/dist/esm/rimraf-windows.js +154 -0
  338. package/node_modules/rimraf/dist/esm/rimraf-windows.js.map +1 -0
  339. package/node_modules/rimraf/dist/esm/use-native.d.ts +4 -0
  340. package/node_modules/rimraf/dist/esm/use-native.d.ts.map +1 -0
  341. package/node_modules/rimraf/dist/esm/use-native.js +15 -0
  342. package/node_modules/rimraf/dist/esm/use-native.js.map +1 -0
  343. package/node_modules/rimraf/package.json +92 -0
  344. package/package.json +152 -0
  345. package/scripts/install-scanners.js +258 -0
  346. package/scripts/watchdog.js +147 -0
  347. package/src/analyzers/CSSAnalyzer.js +297 -0
  348. package/src/analyzers/ConfigValidator.js +690 -0
  349. package/src/analyzers/ESLintAnalyzer.js +320 -0
  350. package/src/analyzers/JavaScriptAnalyzer.js +261 -0
  351. package/src/analyzers/PrettierFormatter.js +247 -0
  352. package/src/analyzers/PythonAnalyzer.js +283 -0
  353. package/src/analyzers/SecurityAnalyzer.js +729 -0
  354. package/src/analyzers/SparrowAnalyzer.js +341 -0
  355. package/src/analyzers/TypeScriptAnalyzer.js +247 -0
  356. package/src/analyzers/codeCloneDetector/analyzer.js +344 -0
  357. package/src/analyzers/codeCloneDetector/detector.js +250 -0
  358. package/src/analyzers/codeCloneDetector/index.js +192 -0
  359. package/src/analyzers/codeCloneDetector/parser.js +199 -0
  360. package/src/analyzers/codeCloneDetector/reporter.js +148 -0
  361. package/src/analyzers/codeCloneDetector/scanner.js +88 -0
  362. package/src/core/agentPool.js +1957 -0
  363. package/src/core/agentScheduler.js +3212 -0
  364. package/src/core/contextManager.js +709 -0
  365. package/src/core/flowExecutor.js +928 -0
  366. package/src/core/messageProcessor.js +808 -0
  367. package/src/core/orchestrator.js +584 -0
  368. package/src/core/stateManager.js +1500 -0
  369. package/src/index.js +972 -0
  370. package/src/interfaces/cli.js +553 -0
  371. package/src/interfaces/terminal/__tests__/smoke/advancedFeatures.test.js +208 -0
  372. package/src/interfaces/terminal/__tests__/smoke/agentControl.test.js +236 -0
  373. package/src/interfaces/terminal/__tests__/smoke/agents.test.js +138 -0
  374. package/src/interfaces/terminal/__tests__/smoke/components.test.js +137 -0
  375. package/src/interfaces/terminal/__tests__/smoke/connection.test.js +350 -0
  376. package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -0
  377. package/src/interfaces/terminal/__tests__/smoke/imports.test.js +332 -0
  378. package/src/interfaces/terminal/__tests__/smoke/messages.test.js +256 -0
  379. package/src/interfaces/terminal/__tests__/smoke/tools.test.js +388 -0
  380. package/src/interfaces/terminal/api/apiClient.js +299 -0
  381. package/src/interfaces/terminal/api/messageRouter.js +262 -0
  382. package/src/interfaces/terminal/api/session.js +266 -0
  383. package/src/interfaces/terminal/api/websocket.js +497 -0
  384. package/src/interfaces/terminal/components/AgentCreator.js +705 -0
  385. package/src/interfaces/terminal/components/AgentEditor.js +678 -0
  386. package/src/interfaces/terminal/components/AgentSwitcher.js +330 -0
  387. package/src/interfaces/terminal/components/ErrorBoundary.js +92 -0
  388. package/src/interfaces/terminal/components/ErrorPanel.js +264 -0
  389. package/src/interfaces/terminal/components/Header.js +28 -0
  390. package/src/interfaces/terminal/components/HelpPanel.js +231 -0
  391. package/src/interfaces/terminal/components/InputBox.js +118 -0
  392. package/src/interfaces/terminal/components/Layout.js +603 -0
  393. package/src/interfaces/terminal/components/LoadingSpinner.js +71 -0
  394. package/src/interfaces/terminal/components/MessageList.js +281 -0
  395. package/src/interfaces/terminal/components/MultilineTextInput.js +251 -0
  396. package/src/interfaces/terminal/components/SearchPanel.js +265 -0
  397. package/src/interfaces/terminal/components/SettingsPanel.js +415 -0
  398. package/src/interfaces/terminal/components/StatusBar.js +65 -0
  399. package/src/interfaces/terminal/components/TextInput.js +127 -0
  400. package/src/interfaces/terminal/config/agentEditorConstants.js +227 -0
  401. package/src/interfaces/terminal/config/constants.js +393 -0
  402. package/src/interfaces/terminal/index.js +168 -0
  403. package/src/interfaces/terminal/state/useAgentControl.js +496 -0
  404. package/src/interfaces/terminal/state/useAgents.js +537 -0
  405. package/src/interfaces/terminal/state/useConnection.js +444 -0
  406. package/src/interfaces/terminal/state/useMessages.js +630 -0
  407. package/src/interfaces/terminal/state/useTools.js +554 -0
  408. package/src/interfaces/terminal/utils/debugLogger.js +44 -0
  409. package/src/interfaces/terminal/utils/settingsStorage.js +232 -0
  410. package/src/interfaces/terminal/utils/theme.js +85 -0
  411. package/src/interfaces/webServer.js +5457 -0
  412. package/src/modules/fileExplorer/controller.js +413 -0
  413. package/src/modules/fileExplorer/index.js +37 -0
  414. package/src/modules/fileExplorer/middleware.js +92 -0
  415. package/src/modules/fileExplorer/routes.js +158 -0
  416. package/src/modules/fileExplorer/types.js +44 -0
  417. package/src/services/agentActivityService.js +399 -0
  418. package/src/services/aiService.js +2618 -0
  419. package/src/services/apiKeyManager.js +334 -0
  420. package/src/services/benchmarkService.js +196 -0
  421. package/src/services/budgetService.js +565 -0
  422. package/src/services/contextInjectionService.js +268 -0
  423. package/src/services/conversationCompactionService.js +1103 -0
  424. package/src/services/credentialVault.js +685 -0
  425. package/src/services/errorHandler.js +810 -0
  426. package/src/services/fileAttachmentService.js +547 -0
  427. package/src/services/flowContextService.js +189 -0
  428. package/src/services/memoryService.js +521 -0
  429. package/src/services/modelRouterService.js +365 -0
  430. package/src/services/modelsService.js +323 -0
  431. package/src/services/ollamaService.js +452 -0
  432. package/src/services/portRegistry.js +336 -0
  433. package/src/services/portTracker.js +223 -0
  434. package/src/services/projectDetector.js +404 -0
  435. package/src/services/promptService.js +372 -0
  436. package/src/services/qualityInspector.js +796 -0
  437. package/src/services/scheduleService.js +725 -0
  438. package/src/services/serviceRegistry.js +386 -0
  439. package/src/services/skillsService.js +486 -0
  440. package/src/services/telegramService.js +920 -0
  441. package/src/services/tokenCountingService.js +316 -0
  442. package/src/services/visualEditorBridge.js +1033 -0
  443. package/src/services/visualEditorServer.js +1727 -0
  444. package/src/services/whatsappService.js +663 -0
  445. package/src/tools/__tests__/webTool.e2e.test.js +569 -0
  446. package/src/tools/__tests__/webTool.unit.test.js +195 -0
  447. package/src/tools/agentCommunicationTool.js +1343 -0
  448. package/src/tools/agentDelayTool.js +498 -0
  449. package/src/tools/asyncToolManager.js +604 -0
  450. package/src/tools/baseTool.js +887 -0
  451. package/src/tools/browserTool.js +897 -0
  452. package/src/tools/cloneDetectionTool.js +581 -0
  453. package/src/tools/codeMapTool.js +857 -0
  454. package/src/tools/dependencyResolverTool.js +1212 -0
  455. package/src/tools/docxTool.js +623 -0
  456. package/src/tools/excelTool.js +636 -0
  457. package/src/tools/fileContentReplaceTool.js +840 -0
  458. package/src/tools/fileTreeTool.js +833 -0
  459. package/src/tools/filesystemTool.js +1217 -0
  460. package/src/tools/helpTool.js +198 -0
  461. package/src/tools/imageTool.js +1034 -0
  462. package/src/tools/importAnalyzerTool.js +1056 -0
  463. package/src/tools/jobDoneTool.js +388 -0
  464. package/src/tools/memoryTool.js +554 -0
  465. package/src/tools/pdfTool.js +627 -0
  466. package/src/tools/seekTool.js +883 -0
  467. package/src/tools/skillsTool.js +276 -0
  468. package/src/tools/staticAnalysisTool.js +2146 -0
  469. package/src/tools/taskManagerTool.js +2836 -0
  470. package/src/tools/terminalTool.js +2486 -0
  471. package/src/tools/userPromptTool.js +474 -0
  472. package/src/tools/videoTool.js +1139 -0
  473. package/src/tools/visionTool.js +507 -0
  474. package/src/tools/visualEditorTool.js +1175 -0
  475. package/src/tools/webTool.js +3114 -0
  476. package/src/tools/whatsappTool.js +457 -0
  477. package/src/types/agent.js +519 -0
  478. package/src/types/contextReference.js +972 -0
  479. package/src/types/conversation.js +730 -0
  480. package/src/types/toolCommand.js +747 -0
  481. package/src/utilities/attachmentValidator.js +288 -0
  482. package/src/utilities/browserStealth.js +630 -0
  483. package/src/utilities/configManager.js +618 -0
  484. package/src/utilities/constants.js +870 -0
  485. package/src/utilities/directoryAccessManager.js +566 -0
  486. package/src/utilities/fileProcessor.js +307 -0
  487. package/src/utilities/humanBehavior.js +453 -0
  488. package/src/utilities/jsonRepair.js +242 -0
  489. package/src/utilities/logger.js +436 -0
  490. package/src/utilities/platformUtils.js +255 -0
  491. package/src/utilities/platformUtils.test.js +98 -0
  492. package/src/utilities/stealthConstants.js +377 -0
  493. package/src/utilities/structuredFileValidator.js +699 -0
  494. package/src/utilities/tagParser.js +878 -0
  495. package/src/utilities/toolConstants.js +415 -0
  496. package/src/utilities/userDataDir.js +300 -0
  497. package/web-ui/build/brands/autopilot/favicon.svg +1 -0
  498. package/web-ui/build/brands/autopilot/logo.webp +0 -0
  499. package/web-ui/build/brands/onbuzz/favicon.svg +1 -0
  500. package/web-ui/build/brands/onbuzz/logo-text.webp +0 -0
  501. package/web-ui/build/brands/onbuzz/logo.webp +0 -0
  502. package/web-ui/build/index.html +15 -0
  503. package/web-ui/build/logo.png +0 -0
  504. package/web-ui/build/logo2.png +0 -0
  505. package/web-ui/build/static/index-SmQFfvBs.js +746 -0
  506. package/web-ui/build/static/index-V2ySwjHp.css +1 -0
@@ -0,0 +1,2146 @@
1
+ /**
2
+ * StaticAnalysisTool - Static code analysis for finding errors without execution
3
+ *
4
+ * Purpose:
5
+ * - Analyze code files for syntax, type, and import errors
6
+ * - Detect programming languages and frameworks
7
+ * - Provide actionable error references with line numbers
8
+ * - Support single file, multiple files, and project-wide analysis
9
+ * - Use official language parsers for accurate results
10
+ */
11
+
12
+ import { BaseTool } from './baseTool.js';
13
+ import TagParser from '../utilities/tagParser.js';
14
+ import DirectoryAccessManager from '../utilities/directoryAccessManager.js';
15
+ import fs from 'fs/promises';
16
+ import path from 'path';
17
+ import crypto from 'crypto';
18
+
19
+ import {
20
+ STATIC_ANALYSIS,
21
+ TOOL_STATUS,
22
+ SYSTEM_DEFAULTS
23
+ } from '../utilities/constants.js';
24
+ import {
25
+ validateContent,
26
+ validateStructuredFile,
27
+ detectFormat,
28
+ getSupportedFormats
29
+ } from '../utilities/structuredFileValidator.js';
30
+
31
+ class StaticAnalysisTool extends BaseTool {
32
+ constructor(config = {}, logger = null) {
33
+ super(config, logger);
34
+
35
+ // Tool metadata
36
+ this.requiresProject = true;
37
+ this.isAsync = false;
38
+ this.timeout = config.timeout || STATIC_ANALYSIS.ANALYSIS_TIMEOUT;
39
+ this.maxConcurrentOperations = config.maxConcurrentOperations || 1;
40
+
41
+ // Analysis settings
42
+ this.maxFileSize = config.maxFileSize || STATIC_ANALYSIS.MAX_FILE_SIZE_FOR_ANALYSIS;
43
+ this.maxFilesPerBatch = config.maxFilesPerBatch || STATIC_ANALYSIS.MAX_FILES_PER_BATCH;
44
+ this.enableCache = config.enableCache !== false && STATIC_ANALYSIS.ENABLE_CACHE;
45
+
46
+ // Cache for analysis results
47
+ this.analysisCache = new Map();
48
+ this.cacheExpiry = STATIC_ANALYSIS.CACHE_DURATION;
49
+
50
+ // Performance optimization settings
51
+ this.parallelAnalysis = config.parallelAnalysis !== false;
52
+ this.maxParallelFiles = config.maxParallelFiles || 10;
53
+ this.useContentHash = config.useContentHash !== false;
54
+
55
+ // Performance metrics
56
+ this.metrics = {
57
+ totalAnalyses: 0,
58
+ cacheHits: 0,
59
+ cacheMisses: 0,
60
+ totalAnalysisTime: 0,
61
+ filesAnalyzed: 0,
62
+ parallelBatches: 0
63
+ };
64
+
65
+ // Directory access manager
66
+ this.directoryAccessManager = new DirectoryAccessManager(config, logger);
67
+
68
+ // Analyzers will be initialized lazily when needed
69
+ this.analyzers = {
70
+ javascript: null,
71
+ typescript: null,
72
+ python: null,
73
+ css: null,
74
+ scss: null,
75
+ less: null,
76
+ eslint: null,
77
+ security: null,
78
+ config: null,
79
+ sparrow: null // Tree-sitter based SAST
80
+ };
81
+
82
+ // Formatters will be initialized lazily when needed
83
+ this.formatters = {
84
+ prettier: null
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Get tool description for LLM consumption
90
+ * @returns {string} Tool description
91
+ */
92
+ getDescription() {
93
+ return `
94
+ Static Code Analysis Tool: Analyze code files for errors without execution
95
+
96
+ This tool performs static analysis on code files to find syntax errors, type errors, import issues, and other problems without running the code. It uses official language parsers for accurate results.
97
+
98
+ SUPPORTED LANGUAGES:
99
+ - JavaScript (.js, .jsx, .mjs, .cjs)
100
+ - TypeScript (.ts, .tsx)
101
+ - Python (.py)
102
+ - CSS (.css)
103
+ - SCSS (.scss, .sass)
104
+ - LESS (.less)
105
+
106
+ USAGE:
107
+ \`\`\`json
108
+ {
109
+ "toolId": "staticanalysis",
110
+ "actions": [
111
+ {
112
+ "type": "analyze",
113
+ "filePath": "src/index.js"
114
+ },
115
+ {
116
+ "type": "analyze-project",
117
+ "directory": "src",
118
+ "pattern": "**/*.{js,ts,py}"
119
+ }
120
+ ]
121
+ }
122
+ \`\`\`
123
+
124
+ ACTION TYPES:
125
+ - analyze: Analyze a single file
126
+ - analyze-project: Analyze all files in a directory
127
+ - fix: Auto-fix code issues
128
+ - format: Format code with Prettier
129
+ - security-scan: Scan for security vulnerabilities (uses external tools if available)
130
+ - security-scan-project: Scan entire project for security issues
131
+ - sparrow-scan: Tree-sitter based SAST scan (no external dependencies, 12 languages)
132
+ - sparrow-scan-project: Project-wide Sparrow SAST scan
133
+ - validate-config: Validate configuration files
134
+ - validate-config-directory: Validate all config files in directory
135
+ - validate-structured: Validate structured file formats (JSON, YAML, XML, TOML, INI, ENV)
136
+
137
+ STRUCTURED FILE FORMATS SUPPORTED:
138
+ JSON (.json, .jsonc, .json5), YAML (.yaml, .yml), XML (.xml), TOML (.toml), INI (.ini), ENV (.env)
139
+
140
+ SPARROW SAST LANGUAGES:
141
+ Python, JavaScript, TypeScript, Go, Java, Ruby, Rust, PHP, C#, Bash, HTML, CSS
142
+
143
+ PARAMETERS:
144
+ - filePath: Path to file to analyze (for single file actions)
145
+ - directory: Directory to analyze (for project-wide actions)
146
+ - pattern: Glob pattern for files to include (optional)
147
+ - includeWarnings: Include warnings in results (true/false, default: true)
148
+ - maxErrors: Maximum number of errors to return (default: all)
149
+ - writeFile: Write fixed/formatted content back to file (for fix/format actions)
150
+ - content: Inline content to validate (for validate-structured, requires format)
151
+ - format: Format override (json, yaml, xml, toml, ini, env) - auto-detected from filePath if not specified
152
+
153
+ EXAMPLES:
154
+
155
+ 1. Analyze a single JavaScript file:
156
+ \`\`\`json
157
+ {
158
+ "toolId": "staticanalysis",
159
+ "actions": [{ "type": "analyze", "filePath": "src/app.js" }]
160
+ }
161
+ \`\`\`
162
+
163
+ 2. Analyze all files in a directory:
164
+ \`\`\`json
165
+ {
166
+ "toolId": "staticanalysis",
167
+ "actions": [{ "type": "analyze-project", "directory": "src", "pattern": "**/*.js" }]
168
+ }
169
+ \`\`\`
170
+
171
+ 3. Auto-fix code issues:
172
+ \`\`\`json
173
+ {
174
+ "toolId": "staticanalysis",
175
+ "actions": [{ "type": "fix", "filePath": "src/app.js", "writeFile": true }]
176
+ }
177
+ \`\`\`
178
+
179
+ 4. Security scan a project:
180
+ \`\`\`json
181
+ {
182
+ "toolId": "staticanalysis",
183
+ "actions": [{ "type": "security-scan-project", "directory": "src" }]
184
+ }
185
+ \`\`\`
186
+
187
+ 5. Validate a JSON/YAML/XML file structure:
188
+ \`\`\`json
189
+ {
190
+ "toolId": "staticanalysis",
191
+ "actions": [{ "type": "validate-structured", "filePath": "config.json" }]
192
+ }
193
+ \`\`\`
194
+
195
+ 6. Validate inline content (specify format):
196
+ \`\`\`json
197
+ {
198
+ "toolId": "staticanalysis",
199
+ "actions": [{ "type": "validate-structured", "content": "{\"key\": \"value\"}", "format": "json" }]
200
+ }
201
+ \`\`\`
202
+
203
+ OUTPUT FORMAT:
204
+ Returns structured error information with: file, line, column, severity, rule, message, category, fixable, suggestion
205
+
206
+ LIMITATIONS:
207
+ - File size limit: ${Math.round(this.maxFileSize / 1024 / 1024)}MB per file
208
+ - Batch limit: ${this.maxFilesPerBatch} files per operation
209
+ - Analysis timeout: ${this.timeout / 1000} seconds
210
+ `;
211
+ }
212
+
213
+ /**
214
+ * Parse parameters from tool command content
215
+ * @param {string} content - Raw tool command content
216
+ * @returns {Object} Parsed parameters
217
+ */
218
+ parseParameters(content) {
219
+ try {
220
+ const params = {};
221
+ const actions = [];
222
+
223
+ this.logger?.debug('StaticAnalysis tool parsing parameters', {
224
+ contentLength: content.length,
225
+ contentPreview: content.substring(0, 200)
226
+ });
227
+
228
+ // Extract self-closing <analyze> tags
229
+ // Pattern: <analyze ...attributes... />
230
+ // We need to capture everything between 'analyze' and '/>' which includes file paths with /
231
+ const analyzePattern = /<analyze\s+(.+?)\/>/g;
232
+ let match;
233
+
234
+ while ((match = analyzePattern.exec(content)) !== null) {
235
+ const attributeString = match[1].trim();
236
+ const parser = new TagParser();
237
+ const attributes = parser.parseAttributes(attributeString);
238
+
239
+ const action = {
240
+ type: 'analyze',
241
+ ...attributes
242
+ };
243
+
244
+ // Normalize attribute names
245
+ if (action['file-path']) {
246
+ action.filePath = action['file-path'];
247
+ delete action['file-path'];
248
+ }
249
+ if (action['include-warnings']) {
250
+ action.includeWarnings = action['include-warnings'] === 'true';
251
+ delete action['include-warnings'];
252
+ }
253
+ if (action['max-errors']) {
254
+ action.maxErrors = parseInt(action['max-errors'], 10);
255
+ delete action['max-errors'];
256
+ }
257
+
258
+ actions.push(action);
259
+ }
260
+
261
+ // Extract self-closing <analyze-project> tags
262
+ const projectPattern = /<analyze-project\s+(.+?)\/>/g;
263
+
264
+ while ((match = projectPattern.exec(content)) !== null) {
265
+ const attributeString = match[1].trim();
266
+ const parser = new TagParser();
267
+ const attributes = parser.parseAttributes(attributeString);
268
+
269
+ const action = {
270
+ type: 'analyze-project',
271
+ ...attributes
272
+ };
273
+
274
+ // Normalize attribute names
275
+ if (action['include-warnings']) {
276
+ action.includeWarnings = action['include-warnings'] === 'true';
277
+ delete action['include-warnings'];
278
+ }
279
+ if (action['max-errors']) {
280
+ action.maxErrors = parseInt(action['max-errors'], 10);
281
+ delete action['max-errors'];
282
+ }
283
+
284
+ actions.push(action);
285
+ }
286
+
287
+ // Extract self-closing <fix> tags
288
+ const fixPattern = /<fix\s+(.+?)\/>/g;
289
+
290
+ while ((match = fixPattern.exec(content)) !== null) {
291
+ const attributeString = match[1].trim();
292
+ const parser = new TagParser();
293
+ const attributes = parser.parseAttributes(attributeString);
294
+
295
+ const action = {
296
+ type: 'fix',
297
+ ...attributes
298
+ };
299
+
300
+ // Normalize attribute names
301
+ if (action['file-path']) {
302
+ action.filePath = action['file-path'];
303
+ delete action['file-path'];
304
+ }
305
+ if (action['write-file']) {
306
+ action.writeFile = action['write-file'] === 'true';
307
+ delete action['write-file'];
308
+ }
309
+
310
+ actions.push(action);
311
+ }
312
+
313
+ // Extract self-closing <format> tags
314
+ const formatPattern = /<format\s+(.+?)\/>/g;
315
+
316
+ while ((match = formatPattern.exec(content)) !== null) {
317
+ const attributeString = match[1].trim();
318
+ const parser = new TagParser();
319
+ const attributes = parser.parseAttributes(attributeString);
320
+
321
+ const action = {
322
+ type: 'format',
323
+ ...attributes
324
+ };
325
+
326
+ // Normalize attribute names
327
+ if (action['file-path']) {
328
+ action.filePath = action['file-path'];
329
+ delete action['file-path'];
330
+ }
331
+ if (action['write-file']) {
332
+ action.writeFile = action['write-file'] === 'true';
333
+ delete action['write-file'];
334
+ }
335
+
336
+ actions.push(action);
337
+ }
338
+
339
+ // Extract self-closing <security-scan> tags
340
+ const securityScanPattern = /<security-scan\s+(.+?)\/>/g;
341
+
342
+ while ((match = securityScanPattern.exec(content)) !== null) {
343
+ const attributeString = match[1].trim();
344
+ const parser = new TagParser();
345
+ const attributes = parser.parseAttributes(attributeString);
346
+
347
+ const action = {
348
+ type: 'security-scan',
349
+ ...attributes
350
+ };
351
+
352
+ // Normalize attribute names
353
+ if (action['file-path']) {
354
+ action.filePath = action['file-path'];
355
+ delete action['file-path'];
356
+ }
357
+ if (action['skip-test-files']) {
358
+ action.skipTestFiles = action['skip-test-files'] === 'true';
359
+ delete action['skip-test-files'];
360
+ }
361
+
362
+ actions.push(action);
363
+ }
364
+
365
+ // Extract self-closing <security-scan-project> tags
366
+ const securityScanProjectPattern = /<security-scan-project\s+(.+?)\/>/g;
367
+
368
+ while ((match = securityScanProjectPattern.exec(content)) !== null) {
369
+ const attributeString = match[1].trim();
370
+ const parser = new TagParser();
371
+ const attributes = parser.parseAttributes(attributeString);
372
+
373
+ const action = {
374
+ type: 'security-scan-project',
375
+ ...attributes
376
+ };
377
+
378
+ // Normalize attribute names
379
+ if (action['skip-test-files']) {
380
+ action.skipTestFiles = action['skip-test-files'] === 'true';
381
+ delete action['skip-test-files'];
382
+ }
383
+
384
+ actions.push(action);
385
+ }
386
+
387
+ // Extract self-closing <validate-config> tags
388
+ const validateConfigPattern = /<validate-config\s+(.+?)\/>/g;
389
+
390
+ while ((match = validateConfigPattern.exec(content)) !== null) {
391
+ const attributeString = match[1].trim();
392
+ const parser = new TagParser();
393
+ const attributes = parser.parseAttributes(attributeString);
394
+
395
+ const action = {
396
+ type: 'validate-config',
397
+ ...attributes
398
+ };
399
+
400
+ // Normalize attribute names
401
+ if (action['file-path']) {
402
+ action.filePath = action['file-path'];
403
+ delete action['file-path'];
404
+ }
405
+
406
+ actions.push(action);
407
+ }
408
+
409
+ // Extract self-closing <validate-config-directory> tags
410
+ const validateConfigDirPattern = /<validate-config-directory\s+(.+?)\/>/g;
411
+
412
+ while ((match = validateConfigDirPattern.exec(content)) !== null) {
413
+ const attributeString = match[1].trim();
414
+ const parser = new TagParser();
415
+ const attributes = parser.parseAttributes(attributeString);
416
+
417
+ const action = {
418
+ type: 'validate-config-directory',
419
+ ...attributes
420
+ };
421
+
422
+ // Normalize attribute names (none specific yet)
423
+
424
+ actions.push(action);
425
+ }
426
+
427
+ // Extract self-closing <validate-structured> tags
428
+ const validateStructuredPattern = /<validate-structured\s+(.+?)\/>/g;
429
+
430
+ while ((match = validateStructuredPattern.exec(content)) !== null) {
431
+ const attributeString = match[1].trim();
432
+ const parser = new TagParser();
433
+ const attributes = parser.parseAttributes(attributeString);
434
+
435
+ const action = {
436
+ type: 'validate-structured',
437
+ ...attributes
438
+ };
439
+
440
+ // Normalize attribute names
441
+ if (action['file-path']) {
442
+ action.filePath = action['file-path'];
443
+ delete action['file-path'];
444
+ }
445
+ if (action['format']) {
446
+ action.format = action['format'];
447
+ }
448
+
449
+ actions.push(action);
450
+ }
451
+
452
+ params.actions = actions;
453
+ params.rawContent = content.trim();
454
+
455
+ this.logger?.debug('Parsed StaticAnalysis tool parameters', {
456
+ totalActions: actions.length,
457
+ actionTypes: actions.map(a => a.type)
458
+ });
459
+
460
+ return params;
461
+
462
+ } catch (error) {
463
+ throw new Error(`Failed to parse static analysis parameters: ${error.message}`);
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Get required parameters
469
+ * @returns {Array<string>} Array of required parameter names
470
+ */
471
+ getRequiredParameters() {
472
+ return ['actions'];
473
+ }
474
+
475
+ /**
476
+ * Custom parameter validation
477
+ * @param {Object} params - Parameters to validate
478
+ * @returns {Object} Validation result
479
+ */
480
+ customValidateParameters(params) {
481
+ const errors = [];
482
+
483
+ if (!params.actions || !Array.isArray(params.actions) || params.actions.length === 0) {
484
+ errors.push('At least one action is required');
485
+ } else {
486
+ // Validate each action
487
+ for (const [index, action] of params.actions.entries()) {
488
+ if (!action.type) {
489
+ errors.push(`Action ${index + 1}: type is required`);
490
+ continue;
491
+ }
492
+
493
+ switch (action.type) {
494
+ case 'analyze':
495
+ if (!action.filePath) {
496
+ errors.push(`Action ${index + 1}: file-path is required for analyze`);
497
+ }
498
+ break;
499
+
500
+ case 'analyze-project':
501
+ if (!action.directory) {
502
+ errors.push(`Action ${index + 1}: directory is required for analyze-project`);
503
+ }
504
+ break;
505
+
506
+ case 'fix':
507
+ if (!action.filePath) {
508
+ errors.push(`Action ${index + 1}: file-path is required for fix`);
509
+ }
510
+ break;
511
+
512
+ case 'format':
513
+ if (!action.filePath) {
514
+ errors.push(`Action ${index + 1}: file-path is required for format`);
515
+ }
516
+ break;
517
+
518
+ case 'security-scan':
519
+ if (!action.filePath) {
520
+ errors.push(`Action ${index + 1}: file-path is required for security-scan`);
521
+ }
522
+ break;
523
+
524
+ case 'security-scan-project':
525
+ if (!action.directory) {
526
+ errors.push(`Action ${index + 1}: directory is required for security-scan-project`);
527
+ }
528
+ break;
529
+
530
+ case 'validate-config':
531
+ if (!action.filePath) {
532
+ errors.push(`Action ${index + 1}: file-path is required for validate-config`);
533
+ }
534
+ break;
535
+
536
+ case 'validate-config-directory':
537
+ if (!action.directory) {
538
+ errors.push(`Action ${index + 1}: directory is required for validate-config-directory`);
539
+ }
540
+ break;
541
+
542
+ case 'sparrow-scan':
543
+ if (!action.filePath) {
544
+ errors.push(`Action ${index + 1}: file-path is required for sparrow-scan`);
545
+ }
546
+ break;
547
+
548
+ case 'sparrow-scan-project':
549
+ if (!action.directory) {
550
+ errors.push(`Action ${index + 1}: directory is required for sparrow-scan-project`);
551
+ }
552
+ break;
553
+
554
+ case 'validate-structured':
555
+ if (!action.filePath && !action.content) {
556
+ errors.push(`Action ${index + 1}: file-path or content is required for validate-structured`);
557
+ }
558
+ break;
559
+
560
+ default:
561
+ errors.push(`Action ${index + 1}: unknown action type: ${action.type}`);
562
+ }
563
+ }
564
+
565
+ // Check batch size limit
566
+ if (params.actions.length > this.maxFilesPerBatch) {
567
+ errors.push(`Too many actions: ${params.actions.length} (max ${this.maxFilesPerBatch})`);
568
+ }
569
+ }
570
+
571
+ return {
572
+ valid: errors.length === 0,
573
+ errors
574
+ };
575
+ }
576
+
577
+ /**
578
+ * Execute tool with parsed parameters
579
+ * @param {Object} params - Parsed parameters
580
+ * @param {Object} context - Execution context
581
+ * @returns {Promise<Object>} Execution result
582
+ */
583
+ async execute(params, context) {
584
+ const { actions } = params;
585
+ const { projectDir, agentId, directoryAccess } = context;
586
+
587
+ // Get directory access configuration
588
+ const accessConfig = directoryAccess ||
589
+ this.directoryAccessManager.createDirectoryAccess({
590
+ workingDirectory: projectDir || process.cwd(),
591
+ writeEnabledDirectories: [projectDir || process.cwd()],
592
+ restrictToProject: true
593
+ });
594
+
595
+ const workingDir = this.directoryAccessManager.getWorkingDirectory(accessConfig);
596
+ const results = {
597
+ files: [],
598
+ summary: {
599
+ totalFiles: 0,
600
+ totalErrors: 0,
601
+ totalWarnings: 0,
602
+ totalInfo: 0,
603
+ errorsByCategory: {},
604
+ filesByLanguage: {},
605
+ filesWithErrors: 0
606
+ }
607
+ };
608
+
609
+ for (const action of actions) {
610
+ try {
611
+ let actionResult;
612
+
613
+ switch (action.type) {
614
+ case 'analyze':
615
+ actionResult = await this.analyzeFile(action.filePath, workingDir, accessConfig, action);
616
+ if (actionResult) {
617
+ results.files.push(actionResult);
618
+ this.updateSummary(results.summary, actionResult);
619
+ }
620
+ break;
621
+
622
+ case 'analyze-project':
623
+ const projectFiles = await this.analyzeProject(action.directory, action.pattern, workingDir, accessConfig, action);
624
+ results.files.push(...projectFiles);
625
+ for (const fileResult of projectFiles) {
626
+ this.updateSummary(results.summary, fileResult);
627
+ }
628
+ break;
629
+
630
+ case 'fix':
631
+ actionResult = await this.fixFile(action.filePath, workingDir, accessConfig, action);
632
+ if (actionResult) {
633
+ results.files.push(actionResult);
634
+ }
635
+ break;
636
+
637
+ case 'format':
638
+ actionResult = await this.formatFile(action.filePath, workingDir, accessConfig, action);
639
+ if (actionResult) {
640
+ results.files.push(actionResult);
641
+ }
642
+ break;
643
+
644
+ case 'security-scan':
645
+ actionResult = await this.securityScanFile(action.filePath, workingDir, accessConfig, action);
646
+ if (actionResult) {
647
+ results.files.push(actionResult);
648
+ this.updateSummary(results.summary, actionResult);
649
+ }
650
+ break;
651
+
652
+ case 'security-scan-project':
653
+ const securityProjectFiles = await this.securityScanProject(action.directory, action.pattern, workingDir, accessConfig, action);
654
+ results.files.push(...securityProjectFiles);
655
+ for (const fileResult of securityProjectFiles) {
656
+ this.updateSummary(results.summary, fileResult);
657
+ }
658
+ break;
659
+
660
+ case 'validate-config':
661
+ actionResult = await this.validateConfigFile(action.filePath, workingDir, accessConfig, action);
662
+ if (actionResult) {
663
+ results.files.push(actionResult);
664
+ this.updateSummary(results.summary, actionResult);
665
+ }
666
+ break;
667
+
668
+ case 'validate-config-directory':
669
+ const configFiles = await this.validateConfigDirectory(action.directory, workingDir, accessConfig, action);
670
+ results.files.push(...configFiles);
671
+ for (const fileResult of configFiles) {
672
+ this.updateSummary(results.summary, fileResult);
673
+ }
674
+ break;
675
+
676
+ case 'sparrow-scan':
677
+ actionResult = await this.sparrowScanFile(action.filePath, workingDir, accessConfig, action);
678
+ if (actionResult) {
679
+ results.files.push(actionResult);
680
+ this.updateSummary(results.summary, actionResult);
681
+ }
682
+ break;
683
+
684
+ case 'sparrow-scan-project':
685
+ const sparrowResult = await this.sparrowScanProject(action.directory, action.pattern, workingDir, accessConfig, action);
686
+ if (sparrowResult.files) {
687
+ results.files.push(...sparrowResult.files);
688
+ for (const fileResult of sparrowResult.files) {
689
+ this.updateSummary(results.summary, fileResult);
690
+ }
691
+ }
692
+ break;
693
+
694
+ case 'validate-structured':
695
+ actionResult = await this.validateStructuredFile(action.filePath, action.content, action.format, workingDir, accessConfig, action);
696
+ if (actionResult) {
697
+ results.files.push(actionResult);
698
+ this.updateSummary(results.summary, actionResult);
699
+ }
700
+ break;
701
+
702
+ default:
703
+ throw new Error(`Unknown action type: ${action.type}`);
704
+ }
705
+
706
+ } catch (error) {
707
+ this.logger?.error('Static analysis action failed', {
708
+ action: action.type,
709
+ error: error.message
710
+ });
711
+
712
+ results.files.push({
713
+ file: action.filePath || action.directory,
714
+ error: error.message,
715
+ success: false
716
+ });
717
+ }
718
+ }
719
+
720
+ return {
721
+ success: true,
722
+ results,
723
+ toolUsed: 'staticanalysis',
724
+ performance: this.getPerformanceMetrics()
725
+ };
726
+ }
727
+
728
+ /**
729
+ * Analyze a single file
730
+ * @private
731
+ */
732
+ async analyzeFile(filePath, workingDir, accessConfig, options = {}) {
733
+ const fullPath = path.isAbsolute(filePath)
734
+ ? path.normalize(filePath)
735
+ : path.resolve(workingDir, filePath);
736
+
737
+ // Validate read access
738
+ const accessResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
739
+ if (!accessResult.allowed) {
740
+ throw new Error(`Read access denied: ${accessResult.reason}`);
741
+ }
742
+
743
+ // Check file exists
744
+ try {
745
+ const stats = await fs.stat(fullPath);
746
+
747
+ if (stats.size > this.maxFileSize) {
748
+ throw new Error(`File too large: ${stats.size} bytes (max ${this.maxFileSize})`);
749
+ }
750
+
751
+ // Detect language from file extension
752
+ const language = this.detectLanguage(fullPath);
753
+
754
+ if (!language) {
755
+ return {
756
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
757
+ fullPath,
758
+ language: 'unknown',
759
+ errors: [],
760
+ warnings: [],
761
+ info: [],
762
+ skipped: true,
763
+ skipReason: 'Unsupported file type'
764
+ };
765
+ }
766
+
767
+ // Read file content
768
+ const content = await fs.readFile(fullPath, 'utf-8');
769
+
770
+ // Check cache (use content hash for more accurate caching)
771
+ const contentHash = this.useContentHash ? this.computeContentHash(content) : null;
772
+ const cacheKey = this.useContentHash
773
+ ? `${fullPath}:${contentHash}`
774
+ : `${fullPath}:${stats.mtime.getTime()}`;
775
+
776
+ if (this.enableCache && this.analysisCache.has(cacheKey)) {
777
+ const cached = this.analysisCache.get(cacheKey);
778
+ if (Date.now() - cached.timestamp < this.cacheExpiry) {
779
+ this.logger?.debug('Using cached analysis result', { file: fullPath });
780
+ this.metrics.cacheHits++;
781
+ this.metrics.totalAnalyses++;
782
+ return cached.result;
783
+ }
784
+ }
785
+
786
+ this.metrics.cacheMisses++;
787
+ this.metrics.totalAnalyses++;
788
+
789
+ // Get analyzer for language
790
+ const analyzer = await this.getAnalyzer(language);
791
+
792
+ if (!analyzer) {
793
+ return {
794
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
795
+ fullPath,
796
+ language,
797
+ errors: [],
798
+ warnings: [],
799
+ info: [],
800
+ skipped: true,
801
+ skipReason: `No analyzer available for ${language}`
802
+ };
803
+ }
804
+
805
+ // Perform analysis with timing
806
+ const analysisStart = Date.now();
807
+ const diagnostics = await analyzer.analyze(fullPath, content, {
808
+ workingDir,
809
+ accessConfig,
810
+ framework: await this.detectFramework(workingDir, language)
811
+ });
812
+ const analysisTime = Date.now() - analysisStart;
813
+
814
+ this.metrics.totalAnalysisTime += analysisTime;
815
+ this.metrics.filesAnalyzed++;
816
+
817
+ // Format results
818
+ const result = {
819
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
820
+ fullPath,
821
+ language,
822
+ framework: await this.detectFramework(workingDir, language),
823
+ errors: diagnostics.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.ERROR),
824
+ warnings: options.includeWarnings !== false
825
+ ? diagnostics.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.WARNING)
826
+ : [],
827
+ info: diagnostics.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.INFO),
828
+ totalIssues: diagnostics.length,
829
+ analyzed: true,
830
+ timestamp: new Date().toISOString()
831
+ };
832
+
833
+ // Apply max errors limit
834
+ if (options.maxErrors && result.errors.length > options.maxErrors) {
835
+ result.errors = result.errors.slice(0, options.maxErrors);
836
+ result.truncated = true;
837
+ }
838
+
839
+ // Cache result
840
+ if (this.enableCache) {
841
+ this.analysisCache.set(cacheKey, {
842
+ result,
843
+ timestamp: Date.now()
844
+ });
845
+ }
846
+
847
+ return result;
848
+
849
+ } catch (error) {
850
+ throw new Error(`Failed to analyze ${filePath}: ${error.message}`);
851
+ }
852
+ }
853
+
854
+ /**
855
+ * Analyze project directory
856
+ * @private
857
+ */
858
+ async analyzeProject(directory, pattern, workingDir, accessConfig, options = {}) {
859
+ const fullDir = path.isAbsolute(directory)
860
+ ? path.normalize(directory)
861
+ : path.resolve(workingDir, directory);
862
+
863
+ // Validate read access
864
+ const accessResult = this.directoryAccessManager.validateReadAccess(fullDir, accessConfig);
865
+ if (!accessResult.allowed) {
866
+ throw new Error(`Read access denied: ${accessResult.reason}`);
867
+ }
868
+
869
+ // Find all matching files
870
+ const files = await this.findFiles(fullDir, pattern);
871
+
872
+ if (files.length > this.maxFilesPerBatch) {
873
+ throw new Error(`Too many files: ${files.length} (max ${this.maxFilesPerBatch})`);
874
+ }
875
+
876
+ // Analyze files (parallel or sequential based on configuration)
877
+ const results = [];
878
+
879
+ if (this.parallelAnalysis && files.length > 1) {
880
+ // Parallel analysis in batches
881
+ this.logger?.debug('Using parallel analysis', {
882
+ totalFiles: files.length,
883
+ batchSize: this.maxParallelFiles
884
+ });
885
+
886
+ for (let i = 0; i < files.length; i += this.maxParallelFiles) {
887
+ const batch = files.slice(i, i + this.maxParallelFiles);
888
+ this.metrics.parallelBatches++;
889
+
890
+ // Report progress
891
+ const progress = {
892
+ completed: i,
893
+ total: files.length,
894
+ percentage: Math.round((i / files.length) * 100)
895
+ };
896
+
897
+ if (options.onProgress) {
898
+ options.onProgress(progress);
899
+ }
900
+
901
+ this.logger?.debug('Analyzing batch', {
902
+ batch: Math.floor(i / this.maxParallelFiles) + 1,
903
+ filesInBatch: batch.length,
904
+ progress: `${progress.completed}/${progress.total}`
905
+ });
906
+
907
+ // Analyze batch in parallel
908
+ const batchPromises = batch.map(async (file) => {
909
+ try {
910
+ const result = await this.analyzeFile(file, workingDir, accessConfig, options);
911
+ return result;
912
+ } catch (error) {
913
+ this.logger?.warn('Failed to analyze file in project', {
914
+ file,
915
+ error: error.message
916
+ });
917
+
918
+ return {
919
+ file: this.directoryAccessManager.createRelativePath(file, accessConfig),
920
+ fullPath: file,
921
+ error: error.message,
922
+ success: false
923
+ };
924
+ }
925
+ });
926
+
927
+ const batchResults = await Promise.all(batchPromises);
928
+ results.push(...batchResults.filter(r => r !== null));
929
+ }
930
+
931
+ // Final progress report
932
+ if (options.onProgress) {
933
+ options.onProgress({
934
+ completed: files.length,
935
+ total: files.length,
936
+ percentage: 100
937
+ });
938
+ }
939
+
940
+ } else {
941
+ // Sequential analysis
942
+ for (const file of files) {
943
+ try {
944
+ const result = await this.analyzeFile(file, workingDir, accessConfig, options);
945
+ if (result) {
946
+ results.push(result);
947
+ }
948
+ } catch (error) {
949
+ this.logger?.warn('Failed to analyze file in project', {
950
+ file,
951
+ error: error.message
952
+ });
953
+
954
+ results.push({
955
+ file: this.directoryAccessManager.createRelativePath(file, accessConfig),
956
+ fullPath: file,
957
+ error: error.message,
958
+ success: false
959
+ });
960
+ }
961
+ }
962
+ }
963
+
964
+ return results;
965
+ }
966
+
967
+ /**
968
+ * Fix code issues in a file
969
+ * @private
970
+ */
971
+ async fixFile(filePath, workingDir, accessConfig, options = {}) {
972
+ const fullPath = path.isAbsolute(filePath)
973
+ ? path.normalize(filePath)
974
+ : path.resolve(workingDir, filePath);
975
+
976
+ // Validate read access
977
+ const readResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
978
+ if (!readResult.allowed) {
979
+ throw new Error(`Read access denied: ${readResult.reason}`);
980
+ }
981
+
982
+ // Validate write access if writeFile is true
983
+ if (options.writeFile) {
984
+ const writeResult = this.directoryAccessManager.validateWriteAccess(fullPath, accessConfig);
985
+ if (!writeResult.allowed) {
986
+ throw new Error(`Write access denied: ${writeResult.reason}`);
987
+ }
988
+ }
989
+
990
+ try {
991
+ // Read file
992
+ const content = await fs.readFile(fullPath, 'utf-8');
993
+
994
+ // Get ESLint analyzer
995
+ const eslintAnalyzer = await this.getESLintAnalyzer();
996
+
997
+ // Fix the code
998
+ const fixResult = await eslintAnalyzer.fix(fullPath, content, {
999
+ workingDir,
1000
+ accessConfig,
1001
+ framework: await this.detectFramework(workingDir, this.detectLanguage(fullPath))
1002
+ });
1003
+
1004
+ // Write file if requested and changes were made
1005
+ if (options.writeFile && fixResult.fixed) {
1006
+ await fs.writeFile(fullPath, fixResult.content, 'utf-8');
1007
+ this.logger?.info('File fixed and written', { file: fullPath });
1008
+ }
1009
+
1010
+ return {
1011
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
1012
+ fullPath,
1013
+ action: 'fix',
1014
+ fixed: fixResult.fixed,
1015
+ fixedCount: fixResult.fixedCount,
1016
+ remainingErrors: fixResult.remainingErrors,
1017
+ remainingWarnings: fixResult.remainingWarnings,
1018
+ changes: fixResult.changes,
1019
+ written: !!(options.writeFile && fixResult.fixed),
1020
+ preview: !options.writeFile && fixResult.fixed ? fixResult.content : undefined
1021
+ };
1022
+
1023
+ } catch (error) {
1024
+ throw new Error(`Failed to fix ${filePath}: ${error.message}`);
1025
+ }
1026
+ }
1027
+
1028
+ /**
1029
+ * Format code in a file
1030
+ * @private
1031
+ */
1032
+ async formatFile(filePath, workingDir, accessConfig, options = {}) {
1033
+ const fullPath = path.isAbsolute(filePath)
1034
+ ? path.normalize(filePath)
1035
+ : path.resolve(workingDir, filePath);
1036
+
1037
+ // Validate read access
1038
+ const readResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
1039
+ if (!readResult.allowed) {
1040
+ throw new Error(`Read access denied: ${readResult.reason}`);
1041
+ }
1042
+
1043
+ // Validate write access if writeFile is true
1044
+ if (options.writeFile) {
1045
+ const writeResult = this.directoryAccessManager.validateWriteAccess(fullPath, accessConfig);
1046
+ if (!writeResult.allowed) {
1047
+ throw new Error(`Write access denied: ${writeResult.reason}`);
1048
+ }
1049
+ }
1050
+
1051
+ try {
1052
+ // Read file
1053
+ const content = await fs.readFile(fullPath, 'utf-8');
1054
+
1055
+ // Get Prettier formatter
1056
+ const prettierFormatter = await this.getPrettierFormatter();
1057
+
1058
+ // Check if file type is supported
1059
+ if (!prettierFormatter.isSupported(fullPath)) {
1060
+ return {
1061
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
1062
+ fullPath,
1063
+ action: 'format',
1064
+ formatted: false,
1065
+ skipped: true,
1066
+ skipReason: 'File type not supported by Prettier'
1067
+ };
1068
+ }
1069
+
1070
+ // Format the code
1071
+ const formatResult = await prettierFormatter.format(fullPath, content, {
1072
+ workingDir,
1073
+ accessConfig
1074
+ });
1075
+
1076
+ // Write file if requested and changes were made
1077
+ if (options.writeFile && formatResult.formatted) {
1078
+ await fs.writeFile(fullPath, formatResult.content, 'utf-8');
1079
+ this.logger?.info('File formatted and written', { file: fullPath });
1080
+ }
1081
+
1082
+ return {
1083
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
1084
+ fullPath,
1085
+ action: 'format',
1086
+ formatted: formatResult.formatted,
1087
+ linesChanged: formatResult.linesChanged,
1088
+ changes: formatResult.changes,
1089
+ written: !!(options.writeFile && formatResult.formatted),
1090
+ preview: !options.writeFile && formatResult.formatted ? formatResult.content : undefined
1091
+ };
1092
+
1093
+ } catch (error) {
1094
+ throw new Error(`Failed to format ${filePath}: ${error.message}`);
1095
+ }
1096
+ }
1097
+
1098
+ /**
1099
+ * Security scan a single file
1100
+ * @private
1101
+ */
1102
+ async securityScanFile(filePath, workingDir, accessConfig, options = {}) {
1103
+ const fullPath = path.isAbsolute(filePath)
1104
+ ? path.normalize(filePath)
1105
+ : path.resolve(workingDir, filePath);
1106
+
1107
+ // Validate read access
1108
+ const accessResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
1109
+ if (!accessResult.allowed) {
1110
+ throw new Error(`Read access denied: ${accessResult.reason}`);
1111
+ }
1112
+
1113
+ try {
1114
+ const stats = await fs.stat(fullPath);
1115
+
1116
+ if (stats.size > this.maxFileSize) {
1117
+ throw new Error(`File too large: ${stats.size} bytes (max ${this.maxFileSize})`);
1118
+ }
1119
+
1120
+ // Detect language
1121
+ const language = this.detectLanguage(fullPath);
1122
+
1123
+ // Security analyzer only supports JS/TS/Python
1124
+ if (!language || !['javascript', 'typescript', 'python'].includes(language)) {
1125
+ return {
1126
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
1127
+ fullPath,
1128
+ language: language || 'unknown',
1129
+ issues: [],
1130
+ skipped: true,
1131
+ skipReason: 'Security scanning only supports JavaScript, TypeScript, and Python files'
1132
+ };
1133
+ }
1134
+
1135
+ // Read file content
1136
+ const content = await fs.readFile(fullPath, 'utf-8');
1137
+
1138
+ // Get security analyzer
1139
+ const securityAnalyzer = await this.getSecurityAnalyzer();
1140
+
1141
+ // Perform security scan
1142
+ const issues = await securityAnalyzer.analyze(fullPath, content, {
1143
+ skipTestFiles: options.skipTestFiles !== false
1144
+ });
1145
+
1146
+ // Categorize issues by severity
1147
+ const result = {
1148
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
1149
+ fullPath,
1150
+ language,
1151
+ action: 'security-scan',
1152
+ critical: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.CRITICAL),
1153
+ errors: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.ERROR),
1154
+ warnings: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.WARNING),
1155
+ info: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.INFO),
1156
+ totalIssues: issues.length,
1157
+ analyzed: true,
1158
+ scannersUsed: issues.map(i => i.scanner).filter((v, i, a) => a.indexOf(v) === i),
1159
+ timestamp: new Date().toISOString()
1160
+ };
1161
+
1162
+ return result;
1163
+
1164
+ } catch (error) {
1165
+ throw new Error(`Failed to security scan ${filePath}: ${error.message}`);
1166
+ }
1167
+ }
1168
+
1169
+ /**
1170
+ * Security scan project directory
1171
+ * @private
1172
+ */
1173
+ async securityScanProject(directory, pattern, workingDir, accessConfig, options = {}) {
1174
+ const fullDir = path.isAbsolute(directory)
1175
+ ? path.normalize(directory)
1176
+ : path.resolve(workingDir, directory);
1177
+
1178
+ // Validate read access
1179
+ const accessResult = this.directoryAccessManager.validateReadAccess(fullDir, accessConfig);
1180
+ if (!accessResult.allowed) {
1181
+ throw new Error(`Read access denied: ${accessResult.reason}`);
1182
+ }
1183
+
1184
+ // Get security analyzer for dependency scanning
1185
+ const securityAnalyzer = await this.getSecurityAnalyzer();
1186
+
1187
+ // Run dependency scans at project level
1188
+ const dependencyIssues = await securityAnalyzer.analyzeProject(fullDir, 'javascript', options);
1189
+
1190
+ // Find all matching files (only JS/TS/Python for security scanning)
1191
+ const searchPattern = pattern || '**/*.{js,jsx,mjs,cjs,ts,tsx,py}';
1192
+ const files = await this.findFiles(fullDir, searchPattern);
1193
+
1194
+ if (files.length > this.maxFilesPerBatch) {
1195
+ throw new Error(`Too many files: ${files.length} (max ${this.maxFilesPerBatch})`);
1196
+ }
1197
+
1198
+ // Scan files (parallel or sequential)
1199
+ const results = [];
1200
+
1201
+ if (this.parallelAnalysis && files.length > 1) {
1202
+ // Parallel scanning in batches
1203
+ this.logger?.debug('Using parallel security scanning', {
1204
+ totalFiles: files.length,
1205
+ batchSize: this.maxParallelFiles
1206
+ });
1207
+
1208
+ for (let i = 0; i < files.length; i += this.maxParallelFiles) {
1209
+ const batch = files.slice(i, i + this.maxParallelFiles);
1210
+
1211
+ if (options.onProgress) {
1212
+ options.onProgress({
1213
+ completed: i,
1214
+ total: files.length,
1215
+ percentage: Math.round((i / files.length) * 100)
1216
+ });
1217
+ }
1218
+
1219
+ const batchPromises = batch.map(async (file) => {
1220
+ try {
1221
+ return await this.securityScanFile(file, workingDir, accessConfig, options);
1222
+ } catch (error) {
1223
+ this.logger?.warn('Failed to security scan file in project', {
1224
+ file,
1225
+ error: error.message
1226
+ });
1227
+
1228
+ return {
1229
+ file: this.directoryAccessManager.createRelativePath(file, accessConfig),
1230
+ fullPath: file,
1231
+ error: error.message,
1232
+ success: false
1233
+ };
1234
+ }
1235
+ });
1236
+
1237
+ const batchResults = await Promise.all(batchPromises);
1238
+ results.push(...batchResults.filter(r => r !== null));
1239
+ }
1240
+
1241
+ if (options.onProgress) {
1242
+ options.onProgress({
1243
+ completed: files.length,
1244
+ total: files.length,
1245
+ percentage: 100
1246
+ });
1247
+ }
1248
+
1249
+ } else {
1250
+ // Sequential scanning
1251
+ for (const file of files) {
1252
+ try {
1253
+ const result = await this.securityScanFile(file, workingDir, accessConfig, options);
1254
+ if (result) {
1255
+ results.push(result);
1256
+ }
1257
+ } catch (error) {
1258
+ this.logger?.warn('Failed to security scan file in project', {
1259
+ file,
1260
+ error: error.message
1261
+ });
1262
+
1263
+ results.push({
1264
+ file: this.directoryAccessManager.createRelativePath(file, accessConfig),
1265
+ fullPath: file,
1266
+ error: error.message,
1267
+ success: false
1268
+ });
1269
+ }
1270
+ }
1271
+ }
1272
+
1273
+ // Add dependency scan results if any
1274
+ if (dependencyIssues.length > 0) {
1275
+ results.push({
1276
+ file: path.join(fullDir, 'package.json'),
1277
+ fullPath: path.join(fullDir, 'package.json'),
1278
+ action: 'dependency-scan',
1279
+ critical: dependencyIssues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.CRITICAL),
1280
+ errors: dependencyIssues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.ERROR),
1281
+ warnings: dependencyIssues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.WARNING),
1282
+ info: dependencyIssues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.INFO),
1283
+ totalIssues: dependencyIssues.length,
1284
+ analyzed: true,
1285
+ scannersUsed: ['npm-audit'],
1286
+ timestamp: new Date().toISOString()
1287
+ });
1288
+ }
1289
+
1290
+ return results;
1291
+ }
1292
+
1293
+ /**
1294
+ * Get Sparrow analyzer (lazy initialization)
1295
+ * @private
1296
+ */
1297
+ async getSparrowAnalyzer() {
1298
+ if (!this.analyzers.sparrow) {
1299
+ const SparrowAnalyzer = (await import('../analyzers/SparrowAnalyzer.js')).default;
1300
+ this.analyzers.sparrow = new SparrowAnalyzer(this.logger);
1301
+ }
1302
+ return this.analyzers.sparrow;
1303
+ }
1304
+
1305
+ /**
1306
+ * Sparrow SAST scan for a single file (tree-sitter based, no external dependencies)
1307
+ * @private
1308
+ */
1309
+ async sparrowScanFile(filePath, workingDir, accessConfig, options = {}) {
1310
+ const fullPath = path.isAbsolute(filePath)
1311
+ ? path.normalize(filePath)
1312
+ : path.resolve(workingDir, filePath);
1313
+
1314
+ // Validate read access
1315
+ const accessResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
1316
+ if (!accessResult.allowed) {
1317
+ throw new Error(`Read access denied: ${accessResult.reason}`);
1318
+ }
1319
+
1320
+ try {
1321
+ const stats = await fs.stat(fullPath);
1322
+
1323
+ if (stats.size > this.maxFileSize) {
1324
+ throw new Error(`File too large: ${stats.size} bytes (max ${this.maxFileSize})`);
1325
+ }
1326
+
1327
+ // Get Sparrow analyzer
1328
+ const sparrowAnalyzer = await this.getSparrowAnalyzer();
1329
+
1330
+ // Check if file is supported
1331
+ if (!sparrowAnalyzer.isSupported(fullPath)) {
1332
+ return {
1333
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
1334
+ fullPath,
1335
+ action: 'sparrow-scan',
1336
+ skipped: true,
1337
+ reason: 'Unsupported file type for Sparrow SAST',
1338
+ analyzed: false,
1339
+ timestamp: new Date().toISOString()
1340
+ };
1341
+ }
1342
+
1343
+ // Perform Sparrow scan
1344
+ const scanResult = await sparrowAnalyzer.scanFile(fullPath, {
1345
+ useBuiltinCheckers: options.useBuiltinCheckers !== false,
1346
+ enabledCheckers: options.enabledCheckers,
1347
+ disabledCheckers: options.disabledCheckers
1348
+ });
1349
+
1350
+ if (!scanResult.success) {
1351
+ throw new Error(scanResult.error || 'Sparrow scan failed');
1352
+ }
1353
+
1354
+ // Categorize issues by severity
1355
+ const result = {
1356
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
1357
+ fullPath,
1358
+ action: 'sparrow-scan',
1359
+ critical: scanResult.issues.filter(i => i.severity === 'critical'),
1360
+ errors: scanResult.issues.filter(i => i.severity === 'error'),
1361
+ warnings: scanResult.issues.filter(i => i.severity === 'warning'),
1362
+ info: scanResult.issues.filter(i => i.severity === 'info'),
1363
+ totalIssues: scanResult.issues.length,
1364
+ analyzed: true,
1365
+ scanner: 'sparrow',
1366
+ executionTime: scanResult.executionTime,
1367
+ timestamp: new Date().toISOString()
1368
+ };
1369
+
1370
+ return result;
1371
+
1372
+ } catch (error) {
1373
+ this.logger?.error('Sparrow scan file failed', { file: fullPath, error: error.message });
1374
+ return {
1375
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
1376
+ fullPath,
1377
+ action: 'sparrow-scan',
1378
+ error: error.message,
1379
+ analyzed: false,
1380
+ success: false,
1381
+ timestamp: new Date().toISOString()
1382
+ };
1383
+ }
1384
+ }
1385
+
1386
+ /**
1387
+ * Sparrow SAST scan for a project directory (tree-sitter based, no external dependencies)
1388
+ * @private
1389
+ */
1390
+ async sparrowScanProject(directory, pattern, workingDir, accessConfig, options = {}) {
1391
+ const fullDir = path.isAbsolute(directory)
1392
+ ? path.normalize(directory)
1393
+ : path.resolve(workingDir, directory);
1394
+
1395
+ // Validate read access
1396
+ const accessResult = this.directoryAccessManager.validateReadAccess(fullDir, accessConfig);
1397
+ if (!accessResult.allowed) {
1398
+ throw new Error(`Read access denied: ${accessResult.reason}`);
1399
+ }
1400
+
1401
+ try {
1402
+ // Get Sparrow analyzer
1403
+ const sparrowAnalyzer = await this.getSparrowAnalyzer();
1404
+
1405
+ // Run project scan with Sparrow
1406
+ const scanResult = await sparrowAnalyzer.scanProject(fullDir, {
1407
+ useBuiltinCheckers: options.useBuiltinCheckers !== false,
1408
+ enabledCheckers: options.enabledCheckers,
1409
+ disabledCheckers: options.disabledCheckers,
1410
+ excludePatterns: options.excludePatterns,
1411
+ languages: options.languages
1412
+ });
1413
+
1414
+ if (!scanResult.success) {
1415
+ throw new Error(scanResult.error || 'Sparrow project scan failed');
1416
+ }
1417
+
1418
+ // Transform file results to standard format
1419
+ const results = scanResult.files.map(fileResult => ({
1420
+ file: this.directoryAccessManager.createRelativePath(fileResult.file, accessConfig),
1421
+ fullPath: fileResult.file,
1422
+ action: 'sparrow-scan',
1423
+ critical: fileResult.issues.filter(i => i.severity === 'critical'),
1424
+ errors: fileResult.issues.filter(i => i.severity === 'error'),
1425
+ warnings: fileResult.issues.filter(i => i.severity === 'warning'),
1426
+ info: fileResult.issues.filter(i => i.severity === 'info'),
1427
+ totalIssues: fileResult.issues.length,
1428
+ analyzed: true,
1429
+ scanner: 'sparrow',
1430
+ timestamp: new Date().toISOString()
1431
+ }));
1432
+
1433
+ return {
1434
+ success: true,
1435
+ files: results,
1436
+ summary: scanResult.summary,
1437
+ executionTime: scanResult.executionTime
1438
+ };
1439
+
1440
+ } catch (error) {
1441
+ this.logger?.error('Sparrow project scan failed', { directory: fullDir, error: error.message });
1442
+ return {
1443
+ success: false,
1444
+ files: [],
1445
+ error: error.message
1446
+ };
1447
+ }
1448
+ }
1449
+
1450
+ /**
1451
+ * Validate a configuration file
1452
+ * @private
1453
+ */
1454
+ async validateConfigFile(filePath, workingDir, accessConfig, options = {}) {
1455
+ const fullPath = path.isAbsolute(filePath)
1456
+ ? path.normalize(filePath)
1457
+ : path.resolve(workingDir, filePath);
1458
+
1459
+ // Validate read access
1460
+ const accessResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
1461
+ if (!accessResult.allowed) {
1462
+ throw new Error(`Read access denied: ${accessResult.reason}`);
1463
+ }
1464
+
1465
+ try {
1466
+ const stats = await fs.stat(fullPath);
1467
+
1468
+ if (stats.size > this.maxFileSize) {
1469
+ throw new Error(`File too large: ${stats.size} bytes (max ${this.maxFileSize})`);
1470
+ }
1471
+
1472
+ // Get config validator
1473
+ const configValidator = await this.getConfigValidator();
1474
+
1475
+ // Perform validation
1476
+ const issues = await configValidator.validate(fullPath, options);
1477
+
1478
+ // Categorize issues by severity
1479
+ const result = {
1480
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
1481
+ fullPath,
1482
+ action: 'validate-config',
1483
+ critical: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.CRITICAL),
1484
+ errors: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.ERROR),
1485
+ warnings: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.WARNING),
1486
+ info: issues.filter(d => d.severity === STATIC_ANALYSIS.SEVERITY.INFO),
1487
+ totalIssues: issues.length,
1488
+ analyzed: true,
1489
+ validatorsUsed: issues.map(i => i.validator).filter((v, i, a) => a.indexOf(v) === i),
1490
+ timestamp: new Date().toISOString()
1491
+ };
1492
+
1493
+ return result;
1494
+
1495
+ } catch (error) {
1496
+ throw new Error(`Failed to validate config ${filePath}: ${error.message}`);
1497
+ }
1498
+ }
1499
+
1500
+ /**
1501
+ * Validate configuration files in a directory
1502
+ * @private
1503
+ */
1504
+ async validateConfigDirectory(directory, workingDir, accessConfig, options = {}) {
1505
+ const fullDir = path.isAbsolute(directory)
1506
+ ? path.normalize(directory)
1507
+ : path.resolve(workingDir, directory);
1508
+
1509
+ // Validate read access
1510
+ const accessResult = this.directoryAccessManager.validateReadAccess(fullDir, accessConfig);
1511
+ if (!accessResult.allowed) {
1512
+ throw new Error(`Read access denied: ${accessResult.reason}`);
1513
+ }
1514
+
1515
+ // Find common config files
1516
+ const configFiles = await this.findConfigFiles(fullDir);
1517
+
1518
+ if (configFiles.length > this.maxFilesPerBatch) {
1519
+ throw new Error(`Too many config files: ${configFiles.length} (max ${this.maxFilesPerBatch})`);
1520
+ }
1521
+
1522
+ // Validate files
1523
+ const results = [];
1524
+
1525
+ for (const file of configFiles) {
1526
+ try {
1527
+ const result = await this.validateConfigFile(file, workingDir, accessConfig, options);
1528
+ if (result) {
1529
+ results.push(result);
1530
+ }
1531
+ } catch (error) {
1532
+ this.logger?.warn('Failed to validate config file', {
1533
+ file,
1534
+ error: error.message
1535
+ });
1536
+
1537
+ results.push({
1538
+ file: this.directoryAccessManager.createRelativePath(file, accessConfig),
1539
+ fullPath: file,
1540
+ error: error.message,
1541
+ success: false
1542
+ });
1543
+ }
1544
+ }
1545
+
1546
+ return results;
1547
+ }
1548
+
1549
+ /**
1550
+ * Validate a structured file (JSON, YAML, XML, TOML, etc.)
1551
+ * Uses the pluggable structuredFileValidator utility
1552
+ * @private
1553
+ */
1554
+ async validateStructuredFile(filePath, content, format, workingDir, accessConfig, options = {}) {
1555
+ // If content is provided directly, validate it
1556
+ if (content && format) {
1557
+ const validationResult = validateContent(content, format, { returnParsed: options.returnParsed });
1558
+
1559
+ return {
1560
+ file: filePath || '<inline-content>',
1561
+ fullPath: null,
1562
+ action: 'validate-structured',
1563
+ format: validationResult.format,
1564
+ valid: validationResult.valid,
1565
+ errors: validationResult.errors.filter(e => e.severity === 'error').map(e => ({
1566
+ line: e.line,
1567
+ column: e.column,
1568
+ message: e.message,
1569
+ severity: STATIC_ANALYSIS.SEVERITY.ERROR,
1570
+ category: 'structure'
1571
+ })),
1572
+ warnings: validationResult.errors.filter(e => e.severity === 'warning').map(e => ({
1573
+ line: e.line,
1574
+ column: e.column,
1575
+ message: e.message,
1576
+ severity: STATIC_ANALYSIS.SEVERITY.WARNING,
1577
+ category: 'structure'
1578
+ })),
1579
+ info: [],
1580
+ totalIssues: validationResult.errors.length,
1581
+ analyzed: true,
1582
+ supportedFormats: getSupportedFormats(),
1583
+ timestamp: new Date().toISOString()
1584
+ };
1585
+ }
1586
+
1587
+ // Otherwise, validate from file path
1588
+ if (!filePath) {
1589
+ throw new Error('Either filePath or content+format must be provided');
1590
+ }
1591
+
1592
+ const fullPath = path.isAbsolute(filePath)
1593
+ ? path.normalize(filePath)
1594
+ : path.resolve(workingDir, filePath);
1595
+
1596
+ // Validate read access
1597
+ const accessResult = this.directoryAccessManager.validateReadAccess(fullPath, accessConfig);
1598
+ if (!accessResult.allowed) {
1599
+ throw new Error(`Read access denied: ${accessResult.reason}`);
1600
+ }
1601
+
1602
+ try {
1603
+ const stats = await fs.stat(fullPath);
1604
+
1605
+ if (stats.size > this.maxFileSize) {
1606
+ throw new Error(`File too large: ${stats.size} bytes (max ${this.maxFileSize})`);
1607
+ }
1608
+
1609
+ // Detect format if not provided
1610
+ const detectedFormat = format || detectFormat(fullPath);
1611
+
1612
+ if (!detectedFormat) {
1613
+ return {
1614
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
1615
+ fullPath,
1616
+ action: 'validate-structured',
1617
+ format: 'unknown',
1618
+ valid: false,
1619
+ errors: [{
1620
+ message: `Cannot detect format for file: ${filePath}. Supported: ${getSupportedFormats().join(', ')}`,
1621
+ severity: STATIC_ANALYSIS.SEVERITY.ERROR,
1622
+ category: 'structure'
1623
+ }],
1624
+ warnings: [],
1625
+ info: [],
1626
+ totalIssues: 1,
1627
+ analyzed: false,
1628
+ supportedFormats: getSupportedFormats(),
1629
+ timestamp: new Date().toISOString()
1630
+ };
1631
+ }
1632
+
1633
+ // Read and validate file
1634
+ const fileContent = await fs.readFile(fullPath, 'utf-8');
1635
+ const validationResult = validateContent(fileContent, detectedFormat, { returnParsed: options.returnParsed });
1636
+
1637
+ return {
1638
+ file: this.directoryAccessManager.createRelativePath(fullPath, accessConfig),
1639
+ fullPath,
1640
+ action: 'validate-structured',
1641
+ format: validationResult.format,
1642
+ valid: validationResult.valid,
1643
+ errors: validationResult.errors.filter(e => e.severity === 'error').map(e => ({
1644
+ line: e.line,
1645
+ column: e.column,
1646
+ message: e.message,
1647
+ severity: STATIC_ANALYSIS.SEVERITY.ERROR,
1648
+ category: 'structure'
1649
+ })),
1650
+ warnings: validationResult.errors.filter(e => e.severity === 'warning').map(e => ({
1651
+ line: e.line,
1652
+ column: e.column,
1653
+ message: e.message,
1654
+ severity: STATIC_ANALYSIS.SEVERITY.WARNING,
1655
+ category: 'structure'
1656
+ })),
1657
+ info: [],
1658
+ totalIssues: validationResult.errors.length,
1659
+ analyzed: true,
1660
+ supportedFormats: getSupportedFormats(),
1661
+ timestamp: new Date().toISOString()
1662
+ };
1663
+
1664
+ } catch (error) {
1665
+ throw new Error(`Failed to validate structured file ${filePath}: ${error.message}`);
1666
+ }
1667
+ }
1668
+
1669
+ /**
1670
+ * Find common configuration files in directory
1671
+ * @private
1672
+ */
1673
+ async findConfigFiles(directory) {
1674
+ const files = [];
1675
+ const configFileNames = [
1676
+ 'package.json',
1677
+ 'tsconfig.json',
1678
+ 'Dockerfile',
1679
+ 'docker-compose.yml',
1680
+ 'docker-compose.yaml',
1681
+ '.env',
1682
+ '.env.example',
1683
+ '.eslintrc.js',
1684
+ '.eslintrc.json',
1685
+ '.prettierrc',
1686
+ '.prettierrc.json'
1687
+ ];
1688
+
1689
+ const configExtensions = ['.yml', '.yaml', '.json', '.tf', '.tfvars'];
1690
+
1691
+ const walk = async (dir) => {
1692
+ const entries = await fs.readdir(dir, { withFileTypes: true });
1693
+
1694
+ for (const entry of entries) {
1695
+ const fullPath = path.join(dir, entry.name);
1696
+
1697
+ if (entry.isDirectory()) {
1698
+ // Check specific directories for config files
1699
+ if (entry.name === '.github' || entry.name === 'kubernetes' || entry.name === 'k8s' || entry.name === 'terraform') {
1700
+ await walk(fullPath);
1701
+ } else if (!['node_modules', '.git', 'dist', 'build'].includes(entry.name)) {
1702
+ // Don't recurse into all subdirectories, only known config dirs
1703
+ // Check this level only
1704
+ continue;
1705
+ }
1706
+ } else if (entry.isFile()) {
1707
+ // Check if it's a known config file
1708
+ if (configFileNames.includes(entry.name)) {
1709
+ files.push(fullPath);
1710
+ } else {
1711
+ // Check if it's in a config directory with config extension
1712
+ const ext = path.extname(entry.name).toLowerCase();
1713
+ if (configExtensions.includes(ext)) {
1714
+ const dirname = path.basename(path.dirname(fullPath));
1715
+ if (dirname === 'kubernetes' || dirname === 'k8s' || dirname === 'terraform' || dirname === 'workflows') {
1716
+ files.push(fullPath);
1717
+ }
1718
+ }
1719
+ }
1720
+ }
1721
+ }
1722
+ };
1723
+
1724
+ await walk(directory);
1725
+ return files;
1726
+ }
1727
+
1728
+ /**
1729
+ * Detect programming language from file extension
1730
+ * @private
1731
+ */
1732
+ detectLanguage(filePath) {
1733
+ const ext = path.extname(filePath).toLowerCase();
1734
+ return STATIC_ANALYSIS.EXTENSION_TO_LANGUAGE[ext] || null;
1735
+ }
1736
+
1737
+ /**
1738
+ * Detect framework from project directory
1739
+ * @private
1740
+ */
1741
+ async detectFramework(projectDir, language) {
1742
+ try {
1743
+ if (language === STATIC_ANALYSIS.LANGUAGE.JAVASCRIPT ||
1744
+ language === STATIC_ANALYSIS.LANGUAGE.TYPESCRIPT) {
1745
+ return await this.detectJSFramework(projectDir);
1746
+ }
1747
+
1748
+ if (language === STATIC_ANALYSIS.LANGUAGE.PYTHON) {
1749
+ return await this.detectPythonFramework(projectDir);
1750
+ }
1751
+
1752
+ return null;
1753
+ } catch (error) {
1754
+ this.logger?.debug('Framework detection failed', { error: error.message });
1755
+ return null;
1756
+ }
1757
+ }
1758
+
1759
+ /**
1760
+ * Detect JavaScript/TypeScript framework
1761
+ * @private
1762
+ */
1763
+ async detectJSFramework(projectDir) {
1764
+ try {
1765
+ const pkgPath = path.join(projectDir, STATIC_ANALYSIS.FRAMEWORK_MANIFESTS.JAVASCRIPT);
1766
+ const pkgContent = await fs.readFile(pkgPath, 'utf-8');
1767
+ const pkg = JSON.parse(pkgContent);
1768
+
1769
+ const deps = {
1770
+ ...pkg.dependencies,
1771
+ ...pkg.devDependencies
1772
+ };
1773
+
1774
+ // Check for frameworks in priority order
1775
+ for (const [name, identifier] of Object.entries(STATIC_ANALYSIS.JS_FRAMEWORKS)) {
1776
+ if (deps[identifier]) {
1777
+ return name.toLowerCase();
1778
+ }
1779
+ }
1780
+
1781
+ return null;
1782
+ } catch (error) {
1783
+ return null;
1784
+ }
1785
+ }
1786
+
1787
+ /**
1788
+ * Detect Python framework
1789
+ * @private
1790
+ */
1791
+ async detectPythonFramework(projectDir) {
1792
+ try {
1793
+ // Try requirements.txt
1794
+ const reqPath = path.join(projectDir, STATIC_ANALYSIS.FRAMEWORK_MANIFESTS.PYTHON);
1795
+ const reqContent = await fs.readFile(reqPath, 'utf-8');
1796
+
1797
+ // Check for frameworks
1798
+ for (const [name, identifier] of Object.entries(STATIC_ANALYSIS.PYTHON_FRAMEWORKS)) {
1799
+ if (reqContent.toLowerCase().includes(identifier)) {
1800
+ return name.toLowerCase();
1801
+ }
1802
+ }
1803
+
1804
+ return null;
1805
+ } catch (error) {
1806
+ // Try pyproject.toml
1807
+ try {
1808
+ const tomlPath = path.join(projectDir, STATIC_ANALYSIS.FRAMEWORK_MANIFESTS.PYTHON_POETRY);
1809
+ const tomlContent = await fs.readFile(tomlPath, 'utf-8');
1810
+
1811
+ for (const [name, identifier] of Object.entries(STATIC_ANALYSIS.PYTHON_FRAMEWORKS)) {
1812
+ if (tomlContent.toLowerCase().includes(identifier)) {
1813
+ return name.toLowerCase();
1814
+ }
1815
+ }
1816
+ } catch {
1817
+ // No framework detected
1818
+ }
1819
+
1820
+ return null;
1821
+ }
1822
+ }
1823
+
1824
+ /**
1825
+ * Find files matching pattern in directory
1826
+ * @private
1827
+ */
1828
+ async findFiles(directory, pattern) {
1829
+ const files = [];
1830
+
1831
+ // Default patterns by language if not specified
1832
+ const searchPattern = pattern || '**/*.{js,jsx,mjs,cjs,ts,tsx,py,css,scss,sass,less}';
1833
+
1834
+ // Parse pattern to extract extensions
1835
+ // Supports patterns like "**/*.ts", "**/*.{js,ts}", "*.js", etc.
1836
+ const getExtensionsFromPattern = (pat) => {
1837
+ const exts = [];
1838
+
1839
+ // Match patterns like *.{js,ts,tsx} or *.js
1840
+ const bracesMatch = pat.match(/\*\.\{([^}]+)\}/);
1841
+ if (bracesMatch) {
1842
+ // Multiple extensions: *.{js,ts,tsx}
1843
+ const extList = bracesMatch[1].split(',').map(e => e.trim());
1844
+ extList.forEach(ext => exts.push(ext.startsWith('.') ? ext : '.' + ext));
1845
+ } else {
1846
+ // Single extension: *.js or **/*.ts
1847
+ const singleMatch = pat.match(/\*\.([a-z]+)$/i);
1848
+ if (singleMatch) {
1849
+ const ext = singleMatch[1];
1850
+ exts.push(ext.startsWith('.') ? ext : '.' + ext);
1851
+ }
1852
+ }
1853
+
1854
+ // If no pattern found, allow all supported extensions
1855
+ if (exts.length === 0) {
1856
+ return null; // null means "all supported extensions"
1857
+ }
1858
+
1859
+ return exts;
1860
+ };
1861
+
1862
+ const allowedExtensions = getExtensionsFromPattern(searchPattern);
1863
+
1864
+ // Simple recursive file search
1865
+ const walk = async (dir) => {
1866
+ const entries = await fs.readdir(dir, { withFileTypes: true });
1867
+
1868
+ for (const entry of entries) {
1869
+ const fullPath = path.join(dir, entry.name);
1870
+
1871
+ if (entry.isDirectory()) {
1872
+ // Skip common ignore directories
1873
+ if (!['node_modules', '.git', 'dist', 'build', '__pycache__', '.venv', 'venv'].includes(entry.name)) {
1874
+ await walk(fullPath);
1875
+ }
1876
+ } else if (entry.isFile()) {
1877
+ const ext = path.extname(entry.name).toLowerCase();
1878
+
1879
+ // Check if file extension is supported
1880
+ if (STATIC_ANALYSIS.EXTENSION_TO_LANGUAGE[ext]) {
1881
+ // If pattern specified, check if extension matches
1882
+ if (allowedExtensions === null || allowedExtensions.includes(ext)) {
1883
+ files.push(fullPath);
1884
+ }
1885
+ }
1886
+ }
1887
+ }
1888
+ };
1889
+
1890
+ await walk(directory);
1891
+ return files;
1892
+ }
1893
+
1894
+ /**
1895
+ * Get analyzer for language (lazy initialization)
1896
+ * @private
1897
+ */
1898
+ async getAnalyzer(language) {
1899
+ try {
1900
+ // Lazy load analyzers
1901
+ if (language === STATIC_ANALYSIS.LANGUAGE.JAVASCRIPT) {
1902
+ if (!this.analyzers.javascript) {
1903
+ const { default: JavaScriptAnalyzer } = await import('../analyzers/JavaScriptAnalyzer.js');
1904
+ this.analyzers.javascript = new JavaScriptAnalyzer(this.logger);
1905
+ }
1906
+ return this.analyzers.javascript;
1907
+ }
1908
+
1909
+ if (language === STATIC_ANALYSIS.LANGUAGE.TYPESCRIPT) {
1910
+ if (!this.analyzers.typescript) {
1911
+ const { default: TypeScriptAnalyzer } = await import('../analyzers/TypeScriptAnalyzer.js');
1912
+ this.analyzers.typescript = new TypeScriptAnalyzer(this.logger);
1913
+ }
1914
+ return this.analyzers.typescript;
1915
+ }
1916
+
1917
+ // Python analyzer
1918
+ if (language === STATIC_ANALYSIS.LANGUAGE.PYTHON) {
1919
+ if (!this.analyzers.python) {
1920
+ const { default: PythonAnalyzer } = await import('../analyzers/PythonAnalyzer.js');
1921
+ this.analyzers.python = new PythonAnalyzer(this.logger);
1922
+ }
1923
+ return this.analyzers.python;
1924
+ }
1925
+
1926
+ // CSS analyzer (handles CSS, SCSS, LESS)
1927
+ if (language === STATIC_ANALYSIS.LANGUAGE.CSS ||
1928
+ language === STATIC_ANALYSIS.LANGUAGE.SCSS ||
1929
+ language === STATIC_ANALYSIS.LANGUAGE.LESS) {
1930
+ if (!this.analyzers.css) {
1931
+ const { default: CSSAnalyzer } = await import('../analyzers/CSSAnalyzer.js');
1932
+ this.analyzers.css = new CSSAnalyzer(this.logger);
1933
+ }
1934
+ return this.analyzers.css;
1935
+ }
1936
+
1937
+ return null;
1938
+ } catch (error) {
1939
+ this.logger?.error('Failed to load analyzer', {
1940
+ language,
1941
+ error: error.message
1942
+ });
1943
+ return null;
1944
+ }
1945
+ }
1946
+
1947
+ /**
1948
+ * Get ESLint analyzer (lazy initialization)
1949
+ * @private
1950
+ */
1951
+ async getESLintAnalyzer() {
1952
+ if (!this.analyzers.eslint) {
1953
+ const { default: ESLintAnalyzer } = await import('../analyzers/ESLintAnalyzer.js');
1954
+ this.analyzers.eslint = new ESLintAnalyzer(this.logger);
1955
+ }
1956
+ return this.analyzers.eslint;
1957
+ }
1958
+
1959
+ /**
1960
+ * Get Prettier formatter (lazy initialization)
1961
+ * @private
1962
+ */
1963
+ async getPrettierFormatter() {
1964
+ if (!this.formatters.prettier) {
1965
+ const { default: PrettierFormatter } = await import('../analyzers/PrettierFormatter.js');
1966
+ this.formatters.prettier = new PrettierFormatter(this.logger);
1967
+ }
1968
+ return this.formatters.prettier;
1969
+ }
1970
+
1971
+ /**
1972
+ * Get Security analyzer (lazy initialization)
1973
+ * @private
1974
+ */
1975
+ async getSecurityAnalyzer() {
1976
+ if (!this.analyzers.security) {
1977
+ const { default: SecurityAnalyzer } = await import('../analyzers/SecurityAnalyzer.js');
1978
+ this.analyzers.security = new SecurityAnalyzer(this.logger);
1979
+ }
1980
+ return this.analyzers.security;
1981
+ }
1982
+
1983
+ /**
1984
+ * Get Config validator (lazy initialization)
1985
+ * @private
1986
+ */
1987
+ async getConfigValidator() {
1988
+ if (!this.analyzers.config) {
1989
+ const { default: ConfigValidator } = await import('../analyzers/ConfigValidator.js');
1990
+ this.analyzers.config = new ConfigValidator(this.logger);
1991
+ }
1992
+ return this.analyzers.config;
1993
+ }
1994
+
1995
+ /**
1996
+ * Update summary statistics
1997
+ * @private
1998
+ */
1999
+ updateSummary(summary, fileResult) {
2000
+ if (fileResult.analyzed) {
2001
+ summary.totalFiles++;
2002
+
2003
+ const criticalCount = fileResult.critical?.length || 0;
2004
+ const errorCount = fileResult.errors?.length || 0;
2005
+ const warningCount = fileResult.warnings?.length || 0;
2006
+ const infoCount = fileResult.info?.length || 0;
2007
+
2008
+ // Initialize totalCritical if not exists (for backward compatibility)
2009
+ if (summary.totalCritical === undefined) {
2010
+ summary.totalCritical = 0;
2011
+ }
2012
+
2013
+ summary.totalCritical += criticalCount;
2014
+ summary.totalErrors += errorCount;
2015
+ summary.totalWarnings += warningCount;
2016
+ summary.totalInfo += infoCount;
2017
+
2018
+ if (criticalCount > 0 || errorCount > 0) {
2019
+ summary.filesWithErrors++;
2020
+ }
2021
+
2022
+ // Count by language
2023
+ if (fileResult.language) {
2024
+ summary.filesByLanguage[fileResult.language] =
2025
+ (summary.filesByLanguage[fileResult.language] || 0) + 1;
2026
+ }
2027
+
2028
+ // Count by category (include critical issues)
2029
+ const allIssues = [
2030
+ ...(fileResult.critical || []),
2031
+ ...(fileResult.errors || []),
2032
+ ...(fileResult.warnings || [])
2033
+ ];
2034
+
2035
+ for (const issue of allIssues) {
2036
+ if (issue.category) {
2037
+ summary.errorsByCategory[issue.category] =
2038
+ (summary.errorsByCategory[issue.category] || 0) + 1;
2039
+ }
2040
+ }
2041
+ }
2042
+ }
2043
+
2044
+ /**
2045
+ * Compute content hash for caching
2046
+ * @private
2047
+ */
2048
+ computeContentHash(content) {
2049
+ return crypto
2050
+ .createHash('sha256')
2051
+ .update(content)
2052
+ .digest('hex')
2053
+ .substring(0, 16); // Use first 16 chars for shorter cache keys
2054
+ }
2055
+
2056
+ /**
2057
+ * Get performance metrics
2058
+ * @returns {Object} Performance metrics
2059
+ */
2060
+ getPerformanceMetrics() {
2061
+ const cacheHitRate = this.metrics.totalAnalyses > 0
2062
+ ? (this.metrics.cacheHits / this.metrics.totalAnalyses) * 100
2063
+ : 0;
2064
+
2065
+ const avgAnalysisTime = this.metrics.filesAnalyzed > 0
2066
+ ? this.metrics.totalAnalysisTime / this.metrics.filesAnalyzed
2067
+ : 0;
2068
+
2069
+ return {
2070
+ ...this.metrics,
2071
+ cacheHitRate: Math.round(cacheHitRate * 10) / 10, // Round to 1 decimal
2072
+ averageAnalysisTime: Math.round(avgAnalysisTime),
2073
+ cacheSize: this.analysisCache.size
2074
+ };
2075
+ }
2076
+
2077
+ /**
2078
+ * Reset performance metrics
2079
+ */
2080
+ resetPerformanceMetrics() {
2081
+ this.metrics = {
2082
+ totalAnalyses: 0,
2083
+ cacheHits: 0,
2084
+ cacheMisses: 0,
2085
+ totalAnalysisTime: 0,
2086
+ filesAnalyzed: 0,
2087
+ parallelBatches: 0
2088
+ };
2089
+ }
2090
+
2091
+ /**
2092
+ * Clear analysis cache
2093
+ */
2094
+ clearCache() {
2095
+ this.analysisCache.clear();
2096
+ this.logger?.debug('Analysis cache cleared');
2097
+ }
2098
+
2099
+ /**
2100
+ * Get supported actions for this tool
2101
+ * @returns {Array<string>} Array of supported action names
2102
+ */
2103
+ getSupportedActions() {
2104
+ return [
2105
+ 'analyze', 'analyze-project', 'fix', 'format',
2106
+ 'security-scan', 'security-scan-project',
2107
+ 'sparrow-scan', 'sparrow-scan-project', // Tree-sitter based SAST
2108
+ 'validate-config', 'validate-config-directory',
2109
+ 'validate-structured' // JSON, YAML, XML, TOML, INI, ENV validation
2110
+ ];
2111
+ }
2112
+
2113
+ /**
2114
+ * Get parameter schema for validation
2115
+ * @returns {Object} Parameter schema
2116
+ */
2117
+ getParameterSchema() {
2118
+ return {
2119
+ type: 'object',
2120
+ properties: {
2121
+ actions: {
2122
+ type: 'array',
2123
+ minItems: 1,
2124
+ items: {
2125
+ type: 'object',
2126
+ properties: {
2127
+ type: {
2128
+ type: 'string',
2129
+ enum: this.getSupportedActions()
2130
+ },
2131
+ filePath: { type: 'string' },
2132
+ directory: { type: 'string' },
2133
+ pattern: { type: 'string' },
2134
+ includeWarnings: { type: 'boolean' },
2135
+ maxErrors: { type: 'number' }
2136
+ },
2137
+ required: ['type']
2138
+ }
2139
+ }
2140
+ },
2141
+ required: ['actions']
2142
+ };
2143
+ }
2144
+ }
2145
+
2146
+ export default StaticAnalysisTool;