mstro-app 0.4.2 → 0.4.4

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 (342) hide show
  1. package/bin/mstro.js +119 -40
  2. package/dist/server/cli/headless/claude-invoker-process.d.ts +11 -0
  3. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
  4. package/dist/server/cli/headless/claude-invoker-process.js +140 -0
  5. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
  6. package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
  7. package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
  8. package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
  9. package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
  10. package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
  11. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
  12. package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
  13. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
  14. package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
  15. package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
  16. package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
  17. package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
  18. package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
  19. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  20. package/dist/server/cli/headless/claude-invoker.js +10 -804
  21. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  22. package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
  23. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
  24. package/dist/server/cli/headless/haiku-assessments.js +281 -0
  25. package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
  26. package/dist/server/cli/headless/headless-logger.d.ts +3 -2
  27. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
  28. package/dist/server/cli/headless/headless-logger.js +28 -5
  29. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  30. package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
  31. package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
  32. package/dist/server/cli/headless/native-timeout-detector.js +99 -0
  33. package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
  34. package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
  35. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  36. package/dist/server/cli/headless/stall-assessor.js +65 -457
  37. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  38. package/dist/server/cli/headless/types.d.ts +4 -1
  39. package/dist/server/cli/headless/types.d.ts.map +1 -1
  40. package/dist/server/cli/improvisation-attachments.d.ts +21 -0
  41. package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
  42. package/dist/server/cli/improvisation-attachments.js +116 -0
  43. package/dist/server/cli/improvisation-attachments.js.map +1 -0
  44. package/dist/server/cli/improvisation-retry.d.ts +52 -0
  45. package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
  46. package/dist/server/cli/improvisation-retry.js +434 -0
  47. package/dist/server/cli/improvisation-retry.js.map +1 -0
  48. package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
  49. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  50. package/dist/server/cli/improvisation-session-manager.js +117 -1079
  51. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  52. package/dist/server/cli/improvisation-types.d.ts +86 -0
  53. package/dist/server/cli/improvisation-types.d.ts.map +1 -0
  54. package/dist/server/cli/improvisation-types.js +10 -0
  55. package/dist/server/cli/improvisation-types.js.map +1 -0
  56. package/dist/server/cli/prompt-builders.d.ts +68 -0
  57. package/dist/server/cli/prompt-builders.d.ts.map +1 -0
  58. package/dist/server/cli/prompt-builders.js +312 -0
  59. package/dist/server/cli/prompt-builders.js.map +1 -0
  60. package/dist/server/index.js +33 -212
  61. package/dist/server/index.js.map +1 -1
  62. package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
  63. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
  64. package/dist/server/mcp/bouncer-haiku.js +152 -0
  65. package/dist/server/mcp/bouncer-haiku.js.map +1 -0
  66. package/dist/server/mcp/bouncer-integration.d.ts +3 -4
  67. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  68. package/dist/server/mcp/bouncer-integration.js +50 -196
  69. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  70. package/dist/server/mcp/security-analysis.d.ts +38 -0
  71. package/dist/server/mcp/security-analysis.d.ts.map +1 -0
  72. package/dist/server/mcp/security-analysis.js +183 -0
  73. package/dist/server/mcp/security-analysis.js.map +1 -0
  74. package/dist/server/mcp/security-audit.d.ts +1 -1
  75. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  76. package/dist/server/mcp/security-patterns.d.ts +1 -25
  77. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  78. package/dist/server/mcp/security-patterns.js +55 -260
  79. package/dist/server/mcp/security-patterns.js.map +1 -1
  80. package/dist/server/server-setup.d.ts +22 -0
  81. package/dist/server/server-setup.d.ts.map +1 -0
  82. package/dist/server/server-setup.js +101 -0
  83. package/dist/server/server-setup.js.map +1 -0
  84. package/dist/server/services/file-explorer-ops.d.ts +24 -0
  85. package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
  86. package/dist/server/services/file-explorer-ops.js +211 -0
  87. package/dist/server/services/file-explorer-ops.js.map +1 -0
  88. package/dist/server/services/files.d.ts +2 -85
  89. package/dist/server/services/files.d.ts.map +1 -1
  90. package/dist/server/services/files.js +7 -427
  91. package/dist/server/services/files.js.map +1 -1
  92. package/dist/server/services/plan/composer.d.ts +1 -1
  93. package/dist/server/services/plan/composer.d.ts.map +1 -1
  94. package/dist/server/services/plan/composer.js +118 -32
  95. package/dist/server/services/plan/composer.js.map +1 -1
  96. package/dist/server/services/plan/config-installer.d.ts +25 -0
  97. package/dist/server/services/plan/config-installer.d.ts.map +1 -0
  98. package/dist/server/services/plan/config-installer.js +182 -0
  99. package/dist/server/services/plan/config-installer.js.map +1 -0
  100. package/dist/server/services/plan/dependency-resolver.d.ts +1 -1
  101. package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
  102. package/dist/server/services/plan/dependency-resolver.js +4 -1
  103. package/dist/server/services/plan/dependency-resolver.js.map +1 -1
  104. package/dist/server/services/plan/executor.d.ts +38 -74
  105. package/dist/server/services/plan/executor.d.ts.map +1 -1
  106. package/dist/server/services/plan/executor.js +274 -460
  107. package/dist/server/services/plan/executor.js.map +1 -1
  108. package/dist/server/services/plan/front-matter.d.ts +18 -0
  109. package/dist/server/services/plan/front-matter.d.ts.map +1 -0
  110. package/dist/server/services/plan/front-matter.js +44 -0
  111. package/dist/server/services/plan/front-matter.js.map +1 -0
  112. package/dist/server/services/plan/output-manager.d.ts +22 -0
  113. package/dist/server/services/plan/output-manager.d.ts.map +1 -0
  114. package/dist/server/services/plan/output-manager.js +97 -0
  115. package/dist/server/services/plan/output-manager.js.map +1 -0
  116. package/dist/server/services/plan/parser-core.d.ts +20 -0
  117. package/dist/server/services/plan/parser-core.d.ts.map +1 -0
  118. package/dist/server/services/plan/parser-core.js +350 -0
  119. package/dist/server/services/plan/parser-core.js.map +1 -0
  120. package/dist/server/services/plan/parser-migration.d.ts +5 -0
  121. package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
  122. package/dist/server/services/plan/parser-migration.js +124 -0
  123. package/dist/server/services/plan/parser-migration.js.map +1 -0
  124. package/dist/server/services/plan/parser.d.ts +11 -3
  125. package/dist/server/services/plan/parser.d.ts.map +1 -1
  126. package/dist/server/services/plan/parser.js +184 -369
  127. package/dist/server/services/plan/parser.js.map +1 -1
  128. package/dist/server/services/plan/prompt-builder.d.ts +17 -0
  129. package/dist/server/services/plan/prompt-builder.d.ts.map +1 -0
  130. package/dist/server/services/plan/prompt-builder.js +137 -0
  131. package/dist/server/services/plan/prompt-builder.js.map +1 -0
  132. package/dist/server/services/plan/review-gate.d.ts +28 -0
  133. package/dist/server/services/plan/review-gate.d.ts.map +1 -0
  134. package/dist/server/services/plan/review-gate.js +191 -0
  135. package/dist/server/services/plan/review-gate.js.map +1 -0
  136. package/dist/server/services/plan/state-reconciler.d.ts +1 -1
  137. package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
  138. package/dist/server/services/plan/state-reconciler.js +59 -7
  139. package/dist/server/services/plan/state-reconciler.js.map +1 -1
  140. package/dist/server/services/plan/types.d.ts +68 -0
  141. package/dist/server/services/plan/types.d.ts.map +1 -1
  142. package/dist/server/services/platform-credentials.d.ts +24 -0
  143. package/dist/server/services/platform-credentials.d.ts.map +1 -0
  144. package/dist/server/services/platform-credentials.js +68 -0
  145. package/dist/server/services/platform-credentials.js.map +1 -0
  146. package/dist/server/services/platform.d.ts +1 -31
  147. package/dist/server/services/platform.d.ts.map +1 -1
  148. package/dist/server/services/platform.js +11 -109
  149. package/dist/server/services/platform.js.map +1 -1
  150. package/dist/server/services/terminal/pty-manager.d.ts +7 -97
  151. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  152. package/dist/server/services/terminal/pty-manager.js +53 -266
  153. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  154. package/dist/server/services/terminal/pty-utils.d.ts +57 -0
  155. package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
  156. package/dist/server/services/terminal/pty-utils.js +141 -0
  157. package/dist/server/services/terminal/pty-utils.js.map +1 -0
  158. package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
  159. package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
  160. package/dist/server/services/websocket/file-definition-handlers.js +153 -0
  161. package/dist/server/services/websocket/file-definition-handlers.js.map +1 -0
  162. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  163. package/dist/server/services/websocket/file-explorer-handlers.js +52 -391
  164. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  165. package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
  166. package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
  167. package/dist/server/services/websocket/file-search-handlers.js +238 -0
  168. package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
  169. package/dist/server/services/websocket/file-utils.js +3 -3
  170. package/dist/server/services/websocket/file-utils.js.map +1 -1
  171. package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
  172. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
  173. package/dist/server/services/websocket/git-branch-handlers.js +110 -0
  174. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
  175. package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
  176. package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
  177. package/dist/server/services/websocket/git-diff-handlers.js +123 -0
  178. package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
  179. package/dist/server/services/websocket/git-handlers.d.ts +2 -31
  180. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  181. package/dist/server/services/websocket/git-handlers.js +35 -541
  182. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  183. package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
  184. package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
  185. package/dist/server/services/websocket/git-log-handlers.js +128 -0
  186. package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
  187. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  188. package/dist/server/services/websocket/git-pr-handlers.js +13 -53
  189. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  190. package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
  191. package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
  192. package/dist/server/services/websocket/git-tag-handlers.js +76 -0
  193. package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
  194. package/dist/server/services/websocket/git-utils.d.ts +43 -0
  195. package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
  196. package/dist/server/services/websocket/git-utils.js +201 -0
  197. package/dist/server/services/websocket/git-utils.js.map +1 -0
  198. package/dist/server/services/websocket/handler.d.ts +2 -0
  199. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  200. package/dist/server/services/websocket/handler.js +37 -112
  201. package/dist/server/services/websocket/handler.js.map +1 -1
  202. package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
  203. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
  204. package/dist/server/services/websocket/plan-board-handlers.js +218 -0
  205. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
  206. package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
  207. package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
  208. package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
  209. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
  210. package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
  211. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  212. package/dist/server/services/websocket/plan-handlers.js +21 -462
  213. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  214. package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
  215. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
  216. package/dist/server/services/websocket/plan-helpers.js +199 -0
  217. package/dist/server/services/websocket/plan-helpers.js.map +1 -0
  218. package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
  219. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
  220. package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
  221. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
  222. package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
  223. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
  224. package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
  225. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
  226. package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
  227. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
  228. package/dist/server/services/websocket/quality-complexity.js +262 -0
  229. package/dist/server/services/websocket/quality-complexity.js.map +1 -0
  230. package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
  231. package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
  232. package/dist/server/services/websocket/quality-fix-agent.js +140 -0
  233. package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
  234. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  235. package/dist/server/services/websocket/quality-handlers.js +34 -346
  236. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  237. package/dist/server/services/websocket/quality-linting.d.ts +9 -0
  238. package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
  239. package/dist/server/services/websocket/quality-linting.js +178 -0
  240. package/dist/server/services/websocket/quality-linting.js.map +1 -0
  241. package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
  242. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
  243. package/dist/server/services/websocket/quality-review-agent.js +206 -0
  244. package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
  245. package/dist/server/services/websocket/quality-service.d.ts +3 -51
  246. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  247. package/dist/server/services/websocket/quality-service.js +9 -651
  248. package/dist/server/services/websocket/quality-service.js.map +1 -1
  249. package/dist/server/services/websocket/quality-tools.d.ts +23 -0
  250. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
  251. package/dist/server/services/websocket/quality-tools.js +208 -0
  252. package/dist/server/services/websocket/quality-tools.js.map +1 -0
  253. package/dist/server/services/websocket/quality-types.d.ts +59 -0
  254. package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
  255. package/dist/server/services/websocket/quality-types.js +101 -0
  256. package/dist/server/services/websocket/quality-types.js.map +1 -0
  257. package/dist/server/services/websocket/session-handlers.d.ts +3 -4
  258. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  259. package/dist/server/services/websocket/session-handlers.js +3 -378
  260. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  261. package/dist/server/services/websocket/session-history.d.ts +4 -0
  262. package/dist/server/services/websocket/session-history.d.ts.map +1 -0
  263. package/dist/server/services/websocket/session-history.js +208 -0
  264. package/dist/server/services/websocket/session-history.js.map +1 -0
  265. package/dist/server/services/websocket/session-initialization.d.ts +5 -0
  266. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
  267. package/dist/server/services/websocket/session-initialization.js +163 -0
  268. package/dist/server/services/websocket/session-initialization.js.map +1 -0
  269. package/dist/server/services/websocket/types.d.ts +12 -2
  270. package/dist/server/services/websocket/types.d.ts.map +1 -1
  271. package/package.json +1 -2
  272. package/server/cli/headless/claude-invoker-process.ts +204 -0
  273. package/server/cli/headless/claude-invoker-stall.ts +164 -0
  274. package/server/cli/headless/claude-invoker-stream.ts +353 -0
  275. package/server/cli/headless/claude-invoker-tools.ts +187 -0
  276. package/server/cli/headless/claude-invoker.ts +15 -1092
  277. package/server/cli/headless/haiku-assessments.ts +365 -0
  278. package/server/cli/headless/headless-logger.ts +26 -5
  279. package/server/cli/headless/native-timeout-detector.ts +117 -0
  280. package/server/cli/headless/stall-assessor.ts +65 -618
  281. package/server/cli/headless/types.ts +4 -1
  282. package/server/cli/improvisation-attachments.ts +148 -0
  283. package/server/cli/improvisation-retry.ts +602 -0
  284. package/server/cli/improvisation-session-manager.ts +140 -1349
  285. package/server/cli/improvisation-types.ts +98 -0
  286. package/server/cli/prompt-builders.ts +370 -0
  287. package/server/index.ts +35 -246
  288. package/server/mcp/bouncer-haiku.ts +182 -0
  289. package/server/mcp/bouncer-integration.ts +87 -248
  290. package/server/mcp/security-analysis.ts +217 -0
  291. package/server/mcp/security-audit.ts +1 -1
  292. package/server/mcp/security-patterns.ts +60 -283
  293. package/server/server-setup.ts +114 -0
  294. package/server/services/file-explorer-ops.ts +293 -0
  295. package/server/services/files.ts +20 -532
  296. package/server/services/plan/composer.ts +140 -35
  297. package/server/services/plan/config-installer.ts +187 -0
  298. package/server/services/plan/dependency-resolver.ts +4 -1
  299. package/server/services/plan/executor.ts +281 -488
  300. package/server/services/plan/front-matter.ts +48 -0
  301. package/server/services/plan/output-manager.ts +113 -0
  302. package/server/services/plan/parser-core.ts +406 -0
  303. package/server/services/plan/parser-migration.ts +128 -0
  304. package/server/services/plan/parser.ts +188 -394
  305. package/server/services/plan/prompt-builder.ts +161 -0
  306. package/server/services/plan/review-gate.ts +212 -0
  307. package/server/services/plan/state-reconciler.ts +68 -7
  308. package/server/services/plan/types.ts +101 -1
  309. package/server/services/platform-credentials.ts +83 -0
  310. package/server/services/platform.ts +16 -131
  311. package/server/services/terminal/pty-manager.ts +66 -313
  312. package/server/services/terminal/pty-utils.ts +176 -0
  313. package/server/services/websocket/file-definition-handlers.ts +165 -0
  314. package/server/services/websocket/file-explorer-handlers.ts +37 -452
  315. package/server/services/websocket/file-search-handlers.ts +291 -0
  316. package/server/services/websocket/file-utils.ts +3 -3
  317. package/server/services/websocket/git-branch-handlers.ts +130 -0
  318. package/server/services/websocket/git-diff-handlers.ts +140 -0
  319. package/server/services/websocket/git-handlers.ts +40 -625
  320. package/server/services/websocket/git-log-handlers.ts +149 -0
  321. package/server/services/websocket/git-pr-handlers.ts +17 -62
  322. package/server/services/websocket/git-tag-handlers.ts +91 -0
  323. package/server/services/websocket/git-utils.ts +230 -0
  324. package/server/services/websocket/handler.ts +39 -112
  325. package/server/services/websocket/plan-board-handlers.ts +277 -0
  326. package/server/services/websocket/plan-execution-handlers.ts +184 -0
  327. package/server/services/websocket/plan-handlers.ts +23 -544
  328. package/server/services/websocket/plan-helpers.ts +215 -0
  329. package/server/services/websocket/plan-issue-handlers.ts +204 -0
  330. package/server/services/websocket/plan-sprint-handlers.ts +252 -0
  331. package/server/services/websocket/quality-complexity.ts +294 -0
  332. package/server/services/websocket/quality-fix-agent.ts +181 -0
  333. package/server/services/websocket/quality-handlers.ts +36 -404
  334. package/server/services/websocket/quality-linting.ts +187 -0
  335. package/server/services/websocket/quality-review-agent.ts +246 -0
  336. package/server/services/websocket/quality-service.ts +11 -762
  337. package/server/services/websocket/quality-tools.ts +209 -0
  338. package/server/services/websocket/quality-types.ts +169 -0
  339. package/server/services/websocket/session-handlers.ts +5 -437
  340. package/server/services/websocket/session-history.ts +222 -0
  341. package/server/services/websocket/session-initialization.ts +209 -0
  342. package/server/services/websocket/types.ts +46 -2
