mstro-app 0.5.0 → 0.5.5

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 (649) hide show
  1. package/LICENSE +129 -190
  2. package/PRIVACY.md +11 -11
  3. package/README.md +75 -28
  4. package/bin/commands/config.js +1 -2
  5. package/bin/mstro.js +55 -5
  6. package/bin/postinstall.js +0 -1
  7. package/dist/server/cli/eta-estimator.d.ts +55 -0
  8. package/dist/server/cli/eta-estimator.d.ts.map +1 -0
  9. package/dist/server/cli/eta-estimator.js +222 -0
  10. package/dist/server/cli/eta-estimator.js.map +1 -0
  11. package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -1
  12. package/dist/server/cli/headless/claude-invoker-process.js +0 -1
  13. package/dist/server/cli/headless/claude-invoker-process.js.map +1 -1
  14. package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -1
  15. package/dist/server/cli/headless/claude-invoker-stall.js +0 -1
  16. package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -1
  17. package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -1
  18. package/dist/server/cli/headless/claude-invoker-stream.js +0 -1
  19. package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -1
  20. package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -1
  21. package/dist/server/cli/headless/claude-invoker-tools.js +0 -1
  22. package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -1
  23. package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
  24. package/dist/server/cli/headless/claude-invoker.js +0 -1
  25. package/dist/server/cli/headless/claude-invoker.js.map +1 -1
  26. package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -1
  27. package/dist/server/cli/headless/haiku-assessments.js +0 -1
  28. package/dist/server/cli/headless/haiku-assessments.js.map +1 -1
  29. package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
  30. package/dist/server/cli/headless/headless-logger.js +0 -1
  31. package/dist/server/cli/headless/headless-logger.js.map +1 -1
  32. package/dist/server/cli/headless/index.d.ts.map +1 -1
  33. package/dist/server/cli/headless/index.js +0 -1
  34. package/dist/server/cli/headless/index.js.map +1 -1
  35. package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -1
  36. package/dist/server/cli/headless/native-timeout-detector.js +0 -1
  37. package/dist/server/cli/headless/native-timeout-detector.js.map +1 -1
  38. package/dist/server/cli/headless/output-utils.d.ts.map +1 -1
  39. package/dist/server/cli/headless/output-utils.js +0 -1
  40. package/dist/server/cli/headless/output-utils.js.map +1 -1
  41. package/dist/server/cli/headless/prompt-utils.d.ts.map +1 -1
  42. package/dist/server/cli/headless/prompt-utils.js +0 -1
  43. package/dist/server/cli/headless/prompt-utils.js.map +1 -1
  44. package/dist/server/cli/headless/resilient-runner.d.ts.map +1 -1
  45. package/dist/server/cli/headless/resilient-runner.js +0 -1
  46. package/dist/server/cli/headless/resilient-runner.js.map +1 -1
  47. package/dist/server/cli/headless/retry-strategies.d.ts.map +1 -1
  48. package/dist/server/cli/headless/retry-strategies.js +0 -1
  49. package/dist/server/cli/headless/retry-strategies.js.map +1 -1
  50. package/dist/server/cli/headless/runner.d.ts.map +1 -1
  51. package/dist/server/cli/headless/runner.js +0 -1
  52. package/dist/server/cli/headless/runner.js.map +1 -1
  53. package/dist/server/cli/headless/stall-assessor.d.ts +50 -0
  54. package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
  55. package/dist/server/cli/headless/stall-assessor.js +64 -10
  56. package/dist/server/cli/headless/stall-assessor.js.map +1 -1
  57. package/dist/server/cli/headless/tool-watchdog.d.ts +21 -0
  58. package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
  59. package/dist/server/cli/headless/tool-watchdog.js +19 -13
  60. package/dist/server/cli/headless/tool-watchdog.js.map +1 -1
  61. package/dist/server/cli/headless/types.d.ts.map +1 -1
  62. package/dist/server/cli/headless/types.js +0 -1
  63. package/dist/server/cli/headless/types.js.map +1 -1
  64. package/dist/server/cli/improvisation-attachments.d.ts.map +1 -1
  65. package/dist/server/cli/improvisation-attachments.js +0 -1
  66. package/dist/server/cli/improvisation-attachments.js.map +1 -1
  67. package/dist/server/cli/improvisation-history-store.d.ts.map +1 -1
  68. package/dist/server/cli/improvisation-history-store.js +5 -2
  69. package/dist/server/cli/improvisation-history-store.js.map +1 -1
  70. package/dist/server/cli/improvisation-movements.d.ts.map +1 -1
  71. package/dist/server/cli/improvisation-movements.js +0 -1
  72. package/dist/server/cli/improvisation-movements.js.map +1 -1
  73. package/dist/server/cli/improvisation-output-queue.d.ts +5 -1
  74. package/dist/server/cli/improvisation-output-queue.d.ts.map +1 -1
  75. package/dist/server/cli/improvisation-output-queue.js +30 -8
  76. package/dist/server/cli/improvisation-output-queue.js.map +1 -1
  77. package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
  78. package/dist/server/cli/improvisation-retry.js +0 -1
  79. package/dist/server/cli/improvisation-retry.js.map +1 -1
  80. package/dist/server/cli/improvisation-session-manager.d.ts +29 -0
  81. package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
  82. package/dist/server/cli/improvisation-session-manager.js +50 -2
  83. package/dist/server/cli/improvisation-session-manager.js.map +1 -1
  84. package/dist/server/cli/improvisation-types.d.ts +2 -0
  85. package/dist/server/cli/improvisation-types.d.ts.map +1 -1
  86. package/dist/server/cli/improvisation-types.js +0 -1
  87. package/dist/server/cli/improvisation-types.js.map +1 -1
  88. package/dist/server/cli/retry/retry-best-result.d.ts.map +1 -1
  89. package/dist/server/cli/retry/retry-best-result.js +0 -1
  90. package/dist/server/cli/retry/retry-best-result.js.map +1 -1
  91. package/dist/server/cli/retry/retry-context-loss.d.ts.map +1 -1
  92. package/dist/server/cli/retry/retry-context-loss.js +0 -1
  93. package/dist/server/cli/retry/retry-context-loss.js.map +1 -1
  94. package/dist/server/cli/retry/retry-premature-completion.d.ts.map +1 -1
  95. package/dist/server/cli/retry/retry-premature-completion.js +1 -2
  96. package/dist/server/cli/retry/retry-premature-completion.js.map +1 -1
  97. package/dist/server/cli/retry/retry-recovery-strategies.d.ts.map +1 -1
  98. package/dist/server/cli/retry/retry-recovery-strategies.js +0 -1
  99. package/dist/server/cli/retry/retry-recovery-strategies.js.map +1 -1
  100. package/dist/server/cli/retry/retry-resume-strategy.d.ts.map +1 -1
  101. package/dist/server/cli/retry/retry-resume-strategy.js +0 -1
  102. package/dist/server/cli/retry/retry-resume-strategy.js.map +1 -1
  103. package/dist/server/cli/retry/retry-runner-factory.d.ts.map +1 -1
  104. package/dist/server/cli/retry/retry-runner-factory.js +0 -1
  105. package/dist/server/cli/retry/retry-runner-factory.js.map +1 -1
  106. package/dist/server/cli/retry/retry-tool-results.d.ts.map +1 -1
  107. package/dist/server/cli/retry/retry-tool-results.js +0 -1
  108. package/dist/server/cli/retry/retry-tool-results.js.map +1 -1
  109. package/dist/server/cli/retry/retry-types.d.ts.map +1 -1
  110. package/dist/server/cli/retry/retry-types.js +0 -1
  111. package/dist/server/cli/retry/retry-types.js.map +1 -1
  112. package/dist/server/engines/EngineEvent.d.ts +126 -0
  113. package/dist/server/engines/EngineEvent.d.ts.map +1 -0
  114. package/dist/server/engines/EngineEvent.js +11 -0
  115. package/dist/server/engines/EngineEvent.js.map +1 -0
  116. package/dist/server/engines/claude/ClaudeCodeEngine.d.ts +47 -0
  117. package/dist/server/engines/claude/ClaudeCodeEngine.d.ts.map +1 -0
  118. package/dist/server/engines/claude/ClaudeCodeEngine.js +338 -0
  119. package/dist/server/engines/claude/ClaudeCodeEngine.js.map +1 -0
  120. package/dist/server/engines/factory.d.ts +21 -0
  121. package/dist/server/engines/factory.d.ts.map +1 -0
  122. package/dist/server/engines/factory.js +152 -0
  123. package/dist/server/engines/factory.js.map +1 -0
  124. package/dist/server/engines/opencode/OpenCodeEngine.d.ts +148 -0
  125. package/dist/server/engines/opencode/OpenCodeEngine.d.ts.map +1 -0
  126. package/dist/server/engines/opencode/OpenCodeEngine.js +630 -0
  127. package/dist/server/engines/opencode/OpenCodeEngine.js.map +1 -0
  128. package/dist/server/engines/opencode/OpenCodeServerManager.d.ts +172 -0
  129. package/dist/server/engines/opencode/OpenCodeServerManager.d.ts.map +1 -0
  130. package/dist/server/engines/opencode/OpenCodeServerManager.js +390 -0
  131. package/dist/server/engines/opencode/OpenCodeServerManager.js.map +1 -0
  132. package/dist/server/engines/opencode/model-catalog.d.ts +94 -0
  133. package/dist/server/engines/opencode/model-catalog.d.ts.map +1 -0
  134. package/dist/server/engines/opencode/model-catalog.js +141 -0
  135. package/dist/server/engines/opencode/model-catalog.js.map +1 -0
  136. package/dist/server/engines/types.d.ts +146 -0
  137. package/dist/server/engines/types.d.ts.map +1 -0
  138. package/dist/server/engines/types.js +4 -0
  139. package/dist/server/engines/types.js.map +1 -0
  140. package/dist/server/index.js +1 -2
  141. package/dist/server/index.js.map +1 -1
  142. package/dist/server/mcp/bouncer-cli.js +0 -1
  143. package/dist/server/mcp/bouncer-cli.js.map +1 -1
  144. package/dist/server/mcp/bouncer-haiku.d.ts +17 -4
  145. package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -1
  146. package/dist/server/mcp/bouncer-haiku.js +8 -125
  147. package/dist/server/mcp/bouncer-haiku.js.map +1 -1
  148. package/dist/server/mcp/bouncer-integration.d.ts +45 -0
  149. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  150. package/dist/server/mcp/bouncer-integration.js +69 -6
  151. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  152. package/dist/server/mcp/classifier/BouncerClassifier.d.ts +34 -0
  153. package/dist/server/mcp/classifier/BouncerClassifier.d.ts.map +1 -0
  154. package/dist/server/mcp/classifier/BouncerClassifier.js +4 -0
  155. package/dist/server/mcp/classifier/BouncerClassifier.js.map +1 -0
  156. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.d.ts +17 -0
  157. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.d.ts.map +1 -0
  158. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js +142 -0
  159. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js.map +1 -0
  160. package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.d.ts +68 -0
  161. package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.d.ts.map +1 -0
  162. package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.js +182 -0
  163. package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.js.map +1 -0
  164. package/dist/server/mcp/classifier/factory.d.ts +70 -0
  165. package/dist/server/mcp/classifier/factory.d.ts.map +1 -0
  166. package/dist/server/mcp/classifier/factory.js +155 -0
  167. package/dist/server/mcp/classifier/factory.js.map +1 -0
  168. package/dist/server/mcp/security-analysis.d.ts.map +1 -1
  169. package/dist/server/mcp/security-analysis.js +0 -1
  170. package/dist/server/mcp/security-analysis.js.map +1 -1
  171. package/dist/server/mcp/security-audit.d.ts.map +1 -1
  172. package/dist/server/mcp/security-audit.js +0 -1
  173. package/dist/server/mcp/security-audit.js.map +1 -1
  174. package/dist/server/mcp/security-patterns.d.ts.map +1 -1
  175. package/dist/server/mcp/security-patterns.js +0 -1
  176. package/dist/server/mcp/security-patterns.js.map +1 -1
  177. package/dist/server/mcp/server.js +0 -1
  178. package/dist/server/mcp/server.js.map +1 -1
  179. package/dist/server/routes/files.d.ts.map +1 -1
  180. package/dist/server/routes/files.js +0 -1
  181. package/dist/server/routes/files.js.map +1 -1
  182. package/dist/server/routes/improvise.d.ts.map +1 -1
  183. package/dist/server/routes/improvise.js +0 -1
  184. package/dist/server/routes/improvise.js.map +1 -1
  185. package/dist/server/routes/index.d.ts.map +1 -1
  186. package/dist/server/routes/index.js +0 -1
  187. package/dist/server/routes/index.js.map +1 -1
  188. package/dist/server/routes/instances.d.ts.map +1 -1
  189. package/dist/server/routes/instances.js +0 -1
  190. package/dist/server/routes/instances.js.map +1 -1
  191. package/dist/server/routes/notifications.d.ts.map +1 -1
  192. package/dist/server/routes/notifications.js +0 -1
  193. package/dist/server/routes/notifications.js.map +1 -1
  194. package/dist/server/server-setup.d.ts.map +1 -1
  195. package/dist/server/server-setup.js +0 -1
  196. package/dist/server/server-setup.js.map +1 -1
  197. package/dist/server/services/analytics.d.ts.map +1 -1
  198. package/dist/server/services/analytics.js +0 -1
  199. package/dist/server/services/analytics.js.map +1 -1
  200. package/dist/server/services/auth.d.ts.map +1 -1
  201. package/dist/server/services/auth.js +0 -1
  202. package/dist/server/services/auth.js.map +1 -1
  203. package/dist/server/services/client-id.d.ts.map +1 -1
  204. package/dist/server/services/client-id.js +0 -1
  205. package/dist/server/services/client-id.js.map +1 -1
  206. package/dist/server/services/file-explorer-ops.d.ts.map +1 -1
  207. package/dist/server/services/file-explorer-ops.js +0 -1
  208. package/dist/server/services/file-explorer-ops.js.map +1 -1
  209. package/dist/server/services/files.d.ts.map +1 -1
  210. package/dist/server/services/files.js +0 -1
  211. package/dist/server/services/files.js.map +1 -1
  212. package/dist/server/services/instances.d.ts.map +1 -1
  213. package/dist/server/services/instances.js +0 -1
  214. package/dist/server/services/instances.js.map +1 -1
  215. package/dist/server/services/pathUtils.d.ts.map +1 -1
  216. package/dist/server/services/pathUtils.js +0 -1
  217. package/dist/server/services/pathUtils.js.map +1 -1
  218. package/dist/server/services/plan/agent-loader.d.ts.map +1 -1
  219. package/dist/server/services/plan/agent-loader.js +0 -1
  220. package/dist/server/services/plan/agent-loader.js.map +1 -1
  221. package/dist/server/services/plan/agent-resolver.d.ts +26 -0
  222. package/dist/server/services/plan/agent-resolver.d.ts.map +1 -0
  223. package/dist/server/services/plan/agent-resolver.js +102 -0
  224. package/dist/server/services/plan/agent-resolver.js.map +1 -0
  225. package/dist/server/services/plan/board-config.d.ts.map +1 -1
  226. package/dist/server/services/plan/board-config.js +0 -1
  227. package/dist/server/services/plan/board-config.js.map +1 -1
  228. package/dist/server/services/plan/composer.d.ts.map +1 -1
  229. package/dist/server/services/plan/composer.js +59 -12
  230. package/dist/server/services/plan/composer.js.map +1 -1
  231. package/dist/server/services/plan/config-installer.d.ts.map +1 -1
  232. package/dist/server/services/plan/config-installer.js +0 -1
  233. package/dist/server/services/plan/config-installer.js.map +1 -1
  234. package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
  235. package/dist/server/services/plan/dependency-resolver.js +0 -1
  236. package/dist/server/services/plan/dependency-resolver.js.map +1 -1
  237. package/dist/server/services/plan/executor.d.ts.map +1 -1
  238. package/dist/server/services/plan/executor.js +48 -4
  239. package/dist/server/services/plan/executor.js.map +1 -1
  240. package/dist/server/services/plan/front-matter.d.ts.map +1 -1
  241. package/dist/server/services/plan/front-matter.js +0 -1
  242. package/dist/server/services/plan/front-matter.js.map +1 -1
  243. package/dist/server/services/plan/issue-classification.d.ts.map +1 -1
  244. package/dist/server/services/plan/issue-classification.js +0 -1
  245. package/dist/server/services/plan/issue-classification.js.map +1 -1
  246. package/dist/server/services/plan/issue-loader.d.ts.map +1 -1
  247. package/dist/server/services/plan/issue-loader.js +0 -1
  248. package/dist/server/services/plan/issue-loader.js.map +1 -1
  249. package/dist/server/services/plan/issue-prompt-builder.d.ts.map +1 -1
  250. package/dist/server/services/plan/issue-prompt-builder.js +33 -2
  251. package/dist/server/services/plan/issue-prompt-builder.js.map +1 -1
  252. package/dist/server/services/plan/issue-retry.d.ts +3 -1
  253. package/dist/server/services/plan/issue-retry.d.ts.map +1 -1
  254. package/dist/server/services/plan/issue-retry.js +2 -1
  255. package/dist/server/services/plan/issue-retry.js.map +1 -1
  256. package/dist/server/services/plan/issue-writer.d.ts.map +1 -1
  257. package/dist/server/services/plan/issue-writer.js +0 -1
  258. package/dist/server/services/plan/issue-writer.js.map +1 -1
  259. package/dist/server/services/plan/output-manager.d.ts.map +1 -1
  260. package/dist/server/services/plan/output-manager.js +0 -1
  261. package/dist/server/services/plan/output-manager.js.map +1 -1
  262. package/dist/server/services/plan/parser-core.d.ts.map +1 -1
  263. package/dist/server/services/plan/parser-core.js +1 -1
  264. package/dist/server/services/plan/parser-core.js.map +1 -1
  265. package/dist/server/services/plan/parser-migration.d.ts.map +1 -1
  266. package/dist/server/services/plan/parser-migration.js +0 -1
  267. package/dist/server/services/plan/parser-migration.js.map +1 -1
  268. package/dist/server/services/plan/parser.d.ts.map +1 -1
  269. package/dist/server/services/plan/parser.js +0 -1
  270. package/dist/server/services/plan/parser.js.map +1 -1
  271. package/dist/server/services/plan/progress-log.d.ts.map +1 -1
  272. package/dist/server/services/plan/progress-log.js +0 -1
  273. package/dist/server/services/plan/progress-log.js.map +1 -1
  274. package/dist/server/services/plan/prompt-builder.d.ts.map +1 -1
  275. package/dist/server/services/plan/prompt-builder.js +0 -1
  276. package/dist/server/services/plan/prompt-builder.js.map +1 -1
  277. package/dist/server/services/plan/readiness-planner.d.ts.map +1 -1
  278. package/dist/server/services/plan/readiness-planner.js +0 -1
  279. package/dist/server/services/plan/readiness-planner.js.map +1 -1
  280. package/dist/server/services/plan/review-gate.d.ts.map +1 -1
  281. package/dist/server/services/plan/review-gate.js +0 -1
  282. package/dist/server/services/plan/review-gate.js.map +1 -1
  283. package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
  284. package/dist/server/services/plan/state-reconciler.js +0 -1
  285. package/dist/server/services/plan/state-reconciler.js.map +1 -1
  286. package/dist/server/services/plan/types.d.ts +1 -0
  287. package/dist/server/services/plan/types.d.ts.map +1 -1
  288. package/dist/server/services/plan/types.js +0 -1
  289. package/dist/server/services/plan/types.js.map +1 -1
  290. package/dist/server/services/plan/watcher.d.ts.map +1 -1
  291. package/dist/server/services/plan/watcher.js +0 -1
  292. package/dist/server/services/plan/watcher.js.map +1 -1
  293. package/dist/server/services/platform-credentials.d.ts.map +1 -1
  294. package/dist/server/services/platform-credentials.js +0 -1
  295. package/dist/server/services/platform-credentials.js.map +1 -1
  296. package/dist/server/services/platform-token-lifecycle.d.ts +70 -0
  297. package/dist/server/services/platform-token-lifecycle.d.ts.map +1 -0
  298. package/dist/server/services/platform-token-lifecycle.js +156 -0
  299. package/dist/server/services/platform-token-lifecycle.js.map +1 -0
  300. package/dist/server/services/platform.d.ts +21 -56
  301. package/dist/server/services/platform.d.ts.map +1 -1
  302. package/dist/server/services/platform.js +98 -142
  303. package/dist/server/services/platform.js.map +1 -1
  304. package/dist/server/services/sentry.d.ts.map +1 -1
  305. package/dist/server/services/sentry.js +0 -1
  306. package/dist/server/services/sentry.js.map +1 -1
  307. package/dist/server/services/settings.d.ts +76 -2
  308. package/dist/server/services/settings.d.ts.map +1 -1
  309. package/dist/server/services/settings.js +127 -5
  310. package/dist/server/services/settings.js.map +1 -1
  311. package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
  312. package/dist/server/services/terminal/pty-manager.js +0 -1
  313. package/dist/server/services/terminal/pty-manager.js.map +1 -1
  314. package/dist/server/services/terminal/pty-utils.d.ts.map +1 -1
  315. package/dist/server/services/terminal/pty-utils.js +0 -1
  316. package/dist/server/services/terminal/pty-utils.js.map +1 -1
  317. package/dist/server/services/websocket/autocomplete.d.ts.map +1 -1
  318. package/dist/server/services/websocket/autocomplete.js +0 -1
  319. package/dist/server/services/websocket/autocomplete.js.map +1 -1
  320. package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -1
  321. package/dist/server/services/websocket/file-definition-handlers.js +0 -1
  322. package/dist/server/services/websocket/file-definition-handlers.js.map +1 -1
  323. package/dist/server/services/websocket/file-download-handler.d.ts.map +1 -1
  324. package/dist/server/services/websocket/file-download-handler.js +0 -1
  325. package/dist/server/services/websocket/file-download-handler.js.map +1 -1
  326. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  327. package/dist/server/services/websocket/file-explorer-handlers.js +0 -1
  328. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  329. package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -1
  330. package/dist/server/services/websocket/file-search-handlers.js +0 -1
  331. package/dist/server/services/websocket/file-search-handlers.js.map +1 -1
  332. package/dist/server/services/websocket/file-upload-handler.d.ts +2 -3
  333. package/dist/server/services/websocket/file-upload-handler.d.ts.map +1 -1
  334. package/dist/server/services/websocket/file-upload-handler.js +4 -7
  335. package/dist/server/services/websocket/file-upload-handler.js.map +1 -1
  336. package/dist/server/services/websocket/file-utils.d.ts.map +1 -1
  337. package/dist/server/services/websocket/file-utils.js +0 -1
  338. package/dist/server/services/websocket/file-utils.js.map +1 -1
  339. package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -1
  340. package/dist/server/services/websocket/git-branch-handlers.js +19 -7
  341. package/dist/server/services/websocket/git-branch-handlers.js.map +1 -1
  342. package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -1
  343. package/dist/server/services/websocket/git-diff-handlers.js +0 -1
  344. package/dist/server/services/websocket/git-diff-handlers.js.map +1 -1
  345. package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
  346. package/dist/server/services/websocket/git-handlers.js +58 -6
  347. package/dist/server/services/websocket/git-handlers.js.map +1 -1
  348. package/dist/server/services/websocket/git-head-watcher.d.ts.map +1 -1
  349. package/dist/server/services/websocket/git-head-watcher.js +0 -1
  350. package/dist/server/services/websocket/git-head-watcher.js.map +1 -1
  351. package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -1
  352. package/dist/server/services/websocket/git-log-handlers.js +0 -1
  353. package/dist/server/services/websocket/git-log-handlers.js.map +1 -1
  354. package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
  355. package/dist/server/services/websocket/git-pr-handlers.js +0 -1
  356. package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
  357. package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -1
  358. package/dist/server/services/websocket/git-tag-handlers.js +0 -1
  359. package/dist/server/services/websocket/git-tag-handlers.js.map +1 -1
  360. package/dist/server/services/websocket/git-utils.d.ts +18 -3
  361. package/dist/server/services/websocket/git-utils.d.ts.map +1 -1
  362. package/dist/server/services/websocket/git-utils.js +58 -8
  363. package/dist/server/services/websocket/git-utils.js.map +1 -1
  364. package/dist/server/services/websocket/git-worktree-handlers.d.ts.map +1 -1
  365. package/dist/server/services/websocket/git-worktree-handlers.js +230 -14
  366. package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
  367. package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
  368. package/dist/server/services/websocket/handler-context.js +0 -1
  369. package/dist/server/services/websocket/handler-context.js.map +1 -1
  370. package/dist/server/services/websocket/handler.d.ts +17 -1
  371. package/dist/server/services/websocket/handler.d.ts.map +1 -1
  372. package/dist/server/services/websocket/handler.js +57 -6
  373. package/dist/server/services/websocket/handler.js.map +1 -1
  374. package/dist/server/services/websocket/index.d.ts.map +1 -1
  375. package/dist/server/services/websocket/index.js +0 -1
  376. package/dist/server/services/websocket/index.js.map +1 -1
  377. package/dist/server/services/websocket/msg-id-tracker.d.ts.map +1 -1
  378. package/dist/server/services/websocket/msg-id-tracker.js +0 -1
  379. package/dist/server/services/websocket/msg-id-tracker.js.map +1 -1
  380. package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -1
  381. package/dist/server/services/websocket/plan-board-handlers.js +0 -1
  382. package/dist/server/services/websocket/plan-board-handlers.js.map +1 -1
  383. package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -1
  384. package/dist/server/services/websocket/plan-execution-handlers.js +6 -2
  385. package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -1
  386. package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
  387. package/dist/server/services/websocket/plan-handlers.js +0 -1
  388. package/dist/server/services/websocket/plan-handlers.js.map +1 -1
  389. package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -1
  390. package/dist/server/services/websocket/plan-helpers.js +0 -1
  391. package/dist/server/services/websocket/plan-helpers.js.map +1 -1
  392. package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -1
  393. package/dist/server/services/websocket/plan-issue-handlers.js +0 -1
  394. package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -1
  395. package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -1
  396. package/dist/server/services/websocket/plan-sprint-handlers.js +0 -1
  397. package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -1
  398. package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -1
  399. package/dist/server/services/websocket/quality-complexity.js +78 -27
  400. package/dist/server/services/websocket/quality-complexity.js.map +1 -1
  401. package/dist/server/services/websocket/quality-eta.d.ts +47 -0
  402. package/dist/server/services/websocket/quality-eta.d.ts.map +1 -0
  403. package/dist/server/services/websocket/quality-eta.js +110 -0
  404. package/dist/server/services/websocket/quality-eta.js.map +1 -0
  405. package/dist/server/services/websocket/quality-grading.d.ts +69 -0
  406. package/dist/server/services/websocket/quality-grading.d.ts.map +1 -0
  407. package/dist/server/services/websocket/quality-grading.js +650 -0
  408. package/dist/server/services/websocket/quality-grading.js.map +1 -0
  409. package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
  410. package/dist/server/services/websocket/quality-handlers.js +145 -8
  411. package/dist/server/services/websocket/quality-handlers.js.map +1 -1
  412. package/dist/server/services/websocket/quality-linting.d.ts.map +1 -1
  413. package/dist/server/services/websocket/quality-linting.js +0 -1
  414. package/dist/server/services/websocket/quality-linting.js.map +1 -1
  415. package/dist/server/services/websocket/quality-operations.d.ts +34 -0
  416. package/dist/server/services/websocket/quality-operations.d.ts.map +1 -0
  417. package/dist/server/services/websocket/quality-operations.js +47 -0
  418. package/dist/server/services/websocket/quality-operations.js.map +1 -0
  419. package/dist/server/services/websocket/quality-persistence.d.ts +23 -0
  420. package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -1
  421. package/dist/server/services/websocket/quality-persistence.js +38 -12
  422. package/dist/server/services/websocket/quality-persistence.js.map +1 -1
  423. package/dist/server/services/websocket/quality-review-agent.d.ts +1 -1
  424. package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
  425. package/dist/server/services/websocket/quality-review-agent.js +105 -57
  426. package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
  427. package/dist/server/services/websocket/quality-service.d.ts +12 -2
  428. package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
  429. package/dist/server/services/websocket/quality-service.js +387 -72
  430. package/dist/server/services/websocket/quality-service.js.map +1 -1
  431. package/dist/server/services/websocket/quality-tools.d.ts +22 -1
  432. package/dist/server/services/websocket/quality-tools.d.ts.map +1 -1
  433. package/dist/server/services/websocket/quality-tools.js +55 -3
  434. package/dist/server/services/websocket/quality-tools.js.map +1 -1
  435. package/dist/server/services/websocket/quality-types.d.ts +52 -3
  436. package/dist/server/services/websocket/quality-types.d.ts.map +1 -1
  437. package/dist/server/services/websocket/quality-types.js +1 -2
  438. package/dist/server/services/websocket/quality-types.js.map +1 -1
  439. package/dist/server/services/websocket/session-handlers.d.ts +3 -1
  440. package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
  441. package/dist/server/services/websocket/session-handlers.js +57 -10
  442. package/dist/server/services/websocket/session-handlers.js.map +1 -1
  443. package/dist/server/services/websocket/session-history.d.ts.map +1 -1
  444. package/dist/server/services/websocket/session-history.js +3 -1
  445. package/dist/server/services/websocket/session-history.js.map +1 -1
  446. package/dist/server/services/websocket/session-initialization.d.ts.map +1 -1
  447. package/dist/server/services/websocket/session-initialization.js +158 -43
  448. package/dist/server/services/websocket/session-initialization.js.map +1 -1
  449. package/dist/server/services/websocket/session-registry.d.ts +25 -0
  450. package/dist/server/services/websocket/session-registry.d.ts.map +1 -1
  451. package/dist/server/services/websocket/session-registry.js +19 -1
  452. package/dist/server/services/websocket/session-registry.js.map +1 -1
  453. package/dist/server/services/websocket/settings-handlers.d.ts +1 -1
  454. package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -1
  455. package/dist/server/services/websocket/settings-handlers.js +35 -5
  456. package/dist/server/services/websocket/settings-handlers.js.map +1 -1
  457. package/dist/server/services/websocket/skill-handlers.d.ts.map +1 -1
  458. package/dist/server/services/websocket/skill-handlers.js +0 -1
  459. package/dist/server/services/websocket/skill-handlers.js.map +1 -1
  460. package/dist/server/services/websocket/skill-watcher.d.ts.map +1 -1
  461. package/dist/server/services/websocket/skill-watcher.js +0 -1
  462. package/dist/server/services/websocket/skill-watcher.js.map +1 -1
  463. package/dist/server/services/websocket/tab-broadcast.d.ts +7 -2
  464. package/dist/server/services/websocket/tab-broadcast.d.ts.map +1 -1
  465. package/dist/server/services/websocket/tab-broadcast.js +10 -3
  466. package/dist/server/services/websocket/tab-broadcast.js.map +1 -1
  467. package/dist/server/services/websocket/tab-event-buffer.d.ts +97 -8
  468. package/dist/server/services/websocket/tab-event-buffer.d.ts.map +1 -1
  469. package/dist/server/services/websocket/tab-event-buffer.js +138 -13
  470. package/dist/server/services/websocket/tab-event-buffer.js.map +1 -1
  471. package/dist/server/services/websocket/tab-event-replay.d.ts +29 -13
  472. package/dist/server/services/websocket/tab-event-replay.d.ts.map +1 -1
  473. package/dist/server/services/websocket/tab-event-replay.js +55 -3
  474. package/dist/server/services/websocket/tab-event-replay.js.map +1 -1
  475. package/dist/server/services/websocket/tab-handlers.d.ts +9 -1
  476. package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
  477. package/dist/server/services/websocket/tab-handlers.js +47 -3
  478. package/dist/server/services/websocket/tab-handlers.js.map +1 -1
  479. package/dist/server/services/websocket/terminal-handlers.d.ts.map +1 -1
  480. package/dist/server/services/websocket/terminal-handlers.js +39 -4
  481. package/dist/server/services/websocket/terminal-handlers.js.map +1 -1
  482. package/dist/server/services/websocket/types.d.ts +30 -7
  483. package/dist/server/services/websocket/types.d.ts.map +1 -1
  484. package/dist/server/services/websocket/types.js +12 -7
  485. package/dist/server/services/websocket/types.js.map +1 -1
  486. package/dist/server/utils/agent-manager.d.ts.map +1 -1
  487. package/dist/server/utils/agent-manager.js +0 -1
  488. package/dist/server/utils/agent-manager.js.map +1 -1
  489. package/dist/server/utils/paths.d.ts.map +1 -1
  490. package/dist/server/utils/paths.js +0 -1
  491. package/dist/server/utils/paths.js.map +1 -1
  492. package/dist/server/utils/port-manager.d.ts.map +1 -1
  493. package/dist/server/utils/port-manager.js +0 -1
  494. package/dist/server/utils/port-manager.js.map +1 -1
  495. package/dist/server/utils/port.d.ts.map +1 -1
  496. package/dist/server/utils/port.js +0 -1
  497. package/dist/server/utils/port.js.map +1 -1
  498. package/package.json +6 -4
  499. package/server/cli/eta-estimator.ts +249 -0
  500. package/server/cli/headless/claude-invoker-process.ts +0 -1
  501. package/server/cli/headless/claude-invoker-stall.ts +0 -1
  502. package/server/cli/headless/claude-invoker-stream.ts +0 -1
  503. package/server/cli/headless/claude-invoker-tools.ts +0 -1
  504. package/server/cli/headless/claude-invoker.ts +0 -1
  505. package/server/cli/headless/haiku-assessments.ts +0 -1
  506. package/server/cli/headless/headless-logger.ts +0 -1
  507. package/server/cli/headless/index.ts +0 -1
  508. package/server/cli/headless/native-timeout-detector.ts +0 -1
  509. package/server/cli/headless/output-utils.ts +0 -1
  510. package/server/cli/headless/prompt-utils.ts +0 -1
  511. package/server/cli/headless/resilient-runner.ts +0 -1
  512. package/server/cli/headless/retry-strategies.ts +0 -1
  513. package/server/cli/headless/runner.ts +0 -1
  514. package/server/cli/headless/stall-assessor.ts +93 -1
  515. package/server/cli/headless/tool-watchdog.ts +21 -1
  516. package/server/cli/headless/types.ts +0 -1
  517. package/server/cli/improvisation-attachments.ts +0 -1
  518. package/server/cli/improvisation-history-store.ts +4 -2
  519. package/server/cli/improvisation-movements.ts +0 -1
  520. package/server/cli/improvisation-output-queue.ts +29 -8
  521. package/server/cli/improvisation-retry.ts +0 -1
  522. package/server/cli/improvisation-session-manager.ts +54 -2
  523. package/server/cli/improvisation-types.ts +2 -1
  524. package/server/cli/retry/retry-best-result.ts +0 -1
  525. package/server/cli/retry/retry-context-loss.ts +0 -1
  526. package/server/cli/retry/retry-premature-completion.ts +1 -2
  527. package/server/cli/retry/retry-recovery-strategies.ts +0 -1
  528. package/server/cli/retry/retry-resume-strategy.ts +0 -1
  529. package/server/cli/retry/retry-runner-factory.ts +0 -1
  530. package/server/cli/retry/retry-tool-results.ts +0 -1
  531. package/server/cli/retry/retry-types.ts +0 -1
  532. package/server/engines/EngineEvent.ts +156 -0
  533. package/server/engines/claude/ClaudeCodeEngine.ts +404 -0
  534. package/server/engines/factory.ts +176 -0
  535. package/server/engines/opencode/OpenCodeEngine.ts +786 -0
  536. package/server/engines/opencode/OpenCodeServerManager.ts +577 -0
  537. package/server/engines/opencode/model-catalog.ts +217 -0
  538. package/server/engines/types.ts +173 -0
  539. package/server/index.ts +1 -2
  540. package/server/mcp/bouncer-cli.ts +0 -1
  541. package/server/mcp/bouncer-haiku.ts +21 -146
  542. package/server/mcp/bouncer-integration.ts +107 -6
  543. package/server/mcp/classifier/BouncerClassifier.ts +40 -0
  544. package/server/mcp/classifier/ClaudeBouncerClassifier.ts +189 -0
  545. package/server/mcp/classifier/OpenCodeBouncerClassifier.ts +305 -0
  546. package/server/mcp/classifier/factory.ts +195 -0
  547. package/server/mcp/security-analysis.ts +0 -1
  548. package/server/mcp/security-audit.ts +0 -1
  549. package/server/mcp/security-patterns.ts +0 -1
  550. package/server/mcp/server.ts +0 -1
  551. package/server/routes/files.ts +0 -1
  552. package/server/routes/improvise.ts +0 -1
  553. package/server/routes/index.ts +0 -1
  554. package/server/routes/instances.ts +0 -1
  555. package/server/routes/notifications.ts +0 -1
  556. package/server/server-setup.ts +0 -1
  557. package/server/services/analytics.ts +0 -1
  558. package/server/services/auth.ts +0 -1
  559. package/server/services/client-id.ts +0 -1
  560. package/server/services/file-explorer-ops.ts +0 -1
  561. package/server/services/files.ts +0 -1
  562. package/server/services/instances.ts +0 -1
  563. package/server/services/pathUtils.ts +0 -1
  564. package/server/services/plan/agent-loader.ts +0 -1
  565. package/server/services/plan/agent-resolver.ts +115 -0
  566. package/server/services/plan/agents/code-review.md +43 -11
  567. package/server/services/plan/board-config.ts +0 -1
  568. package/server/services/plan/composer.ts +63 -12
  569. package/server/services/plan/config-installer.ts +0 -1
  570. package/server/services/plan/dependency-resolver.ts +0 -1
  571. package/server/services/plan/executor.ts +48 -4
  572. package/server/services/plan/front-matter.ts +0 -1
  573. package/server/services/plan/issue-classification.ts +0 -1
  574. package/server/services/plan/issue-loader.ts +0 -1
  575. package/server/services/plan/issue-prompt-builder.ts +39 -2
  576. package/server/services/plan/issue-retry.ts +5 -2
  577. package/server/services/plan/issue-writer.ts +0 -1
  578. package/server/services/plan/output-manager.ts +0 -1
  579. package/server/services/plan/parser-core.ts +1 -1
  580. package/server/services/plan/parser-migration.ts +0 -1
  581. package/server/services/plan/parser.ts +0 -1
  582. package/server/services/plan/progress-log.ts +0 -1
  583. package/server/services/plan/prompt-builder.ts +0 -1
  584. package/server/services/plan/readiness-planner.ts +0 -1
  585. package/server/services/plan/review-gate.ts +0 -1
  586. package/server/services/plan/state-reconciler.ts +0 -1
  587. package/server/services/plan/types.ts +4 -1
  588. package/server/services/plan/watcher.ts +0 -1
  589. package/server/services/platform-credentials.ts +0 -1
  590. package/server/services/platform-token-lifecycle.ts +171 -0
  591. package/server/services/platform.ts +106 -148
  592. package/server/services/sentry.ts +0 -1
  593. package/server/services/settings.ts +161 -5
  594. package/server/services/terminal/pty-manager.ts +0 -1
  595. package/server/services/terminal/pty-utils.ts +0 -1
  596. package/server/services/websocket/autocomplete.ts +0 -1
  597. package/server/services/websocket/file-definition-handlers.ts +0 -1
  598. package/server/services/websocket/file-download-handler.ts +0 -1
  599. package/server/services/websocket/file-explorer-handlers.ts +0 -1
  600. package/server/services/websocket/file-search-handlers.ts +0 -1
  601. package/server/services/websocket/file-upload-handler.ts +6 -5
  602. package/server/services/websocket/file-utils.ts +0 -1
  603. package/server/services/websocket/git-branch-handlers.ts +20 -7
  604. package/server/services/websocket/git-diff-handlers.ts +0 -1
  605. package/server/services/websocket/git-handlers.ts +66 -10
  606. package/server/services/websocket/git-head-watcher.ts +0 -1
  607. package/server/services/websocket/git-log-handlers.ts +0 -1
  608. package/server/services/websocket/git-pr-handlers.ts +0 -1
  609. package/server/services/websocket/git-tag-handlers.ts +0 -1
  610. package/server/services/websocket/git-utils.ts +69 -9
  611. package/server/services/websocket/git-worktree-handlers.ts +260 -17
  612. package/server/services/websocket/handler-context.ts +0 -1
  613. package/server/services/websocket/handler.ts +62 -6
  614. package/server/services/websocket/index.ts +0 -1
  615. package/server/services/websocket/msg-id-tracker.ts +0 -1
  616. package/server/services/websocket/plan-board-handlers.ts +0 -1
  617. package/server/services/websocket/plan-execution-handlers.ts +6 -2
  618. package/server/services/websocket/plan-handlers.ts +0 -1
  619. package/server/services/websocket/plan-helpers.ts +0 -1
  620. package/server/services/websocket/plan-issue-handlers.ts +0 -1
  621. package/server/services/websocket/plan-sprint-handlers.ts +0 -1
  622. package/server/services/websocket/quality-complexity.ts +80 -27
  623. package/server/services/websocket/quality-eta.ts +155 -0
  624. package/server/services/websocket/quality-grading.ts +834 -0
  625. package/server/services/websocket/quality-handlers.ts +153 -8
  626. package/server/services/websocket/quality-linting.ts +0 -1
  627. package/server/services/websocket/quality-operations.ts +72 -0
  628. package/server/services/websocket/quality-persistence.ts +47 -8
  629. package/server/services/websocket/quality-review-agent.ts +154 -65
  630. package/server/services/websocket/quality-service.ts +415 -68
  631. package/server/services/websocket/quality-tools.ts +62 -3
  632. package/server/services/websocket/quality-types.ts +61 -4
  633. package/server/services/websocket/session-handlers.ts +64 -11
  634. package/server/services/websocket/session-history.ts +3 -1
  635. package/server/services/websocket/session-initialization.ts +189 -47
  636. package/server/services/websocket/session-registry.ts +37 -1
  637. package/server/services/websocket/settings-handlers.ts +41 -5
  638. package/server/services/websocket/skill-handlers.ts +0 -1
  639. package/server/services/websocket/skill-watcher.ts +0 -1
  640. package/server/services/websocket/tab-broadcast.ts +10 -3
  641. package/server/services/websocket/tab-event-buffer.ts +143 -12
  642. package/server/services/websocket/tab-event-replay.ts +70 -4
  643. package/server/services/websocket/tab-handlers.ts +53 -6
  644. package/server/services/websocket/terminal-handlers.ts +39 -3
  645. package/server/services/websocket/types.ts +39 -8
  646. package/server/utils/agent-manager.ts +0 -1
  647. package/server/utils/paths.ts +0 -1
  648. package/server/utils/port-manager.ts +0 -1
  649. package/server/utils/port.ts +0 -1
