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
@@ -0,0 +1,650 @@
1
+ // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
2
+ // ============================================================================
3
+ // Category -> Dimension Mapping
4
+ // ============================================================================
5
+ const SECURITY_CATEGORIES = new Set(['security']);
6
+ const RELIABILITY_CATEGORIES = new Set(['bugs', 'logic', 'performance', 'complexity', 'build']);
7
+ const MAINTAINABILITY_CATEGORIES = new Set([
8
+ 'lint',
9
+ 'linting',
10
+ 'format',
11
+ 'file-length',
12
+ 'function-length',
13
+ 'architecture',
14
+ 'oop',
15
+ 'maintainability',
16
+ ]);
17
+ /**
18
+ * Map a finding category to one of the three quality dimensions.
19
+ * Unknown categories default to maintainability (the catch-all bucket) so
20
+ * that surprise categories never silently disappear from the grade.
21
+ */
22
+ export function categoryToDimension(category) {
23
+ if (SECURITY_CATEGORIES.has(category))
24
+ return 'security';
25
+ if (RELIABILITY_CATEGORIES.has(category))
26
+ return 'reliability';
27
+ if (MAINTAINABILITY_CATEGORIES.has(category))
28
+ return 'maintainability';
29
+ return 'maintainability';
30
+ }
31
+ /** Categories that represent architectural problems — used by the arch penalty. */
32
+ const ARCHITECTURE_CATEGORIES = new Set(['architecture', 'oop']);
33
+ // ============================================================================
34
+ // Score Bands & Modifier Math
35
+ // ============================================================================
36
+ /**
37
+ * Score boundaries for each base grade. Note the gap between C (70+) and F+
38
+ * (≤69): the band 60-69 maps to F+ instead of D, per product spec ("60s and
39
+ * below is F").
40
+ */
41
+ const BASE_BAND_TOP = {
42
+ A: 100,
43
+ B: 89,
44
+ C: 79,
45
+ F: 69, // F covers 56-69 (F+ for 65-69, F for 56-64) — F- splits off below
46
+ };
47
+ const BASE_BAND_BOTTOM = {
48
+ A: 90,
49
+ B: 80,
50
+ C: 70,
51
+ F: 56, // F- covers 0-55 — handled specially in scoreToGrade()
52
+ };
53
+ /**
54
+ * Convert a 0-100 score to the full letter grade including +/- modifier.
55
+ *
56
+ * Within an A/B/C band, the band is split into thirds:
57
+ * X- bottom third (e.g., A-: 90-92)
58
+ * X middle third (e.g., A : 93-96)
59
+ * X+ top third (e.g., A+: 97-100)
60
+ *
61
+ * The F band uses two slices instead of three because there is no academic
62
+ * "F0" anchor and the user wanted F+/F/F-:
63
+ * F- 0-55 "critically broken"
64
+ * F 56-64 "broken"
65
+ * F+ 65-69 "barely failing"
66
+ *
67
+ * Compile/critical-severity hard caps are applied separately, not by score.
68
+ */
69
+ export function scoreToGrade(score) {
70
+ if (score >= 97)
71
+ return 'A+';
72
+ if (score >= 93)
73
+ return 'A';
74
+ if (score >= 90)
75
+ return 'A-';
76
+ if (score >= 87)
77
+ return 'B+';
78
+ if (score >= 83)
79
+ return 'B';
80
+ if (score >= 80)
81
+ return 'B-';
82
+ if (score >= 77)
83
+ return 'C+';
84
+ if (score >= 73)
85
+ return 'C';
86
+ if (score >= 70)
87
+ return 'C-';
88
+ if (score >= 65)
89
+ return 'F+';
90
+ if (score >= 56)
91
+ return 'F';
92
+ return 'F-';
93
+ }
94
+ /**
95
+ * Legacy single-letter conversion. Returns the *base* grade only (no
96
+ * modifier) for compatibility with callers that pre-date the +/- rollout
97
+ * (`scoreBreakdown.categoryPenalties[].grade`, etc.). New surfaces should
98
+ * call `scoreToGrade()` instead.
99
+ */
100
+ export function gradeFromScore(score) {
101
+ const full = scoreToGrade(score);
102
+ // Strip the modifier so legacy callers still see exactly one of A/B/C/F.
103
+ return baseGradeOf(full);
104
+ }
105
+ /** Strip the +/- modifier from a letter grade. */
106
+ function baseGradeOf(g) {
107
+ if (g === 'N/A' || g === 'D')
108
+ return g;
109
+ if (g.startsWith('A'))
110
+ return 'A';
111
+ if (g.startsWith('B'))
112
+ return 'B';
113
+ if (g.startsWith('C'))
114
+ return 'C';
115
+ return 'F';
116
+ }
117
+ /**
118
+ * Linearly interpolate a score within a base band.
119
+ *
120
+ * `position` is in [0, 1]: 0 = "as bad as this grade gets" (band bottom),
121
+ * 1 = "as good as this grade gets" (band top, just below the next grade).
122
+ */
123
+ function scoreInBand(grade, position) {
124
+ const clamped = Math.max(0, Math.min(1, position));
125
+ const bottom = BASE_BAND_BOTTOM[grade];
126
+ const top = BASE_BAND_TOP[grade];
127
+ return Math.round(bottom + (top - bottom) * clamped);
128
+ }
129
+ // ============================================================================
130
+ // Severity Helpers
131
+ // ============================================================================
132
+ function isSeverity(s) {
133
+ return s === 'critical' || s === 'high' || s === 'medium' || s === 'low';
134
+ }
135
+ function countSeverities(findings) {
136
+ const counts = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
137
+ for (const f of findings) {
138
+ if (!isSeverity(f.severity))
139
+ continue;
140
+ counts[f.severity]++;
141
+ counts.total++;
142
+ }
143
+ return counts;
144
+ }
145
+ function worstSeverity(counts) {
146
+ if (counts.critical > 0)
147
+ return 'critical';
148
+ if (counts.high > 0)
149
+ return 'high';
150
+ if (counts.medium > 0)
151
+ return 'medium';
152
+ if (counts.low > 0)
153
+ return 'low';
154
+ return null;
155
+ }
156
+ // ============================================================================
157
+ // Security Dimension
158
+ // ============================================================================
159
+ /**
160
+ * Security grading — strictest of the three dimensions. Any medium-or-worse
161
+ * security finding immediately drops the grade below B because security
162
+ * issues can't be amortized over codebase size.
163
+ *
164
+ * A critical security issue caps at F- (the worst grade). One low-severity
165
+ * finding still earns a B- because every team has a few.
166
+ */
167
+ function gradeSecurity(findings) {
168
+ const counts = countSeverities(findings);
169
+ const worst = worstSeverity(counts);
170
+ if (counts.total === 0) {
171
+ return makeDimension('security', 100, '0 security findings', 0, null);
172
+ }
173
+ if (counts.critical > 0) {
174
+ // Critical security issue → F-, not just F. There's no recovering by
175
+ // averaging this away across a clean codebase.
176
+ return makeDimension('security', Math.max(0, 55 - counts.critical * 5), `${counts.critical} critical-severity security ${pluralize('issue', counts.critical)}`, counts.total, worst);
177
+ }
178
+ let baseGrade;
179
+ let position;
180
+ let rationale;
181
+ if (counts.high > 0) {
182
+ baseGrade = 'F';
183
+ position = 1 / (1 + counts.high);
184
+ rationale = `${counts.high} high-severity security ${pluralize('issue', counts.high)}`;
185
+ }
186
+ else if (counts.medium > 0) {
187
+ baseGrade = 'C';
188
+ position = 1 / (1 + counts.medium);
189
+ rationale = `${counts.medium} medium-severity security ${pluralize('issue', counts.medium)}`;
190
+ }
191
+ else {
192
+ // Only low-severity findings.
193
+ baseGrade = 'B';
194
+ position = 1 / Math.max(1, counts.low);
195
+ rationale = `${counts.low} low-severity security ${pluralize('issue', counts.low)}`;
196
+ }
197
+ const score = scoreInBand(baseGrade, position);
198
+ return makeDimension('security', score, rationale, counts.total, worst);
199
+ }
200
+ function reliabilityByCount(n) {
201
+ // Stricter than Maintainability's count ladder: a couple of real bugs hurt
202
+ // more than a couple of lint warnings, but a single isolated medium bug on
203
+ // a small project shouldn't pin the codebase at C.
204
+ const label = `${n} reliability ${pluralize('issue', n)}`;
205
+ if (n <= 2)
206
+ return { grade: 'A', position: 1 - n / 2, label };
207
+ if (n <= 6)
208
+ return { grade: 'B', position: 1 - (n - 2) / 4, label };
209
+ if (n <= 15)
210
+ return { grade: 'C', position: 1 - (n - 6) / 9, label };
211
+ return { grade: 'F', position: 1 / (1 + (n - 15) / 15), label };
212
+ }
213
+ function reliabilityByDensity(n, kloc) {
214
+ // Density thresholds are tighter than Maintainability (5/10/25). A 50 KLOC
215
+ // codebase with 100 reliability bugs (density 2) is "minor cleanup", not
216
+ // pristine — but 1.4/KLOC is still A-band because real-world projects
217
+ // never get to zero. The escape hatch handles severity outliers above this.
218
+ const density = n / kloc;
219
+ const label = `${roundOne(density)} reliability ${pluralize('issue', n)} / KLOC`;
220
+ if (density < 1.5)
221
+ return { grade: 'A', position: 1 - density / 1.5, label };
222
+ if (density < 4)
223
+ return { grade: 'B', position: 1 - (density - 1.5) / 2.5, label };
224
+ if (density < 8)
225
+ return { grade: 'C', position: 1 - (density - 4) / 4, label };
226
+ return { grade: 'F', position: 1 / (1 + (density - 8) / 8), label };
227
+ }
228
+ function reliabilityEscape(counts) {
229
+ if (counts.critical > 0) {
230
+ return { grade: 'F', note: `${counts.critical} critical-severity ${pluralize('bug', counts.critical)}` };
231
+ }
232
+ if (counts.high > 0) {
233
+ return { grade: 'C', note: `${counts.high} high-severity ${pluralize('bug', counts.high)}` };
234
+ }
235
+ return null;
236
+ }
237
+ /**
238
+ * Reliability grading — density-based with a severity escape hatch.
239
+ *
240
+ * - Empty / ≤1 low: A-band (clean by convention).
241
+ * - Density-based grade (≥5 KLOC) or count-based grade (<5 KLOC) drives
242
+ * the baseline. Both ladders mirror Maintainability's so reliability and
243
+ * maintainability remain comparable at a glance.
244
+ * - Severity escape: critical → F, high → C. This matches Maintainability and
245
+ * prevents a handful of medium-density bugs from being silently rated A
246
+ * when at least one is severe.
247
+ *
248
+ * Build/compile errors flow in via `build` category with severity `critical`
249
+ * and therefore land at F via the escape hatch — no special-case branching.
250
+ */
251
+ function gradeReliability(findings, totalLines) {
252
+ const counts = countSeverities(findings);
253
+ const worst = worstSeverity(counts);
254
+ const kloc = Math.max(totalLines / 1000, 1.0);
255
+ if (counts.total === 0) {
256
+ return makeDimension('reliability', 100, '0 reliability findings', 0, null);
257
+ }
258
+ // ≤1 low and nothing else is treated as clean — every team has one.
259
+ if (counts.low <= 1 && counts.medium === 0 && counts.high === 0 && counts.critical === 0) {
260
+ return makeDimension('reliability', scoreInBand('A', 0.5), '1 low-severity reliability issue', counts.total, worst);
261
+ }
262
+ const band = kloc < 5 ? reliabilityByCount(counts.total) : reliabilityByDensity(counts.total, kloc);
263
+ const severityCap = reliabilityEscape(counts);
264
+ const useCap = severityCap && baseIsWorse(severityCap.grade, band.grade);
265
+ const finalGrade = useCap ? severityCap.grade : band.grade;
266
+ const finalPosition = useCap ? 0.5 : band.position;
267
+ const rationale = useCap ? `${band.label}, ${severityCap.note}` : band.label;
268
+ return makeDimension('reliability', scoreInBand(finalGrade, finalPosition), rationale, counts.total, worst);
269
+ }
270
+ function maintainabilityByCount(n) {
271
+ const label = `${n} maintainability ${pluralize('issue', n)}`;
272
+ if (n <= 5)
273
+ return { grade: 'A', position: 1 - n / 5, label };
274
+ if (n <= 15)
275
+ return { grade: 'B', position: 1 - (n - 5) / 10, label };
276
+ if (n <= 30)
277
+ return { grade: 'C', position: 1 - (n - 15) / 15, label };
278
+ return { grade: 'F', position: 1 / (1 + (n - 30) / 30), label };
279
+ }
280
+ function maintainabilityByDensity(n, kloc) {
281
+ const density = n / kloc;
282
+ const label = `${roundOne(density)} ${pluralize('issue', n)} / KLOC`;
283
+ if (density < 5)
284
+ return { grade: 'A', position: 1 - density / 5, label };
285
+ if (density < 10)
286
+ return { grade: 'B', position: 1 - (density - 5) / 5, label };
287
+ if (density < 25)
288
+ return { grade: 'C', position: 1 - (density - 10) / 15, label };
289
+ return { grade: 'F', position: 1 / (1 + (density - 25) / 25), label };
290
+ }
291
+ function maintainabilityEscape(counts) {
292
+ if (counts.critical > 0) {
293
+ return { grade: 'F', note: `${counts.critical} critical-severity ${pluralize('issue', counts.critical)}` };
294
+ }
295
+ if (counts.high > 0) {
296
+ return { grade: 'C', note: `${counts.high} high-severity ${pluralize('issue', counts.high)}` };
297
+ }
298
+ return null;
299
+ }
300
+ /**
301
+ * Maintainability uses a density-based grade (issues per KLOC) once the
302
+ * codebase is at least 5 KLOC. For smaller codebases, density is too noisy
303
+ * (one extra lint issue moves density by 1.0+), so we fall back to absolute
304
+ * counts — preventing tiny projects from being unfairly penalized.
305
+ *
306
+ * Severity escape hatch: a critical maintainability finding (e.g., a 3000-
307
+ * line file with high cohesion-violation severity) caps at F; a high-severity
308
+ * one caps at C. "Worst wins" — we take min of density-grade and severity-cap.
309
+ */
310
+ function gradeMaintainability(findings, totalLines) {
311
+ const counts = countSeverities(findings);
312
+ const kloc = Math.max(totalLines / 1000, 1.0);
313
+ if (counts.total === 0) {
314
+ return makeDimension('maintainability', 100, '0 maintainability findings', 0, null);
315
+ }
316
+ const band = kloc < 5 ? maintainabilityByCount(counts.total) : maintainabilityByDensity(counts.total, kloc);
317
+ const severityCap = maintainabilityEscape(counts);
318
+ const useCap = severityCap && baseIsWorse(severityCap.grade, band.grade);
319
+ const finalGrade = useCap ? severityCap.grade : band.grade;
320
+ const finalPosition = useCap ? 0.5 : band.position;
321
+ const rationale = useCap ? `${band.label}, ${severityCap.note}` : band.label;
322
+ return makeDimension('maintainability', scoreInBand(finalGrade, finalPosition), rationale, counts.total, worstSeverity(counts));
323
+ }
324
+ // ============================================================================
325
+ // Architectural Penalty
326
+ // ============================================================================
327
+ /**
328
+ * Drop a dimension's grade by N letters because of architectural findings.
329
+ *
330
+ * Rationale: a high-severity architectural problem (god class, leaky
331
+ * abstraction, broken layering) is qualitatively different from a long-file
332
+ * lint warning — it pollutes every change that touches the affected code.
333
+ * The user spec calls for explicit letter-grade drops:
334
+ *
335
+ * - 1 high-severity arch issue → drop 1 letter
336
+ * - 2+ high-severity arch issues → drop 2 letters
337
+ * - any critical-severity arch issue → drop 2 letters
338
+ *
339
+ * Letters drop A → B → C → F → F-. We never go lower than F-. The drop is
340
+ * applied AFTER the dimension's normal grading so the displayed score still
341
+ * reflects the underlying finding count, but the letter grade carries the
342
+ * architectural weight that a density-based score would otherwise miss.
343
+ */
344
+ function archDropCount(archFindings) {
345
+ let highCount = 0;
346
+ let criticalCount = 0;
347
+ for (const f of archFindings) {
348
+ if (f.severity === 'critical')
349
+ criticalCount++;
350
+ else if (f.severity === 'high')
351
+ highCount++;
352
+ }
353
+ if (criticalCount >= 1)
354
+ return 2;
355
+ if (highCount >= 2)
356
+ return 2;
357
+ if (highCount >= 1)
358
+ return 1;
359
+ return 0;
360
+ }
361
+ const BASE_LETTERS = ['A', 'B', 'C', 'F'];
362
+ function gradeModifier(grade) {
363
+ if (grade.endsWith('+'))
364
+ return '+';
365
+ if (grade.endsWith('-'))
366
+ return '-';
367
+ return '';
368
+ }
369
+ function applyModifierToTargetBase(targetBase, modifier) {
370
+ // F's modifier semantics differ from A/B/C: F+ is "barely failing" while
371
+ // A+/B+/C+ are "top of band." For simplicity we map any modifier on F to
372
+ // its matching variant, and use F- (the worst) for any post-F overshoot.
373
+ if (targetBase === 'F') {
374
+ if (modifier === '+')
375
+ return 'F+';
376
+ if (modifier === '-')
377
+ return 'F-';
378
+ return 'F';
379
+ }
380
+ if (modifier === '+')
381
+ return `${targetBase}+`;
382
+ if (modifier === '-')
383
+ return `${targetBase}-`;
384
+ return targetBase;
385
+ }
386
+ /**
387
+ * Drop a grade by N "letters." A "letter" here means a full base-grade step
388
+ * (A → B → C → F → F-), preserving the modifier when possible. So A+ dropped
389
+ * by 1 becomes B+, not A. Stops at F-.
390
+ */
391
+ function dropGradeByLetters(grade, letters) {
392
+ if (letters <= 0 || grade === 'N/A' || grade === 'D')
393
+ return grade;
394
+ const baseLetter = baseGradeOf(grade);
395
+ const baseIdx = BASE_LETTERS.indexOf(baseLetter);
396
+ if (baseIdx === -1)
397
+ return grade;
398
+ const targetBaseIdx = baseIdx + letters;
399
+ // Past the F base — bottom out at F- (the absolute worst grade).
400
+ if (targetBaseIdx > 3)
401
+ return 'F-';
402
+ const targetBase = BASE_LETTERS[targetBaseIdx];
403
+ return applyModifierToTargetBase(targetBase, gradeModifier(grade));
404
+ }
405
+ function applyArchPenalty(dim, archFindings) {
406
+ const drop = archDropCount(archFindings);
407
+ if (drop === 0)
408
+ return dim;
409
+ const dropped = dropGradeByLetters(dim.grade, drop);
410
+ if (dropped === dim.grade)
411
+ return dim;
412
+ const archCount = archFindings.length;
413
+ const noun = pluralize('architectural finding', archCount);
414
+ const note = `dropped ${drop} ${pluralize('letter', drop)} by ${archCount} ${noun}`;
415
+ return {
416
+ ...dim,
417
+ grade: dropped,
418
+ // Re-anchor score to the new band's midpoint so score and letter agree.
419
+ score: anchorScoreToGrade(dropped, dim.score),
420
+ rationale: dim.rationale === '0 maintainability findings' || dim.findingCount === 0
421
+ ? note
422
+ : `${dim.rationale}; ${note}`,
423
+ };
424
+ }
425
+ /**
426
+ * Re-snap a score to fall within the band of the given grade. Used after
427
+ * applying the architectural penalty so the displayed score never disagrees
428
+ * with the displayed letter (e.g., grade C with score 89 would be jarring).
429
+ *
430
+ * If the original score is already in-band, keep it; otherwise pick the
431
+ * band's midpoint as a sensible default.
432
+ */
433
+ function anchorScoreToGrade(grade, originalScore) {
434
+ if (grade === 'N/A' || grade === 'D')
435
+ return originalScore;
436
+ const ranges = {
437
+ 'A+': [97, 100], A: [93, 96], 'A-': [90, 92],
438
+ 'B+': [87, 89], B: [83, 86], 'B-': [80, 82],
439
+ 'C+': [77, 79], C: [73, 76], 'C-': [70, 72],
440
+ 'F+': [65, 69], F: [56, 64], 'F-': [0, 55],
441
+ };
442
+ const [lo, hi] = ranges[grade];
443
+ if (originalScore >= lo && originalScore <= hi)
444
+ return originalScore;
445
+ return Math.round((lo + hi) / 2);
446
+ }
447
+ // ============================================================================
448
+ // Grade Comparison Helpers
449
+ // ============================================================================
450
+ const BASE_RANK = { F: 1, C: 2, B: 3, A: 4 };
451
+ function baseIsWorse(a, b) {
452
+ return BASE_RANK[a] < BASE_RANK[b];
453
+ }
454
+ const FULL_RANK = {
455
+ 'F-': 0, F: 1, 'F+': 2,
456
+ 'C-': 3, C: 4, 'C+': 5,
457
+ 'B-': 6, B: 7, 'B+': 8,
458
+ 'A-': 9, A: 10, 'A+': 11,
459
+ };
460
+ function gradeRank(g) {
461
+ if (g === 'N/A')
462
+ return -1;
463
+ if (g === 'D')
464
+ return 1.5; // legacy: between F+ and C-
465
+ return FULL_RANK[g];
466
+ }
467
+ function worstOf(grades) {
468
+ let worst = 'A+';
469
+ for (const g of grades) {
470
+ if (g === 'N/A')
471
+ continue;
472
+ if (gradeRank(g) < gradeRank(worst))
473
+ worst = g;
474
+ }
475
+ return worst;
476
+ }
477
+ // ============================================================================
478
+ // Misc Helpers
479
+ // ============================================================================
480
+ function pluralize(word, n) {
481
+ return n === 1 ? word : `${word}s`;
482
+ }
483
+ function roundOne(n) {
484
+ return Math.round(n * 10) / 10;
485
+ }
486
+ function dimensionDisplayName(name) {
487
+ return name.charAt(0).toUpperCase() + name.slice(1);
488
+ }
489
+ function makeDimension(name, score, rationale, findingCount, worst) {
490
+ return {
491
+ name,
492
+ score,
493
+ grade: scoreToGrade(score),
494
+ rationale,
495
+ available: true,
496
+ findingCount,
497
+ worstSeverity: worst,
498
+ };
499
+ }
500
+ function naDimension(name) {
501
+ return {
502
+ name,
503
+ score: 0,
504
+ grade: 'N/A',
505
+ rationale: 'No tools available to evaluate',
506
+ available: false,
507
+ findingCount: 0,
508
+ worstSeverity: null,
509
+ };
510
+ }
511
+ // ============================================================================
512
+ // Top-Level Entry Point
513
+ // ============================================================================
514
+ function bucketByDimension(findings) {
515
+ const security = [];
516
+ const reliability = [];
517
+ const maintainability = [];
518
+ const architecture = [];
519
+ for (const f of findings) {
520
+ if (ARCHITECTURE_CATEGORIES.has(f.category))
521
+ architecture.push(f);
522
+ const dim = categoryToDimension(f.category);
523
+ if (dim === 'security')
524
+ security.push(f);
525
+ else if (dim === 'reliability')
526
+ reliability.push(f);
527
+ else
528
+ maintainability.push(f);
529
+ }
530
+ return { security, reliability, maintainability, architecture };
531
+ }
532
+ function isDimensionAvailable(dim, hasFindings, options) {
533
+ if (options?.forceNA?.has(dim))
534
+ return false;
535
+ const explicit = options?.availableDimensions;
536
+ if (explicit)
537
+ return explicit.has(dim);
538
+ // Auto-detect: maintainability always on, security/reliability iff findings exist.
539
+ return dim === 'maintainability' ? true : hasFindings;
540
+ }
541
+ /**
542
+ * Combine the available dimensions into a single overall grade + score.
543
+ *
544
+ * "Worst dimension wins" for the letter grade — a single failing dimension
545
+ * caps the overall score, matching how SonarQube's quality gate behaves.
546
+ * The numeric score is `min(avg, worst)` so a great Maintainability score
547
+ * can't paper over a Security failure.
548
+ */
549
+ function computeOverall(availableDims) {
550
+ if (availableDims.length === 0) {
551
+ return { grade: 'N/A', score: 0 };
552
+ }
553
+ const grades = availableDims.map((d) => d.grade);
554
+ const scores = availableDims.map((d) => d.score);
555
+ const avg = scores.reduce((s, n) => s + n, 0) / scores.length;
556
+ const worst = worstOf(grades);
557
+ // Re-snap the displayed score so it lives in the worst dimension's band —
558
+ // otherwise we'd display a B-letter with a C-numeric score (or vice versa).
559
+ const blendedScore = Math.round(Math.min(avg, Math.min(...scores)));
560
+ return { grade: worst, score: anchorScoreToGrade(worst, blendedScore) };
561
+ }
562
+ export function computeQualityRating(allFindings, totalLines, options) {
563
+ const buckets = bucketByDimension(allFindings);
564
+ // Initial dimension grades, before architectural penalty.
565
+ const security = isDimensionAvailable('security', buckets.security.length > 0, options)
566
+ ? gradeSecurity(buckets.security)
567
+ : naDimension('security');
568
+ const reliabilityRaw = isDimensionAvailable('reliability', buckets.reliability.length > 0, options)
569
+ ? gradeReliability(buckets.reliability, totalLines)
570
+ : naDimension('reliability');
571
+ const maintainabilityRaw = isDimensionAvailable('maintainability', true, options)
572
+ ? gradeMaintainability(buckets.maintainability, totalLines)
573
+ : naDimension('maintainability');
574
+ // Architectural penalty: hits whichever dimension(s) have arch findings
575
+ // bucketed into them (currently maintainability via the category map).
576
+ const archFindings = buckets.architecture;
577
+ const maintainability = maintainabilityRaw.available
578
+ ? applyArchPenalty(maintainabilityRaw, archFindings)
579
+ : maintainabilityRaw;
580
+ const dimensions = [security, reliabilityRaw, maintainability];
581
+ const availableDims = dimensions.filter((d) => d.available);
582
+ const overall = computeOverall(availableDims);
583
+ const qualityGate = computeQualityGate(security, reliabilityRaw, archFindings.length);
584
+ const gradeRationale = computeGradeRationale(availableDims, overall.grade, allFindings.length);
585
+ return {
586
+ overall,
587
+ dimensions,
588
+ qualityGate,
589
+ gradeRationale,
590
+ };
591
+ }
592
+ // ============================================================================
593
+ // Quality Gate
594
+ // ============================================================================
595
+ /**
596
+ * The Quality Gate is a coarse PASS/FAIL signal layered on top of the grades.
597
+ * It only fires for the most user-actionable thresholds — any C-or-worse
598
+ * security grade, any F-tier reliability grade, or 2+ high-severity
599
+ * architectural findings. N/A dimensions never trigger a fail (we don't fail
600
+ * on missing data).
601
+ */
602
+ function isFTier(g) {
603
+ return g === 'F+' || g === 'F' || g === 'F-' || g === 'D';
604
+ }
605
+ function isCorWorse(g) {
606
+ return baseGradeOf(g) === 'C' || isFTier(g);
607
+ }
608
+ function computeQualityGate(security, reliability, archFindingCount) {
609
+ const failingConditions = [];
610
+ if (security.available && isCorWorse(security.grade)) {
611
+ failingConditions.push(`Security grade ${security.grade} — ${security.rationale}`);
612
+ }
613
+ if (reliability.available && isFTier(reliability.grade)) {
614
+ failingConditions.push(`Reliability grade ${reliability.grade} — ${reliability.rationale}`);
615
+ }
616
+ if (archFindingCount >= 2) {
617
+ failingConditions.push(`${archFindingCount} architectural findings`);
618
+ }
619
+ return {
620
+ passed: failingConditions.length === 0,
621
+ failingConditions,
622
+ };
623
+ }
624
+ // ============================================================================
625
+ // Grade Rationale
626
+ // ============================================================================
627
+ function computeGradeRationale(availableDims, overallGrade, totalFindingCount) {
628
+ if (totalFindingCount === 0) {
629
+ return 'Clean — no findings detected';
630
+ }
631
+ if (availableDims.length === 0 || overallGrade === 'N/A') {
632
+ return 'No dimensions available to grade';
633
+ }
634
+ // All available dimensions share the same base letter -> "consistent
635
+ // quality". With +/- modifiers it's normal for sibling dimensions to land
636
+ // at A vs A+ depending on within-band position; calling that "inconsistent"
637
+ // would be misleading. We compare base letters so the user-facing message
638
+ // captures the high-level shape rather than every minor band difference.
639
+ const firstBase = baseGradeOf(availableDims[0].grade);
640
+ const allSameBase = availableDims.every((d) => baseGradeOf(d.grade) === firstBase);
641
+ if (allSameBase) {
642
+ return `All dimensions ${firstBase}-tier — consistent quality`;
643
+ }
644
+ // Find the dimension that pinned the overall grade (worst available).
645
+ const worstDim = availableDims.find((d) => d.grade === overallGrade) ??
646
+ // Fallback shouldn't fire since overallGrade was derived from availableDims.
647
+ availableDims[0];
648
+ return `Capped at ${overallGrade} by ${dimensionDisplayName(worstDim.name)} (${worstDim.rationale})`;
649
+ }
650
+ //# sourceMappingURL=quality-grading.js.map