@@ -1,41 +1,25 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
3
 
4
+ /**
5
+ * Quality Handlers — WebSocket message router for quality scanning,
6
+ * code review, and fix operations.
7
+ *
8
+ * Agent logic lives in focused modules:
9
+ * - quality-review-agent.ts — AI code review prompt, parsing, handler
10
+ * - quality-fix-agent.ts — AI fix prompt, progress tracking, handler
11
+ */
12
+
4
13
  import { join } from 'node:path';
5
- import { runWithFileLogger } from '../../cli/headless/headless-logger.js';
6
- import { HeadlessRunner } from '../../cli/headless/index.js';
7
- import type { ToolUseEvent } from '../../cli/headless/types.js';
8
14
  import type { HandlerContext } from './handler-context.js';
15
+ import type { FindingForFix } from './quality-fix-agent.js';
16
+ import { handleFixIssues } from './quality-fix-agent.js';
9
17
  import { QualityPersistence } from './quality-persistence.js';
10
- import { detectTools, installTools, recomputeWithAiReview, runQualityScan } from './quality-service.js';
18
+ import { handleCodeReview } from './quality-review-agent.js';
19
+ import { detectTools, installTools, runQualityScan } from './quality-service.js';
11
20
  import type { WebSocketMessage, WSContext } from './types.js';
