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,466 +1,11 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
2
  // Licensed under the MIT License. See LICENSE file for details.
3
- import { spawn } from 'node:child_process';
4
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
5
- import { extname, join, relative } from 'node:path';
6
- // ============================================================================
7
- // Constants
8
- // ============================================================================
9
- const ECOSYSTEM_TOOLS = {
10
- node: [
11
- { name: 'eslint', check: ['npx', 'eslint', '--version'], category: 'linter', installCmd: 'npm install -D eslint' },
12
- { name: 'biome', check: ['npx', '@biomejs/biome', '--version'], category: 'linter', installCmd: 'npm install -D @biomejs/biome' },
13
- { name: 'prettier', check: ['npx', 'prettier', '--version'], category: 'formatter', installCmd: 'npm install -D prettier' },
14
- { name: 'typescript', check: ['npx', 'tsc', '--version'], category: 'general', installCmd: 'npm install -D typescript' },
15
- ],
16
- python: [
17
- { name: 'ruff', check: ['ruff', '--version'], category: 'linter', installCmd: 'uv tool install ruff || pip install ruff' },
18
- { name: 'black', check: ['black', '--version'], category: 'formatter', installCmd: 'uv tool install black || pip install black' },
19
- { name: 'radon', check: ['radon', '--version'], category: 'complexity', installCmd: 'uv tool install radon || pip install radon' },
20
- ],
21
- rust: [
22
- { name: 'clippy', check: ['cargo', 'clippy', '--version'], category: 'linter', installCmd: 'rustup component add clippy' },
23
- { name: 'rustfmt', check: ['rustfmt', '--version'], category: 'formatter', installCmd: 'rustup component add rustfmt' },
24
- ],
25
- go: [
26
- { name: 'golangci-lint', check: ['golangci-lint', '--version'], category: 'linter', installCmd: 'go install github.com/golangci-lint/golangci-lint/cmd/golangci-lint@latest' },
27
- { name: 'gofmt', check: ['gofmt', '-h'], category: 'formatter', installCmd: '(built-in with Go)' },
28
- ],
29
- swift: [
30
- { name: 'swiftlint', check: ['swiftlint', '--version'], category: 'linter', installCmd: 'brew install swiftlint' },
31
- { name: 'swiftformat', check: ['swiftformat', '--version'], category: 'formatter', installCmd: 'brew install swiftformat' },
32
- ],
33
- kotlin: [
34
- { name: 'ktlint', check: ['ktlint', '--version'], category: 'linter', installCmd: 'brew install ktlint' },
35
- { name: 'ktfmt', check: ['ktfmt', '--version'], category: 'formatter', installCmd: 'brew install ktfmt' },
36
- ],
37
- unknown: [],
38
- };
39
- const SOURCE_EXTENSIONS = new Set([
40
- '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
41
- '.py', '.pyi',
42
- '.rs',
43
- '.go',
44
- '.java', '.kt',
45
- '.cs',
46
- '.rb',
47
- '.php',
48
- '.swift',
49
- '.c', '.cpp', '.h', '.hpp',
50
- ]);
51
- const IGNORE_DIRS = new Set([
52
- 'node_modules', '.git', 'dist', 'build', '.next', '__pycache__',
53
- 'target', 'vendor', '.venv', 'venv', '.tox', 'coverage',
54
- '.mstro', '.cache', '.turbo', '.output',
55
- ]);
56
- const FILE_LENGTH_THRESHOLD = 300;
57
- const FUNCTION_LENGTH_THRESHOLD = 50;
58
- const TOTAL_STEPS = 7;
59
- function hasInstalledToolInCategory(installedSet, ecosystems, category) {
60
- for (const eco of ecosystems) {
61
- const specs = ECOSYSTEM_TOOLS[eco] || [];
62
- for (const spec of specs) {
63
- if (spec.category === category && installedSet.has(spec.name))
64
- return true;
65
- }
66
- }
67
- return false;
68
- }
69
- // ============================================================================
70
- // Ecosystem Detection
71
- // ============================================================================
72
- export function detectEcosystem(dirPath) {
73
- const ecosystems = [];
74
- try {
75
- const files = readdirSync(dirPath);
76
- if (files.includes('package.json'))
77
- ecosystems.push('node');
78
- if (files.includes('pyproject.toml') || files.includes('setup.py') || files.includes('requirements.txt'))
79
- ecosystems.push('python');
80
- if (files.includes('Cargo.toml'))
81
- ecosystems.push('rust');
82
- if (files.includes('go.mod'))
83
- ecosystems.push('go');
84
- if (files.includes('Package.swift') || files.some(f => f.endsWith('.xcodeproj') || f.endsWith('.xcworkspace')))
85
- ecosystems.push('swift');
86
- if (files.includes('build.gradle') || files.includes('build.gradle.kts'))
87
- ecosystems.push('kotlin');
88
- }
89
- catch {
90
- // Directory not readable
91
- }
92
- if (ecosystems.length === 0)
93
- ecosystems.push('unknown');
94
- return ecosystems;
95
- }
96
- /** Detect the Node.js package manager from lockfiles */
97
- function detectNodePackageManager(dirPath) {
98
- try {
99
- const files = readdirSync(dirPath);
100
- if (files.includes('bun.lockb') || files.includes('bun.lock'))
101
- return 'bun';
102
- if (files.includes('pnpm-lock.yaml'))
103
- return 'pnpm';
104
- if (files.includes('yarn.lock'))
105
- return 'yarn';
106
- }
107
- catch {
108
- // Directory not readable
109
- }
110
- return 'npm';
111
- }
112
- /** Build the install command for a Node.js dev dependency */
113
- function nodeInstallCmd(pm, pkg) {
114
- switch (pm) {
115
- case 'yarn': return `yarn add -D ${pkg}`;
116
- case 'pnpm': return `pnpm add -D ${pkg}`;
117
- case 'bun': return `bun add -d ${pkg}`;
118
- default: return `npm install -D ${pkg}`;
119
- }
120
- }
121
- // ============================================================================
122
- // Tool Detection
123
- // ============================================================================
124
- async function checkToolInstalled(check, cwd) {
125
- return new Promise((resolve) => {
126
- const proc = spawn(check[0], check.slice(1), {
127
- cwd,
128
- stdio: ['ignore', 'pipe', 'pipe'],
129
- timeout: 10000,
130
- });
131
- proc.on('close', (code) => resolve(code === 0));
132
- proc.on('error', () => resolve(false));
133
- });
134
- }
135
- export async function detectTools(dirPath) {
136
- const ecosystems = detectEcosystem(dirPath);
137
- const tools = [];
138
- const nodePm = ecosystems.includes('node') ? detectNodePackageManager(dirPath) : 'npm';
139
- for (const eco of ecosystems) {
140
- const specs = ECOSYSTEM_TOOLS[eco] || [];
141
- for (const spec of specs) {
142
- const installed = await checkToolInstalled(spec.check, dirPath);
143
- // For node tools, resolve install command using the project's package manager
144
- const installCommand = eco === 'node'
145
- ? nodeInstallCmd(nodePm, spec.installCmd.replace(/^npm install -D /, ''))
146
- : spec.installCmd;
147
- tools.push({
148
- name: spec.name,
149
- installed,
150
- installCommand,
151
- category: spec.category,
152
- });
153
- }
154
- }
155
- return { tools, ecosystem: ecosystems };
156
- }
157
- // ============================================================================
158
- // Tool Installation
159
- // ============================================================================
160
- export async function installTools(dirPath, toolNames) {
161
- const { tools } = await detectTools(dirPath);
162
- const toInstall = tools.filter((t) => !t.installed && (!toolNames || toolNames.includes(t.name)));
163
- const failures = [];
164
- for (const tool of toInstall) {
165
- if (tool.installCommand.startsWith('('))
166
- continue; // built-in, skip
167
- // Support chained commands with || (try first, fallback to second)
168
- const commands = tool.installCommand.split(' || ');
169
- let installed = false;
170
- for (const cmd of commands) {
171
- const parts = cmd.trim().split(' ');
172
- const result = await runCommand(parts[0], parts.slice(1), dirPath);
173
- if (result.exitCode === 0) {
174
- installed = true;
175
- break;
176
- }
177
- }
178
- if (!installed) {
179
- failures.push(`${tool.name}: all install methods failed`);
180
- }
181
- }
182
- // Re-detect after install
183
- const detected = await detectTools(dirPath);
184
- // Check if any requested tools are still missing after install
185
- const requestedNames = new Set(toolNames ?? toInstall.map((t) => t.name));
186
- const stillMissing = detected.tools.filter((t) => !t.installed && requestedNames.has(t.name)).map((t) => t.name);
187
- if (stillMissing.length > 0) {
188
- const detail = failures.length > 0 ? ` ${failures.join('; ')}` : '';
189
- throw new Error(`Failed to install: ${stillMissing.join(', ')}.${detail}`);
190
- }
191
- return detected;
192
- }
193
- function tryStatSync(path) {
194
- try {
195
- return statSync(path);
196
- }
197
- catch {
198
- return null;
199
- }
200
- }
201
- function tryReadFile(path) {
202
- try {
203
- return readFileSync(path, 'utf-8');
204
- }
205
- catch {
206
- return null;
207
- }
208
- }
209
- function tryReaddirSync(dir) {
210
- try {
211
- return readdirSync(dir);
212
- }
213
- catch {
214
- return null;
215
- }
216
- }
217
- function tryReadSourceFile(fullPath, rootPath) {
218
- const content = tryReadFile(fullPath);
219
- if (!content)
220
- return null;
221
- return {
222
- path: fullPath,
223
- relativePath: relative(rootPath, fullPath),
224
- lines: content.split('\n').length,
225
- content,
226
- };
227
- }
228
- function processEntry(entry, dir, rootPath, stack, files) {
229
- if (IGNORE_DIRS.has(entry))
230
- return;
231
- const fullPath = join(dir, entry);
232
- const stat = tryStatSync(fullPath);
233
- if (!stat)
234
- return;
235
- if (stat.isDirectory()) {
236
- stack.push(fullPath);
237
- return;
238
- }
239
- if (!stat.isFile() || !SOURCE_EXTENSIONS.has(extname(entry).toLowerCase()))
240
- return;
241
- const sourceFile = tryReadSourceFile(fullPath, rootPath);
242
- if (sourceFile)
243
- files.push(sourceFile);
244
- }
245
- function collectSourceFiles(dirPath, rootPath) {
246
- const files = [];
247
- const stack = [dirPath];
248
- while (stack.length > 0) {
249
- const dir = stack.pop();
250
- const entries = tryReaddirSync(dir);
251
- if (!entries)
252
- continue;
253
- for (const entry of entries) {
254
- processEntry(entry, dir, rootPath, stack, files);
255
- }
256
- }
257
- return files;
258
- }
259
- // ============================================================================
260
- // Command Runner
261
- // ============================================================================
262
- function runCommand(cmd, args, cwd) {
263
- return new Promise((resolve) => {
264
- const proc = spawn(cmd, args, {
265
- cwd,
266
- stdio: ['ignore', 'pipe', 'pipe'],
267
- timeout: 120000,
268
- });
269
- let stdout = '';
270
- let stderr = '';
271
- proc.stdout?.on('data', (d) => { stdout += d.toString(); });
272
- proc.stderr?.on('data', (d) => { stderr += d.toString(); });
273
- proc.on('close', (code) => resolve({ stdout, stderr, exitCode: code ?? 1 }));
274
- proc.on('error', (err) => resolve({ stdout: '', stderr: err.message, exitCode: 1 }));
275
- });
276
- }
277
- function newLintAccumulator() {
278
- return { errors: 0, warnings: 0, findings: [], ran: false };
279
- }
280
- function biomeSeverity(severity) {
281
- if (severity === 'error')
282
- return 'high';
283
- if (severity === 'warning')
284
- return 'medium';
285
- return 'low';
286
- }
287
- function processBiomeDiagnostic(d, acc) {
288
- const sev = biomeSeverity(d.severity);
289
- if (d.severity === 'error')
290
- acc.errors++;
291
- else
292
- acc.warnings++;
293
- const location = d.location;
294
- const span = location?.span ?? {};
295
- const start = span.start ?? {};
296
- const message = d.message;
297
- acc.findings.push({
298
- severity: sev,
299
- category: 'linting',
300
- file: location?.path || '',
301
- line: start.line ?? null,
302
- title: d.category || 'Lint issue',
303
- description: (typeof message === 'object' ? message?.text : message) || '',
304
- });
305
- }
306
- function parseBiomeDiagnostics(stdout, acc) {
307
- const parsed = JSON.parse(stdout);
308
- if (!parsed.diagnostics)
309
- return;
310
- for (const d of parsed.diagnostics) {
311
- processBiomeDiagnostic(d, acc);
312
- }
313
- }
314
- async function lintWithBiome(dirPath, acc) {
315
- const result = await runCommand('npx', ['@biomejs/biome', 'lint', '--reporter=json', '.'], dirPath);
316
- if (result.exitCode > 1)
317
- return;
318
- acc.ran = true;
319
- try {
320
- parseBiomeDiagnostics(result.stdout, acc);
321
- }
322
- catch {
323
- // JSON parse failed, try line counting
324
- acc.errors += (result.stdout.match(/error/gi) || []).length;
325
- acc.warnings += (result.stdout.match(/warning/gi) || []).length;
326
- acc.ran = acc.errors > 0 || acc.warnings > 0 || result.exitCode === 0;
327
- }
328
- }
329
- async function lintWithEslint(dirPath, acc) {
330
- const result = await runCommand('npx', ['eslint', '--format=json', '.'], dirPath);
331
- acc.ran = true;
332
- try {
333
- const parsed = JSON.parse(result.stdout);
334
- for (const file of parsed) {
335
- for (const msg of file.messages || []) {
336
- if (msg.severity === 2)
337
- acc.errors++;
338
- else
339
- acc.warnings++;
340
- acc.findings.push({
341
- severity: msg.severity === 2 ? 'high' : 'medium',
342
- category: 'linting',
343
- file: relative(dirPath, file.filePath),
344
- line: msg.line ?? null,
345
- title: msg.ruleId || 'Lint issue',
346
- description: msg.message,
347
- });
348
- }
349
- }
350
- }
351
- catch {
352
- acc.errors += (result.stderr.match(/error/gi) || []).length;
353
- acc.warnings += (result.stderr.match(/warning/gi) || []).length;
354
- }
355
- }
356
- async function lintNode(dirPath, acc) {
357
- const biomeConfig = existsSync(join(dirPath, 'biome.json')) || existsSync(join(dirPath, 'biome.jsonc'));
358
- if (biomeConfig) {
359
- await lintWithBiome(dirPath, acc);
360
- }
361
- else {
362
- await lintWithEslint(dirPath, acc);
363
- }
364
- }
365
- async function lintPython(dirPath, acc) {
366
- const result = await runCommand('ruff', ['check', '--output-format=json', '.'], dirPath);
367
- if (result.exitCode !== 0 && !result.stdout.trim().startsWith('['))
368
- return;
369
- acc.ran = true;
370
- try {
371
- const parsed = JSON.parse(result.stdout);
372
- for (const item of parsed) {
373
- const sev = item.code?.startsWith('E') ? 'high' : 'medium';
374
- if (sev === 'high')
375
- acc.errors++;
376
- else
377
- acc.warnings++;
378
- acc.findings.push({
379
- severity: sev,
380
- category: 'linting',
381
- file: item.filename ? relative(dirPath, item.filename) : '',
382
- line: item.location?.row ?? null,
383
- title: item.code || 'Lint issue',
384
- description: item.message || '',
385
- });
386
- }
387
- }
388
- catch { /* ignore */ }
389
- }
390
- function processClippyMessage(msg, acc) {
391
- if (msg.reason !== 'compiler-message' || !msg.message)
392
- return;
393
- const message = msg.message;
394
- const level = message.level;
395
- if (level === 'error')
396
- acc.errors++;
397
- else if (level === 'warning')
398
- acc.warnings++;
399
- const spans = message.spans;
400
- const span = spans?.[0];
401
- const code = message.code;
402
- acc.findings.push({
403
- severity: level === 'error' ? 'high' : 'medium',
404
- category: 'linting',
405
- file: span?.file_name || '',
406
- line: span?.line_start ?? null,
407
- title: code?.code || 'Clippy',
408
- description: message.message || '',
409
- });
410
- }
411
- function parseClippyOutput(stdout, acc) {
412
- for (const line of stdout.split('\n')) {
413
- try {
414
- const msg = JSON.parse(line);
415
- processClippyMessage(msg, acc);
416
- }
417
- catch { /* not JSON line */ }
418
- }
419
- }
420
- async function lintRust(dirPath, acc) {
421
- const result = await runCommand('cargo', ['clippy', '--message-format=json', '--', '-W', 'clippy::all'], dirPath);
422
- if (result.exitCode > 1)
423
- return;
424
- acc.ran = true;
425
- parseClippyOutput(result.stdout, acc);
426
- }
427
- function computeLintScore(totalErrors, totalWarnings, totalLines) {
428
- const kloc = Math.max(totalLines / 1000, 1);
429
- const penaltyRaw = totalErrors * 10 + totalWarnings * 3;
430
- const penaltyPerKloc = penaltyRaw / kloc;
431
- let score;
432
- if (penaltyPerKloc === 0)
433
- score = 100;
434
- else if (penaltyPerKloc <= 5)
435
- score = 100 - penaltyPerKloc * 2;
436
- else if (penaltyPerKloc <= 20)
437
- score = 90 - (penaltyPerKloc - 5) * 2;
438
- else if (penaltyPerKloc <= 50)
439
- score = 60 - (penaltyPerKloc - 20) * 1.5;
440
- else
441
- score = Math.max(0, 15 - (penaltyPerKloc - 50) * 0.3);
442
- return Math.round(Math.max(0, Math.min(100, score)));
443
- }
444
- async function analyzeLinting(dirPath, ecosystems, files) {
445
- const acc = newLintAccumulator();
446
- if (ecosystems.includes('node'))
447
- await lintNode(dirPath, acc);
448
- if (ecosystems.includes('python'))
449
- await lintPython(dirPath, acc);
450
- if (ecosystems.includes('rust'))
451
- await lintRust(dirPath, acc);
452
- if (!acc.ran) {
453
- return { score: 0, findings: [], available: false, issueCount: 0 };
454
- }
455
- const totalLines = files.reduce((sum, f) => sum + f.lines, 0);
456
- const score = computeLintScore(acc.errors, acc.warnings, totalLines);
457
- return {
458
- score,
459
- findings: acc.findings.slice(0, 100),
460
- available: true,
461
- issueCount: acc.errors + acc.warnings,
462
- };
463
- }
3
+ import { extname } from 'node:path';
4
+ import { analyzeComplexity, analyzeFunctionLength } from './quality-complexity.js';
5
+ import { analyzeLinting } from './quality-linting.js';
6
+ import { collectSourceFiles, detectEcosystem, runCommand } from './quality-tools.js';
7
+ import { FILE_LENGTH_THRESHOLD, hasInstalledToolInCategory, TOTAL_STEPS } from './quality-types.js';
8
+ export { detectEcosystem, detectTools, installTools } from './quality-tools.js';
464
9
  // ============================================================================