@@ -1,11 +1,11 @@
1
1
  // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
- // Licensed under the MIT License. See LICENSE file for details.
3
2
 
4
3
  import { extname } from 'node:path';
5
4
  import { analyzeComplexity, analyzeFunctionLength } from './quality-complexity.js';
5
+ import { computeQualityRating, gradeFromScore } from './quality-grading.js';
6
6
  import { analyzeLinting } from './quality-linting.js';
7
- import { chunkFileList, collectSourceFiles, detectEcosystem, filesByExt, runCommand, type SourceFile } from './quality-tools.js';
8
- import { type CategoryPenalty, type CategoryScore, type Ecosystem, FILE_LENGTH_THRESHOLD, hasInstalledToolInCategory, type QualityFinding, type QualityResults, type ScanProgress, type ScoreBreakdown, TOTAL_STEPS } from './quality-types.js';
7
+ import { chunkFileList, collectSourceFiles, detectEcosystem, filesByExt, isTestFile, runCommand, type SourceFile } from './quality-tools.js';
8
+ import { type CategoryPenalty, type CategoryScore, type DimensionName, type Ecosystem, FILE_LENGTH_THRESHOLD, hasInstalledToolInCategory, type QualityFinding, type QualityResults, type ScanProgress, type ScoreBreakdown, TOTAL_STEPS } from './quality-types.js';
9
9
 