12
21
 
13
- const TOOL_MESSAGES: Record<string, string> = {
14
- Read: 'Reading files to understand issues...',
15
- Edit: 'Applying fixes...',
16
- Write: 'Writing fixes...',
17
- Grep: 'Searching for related code...',
18
- Bash: 'Running verification...',
19
- };
20
-
21
- function createToolProgressCallback(ctx: HandlerContext, ws: WSContext, reportPath: string) {
22
- const seenTools = new Set<string>();
23
- return (event: ToolUseEvent) => {
24
- if (event.type === 'tool_start' && event.toolName && !seenTools.has(event.toolName)) {
25
- seenTools.add(event.toolName);
26
- const message = TOOL_MESSAGES[event.toolName];
27
- if (message) {
28
- ctx.send(ws, { type: 'qualityFixProgress', data: { path: reportPath, message } });
29
- }
30
- }
31
- if (event.type === 'tool_complete' && event.toolName === 'Edit' && event.completeInput?.file_path) {
32
- ctx.send(ws, {
33
- type: 'qualityFixProgress',
34
- data: { path: reportPath, message: `Fixed ${String(event.completeInput.file_path).split('/').slice(-2).join('/')}` },
35
- });
36
- }
37
- };
38
- }
22
+ // ── Shared state ──────────────────────────────────────────────
39
23
 