465
10
  // Formatting Analysis
466
11
  // ============================================================================
@@ -471,7 +16,6 @@ async function analyzeFormatting(dirPath, ecosystems, files) {
471
16
  if (ecosystems.includes('node')) {
472
17
  const result = await runCommand('npx', ['prettier', '--check', '.'], dirPath);
473
18
  ran = true;
474
- // prettier --check outputs filenames of unformatted files to stdout
475
19
  const unformatted = result.stdout.split('\n').filter((l) => l.trim() && !l.startsWith('Checking'));
476
20
  const nodeFiles = files.filter((f) => ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(extname(f.path)));
477
21
  totalFiles += nodeFiles.length;
@@ -530,192 +74,6 @@ function analyzeFileLength(files) {
530
74
  const score = Math.round(totalScore / files.length);
531
75
  return { score: Math.min(100, score), findings: findings.slice(0, 50), issueCount: findings.length };
532
76
  }
533
- // Match function declarations, arrow functions assigned to const/let, and methods
534
- const JS_FUNC_PATTERN = /^(\s*)(export\s+)?(async\s+)?function\s+(\w+)|^(\s*)(export\s+)?(const|let|var)\s+(\w+)\s*=\s*(async\s+)?\(|^(\s*)(public|private|protected)?\s*(async\s+)?(\w+)\s*\(/;
535
- function countBraceDeltas(line) {
536
- let delta = 0;
537
- for (const ch of line) {
538
- if (ch === '{')
539
- delta++;
540
- else if (ch === '}')
541
- delta--;
542
- }
543
- return delta;
544
- }
545
- function matchJsFuncStart(line) {
546
- const match = JS_FUNC_PATTERN.exec(line);
547
- if (!match)
548
- return null;
549
- const name = match[4] || match[8] || match[13] || 'anonymous';
550
- const indent = (match[1] || match[5] || match[10] || '').length;
551
- return { name, indent };
552
- }
553
- function extractJsFunctions(file) {
554
- const functions = [];
555
- const lines = file.content.split('\n');
556
- let braceDepth = 0;
557
- let currentFunc = null;
558
- let funcStartBraceDepth = 0;
559
- for (let i = 0; i < lines.length; i++) {
560
- if (!currentFunc) {
561
- const funcStart = matchJsFuncStart(lines[i]);
562
- if (funcStart) {
563
- currentFunc = { name: funcStart.name, startLine: i + 1, indent: funcStart.indent };
564
- funcStartBraceDepth = braceDepth;
565
- }
566
- }
567
- braceDepth += countBraceDeltas(lines[i]);
568
- if (currentFunc && braceDepth <= funcStartBraceDepth && i > currentFunc.startLine - 1) {
569
- functions.push({
570
- name: currentFunc.name,
571
- file: file.relativePath,
572
- startLine: currentFunc.startLine,
573
- lines: i + 1 - currentFunc.startLine + 1,
574
- });
575
- currentFunc = null;
576
- }
577
- }
578
- return functions;
579
- }
580
- function extractPyFunctions(file) {
581
- const functions = [];
582
- const lines = file.content.split('\n');
583
- const defPattern = /^(\s*)(async\s+)?def\s+(\w+)/;
584
- let currentFunc = null;
585
- for (let i = 0; i < lines.length; i++) {
586
- const match = defPattern.exec(lines[i]);
587
- if (match) {
588
- if (currentFunc) {
589
- functions.push({
590
- name: currentFunc.name,
591
- file: file.relativePath,
592
- startLine: currentFunc.startLine,
593
- lines: i - currentFunc.startLine + 1,
594
- });
595
- }
596
- currentFunc = { name: match[3], startLine: i + 1, indent: match[1].length };
597
- }
598
- else if (currentFunc && lines[i].trim() && !lines[i].startsWith(' '.repeat(currentFunc.indent + 1)) && !lines[i].startsWith('\t')) {
599
- functions.push({
600
- name: currentFunc.name,
601
- file: file.relativePath,
602
- startLine: currentFunc.startLine,
603
- lines: i - currentFunc.startLine + 1,
604
- });
605
- currentFunc = null;
606
- }
607
- }
608
- if (currentFunc) {
609
- functions.push({
610
- name: currentFunc.name,
611
- file: file.relativePath,
612
- startLine: currentFunc.startLine,
613
- lines: lines.length - currentFunc.startLine + 1,
614
- });
615
- }
616
- return functions;
617
- }
618
- function extractFunctions(file) {
619
- const ext = extname(file.path).toLowerCase();
620
- if (['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(ext))
621
- return extractJsFunctions(file);
622
- if (['.py', '.pyi'].includes(ext))
623
- return extractPyFunctions(file);
624
- return [];
625
- }
626
- function analyzeFunctionLength(files) {
627
- const allFunctions = [];
628
- for (const file of files) {
629
- allFunctions.push(...extractFunctions(file));
630
- }
631
- if (allFunctions.length === 0)
632
- return { score: 100, findings: [], issueCount: 0 };
633
- const findings = [];
634
- let totalScore = 0;
635
- for (const func of allFunctions) {
636
- const ratio = Math.max(1, func.lines / FUNCTION_LENGTH_THRESHOLD);
637
- const funcScore = 100 / ratio ** 1.5;
638
- totalScore += funcScore;
639
- if (func.lines > FUNCTION_LENGTH_THRESHOLD) {
640
- findings.push({
641
- severity: func.lines > FUNCTION_LENGTH_THRESHOLD * 3 ? 'high' : func.lines > FUNCTION_LENGTH_THRESHOLD * 2 ? 'medium' : 'low',
642
- category: 'function-length',
643
- file: func.file,
644
- line: func.startLine,
645
- title: `${func.name}() has ${func.lines} lines (threshold: ${FUNCTION_LENGTH_THRESHOLD})`,
646
- description: `Function "${func.name}" exceeds the recommended length by ${func.lines - FUNCTION_LENGTH_THRESHOLD} lines.`,
647
- });
648
- }
649
- }
650
- const score = Math.round(totalScore / allFunctions.length);
651
- return { score: Math.min(100, score), findings: findings.slice(0, 50), issueCount: findings.length };
652
- }
653
- // ============================================================================
654
- // Cyclomatic Complexity (Heuristic)
655
- // ============================================================================
656
- function countCyclomaticComplexity(funcContent) {
657
- let cc = 1; // base
658
- cc += (funcContent.match(/\bif\b/g) || []).length;
659
- cc += (funcContent.match(/\belse\s+if\b/g) || []).length;
660
- cc += (funcContent.match(/\bfor\b/g) || []).length;
661
- cc += (funcContent.match(/\bwhile\b/g) || []).length;
662
- cc += (funcContent.match(/\bcase\b/g) || []).length;
663
- cc += (funcContent.match(/\bcatch\b/g) || []).length;
664
- cc += (funcContent.match(/&&|\|\|/g) || []).length;
665
- cc += (funcContent.match(/\?\s*[^:]/g) || []).length; // ternary
666
- return cc;
667
- }
668
- function complexityToScore(cc) {
669
- if (cc <= 5)
670
- return 100;
671
- if (cc <= 10)
672
- return 100 - (cc - 5) * 5;
673
- if (cc <= 15)
674
- return 75 - (cc - 10) * 5;
675
- if (cc <= 20)
676
- return 50 - (cc - 15) * 5;
677
- return Math.max(0, 25 - (cc - 20) * 2.5);
678
- }
679
- function getFuncContent(file, func) {
680
- return file.content.split('\n').slice(func.startLine - 1, func.startLine - 1 + func.lines).join('\n');
681
- }
682
- function complexitySeverity(cc) {
683
- if (cc > 20)
684
- return 'high';
685
- if (cc > 15)
686
- return 'medium';
687
- return 'low';
688
- }
689
- function analyzeFunc(file, func, acc) {
690
- const funcContent = getFuncContent(file, func);
691
- const cc = countCyclomaticComplexity(funcContent);
692
- const funcScore = complexityToScore(cc);
693
- acc.weightedScore += funcScore * func.lines;
694
- acc.weight += func.lines;
695
- if (cc > 10) {
696
- acc.findings.push({
697
- severity: complexitySeverity(cc),
698
- category: 'complexity',
699
- file: func.file,
700
- line: func.startLine,
701
- title: `${func.name}() has cyclomatic complexity ${cc}`,
702
- description: `Complexity of ${cc} exceeds the recommended threshold of 10. Consider refactoring into smaller functions.`,
703
- });
704
- }
705
- }
706
- function analyzeComplexity(files) {
707
- const acc = { weightedScore: 0, weight: 0, findings: [] };
708
- for (const file of files) {
709
- const functions = extractFunctions(file);
710
- for (const func of functions) {
711
- analyzeFunc(file, func, acc);
712
- }
713
- }
714
- if (acc.weight === 0)
715
- return { score: 100, findings: [], issueCount: 0 };
716
- const score = Math.round(acc.weightedScore / acc.weight);
717
- return { score: Math.min(100, score), findings: acc.findings.slice(0, 50), issueCount: acc.findings.length };
718
- }
719
77
  // ============================================================================
720
78
  // Scoring
721
79
  // ============================================================================
@@ -792,9 +150,9 @@ export async function runQualityScan(dirPath, onProgress, installedToolNames) {
792
150
  const fmtResult = hasFormatter
793
151
  ? await analyzeFormatting(dirPath, ecosystems, files)
794
152
  : { score: 0, available: false, issueCount: 0 };
795
- // Step 4: Analyze complexity
153
+ // Step 4: Analyze complexity (using real tools: Biome, ESLint, radon)
796
154
  progress('Analyzing complexity', 4);
797
- const complexityResult = analyzeComplexity(files);
155
+ const complexityResult = await analyzeComplexity(dirPath, ecosystems, installedToolNames);
798
156
  // Step 5: Check file lengths
799
157
  progress('Checking file lengths', 5);
800
158
  const fileLengthResult = analyzeFileLength(files);
@@ -825,7 +183,7 @@ export async function runQualityScan(dirPath, onProgress, installedToolNames) {
825
183
  score: complexityResult.score,
826
184
  weight: DEFAULT_WEIGHTS.complexity,
827
185
  effectiveWeight: DEFAULT_WEIGHTS.complexity,
828
- available: true,
186
+ available: complexityResult.available,
829
187
  issueCount: complexityResult.issueCount,
830
188
  },
831
189
  {