10
10
  const NODE_FMT_EXTS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
11
11
  const PY_FMT_EXTS = ['.py', '.pyi'];
@@ -112,6 +112,295 @@ async function analyzeFormatting(
112
112
  return { score, available: true, issueCount: acc.totalFiles - acc.passingFiles, findings: acc.findings.slice(0, 50) };
113
113
  }
114
114
 
115
+ // ============================================================================
116
+ // Build / Compile Error Detection
117
+ // ============================================================================
118
+ //
119
+ // A codebase that does not compile is, by the user's spec, an automatic F.
120
+ // We capture compile failures as `category: 'build'` findings with severity
121
+ // `critical` so they map to the Reliability dimension and trigger the
122
+ // "critical → F-" path through the standard severity logic — no special-
123
+ // case branching elsewhere in the grading module.
124
+ //
125
+ // Per ecosystem:
126
+ // - Node: tsc --noEmit (only if a tsconfig.json is present)
127
+ // - Rust: cargo check (idiomatic compile-test for crates)
128
+ // - Other: skipped — Python has no canonical "is it valid" check, Go
129
+ // projects vary too much in module structure for `go build ./...`
130
+ // to be reliable, and Swift/Kotlin compile via larger build
131
+ // systems we don't want to spawn from a quality scan.
132
+ //
133
+ // Findings are capped at the first 5 errors per check so a totally broken
134
+ // codebase doesn't produce 200 individual findings — one critical finding is
135
+ // enough to pin the grade.
136
+
137
+ const BUILD_FINDING_CAP = 5;
138
+
139
+ function tscOutputToFindings(output: string, dirPath: string): QualityFinding[] {
140
+ const findings: QualityFinding[] = [];
141
+ // tsc error format: `path/to/file.ts(line,col): error TS####: message`
142
+ const errorPattern = /^(.+?)\((\d+),\d+\):\s+error\s+TS\d+:\s+(.+)$/gm;
143
+ for (const match of output.matchAll(errorPattern)) {
144
+ if (findings.length >= BUILD_FINDING_CAP) break;
145
+ const filePath = match[1].replace(`${dirPath}/`, '').replace(/^\.\//, '');
146
+ findings.push({
147
+ severity: 'critical',
148
+ category: 'build',
149
+ file: filePath,
150
+ line: Number.parseInt(match[2], 10) || null,
151
+ title: `TypeScript build error`,
152
+ description: match[3].trim(),
153
+ suggestion: 'Resolve compile errors before merging — broken builds block all other quality work.',
154
+ });
155
+ }
156
+ return findings;
157
+ }
158
+
159
+ function cargoCheckOutputToFindings(output: string, dirPath: string): QualityFinding[] {
160
+ const findings: QualityFinding[] = [];
161
+ // cargo emits one JSON object per line in --message-format=json mode; in
162
+ // plain mode it emits "error[E####]: message\n --> path:line:col"
163
+ const errorPattern = /^error(?:\[E\d+\])?:\s+(.+?)$\s+-->\s+([^:\s]+):(\d+):\d+/gm;
164
+ for (const match of output.matchAll(errorPattern)) {
165
+ if (findings.length >= BUILD_FINDING_CAP) break;
166
+ findings.push({
167
+ severity: 'critical',
168
+ category: 'build',
169
+ file: match[2].replace(`${dirPath}/`, ''),
170
+ line: Number.parseInt(match[3], 10) || null,
171
+ title: `Rust build error`,
172
+ description: match[1].trim(),
173
+ suggestion: 'Resolve compile errors before merging — broken builds block all other quality work.',
174
+ });
175
+ }
176
+ return findings;
177
+ }
178
+
179
+ async function checkNodeBuild(dirPath: string, installed: Set<string> | null): Promise<QualityFinding[]> {
180
+ // Only run if TypeScript is installed. Avoids npm-installing tsc on the fly
181
+ // (slow + side-effecting) and cleanly skips JS-only projects.
182
+ if (installed && !installed.has('typescript')) return [];
183
+
184
+ // Only run if a tsconfig.json exists at the project root — otherwise tsc
185
+ // will pick up arbitrary nearby configs in monorepos and produce confusing
186
+ // results.
187
+ let hasTsconfig = false;
188
+ try {
189
+ const { readFileSync } = await import('node:fs');
190
+ readFileSync(`${dirPath}/tsconfig.json`, 'utf-8');
191
+ hasTsconfig = true;
192
+ } catch {
193
+ return [];
194
+ }
195
+ if (!hasTsconfig) return [];
196
+
197
+ const result = await runCommand('npx', ['tsc', '--noEmit', '--pretty', 'false'], dirPath);
198
+ if (result.exitCode === 0) return [];
199
+ // Combine stdout + stderr — tsc writes errors to stdout in --pretty=false.
200
+ return tscOutputToFindings(`${result.stdout}\n${result.stderr}`, dirPath);
201
+ }
202
+
203
+ async function checkRustBuild(dirPath: string): Promise<QualityFinding[]> {
204
+ const result = await runCommand('cargo', ['check', '--message-format=human'], dirPath);
205
+ if (result.exitCode === 0) return [];
206
+ return cargoCheckOutputToFindings(`${result.stdout}\n${result.stderr}`, dirPath);
207
+ }
208
+
209
+ async function analyzeBuildErrors(
210
+ dirPath: string,
211
+ ecosystems: Ecosystem[],
212
+ installed: Set<string> | null,
213
+ ): Promise<{ findings: QualityFinding[]; available: boolean }> {
214
+ const findings: QualityFinding[] = [];
215
+ let ran = false;
216
+
217
+ if (ecosystems.includes('node')) {
218
+ const nodeFindings = await checkNodeBuild(dirPath, installed);
219
+ if (nodeFindings.length > 0) ran = true;
220
+ findings.push(...nodeFindings);
221
+ }
222
+ if (ecosystems.includes('rust')) {
223
+ ran = true;
224
+ findings.push(...(await checkRustBuild(dirPath)));
225
+ }
226
+
227
+ // `available` only matters for the dimension-availability heuristic; the
228
+ // findings drive the actual grade. For build, "available" tracks whether
229
+ // we ran a build check at all (so a clean tsc output still counts).
230
+ return { findings, available: ran || ecosystems.includes('node') || ecosystems.includes('rust') };
231
+ }
232
+
233
+ // ============================================================================
234
+ // File Cohesion Analysis (LCOM-inspired)
235
+ // ============================================================================
236
+ //
237
+ // Long files are not all equal. A 1500-line file with one focused public
238
+ // surface (one class, one large function, several private helpers) is fine —
239
+ // it's cohesive. A 1500-line file mixing config + parsing + rendering + IO is
240
+ // a real maintenance hazard.
241
+ //
242
+ // We compute a 0-1 "mixed-concerns score" per file using cheap textual
243
+ // signals (no AST parsing — we already have the file content in memory):
244
+ //
245
+ // - Top-level export count — many independent exports = many concerns.
246
+ // - Distinct top-level identifier prefixes — cohesive files share a domain
247
+ // vocabulary (e.g., everything starts with `User…`); mixed files do not.
248
+ // - Distinct import roots — files that import from many unrelated modules
249
+ // are usually doing many unrelated things.
250
+ // - Section-divider density — `// ===` style dividers signal that the
251
+ // author is mentally separating concerns; many sections + low export
252
+ // overlap = mixed.
253
+ //
254
+ // The score then modulates the severity of any file-length violation:
255
+ //
256
+ // cohesion ≤ 0.30 → SUPPRESS the finding (the file is long but focused)
257
+ // cohesion ≤ 0.55 → low severity
258
+ // cohesion ≤ 0.75 → medium severity
259
+ // cohesion > 0.75 → high severity
260
+ //
261
+ // This implements the user's requirement that a 1000-line file might be
262
+ // "just fine" while another 1000-line file is a "severe mix of concerns."
263
+ // ============================================================================
264
+
265
+ const TOP_LEVEL_EXPORT_PATTERN = /^export\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var|interface|type|enum)\s+(\w+)/gm;
266
+ const TOP_LEVEL_DECL_PATTERN = /^(?:export\s+)?(?:async\s+)?(?:function|class)\s+(\w+)/gm;
267
+ const PY_TOP_LEVEL_PATTERN = /^(?:def|class)\s+(\w+)/gm;
268
+ const IMPORT_PATTERN = /^(?:import\s+.+from\s+['"]([^'"]+)['"]|from\s+([^\s]+)\s+import|import\s+([^\s]+))/gm;
269
+ const SECTION_DIVIDER_PATTERN = /^\s*(?:\/\/|#)\s*={3,}|^\s*(?:\/\/|#)\s*-{3,}/gm;
270
+
271
+ /** Group identifiers by their leading word-prefix and return how many distinct groups exist. */
272
+ function distinctIdentifierPrefixes(names: string[]): number {
273
+ if (names.length === 0) return 0;
274
+ const prefixes = new Set<string>();
275
+ for (const name of names) {
276
+ // Split on camelCase / snake_case boundaries; take the first segment.
277
+ const first = name.replace(/[A-Z][a-z]+|_+/g, (m, _o, _s) => `${m.replace(/_/g, '')}`).split('').filter(Boolean)[0] ?? name;
278
+ const lowered = first.toLowerCase();
279
+ if (lowered.length >= 2) prefixes.add(lowered);
280
+ }
281
+ return prefixes.size;
282
+ }
283
+
284
+ /** Extract the path "root" from an import specifier (the first non-dot segment). */
285
+ function importRoot(spec: string): string {
286
+ const trimmed = spec.replace(/^['"]|['"]$/g, '').trim();
287
+ if (!trimmed) return '';
288
+ if (trimmed.startsWith('.')) return 'relative';
289
+ return trimmed.split('/')[0].replace(/^@/, '');
290
+ }
291
+
292
+ interface CohesionSignals {
293
+ exports: number;
294
+ decls: number;
295
+ prefixes: number;
296
+ importRoots: number;
297
+ dividers: number;
298
+ isJs: boolean;
299
+ isPy: boolean;
300
+ }
301
+
302
+ function jsDeclNames(content: string): { exports: string[]; decls: string[] } {
303
+ const exports: string[] = [];
304
+ const decls: string[] = [];
305
+ for (const match of content.matchAll(TOP_LEVEL_EXPORT_PATTERN)) exports.push(match[1]);
306
+ for (const match of content.matchAll(TOP_LEVEL_DECL_PATTERN)) decls.push(match[1]);
307
+ return { exports, decls };
308
+ }
309
+
310
+ function pyDeclNames(content: string): { exports: string[]; decls: string[] } {
311
+ const exports: string[] = [];
312
+ const decls: string[] = [];
313
+ for (const match of content.matchAll(PY_TOP_LEVEL_PATTERN)) {
314
+ decls.push(match[1]);
315
+ // Python doesn't have explicit "export" — public iff it doesn't start with "_".
316
+ if (!match[1].startsWith('_')) exports.push(match[1]);
317
+ }
318
+ return { exports, decls };
319
+ }
320
+
321
+ function importRootCount(content: string): number {
322
+ const roots = new Set<string>();
323
+ for (const match of content.matchAll(IMPORT_PATTERN)) {
324
+ const spec = match[1] ?? match[2] ?? match[3] ?? '';
325
+ const root = importRoot(spec);
326
+ if (root) roots.add(root);
327
+ }
328
+ return roots.size;
329
+ }
330
+
331
+ function collectCohesionSignals(file: SourceFile): CohesionSignals {
332
+ const ext = extname(file.path).toLowerCase();
333
+ const isJs = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(ext);
334
+ const isPy = ['.py', '.pyi'].includes(ext);
335
+
336
+ const { exports, decls } = isJs
337
+ ? jsDeclNames(file.content)
338
+ : isPy
339
+ ? pyDeclNames(file.content)
340
+ : { exports: [], decls: [] };
341
+
342
+ const allNames = exports.length > 0 ? exports : decls;
343
+
344
+ return {
345
+ exports: exports.length,
346
+ decls: decls.length,
347
+ prefixes: distinctIdentifierPrefixes(allNames),
348
+ importRoots: importRootCount(file.content),
349
+ dividers: (file.content.match(SECTION_DIVIDER_PATTERN) || []).length,
350
+ isJs,
351
+ isPy,
352
+ };
353
+ }
354
+
355
+ /**
356
+ * Compute a 0-1 "mixed-concerns" score for a file. 0 = highly cohesive,
357
+ * 1 = many unrelated concerns. The formula combines four signals with
358
+ * empirically chosen weights — tuned so that:
359
+ *
360
+ * - A 2000-line file with 1 class + helpers scores ~0.15
361
+ * - A 2000-line file with 8 unrelated exports scores ~0.85
362
+ * - The CLI quality-tools.ts (one domain, many helpers) scores < 0.4
363
+ * - A miscellaneous "utils.ts" (string + date + DOM helpers) scores > 0.7
364
+ *
365
+ * Returns 0 for files we can't analyze (non-JS/Py), since we don't want to
366
+ * fabricate a violation for languages we can't introspect.
367
+ */
368
+ function computeMixedConcernsScore(file: SourceFile): number {
369
+ const sig = collectCohesionSignals(file);
370
+ if (!sig.isJs && !sig.isPy) return 0;
371
+
372
+ // Each component is independently normalized to [0, 1]; the final score
373
+ // averages them with slight weighting toward identifier-prefix variance
374
+ // (the strongest cohesion signal in practice).
375
+ const exportComponent = sig.exports <= 2 ? 0 : Math.min(1, (sig.exports - 2) / 12);
376
+ const prefixComponent = sig.prefixes <= 1 ? 0 : Math.min(1, (sig.prefixes - 1) / 6);
377
+ const importComponent = sig.importRoots <= 4 ? 0 : Math.min(1, (sig.importRoots - 4) / 12);
378
+ const dividerComponent = sig.dividers <= 2 ? 0 : Math.min(1, (sig.dividers - 2) / 6);
379
+
380
+ return Math.min(
381
+ 1,
382
+ 0.30 * prefixComponent +
383
+ 0.30 * exportComponent +
384
+ 0.25 * importComponent +
385
+ 0.15 * dividerComponent,
386
+ );
387
+ }
388
+
389
+ /**
390
+ * Map a file's mixed-concerns score to a severity for the file-length
391
+ * finding. Returns `null` to suppress the finding entirely when the file is
392
+ * cohesive enough that its length isn't a real concern.
393
+ */
394
+ function severityFromCohesion(mixed: number, lines: number): QualityFinding['severity'] | null {
395
+ // Files that are absurdly long (>5x threshold) emit a finding regardless
396
+ // of cohesion — a 5000-line file is always worth flagging even if focused.
397
+ const isAbsurd = lines > FILE_LENGTH_THRESHOLD * 5;
398
+ if (mixed <= 0.30 && !isAbsurd) return null;
399
+ if (mixed <= 0.55) return 'low';
400
+ if (mixed <= 0.75) return 'medium';
401
+ return 'high';
402
+ }
403
+
115
404
  // ============================================================================
116
405
  // File Length Analysis
117
406
  // ============================================================================
@@ -121,38 +410,68 @@ function analyzeFileLength(files: SourceFile[]): { score: number; findings: Qual
121
410
 
122
411
  const findings: QualityFinding[] = [];
123
412
  let totalScore = 0;
413
+ let scoredFiles = 0;
124
414
 
125
415
  for (const file of files) {
416
+ // Test files are exempt from structural-length checks: a long test file
417
+ // is normally just many independent small tests, which is a feature.
418
+ // Excluding them from both scoring and finding emission keeps the
419
+ // dimension's score honest (otherwise a clean prod codebase with a
420
+ // huge test file would be unfairly penalised on file-length).
421
+ if (isTestFile(file.relativePath)) continue;
422
+
126
423
  const ratio = Math.max(1, file.lines / FILE_LENGTH_THRESHOLD);
127
424
  const fileScore = 100 / ratio ** 1.5;
128
425
  totalScore += fileScore;
426
+ scoredFiles++;
129
427
 
130
428
  if (file.lines > FILE_LENGTH_THRESHOLD) {
429
+ const mixedScore = computeMixedConcernsScore(file);
430
+ const severity = severityFromCohesion(mixedScore, file.lines);
431
+ if (!severity) continue; // Cohesive long file — not actually a violation.
432
+
433
+ const cohesionPct = Math.round((1 - mixedScore) * 100);
131
434
  findings.push({
132
- severity: file.lines > FILE_LENGTH_THRESHOLD * 3 ? 'high' : file.lines > FILE_LENGTH_THRESHOLD * 2 ? 'medium' : 'low',
435
+ severity,
133
436
  category: 'file-length',
134
437
  file: file.relativePath,
135
438
  line: null,
136
- title: `File has ${file.lines} lines (threshold: ${FILE_LENGTH_THRESHOLD})`,
137
- description: `This file exceeds the recommended length of ${FILE_LENGTH_THRESHOLD} lines by ${file.lines - FILE_LENGTH_THRESHOLD} lines.`,
439
+ title: `File has ${file.lines} lines (threshold: ${FILE_LENGTH_THRESHOLD}, cohesion: ${cohesionPct}%)`,
440
+ description:
441
+ `Exceeds the ${FILE_LENGTH_THRESHOLD}-line threshold by ${file.lines - FILE_LENGTH_THRESHOLD} lines. ` +
442
+ `Mixed-concerns score is ${roundOne(mixedScore)} (0 = focused, 1 = many concerns); ` +
443
+ `severity reflects how mixed the file's responsibilities appear. ` +
444
+ (mixedScore > 0.55
445
+ ? 'Consider splitting unrelated exports into separate modules.'
446
+ : 'The file is long but reasonably focused — split only if a clear seam exists.'),
138
447
  });
139
448
  }
140
449
  }
141
450
 
142
- const score = Math.round(totalScore / files.length);
451
+ if (scoredFiles === 0) return { score: 100, findings: [], issueCount: 0 };
452
+ const score = Math.round(totalScore / scoredFiles);
143
453
  return { score: Math.min(100, score), findings: findings.slice(0, 50), issueCount: findings.length };
144
454
  }
145
455
 
456
+ function roundOne(n: number): number {
457
+ return Math.round(n * 10) / 10;
458
+ }
459
+
146
460
  // ============================================================================
147
- // Deterministic Scoring — penalty-density exponential decay
461
+ // Legacy Scoring Breakdown produces the per-category penalty data still
462
+ // consumed by older UI surfaces and persisted reports. The canonical grade
463
+ // now comes from the multi-dimensional `computeQualityRating`; this function
464
+ // only fills in `scoreBreakdown` so existing dashboards keep rendering.
148
465
  // ============================================================================
149
466
 
467
+ /**
468
+ * Score-to-grade conversion for legacy callers. Delegates to the shared
469
+ * grading module so there is one source of truth. Returns `string` to satisfy
470
+ * the wider `QualityResults.grade` field — `gradeFromScore` itself never
471
+ * returns the `'N/A'` variant of the shared `Grade` union.
472
+ */
150
473
  function computeGrade(score: number): string {
151
- if (score >= 90) return 'A';
152
- if (score >= 80) return 'B';
153
- if (score >= 70) return 'C';
154
- if (score >= 60) return 'D';
155
- return 'F';
474
+ return gradeFromScore(score);
156
475
  }
157
476
 
158
477
  const SEVERITY_WEIGHT: Record<string, number> = {
@@ -249,10 +568,27 @@ export function computeFormulaScore(
249
568
 
250
569
  export type ProgressCallback = (progress: ScanProgress) => void;
251
570
 
571
+ /**
572
+ * Sentinel thrown when a scan is cancelled mid-flight via the `signal`
573
+ * argument. Callers should treat it as a clean cancellation, not a scan
574
+ * failure (no `qualityError` payload, no persisted partial result).
575
+ */
576
+ export class QualityScanAbortedError extends Error {
577
+ constructor() {
578
+ super('Quality scan aborted');
579
+ this.name = 'QualityScanAbortedError';
580
+ }
581
+ }
582
+
583
+ function checkAborted(signal: AbortSignal | undefined): void {
584
+ if (signal?.aborted) throw new QualityScanAbortedError();
585
+ }
586
+
252
587
  export async function runQualityScan(
253
588
  dirPath: string,
254
589
  onProgress?: ProgressCallback,
255
590
  installedToolNames?: string[],
591
+ signal?: AbortSignal,
256
592
  ): Promise<QualityResults> {
257
593
  const ecosystems = detectEcosystem(dirPath);
258
594
 
@@ -264,10 +600,12 @@ export async function runQualityScan(
264
600
  };
265
601
 
266
602
  // Step 1: Collect source files
603
+ checkAborted(signal);
267
604
  progress('Collecting source files', 1);
268
605
  const files = await collectSourceFiles(dirPath, dirPath);
269
606
 
270
607
  // Step 2: Run linting (only if a linter is installed)
608
+ checkAborted(signal);
271
609
  progress('Running linters', 2);
272
610
  const hasLinter = !installedSet || hasInstalledToolInCategory(installedSet, ecosystems, 'linter');
273
611
  const lintResult = hasLinter
@@ -275,28 +613,39 @@ export async function runQualityScan(
275
613
  : { score: 0, findings: [], available: false, issueCount: 0 };
276
614
 
277
615
  // Step 3: Check formatting (only if a formatter is installed)
616
+ checkAborted(signal);
278
617
  progress('Checking formatting', 3);
279
618
  const hasFormatter = !installedSet || hasInstalledToolInCategory(installedSet, ecosystems, 'formatter');
280
619
  const fmtResult = hasFormatter
281
620
  ? await analyzeFormatting(dirPath, ecosystems, files)
282
621
  : { score: 0, available: false, issueCount: 0, findings: [] as QualityFinding[] };
283
622
 
284
- // Step 4: Analyze complexity (using real tools: Biome, ESLint, radon)
285
- progress('Analyzing complexity', 4);
623
+ // Step 4: Check for build/compile errors (auto-F if any are found)
624
+ checkAborted(signal);
625
+ progress('Checking build', 4);
626
+ const buildResult = await analyzeBuildErrors(dirPath, ecosystems, installedSet);
627
+
628
+ // Step 5: Analyze complexity (using real tools: Biome, ESLint, radon)
629
+ checkAborted(signal);
630
+ progress('Analyzing complexity', 5);
286
631
  const complexityResult = await analyzeComplexity(dirPath, ecosystems, files, installedToolNames);
287
632
 
288
- // Step 5: Check file lengths
289
- progress('Checking file lengths', 5);
633
+ // Step 6: Check file lengths
634
+ checkAborted(signal);
635
+ progress('Checking file lengths', 6);
290
636
  const fileLengthResult = analyzeFileLength(files);
291
637
 
292
- // Step 6: Check function lengths
293
- progress('Checking function lengths', 6);
638
+ // Step 7: Check function lengths
639
+ checkAborted(signal);
640
+ progress('Checking function lengths', 7);
294
641
  const funcLengthResult = analyzeFunctionLength(files);
295
642
 
296
- // Step 7: Compute scores
297
- progress('Computing scores', 7);
643
+ // Step 8: Compute scores
644
+ checkAborted(signal);
645
+ progress('Computing scores', 8);
298
646
 
299
647
  const allFindings = [
648
+ ...buildResult.findings,
300
649
  ...lintResult.findings,
301
650
  ...fmtResult.findings,
302
651
  ...complexityResult.findings,
@@ -305,54 +654,34 @@ export async function runQualityScan(
305
654
  ];
306
655
 
307
656
  const totalLines = files.reduce((sum, f) => sum + f.lines, 0);
308
- const { score: overall, breakdown } = computeFormulaScore(allFindings, totalLines);
657
+ // Legacy breakdown drives the per-category panels and persisted `scoreBreakdown`.
658
+ const { breakdown } = computeFormulaScore(allFindings, totalLines);
659
+ // Canonical multi-dimensional rating — drives `overall`, `grade`, and the
660
+ // new dimensions/qualityGate/gradeRationale fields. Force Maintainability
661
+ // to N/A when no real maintainability tool ran (file/function length alone
662
+ // would otherwise let a clean codebase claim grade A even though linting
663
+ // was never checked).
664
+ const forceNA = new Set<DimensionName>();
665
+ if (!hasLinter && !hasFormatter && !complexityResult.available) {
666
+ forceNA.add('maintainability');
667
+ }
668
+ const rating = computeQualityRating(allFindings, totalLines, { forceNA });
669
+
670
+ // Build score: 100 if no compile errors, 0 if any (one error breaks everything).
671
+ const buildScore = buildResult.findings.length === 0 ? 100 : 0;
309
672
 
310
673
  const categories: CategoryScore[] = [
311
- {
312
- name: 'Linting',
313
- score: lintResult.score,
314
- weight: 0,
315
- effectiveWeight: 0,
316
- available: lintResult.available,
317
- issueCount: lintResult.issueCount,
318
- },
319
- {
320
- name: 'Formatting',
321
- score: fmtResult.score,
322
- weight: 0,
323
- effectiveWeight: 0,
324
- available: fmtResult.available,
325
- issueCount: fmtResult.issueCount,
326
- },
327
- {
328
- name: 'Complexity',
329
- score: complexityResult.score,
330
- weight: 0,
331
- effectiveWeight: 0,
332
- available: complexityResult.available,
333
- issueCount: complexityResult.issueCount,
334
- },
335
- {
336
- name: 'File Length',
337
- score: fileLengthResult.score,
338
- weight: 0,
339
- effectiveWeight: 0,
340
- available: true,
341
- issueCount: fileLengthResult.issueCount,
342
- },
343
- {
344
- name: 'Function Length',
345
- score: funcLengthResult.score,
346
- weight: 0,
347
- effectiveWeight: 0,
348
- available: true,
349
- issueCount: funcLengthResult.issueCount,
350
- },
674
+ { name: 'Build', score: buildScore, available: buildResult.available, issueCount: buildResult.findings.length },
675
+ { name: 'Linting', score: lintResult.score, available: lintResult.available, issueCount: lintResult.issueCount },
676
+ { name: 'Formatting', score: fmtResult.score, available: fmtResult.available, issueCount: fmtResult.issueCount },
677
+ { name: 'Complexity', score: complexityResult.score, available: complexityResult.available, issueCount: complexityResult.issueCount },
678
+ { name: 'File Length', score: fileLengthResult.score, available: true, issueCount: fileLengthResult.issueCount },
679
+ { name: 'Function Length', score: funcLengthResult.score, available: true, issueCount: funcLengthResult.issueCount },
351
680
  ];
352
681
 
353
682
  return {
354
- overall,
355
- grade: computeGrade(overall),
683
+ overall: rating.overall.score,
684
+ grade: rating.overall.grade,
356
685
  categories,
357
686
  findings: allFindings.slice(0, 200),
358
687
  codeReview: [],
@@ -361,6 +690,9 @@ export async function runQualityScan(
361
690
  timestamp: new Date().toISOString(),
362
691
  ecosystem: ecosystems,
363
692
  scoreBreakdown: breakdown,
693
+ dimensions: rating.dimensions,
694
+ qualityGate: rating.qualityGate,
695
+ gradeRationale: rating.gradeRationale,
364
696
  };
365
697
  }
366
698
 
@@ -370,20 +702,35 @@ export async function runQualityScan(
370
702
 
371
703
  /**
372
704
  * Recompute the overall score after AI code review findings become available.
373
- * Merges CLI + AI findings and runs the deterministic penalty-density formula.
705
+ * Merges CLI + AI findings, runs the canonical multi-dimensional rating, and
706
+ * recomputes the legacy `scoreBreakdown` so both new and old UI surfaces stay
707
+ * in sync after the merge.
374
708
  */
375
709
  export function recomputeWithAiReview(
376
710
  results: QualityResults,
377
711
  aiFindings: Array<{ severity: string; category: string }>,
378
712
  ): QualityResults {
379
713
  const allFindings = [...results.findings, ...aiFindings];
380
- const { score: overall, breakdown } = computeFormulaScore(allFindings, results.totalLines);
714
+ const { breakdown } = computeFormulaScore(allFindings, results.totalLines);
715
+ // Preserve the Maintainability N/A signal across AI re-merges by deriving
716
+ // forceNA from category availability snapshotted in the original scan.
717
+ const forceNA = new Set<DimensionName>();
718
+ const linting = results.categories.find((c) => c.name === 'Linting');
719
+ const formatting = results.categories.find((c) => c.name === 'Formatting');
720
+ const complexity = results.categories.find((c) => c.name === 'Complexity');
721
+ if (!linting?.available && !formatting?.available && !complexity?.available) {
722
+ forceNA.add('maintainability');
723
+ }
724
+ const rating = computeQualityRating(allFindings, results.totalLines, { forceNA });
381
725
 
382
726
  return {
383
727
  ...results,
384
- overall,
385
- grade: computeGrade(overall),
728
+ overall: rating.overall.score,
729
+ grade: rating.overall.grade,
386
730
  codeReview: results.codeReview,
387
731
  scoreBreakdown: breakdown,
732
+ dimensions: rating.dimensions,
733
+ qualityGate: rating.qualityGate,
734
+ gradeRationale: rating.gradeRationale,
388
735
  };
389
736
  }