40
24
  const persistenceCache = new Map<string, QualityPersistence>();
41
25
  const activeReviews = new Set<string>();
@@ -49,6 +33,14 @@ function getPersistence(workingDir: string): QualityPersistence {
49
33
  return persistence;
50
34
  }
51
35
 
36
+ function resolvePath(workingDir: string, dirPath?: string): string {
37
+ if (!dirPath || dirPath === '.' || dirPath === './') return workingDir;
38
+ if (dirPath.startsWith('/')) return dirPath;
39
+ return join(workingDir, dirPath);
40
+ }
41
+
42
+ // ── Message router ────────────────────────────────────────────
43
+
52
44
  export function handleQualityMessage(
53
45
  ctx: HandlerContext,
54
46
  ws: WSContext,
@@ -60,8 +52,18 @@ export function handleQualityMessage(
60
52
  qualityDetectTools: () => handleDetectTools(ctx, ws, msg, workingDir),
61
53
  qualityScan: () => handleScan(ctx, ws, msg, workingDir),
62
54
  qualityInstallTools: () => handleInstallTools(ctx, ws, msg, workingDir),
63
- qualityCodeReview: () => handleCodeReview(ctx, ws, msg, workingDir),
64
- qualityFixIssues: () => handleFixIssues(ctx, ws, msg, workingDir),
55
+ qualityCodeReview: () => {
56
+ const dirPath = resolvePath(workingDir, msg.data?.path);
57
+ const reportPath = msg.data?.path || '.';
58
+ handleCodeReview(ctx, ws, reportPath, dirPath, workingDir, activeReviews, getPersistence);
59
+ },
60
+ qualityFixIssues: () => {
61
+ const dirPath = resolvePath(workingDir, msg.data?.path);
62
+ const reportPath = msg.data?.path || '.';
63
+ const section: string | undefined = msg.data?.section;
64
+ const findings: FindingForFix[] = msg.data?.findings || [];
65
+ handleFixIssues(ctx, ws, reportPath, dirPath, workingDir, section, findings, getPersistence);
66
+ },
65
67
  qualityLoadState: () => handleLoadState(ctx, ws, workingDir),
66
68
  qualitySaveDirectories: () => handleSaveDirectories(ctx, ws, msg, workingDir),
67
69
  };
@@ -80,11 +82,7 @@ export function handleQualityMessage(
80
82
  }
81
83
  }
82
84
 
83
- function resolvePath(workingDir: string, dirPath?: string): string {
84
- if (!dirPath || dirPath === '.' || dirPath === './') return workingDir;
85
- if (dirPath.startsWith('/')) return dirPath;
86
- return join(workingDir, dirPath);
87
- }
85
+ // ── Scan / detect / install handlers ──────────────────────────
88
86
 
89
87
  async function handleLoadState(
90
88
  ctx: HandlerContext,
@@ -94,10 +92,7 @@ async function handleLoadState(
94
92
  try {
95
93
  const persistence = getPersistence(workingDir);
96
94
  const state = persistence.loadState();
97
- ctx.send(ws, {
98
- type: 'qualityStateLoaded',
99
- data: state,
100
- });
95
+ ctx.send(ws, { type: 'qualityStateLoaded', data: state });
101
96
  } catch (error) {
102
97
  ctx.send(ws, {
103
98
  type: 'qualityError',
@@ -155,7 +150,6 @@ async function handleScan(
155
150
  const reportPath = msg.data?.path || '.';
156
151
 
157
152
  try {
158
- // Detect installed tools so the scan can skip unavailable categories
159
153
  const { tools: detectedTools } = await detectTools(dirPath);
160
154
  const installedToolNames = detectedTools.filter((t) => t.installed).map((t) => t.name);
161
155
 
@@ -170,7 +164,6 @@ async function handleScan(
170
164
  data: { path: reportPath, results },
171
165
  });
172
166
 
173
- // Persist report and append to history
174
167
  try {
175
168
  const persistence = getPersistence(workingDir);
176
169
  persistence.saveReport(reportPath, results);
@@ -215,364 +208,3 @@ async function handleInstallTools(
215
208
  });
216
209
  }
217
210
  }
218
-
219
- // ============================================================================
220
- // Code Review Agent
221
- // ============================================================================
222
-
223
- function buildCodeReviewPrompt(dirPath: string): string {
224
- return `You are an expert code review agent. Your task is to perform a comprehensive, language-agnostic code review of the project in the current working directory.
225
-
226
- IMPORTANT: Your current working directory is "${dirPath}". Only review files within this directory. Do NOT traverse parent directories or review files outside this path.
227
-
228
- ## Review Process
229
-
230
- 1. **Discover**: Use Glob to find source files (e.g. "**/*.{ts,tsx,js,py,rs,go,java,rb,php}"). Understand the project structure. Only search within the current directory.
231
- 2. **Read**: Read the most important files — entry points, core modules, handlers, services. Prioritize files with recent git changes (\`git diff --name-only HEAD~5\` via Bash if available).
232
- 3. **Analyze**: Look for real, actionable issues across these categories:
233
- - **security**: Injection vulnerabilities (SQL, XSS, command), hardcoded secrets/credentials, auth bypasses, insecure crypto, path traversal, SSRF, unsafe deserialization
234
- - **bugs**: Null/undefined errors, race conditions, logic errors, unhandled edge cases, off-by-one errors, resource leaks, incorrect error handling
235
- - **performance**: N+1 queries, unnecessary re-renders, missing memoization, blocking I/O in hot paths, unbounded data structures, missing pagination
236
- - **maintainability**: God functions (>100 lines), deep nesting (>4 levels), duplicated logic, missing error handling at system boundaries, tight coupling
237
-
238
- ## Rules
239
-
240
- - Only report findings you are >80% confident about. No speculative or low-confidence issues.
241
- - Focus on bugs and security over style. Skip formatting, naming preferences, and minor nits.
242
- - Each finding MUST reference a specific file and line number. Do not report vague or file-level issues.
243
- - Limit to the 20 most important findings, ranked by severity.
244
- - Do NOT modify any files. This is a read-only review.
245
-
246
- ## Output
247
-
248
- After your analysis, output EXACTLY one JSON code block with your findings. No other text after the JSON block.
249
-
250
- \`\`\`json
251
- {
252
- "findings": [
253
- {
254
- "severity": "critical|high|medium|low",
255
- "category": "security|bugs|performance|maintainability",
256
- "file": "relative/path/to/file.ts",
257
- "line": 42,
258
- "title": "Short title describing the issue",
259
- "description": "What the problem is and why it matters.",
260
- "suggestion": "How to fix it."
261
- }
262
- ],
263
- "summary": "Brief 1-2 sentence summary of overall code quality."
264
- }
265
- \`\`\``;
266
- }
267
-
268
- interface CodeReviewFinding {
269
- severity: 'critical' | 'high' | 'medium' | 'low';
270
- category: 'security' | 'bugs' | 'performance' | 'maintainability';
271
- file: string;
272
- line: number | null;
273
- title: string;
274
- description: string;
275
- suggestion?: string;
276
- }
277
-
278
- const VALID_SEVERITIES = new Set(['critical', 'high', 'medium', 'low']);
279
- const VALID_CATEGORIES = new Set(['security', 'bugs', 'performance', 'maintainability']);
280
-
281
- function normalizeFinding(f: Record<string, unknown>): CodeReviewFinding | null {
282
- if (typeof f.file !== 'string' || typeof f.title !== 'string') return null;
283
- return {
284
- severity: VALID_SEVERITIES.has(f.severity as string) ? f.severity as CodeReviewFinding['severity'] : 'medium',
285
- category: VALID_CATEGORIES.has(f.category as string) ? f.category as CodeReviewFinding['category'] : 'maintainability',
286
- file: f.file as string,
287
- line: typeof f.line === 'number' ? f.line : null,
288
- title: f.title as string,
289
- description: typeof f.description === 'string' ? f.description : '',
290
- suggestion: typeof f.suggestion === 'string' ? f.suggestion : undefined,
291
- };
292
- }
293
-
294
- function extractJson(response: string): string {
295
- // Try ```json ... ``` first, then plain ``` ... ```, then largest {...} block
296
- const fencedJson = response.match(/```json\s*([\s\S]*?)```/);
297
- if (fencedJson) return fencedJson[1].trim();
298
-
299
- const fencedPlain = response.match(/```\s*([\s\S]*?)```/);
300
- if (fencedPlain) return fencedPlain[1].trim();
301
-
302
- const braceMatch = response.match(/\{[\s\S]*\}/);
303
- if (braceMatch) return braceMatch[0].trim();
304
-
305
- return response.trim();
306
- }
307
-
308
- function parseCodeReviewResponse(response: string): { findings: CodeReviewFinding[]; summary: string } {
309
- const jsonStr = extractJson(response);
310
-
311
- try {
312
- const parsed = JSON.parse(jsonStr);
313
- const rawFindings: Record<string, unknown>[] = Array.isArray(parsed.findings) ? parsed.findings : [];
314
- const findings = rawFindings.map(normalizeFinding).filter((f): f is CodeReviewFinding => f !== null);
315
- const summary = typeof parsed.summary === 'string' ? parsed.summary : `Found ${findings.length} issue(s).`;
316
- return { findings, summary };
317
- } catch {
318
- return { findings: [], summary: 'Failed to parse code review results.' };
319
- }
320
- }
321
-
322
- const TOOL_START_MESSAGES: Record<string, string> = {
323
- Glob: 'Discovering project files...',
324
- Read: 'Reading source files...',
325
- Grep: 'Searching codebase...',
326
- Bash: 'Running analysis command...',
327
- };
328
-
329
- function getToolCompleteMessage(event: ToolUseEvent): string | null {
330
- const input = event.completeInput;
331
- if (!input) return null;
332
- if (event.toolName === 'Read' && input.file_path) {
333
- return `Reviewed ${String(input.file_path).split('/').slice(-2).join('/')}`;
334
- }
335
- if (event.toolName === 'Grep' && input.pattern) {
336
- return `Searched for "${String(input.pattern).slice(0, 40)}"`;
337
- }
338
- return null;
339
- }
340
-
341
- function createCodeReviewProgressTracker() {
342
- const seenToolStarts = new Set<string>();
343
-
344
- return (event: ToolUseEvent): string | null => {
345
- if (event.type === 'tool_start' && event.toolName) {
346
- if (seenToolStarts.has(event.toolName)) return null;
347
- seenToolStarts.add(event.toolName);
348
- return TOOL_START_MESSAGES[event.toolName] ?? null;
349
- }
350
- if (event.type === 'tool_complete') return getToolCompleteMessage(event);
351
- return null;
352
- };
353
- }
354
-
355
- async function handleCodeReview(
356
- ctx: HandlerContext,
357
- ws: WSContext,
358
- msg: WebSocketMessage,
359
- workingDir: string,
360
- ): Promise<void> {
361
- const dirPath = resolvePath(workingDir, msg.data?.path);
362
- const reportPath = msg.data?.path || '.';
363
-
364
- if (activeReviews.has(dirPath)) {
365
- ctx.send(ws, {
366
- type: 'qualityError',
367
- data: { path: reportPath, error: 'A code review is already running for this directory.' },
368
- });
369
- return;
370
- }
371
-
372
- activeReviews.add(dirPath);
373
- try {
374
- // Send initial progress
375
- ctx.send(ws, {
376
- type: 'qualityCodeReviewProgress',
377
- data: { path: reportPath, message: 'Starting AI code review...' },
378
- });
379
-
380
- const runner = new HeadlessRunner({
381
- workingDir: dirPath,
382
- directPrompt: buildCodeReviewPrompt(dirPath),
383
- stallWarningMs: 120_000,
384
- stallKillMs: 600_000,
385
- stallHardCapMs: 900_000,
386
- toolUseCallback: (() => {
387
- const getProgressMessage = createCodeReviewProgressTracker();
388
- return (event: ToolUseEvent) => {
389
- const message = getProgressMessage(event);
390
- if (message) {
391
- ctx.send(ws, {
392
- type: 'qualityCodeReviewProgress',
393
- data: { path: reportPath, message },
394
- });
395
- }
396
- };
397
- })(),
398
- });
399
-
400
- ctx.send(ws, {
401
- type: 'qualityCodeReviewProgress',
402
- data: { path: reportPath, message: 'Claude is analyzing your codebase...' },
403
- });
404
-
405
- const result = await runWithFileLogger('code-review', () => runner.run());
406
-
407
- ctx.send(ws, {
408
- type: 'qualityCodeReviewProgress',
409
- data: { path: reportPath, message: 'Generating review report...' },
410
- });
411
-
412
- const responseText = result.assistantResponse || '';
413
- const { findings, summary } = parseCodeReviewResponse(responseText);
414
-
415
- // Recompute overall score with AI review findings included
416
- let updatedResults: import('./quality-service.js').QualityResults | null = null;
417
- try {
418
- const persistence = getPersistence(workingDir);
419
- const existingReport = persistence.loadReport(reportPath);
420
- if (existingReport) {
421
- updatedResults = recomputeWithAiReview(existingReport, findings);
422
- updatedResults = { ...updatedResults, codeReview: findings as unknown as typeof updatedResults.codeReview };
423
- persistence.saveReport(reportPath, updatedResults);
424
- persistence.appendHistory(updatedResults, reportPath);
425
- }
426
- persistence.saveCodeReview(reportPath, findings as unknown as Record<string, unknown>[], summary);
427
- } catch {
428
- // Persistence failure should not break the review flow
429
- }
430
-
431
- ctx.send(ws, {
432
- type: 'qualityCodeReview',
433
- data: { path: reportPath, findings, summary, results: updatedResults },
434
- });
435
- } catch (error) {
436
- ctx.send(ws, {
437
- type: 'qualityError',
438
- data: { path: reportPath, error: error instanceof Error ? error.message : String(error) },
439
- });
440
- } finally {
441
- activeReviews.delete(dirPath);
442
- }
443
- }
444
-
445
- // ============================================================================
446
- // Fix Issues Agent
447
- // ============================================================================
448
-
449
- interface FindingForFix {
450
- severity: string;
451
- category: string;
452
- file: string;
453
- line: number | null;
454
- title: string;
455
- description: string;
456
- suggestion?: string;
457
- }
458
-
459
- function buildFixPrompt(findings: FindingForFix[], section?: string): string {
460
- const filtered = section ? findings.filter((f) => f.category === section) : findings;
461
- const sorted = filtered.sort((a, b) => {
462
- const order: Record<string, number> = { critical: 0, high: 1, medium: 2, low: 3 };
463
- return (order[a.severity] ?? 4) - (order[b.severity] ?? 4);
464
- });
465
-
466
- const issueList = sorted.slice(0, 30).map((f, i) => {
467
- const loc = f.line ? `${f.file}:${f.line}` : f.file;
468
- const parts = [`${i + 1}. [${f.severity.toUpperCase()}] ${loc} — ${f.title}`];
469
- if (f.description) parts.push(` ${f.description}`);
470
- if (f.suggestion) parts.push(` Suggestion: ${f.suggestion}`);
471
- return parts.join('\n');
472
- }).join('\n\n');
473
-
474
- return `You are a code quality fix agent. Fix the following quality issues in the codebase.
475
-
476
- ## Issues to Fix (${sorted.length} total, showing top ${Math.min(30, sorted.length)})
477
-
478
- ${issueList}
479
-
480
- ## Rules
481
-
482
- - Fix each issue by editing the relevant file at the specified location.
483
- - For complexity issues: refactor into smaller functions. For long files: split or extract modules. For long functions: break into smaller functions.
484
- - For security issues: apply the suggested fix or use secure coding best practices.
485
- - For bugs: fix the root cause, not just the symptom.
486
- - For linting/formatting: apply the standard for the project.
487
- - Do NOT introduce new issues. Make minimal, focused changes.
488
- - After fixing, verify the changes compile/pass linting if tools are available.
489
- - Work through the issues systematically from most to least severe.`;
490
- }
491
-
492
- const activeFixes = new Set<string>();
493
-
494
- async function handleFixIssues(
495
- ctx: HandlerContext,
496
- ws: WSContext,
497
- msg: WebSocketMessage,
498
- workingDir: string,
499
- ): Promise<void> {
500
- const dirPath = resolvePath(workingDir, msg.data?.path);
501
- const reportPath = msg.data?.path || '.';
502
- const section: string | undefined = msg.data?.section;
503
- const findings: FindingForFix[] = msg.data?.findings || [];
504
-
505
- if (activeFixes.has(dirPath)) {
506
- ctx.send(ws, {
507
- type: 'qualityError',
508
- data: { path: reportPath, error: 'A fix operation is already running for this directory.' },
509
- });
510
- return;
511
- }
512
-
513
- if (findings.length === 0) {
514
- ctx.send(ws, {
515
- type: 'qualityError',
516
- data: { path: reportPath, error: 'No findings to fix.' },
517
- });
518
- return;
519
- }
520
-
521
- activeFixes.add(dirPath);
522
- try {
523
- ctx.send(ws, {
524
- type: 'qualityFixProgress',
525
- data: { path: reportPath, message: 'Starting Claude Code to fix issues...' },
526
- });
527
-
528
- const prompt = buildFixPrompt(findings, section);
529
-
530
- const runner = new HeadlessRunner({
531
- workingDir: dirPath,
532
- directPrompt: prompt,
533
- stallWarningMs: 120_000,
534
- stallKillMs: 600_000,
535
- stallHardCapMs: 900_000,
536
- toolUseCallback: createToolProgressCallback(ctx, ws, reportPath),
537
- });
538
-
539
- await runWithFileLogger('code-review-fix', () => runner.run());
540
-
541
- ctx.send(ws, {
542
- type: 'qualityFixProgress',
543
- data: { path: reportPath, message: 'Fixes applied. Re-running quality checks...' },
544
- });
545
-
546
- // Re-run quality scan after fixing
547
- const { tools: detectedTools } = await detectTools(dirPath);
548
- const installedToolNames = detectedTools.filter((t) => t.installed).map((t) => t.name);
549
-
550
- const results = await runQualityScan(dirPath, (progress) => {
551
- ctx.send(ws, {
552
- type: 'qualityScanProgress',
553
- data: { path: reportPath, progress },
554
- });
555
- }, installedToolNames);
556
-
557
- ctx.send(ws, {
558
- type: 'qualityFixComplete',
559
- data: { path: reportPath, results },
560
- });
561
-
562
- // Persist
563
- try {
564
- const persistence = getPersistence(workingDir);
565
- persistence.saveReport(reportPath, results);
566
- persistence.appendHistory(results, reportPath);
567
- } catch {
568
- // Persistence failure should not break the fix flow
569
- }
570
- } catch (error) {
571
- ctx.send(ws, {
572
- type: 'qualityError',
573
- data: { path: reportPath, error: error instanceof Error ? error.message : String(error) },
574
- });
575
- } finally {
576
- activeFixes.delete(dirPath);
577
- }
578
- }
@@ -0,0 +1,187 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file for details.
3
+
4
+ import { existsSync } from 'node:fs';
5
+ import { join, relative } from 'node:path';
6
+ import { runCommand, type SourceFile } from './quality-tools.js';
7
+ import { biomeDiagToFinding, type Ecosystem, isBiomeComplexityDiagnostic, isEslintComplexityRule, type QualityFinding } from './quality-types.js';
8
+
9
+ interface LintAccumulator {
10
+ errors: number;
11
+ warnings: number;
12
+ findings: QualityFinding[];
13
+ ran: boolean;
14
+ }
15
+
16
+ function newLintAccumulator(): LintAccumulator {
17
+ return { errors: 0, warnings: 0, findings: [], ran: false };
18
+ }
19
+
20
+ function processBiomeDiagnostic(d: Record<string, unknown>, acc: LintAccumulator): void {
21
+ if (isBiomeComplexityDiagnostic(d)) return;
22
+ if (d.severity === 'error') acc.errors++;
23
+ else acc.warnings++;
24
+ acc.findings.push(biomeDiagToFinding(d, 'linting'));
25
+ }
26
+
27
+ function parseBiomeDiagnostics(stdout: string, acc: LintAccumulator): void {
28
+ const parsed = JSON.parse(stdout);
29
+ if (!parsed.diagnostics) return;
30
+ for (const d of parsed.diagnostics) {
31
+ processBiomeDiagnostic(d, acc);
32
+ }
33
+ }
34
+
35
+ async function lintWithBiome(dirPath: string, acc: LintAccumulator): Promise<void> {
36
+ const result = await runCommand('npx', ['@biomejs/biome', 'lint', '--reporter=json', '.'], dirPath);
37
+ if (result.exitCode > 1) return;
38
+
39
+ acc.ran = true;
40
+ try {
41
+ parseBiomeDiagnostics(result.stdout, acc);
42
+ } catch {
43
+ acc.errors += (result.stdout.match(/error/gi) || []).length;
44
+ acc.warnings += (result.stdout.match(/warning/gi) || []).length;
45
+ acc.ran = acc.errors > 0 || acc.warnings > 0 || result.exitCode === 0;
46
+ }
47
+ }
48
+
49
+ function processEslintMessage(
50
+ msg: Record<string, unknown>,
51
+ filePath: string,
52
+ dirPath: string,
53
+ acc: LintAccumulator,
54
+ ): void {
55
+ if (isEslintComplexityRule(msg.ruleId as string)) return;
56
+ if (msg.severity === 2) acc.errors++;
57
+ else acc.warnings++;
58
+ acc.findings.push({
59
+ severity: msg.severity === 2 ? 'high' : 'medium',
60
+ category: 'linting',
61
+ file: relative(dirPath, filePath),
62
+ line: (msg.line as number) ?? null,
63
+ title: (msg.ruleId as string) || 'Lint issue',
64
+ description: (msg.message as string) || '',
65
+ });
66
+ }
67
+
68
+ async function lintWithEslint(dirPath: string, acc: LintAccumulator): Promise<void> {
69
+ const result = await runCommand('npx', ['eslint', '--format=json', '.'], dirPath);
70
+ acc.ran = true;
71
+ try {
72
+ const parsed = JSON.parse(result.stdout);
73
+ for (const file of parsed) {
74
+ for (const msg of file.messages || []) {
75
+ processEslintMessage(msg, file.filePath, dirPath, acc);
76
+ }
77
+ }
78
+ } catch {
79
+ acc.errors += (result.stderr.match(/error/gi) || []).length;
80
+ acc.warnings += (result.stderr.match(/warning/gi) || []).length;
81
+ }
82
+ }
83
+
84
+ async function lintNode(dirPath: string, acc: LintAccumulator): Promise<void> {
85
+ const biomeConfig = existsSync(join(dirPath, 'biome.json')) || existsSync(join(dirPath, 'biome.jsonc'));
86
+ if (biomeConfig) {
87
+ await lintWithBiome(dirPath, acc);
88
+ } else {
89
+ await lintWithEslint(dirPath, acc);
90
+ }
91
+ }
92
+
93
+ async function lintPython(dirPath: string, acc: LintAccumulator): Promise<void> {
94
+ const result = await runCommand('ruff', ['check', '--output-format=json', '.'], dirPath);
95
+ if (result.exitCode !== 0 && !result.stdout.trim().startsWith('[')) return;
96
+
97
+ acc.ran = true;
98
+ try {
99
+ const parsed = JSON.parse(result.stdout);
100
+ for (const item of parsed) {
101
+ const sev = item.code?.startsWith('E') ? 'high' : 'medium';
102
+ if (sev === 'high') acc.errors++;
103
+ else acc.warnings++;
104
+ acc.findings.push({
105
+ severity: sev,
106
+ category: 'linting',
107
+ file: item.filename ? relative(dirPath, item.filename) : '',
108
+ line: item.location?.row ?? null,
109
+ title: item.code || 'Lint issue',
110
+ description: item.message || '',
111
+ });
112
+ }
113
+ } catch { /* ignore */ }
114
+ }
115
+
116
+ function processClippyMessage(msg: Record<string, unknown>, acc: LintAccumulator): void {
117
+ if (msg.reason !== 'compiler-message' || !msg.message) return;
118
+ const message = msg.message as Record<string, unknown>;
119
+ const level = message.level as string;
120
+ if (level === 'error') acc.errors++;
121
+ else if (level === 'warning') acc.warnings++;
122
+ const spans = message.spans as Array<Record<string, unknown>> | undefined;
123
+ const span = spans?.[0];
124
+ const code = message.code as Record<string, unknown> | undefined;
125
+ acc.findings.push({
126
+ severity: level === 'error' ? 'high' : 'medium',
127
+ category: 'linting',
128
+ file: (span?.file_name as string) || '',
129
+ line: (span?.line_start as number) ?? null,
130
+ title: (code?.code as string) || 'Clippy',
131
+ description: (message.message as string) || '',
132
+ });
133
+ }
134
+
135
+ async function lintRust(dirPath: string, acc: LintAccumulator): Promise<void> {
136
+ const result = await runCommand('cargo', ['clippy', '--message-format=json', '--', '-W', 'clippy::all'], dirPath);
137
+ if (result.exitCode > 1) return;
138
+
139
+ acc.ran = true;
140
+ for (const line of result.stdout.split('\n')) {
141
+ try {
142
+ const msg = JSON.parse(line);
143
+ processClippyMessage(msg, acc);
144
+ } catch { /* not JSON line */ }
145
+ }
146
+ }
147
+
148
+ function computeLintScore(totalErrors: number, totalWarnings: number, totalLines: number): number {
149
+ const kloc = Math.max(totalLines / 1000, 1);
150
+ const penaltyRaw = totalErrors * 10 + totalWarnings * 3;
151
+ const penaltyPerKloc = penaltyRaw / kloc;
152
+
153
+ let score: number;
154
+ if (penaltyPerKloc === 0) score = 100;
155
+ else if (penaltyPerKloc <= 5) score = 100 - penaltyPerKloc * 2;
156
+ else if (penaltyPerKloc <= 20) score = 90 - (penaltyPerKloc - 5) * 2;
157
+ else if (penaltyPerKloc <= 50) score = 60 - (penaltyPerKloc - 20) * 1.5;
158
+ else score = Math.max(0, 15 - (penaltyPerKloc - 50) * 0.3);
159
+
160
+ return Math.round(Math.max(0, Math.min(100, score)));
161
+ }
162
+
163
+ export async function analyzeLinting(
164
+ dirPath: string,
165
+ ecosystems: Ecosystem[],
166
+ files: SourceFile[],
167
+ ): Promise<{ score: number; findings: QualityFinding[]; available: boolean; issueCount: number }> {
168
+ const acc = newLintAccumulator();
169
+
170
+ if (ecosystems.includes('node')) await lintNode(dirPath, acc);
171
+ if (ecosystems.includes('python')) await lintPython(dirPath, acc);
172
+ if (ecosystems.includes('rust')) await lintRust(dirPath, acc);
173
+
174
+ if (!acc.ran) {
175
+ return { score: 0, findings: [], available: false, issueCount: 0 };
176
+ }
177
+
178
+ const totalLines = files.reduce((sum, f) => sum + f.lines, 0);
179
+ const score = computeLintScore(acc.errors, acc.warnings, totalLines);
180
+
181
+ return {
182
+ score,
183
+ findings: acc.findings.slice(0, 100),
184
+ available: true,
185
+ issueCount: acc.errors + acc.warnings,
186
+ };
187
+ }