specrails-desktop 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. package/.claude/commands/specrails/batch-implement.md +287 -0
  2. package/.claude/commands/specrails/compat-check.md +271 -0
  3. package/.claude/commands/specrails/doctor.md +62 -0
  4. package/.claude/commands/specrails/enrich.md +1635 -0
  5. package/.claude/commands/specrails/explore-spec.md +173 -0
  6. package/.claude/commands/specrails/health-check.md +527 -0
  7. package/.claude/commands/specrails/implement.md +1457 -0
  8. package/.claude/commands/specrails/memory-inspect.md +259 -0
  9. package/.claude/commands/specrails/opsx-diff.md +419 -0
  10. package/.claude/commands/specrails/propose-spec.md +102 -0
  11. package/.claude/commands/specrails/reconfig.md +89 -0
  12. package/.claude/commands/specrails/refactor-recommender.md +212 -0
  13. package/.claude/commands/specrails/retry.md +363 -0
  14. package/.claude/commands/specrails/telemetry.md +552 -0
  15. package/.claude/commands/specrails/why.md +96 -0
  16. package/LICENSE +21 -0
  17. package/README.md +290 -0
  18. package/cli/dist/specrails-desktop.js +1098 -0
  19. package/client/dist/assets/ActivityFeedPage-Gy4x8dBt.js +1 -0
  20. package/client/dist/assets/AgentsPage-CPgu--Fb.js +86 -0
  21. package/client/dist/assets/AnalyticsPage-B5sJEee2.js +1 -0
  22. package/client/dist/assets/BarChart-7IMQ8HY1.js +33 -0
  23. package/client/dist/assets/CodePage-CBdFvbwe.js +2 -0
  24. package/client/dist/assets/DesktopAnalyticsPage-w0rdTq4w.js +1 -0
  25. package/client/dist/assets/DocsDialog-BZUYM7wm.js +11 -0
  26. package/client/dist/assets/DocsPage-9QglWl46.js +11 -0
  27. package/client/dist/assets/ExportDropdown-BLZFXtNi.js +1 -0
  28. package/client/dist/assets/IntegrationsPage-BxBE4y99.js +3 -0
  29. package/client/dist/assets/JobDetailPage-DydWx_5S.js +16 -0
  30. package/client/dist/assets/JobsPage-20ibw0IO.js +1 -0
  31. package/client/dist/assets/abap-Bw6f2wDG.js +1 -0
  32. package/client/dist/assets/activity-BEIp_Y1A.js +1 -0
  33. package/client/dist/assets/activity-BdrPln96.js +1 -0
  34. package/client/dist/assets/activity-CpkRS8Sx.js +1 -0
  35. package/client/dist/assets/activity-DKCpESPt.js +1 -0
  36. package/client/dist/assets/activity-DOUVEjJi.js +1 -0
  37. package/client/dist/assets/activity-DRwkql_y.js +1 -0
  38. package/client/dist/assets/activity-DcDQ7tjw.js +1 -0
  39. package/client/dist/assets/activity-Dv6H7wEr.js +1 -0
  40. package/client/dist/assets/addon-image-3WCl5Vhd.js +1 -0
  41. package/client/dist/assets/addon-ligatures-C5OdliKs.js +2 -0
  42. package/client/dist/assets/addon-webgl-BbX6pSjl.js +44 -0
  43. package/client/dist/assets/addspec-B5yl4Loj.js +1 -0
  44. package/client/dist/assets/addspec-BEeF5-zc.js +1 -0
  45. package/client/dist/assets/addspec-D33ocMxf.js +1 -0
  46. package/client/dist/assets/addspec-DFswZ0jK.js +1 -0
  47. package/client/dist/assets/addspec-DRE-jZv7.js +1 -0
  48. package/client/dist/assets/addspec-DVZ15Jp8.js +1 -0
  49. package/client/dist/assets/addspec-Fkv91Opc.js +1 -0
  50. package/client/dist/assets/addspec-GWm4ffKl.js +1 -0
  51. package/client/dist/assets/agents-1nCDWRmP.js +1 -0
  52. package/client/dist/assets/agents-Bm9rPqnt.js +1 -0
  53. package/client/dist/assets/agents-CMxtJMLD.js +1 -0
  54. package/client/dist/assets/agents-DK-Dlc0i.js +1 -0
  55. package/client/dist/assets/agents-Q6Ldfpxx.js +1 -0
  56. package/client/dist/assets/agents-TeOSy-ax.js +1 -0
  57. package/client/dist/assets/agents-iTqjRajS.js +1 -0
  58. package/client/dist/assets/agents-s87sMGzL.js +1 -0
  59. package/client/dist/assets/agentstudio-B6Wb59E7.js +1 -0
  60. package/client/dist/assets/agentstudio-BADhZ41e.js +1 -0
  61. package/client/dist/assets/agentstudio-BSnWLR63.js +1 -0
  62. package/client/dist/assets/agentstudio-BdidyBzZ.js +1 -0
  63. package/client/dist/assets/agentstudio-CxlUllqI.js +1 -0
  64. package/client/dist/assets/agentstudio-D3I62TLJ.js +1 -0
  65. package/client/dist/assets/agentstudio-DuH9TogZ.js +1 -0
  66. package/client/dist/assets/agentstudio-Kw88_dUF.js +1 -0
  67. package/client/dist/assets/aiedit-BWxHGsYA.js +1 -0
  68. package/client/dist/assets/aiedit-D2ji6Qy0.js +1 -0
  69. package/client/dist/assets/aiedit-DAhZTvtk.js +1 -0
  70. package/client/dist/assets/aiedit-DJMny-D5.js +1 -0
  71. package/client/dist/assets/aiedit-DOcxERkU.js +1 -0
  72. package/client/dist/assets/aiedit-DvrcbwGv.js +1 -0
  73. package/client/dist/assets/aiedit-TTwzL1TS.js +1 -0
  74. package/client/dist/assets/aiedit-WBSjT_C1.js +1 -0
  75. package/client/dist/assets/analytics-BIdr0YfL.js +1 -0
  76. package/client/dist/assets/analytics-C6EzgtdE.js +1 -0
  77. package/client/dist/assets/analytics-C9Zc-rkM.js +1 -0
  78. package/client/dist/assets/analytics-CVx3YOc0.js +1 -0
  79. package/client/dist/assets/analytics-CYj0tfj7.js +1 -0
  80. package/client/dist/assets/analytics-CnY4kNG3.js +1 -0
  81. package/client/dist/assets/analytics-CrPCZRJ-.js +1 -0
  82. package/client/dist/assets/analytics-DMCto-TF.js +1 -0
  83. package/client/dist/assets/apex-Cw8_REBo.js +1 -0
  84. package/client/dist/assets/atom-one-dark-B-oHczHB.css +1 -0
  85. package/client/dist/assets/attachments-BIsSSnHJ.js +1 -0
  86. package/client/dist/assets/attachments-BW4L3l2L.js +1 -0
  87. package/client/dist/assets/attachments-Bcf6BG6V.js +1 -0
  88. package/client/dist/assets/attachments-Bke8sCU4.js +1 -0
  89. package/client/dist/assets/attachments-COcrGRFz.js +1 -0
  90. package/client/dist/assets/attachments-DYHGA2Dj.js +1 -0
  91. package/client/dist/assets/attachments-Dd92KpUH.js +1 -0
  92. package/client/dist/assets/attachments-DzdU6DV6.js +1 -0
  93. package/client/dist/assets/azcli-Cz6HAoOw.js +1 -0
  94. package/client/dist/assets/bat-CcJ-xyqL.js +1 -0
  95. package/client/dist/assets/bicep-z1WDCKYz.js +2 -0
  96. package/client/dist/assets/browser-5ErDlJoR.js +1 -0
  97. package/client/dist/assets/browser-Bc-YdlVg.js +1 -0
  98. package/client/dist/assets/browser-BlYF4OOq.js +1 -0
  99. package/client/dist/assets/browser-CT-ReZGt.js +1 -0
  100. package/client/dist/assets/browser-DGITz3fC.js +1 -0
  101. package/client/dist/assets/browser-JsAIGCEW.js +1 -0
  102. package/client/dist/assets/browser-M5-rbPlw.js +1 -0
  103. package/client/dist/assets/browser-Qya9cARy.js +1 -0
  104. package/client/dist/assets/cameligo-BRewOpfa.js +1 -0
  105. package/client/dist/assets/chat-BEGuC03z.js +1 -0
  106. package/client/dist/assets/chat-BEW60P_u.js +1 -0
  107. package/client/dist/assets/chat-BQNMD0PL.js +1 -0
  108. package/client/dist/assets/chat-BsbNGPW9.js +1 -0
  109. package/client/dist/assets/chat-CboQguCi.js +1 -0
  110. package/client/dist/assets/chat-DRCa9pOt.js +1 -0
  111. package/client/dist/assets/chat-DwUm6W9z.js +1 -0
  112. package/client/dist/assets/chat-yoXwguQu.js +1 -0
  113. package/client/dist/assets/chunk-CilyBKbf.js +1 -0
  114. package/client/dist/assets/clojure-DBjRWN6g.js +1 -0
  115. package/client/dist/assets/clsx-DnqN-uhr.js +1 -0
  116. package/client/dist/assets/code-AL1rVIMb.js +1 -0
  117. package/client/dist/assets/code-C0BKpkht.js +1 -0
  118. package/client/dist/assets/code-C0FTS3ew.js +1 -0
  119. package/client/dist/assets/code-CPcHxzxw.js +1 -0
  120. package/client/dist/assets/code-D3ryDniw.js +1 -0
  121. package/client/dist/assets/code-D3zVVQTj.js +1 -0
  122. package/client/dist/assets/code-PCmfS3dn.js +1 -0
  123. package/client/dist/assets/code-exI0G5Wd.js +1 -0
  124. package/client/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
  125. package/client/dist/assets/coffee-Cfk_XHGR.js +1 -0
  126. package/client/dist/assets/commands-B772IyDa.js +1 -0
  127. package/client/dist/assets/commands-BDDp6xFG.js +1 -0
  128. package/client/dist/assets/commands-CJxCry-o.js +1 -0
  129. package/client/dist/assets/commands-CfgY-_of.js +1 -0
  130. package/client/dist/assets/commands-DLrvnPNg.js +1 -0
  131. package/client/dist/assets/commands-IXMOKBYt.js +1 -0
  132. package/client/dist/assets/commands-UD1NzmwX.js +1 -0
  133. package/client/dist/assets/commands-sqrqsxyE.js +1 -0
  134. package/client/dist/assets/common-DCr6VzJ7.js +1 -0
  135. package/client/dist/assets/common-Dard9UNH.js +1 -0
  136. package/client/dist/assets/common-DeDELLZJ.js +1 -0
  137. package/client/dist/assets/common-DltqHaAe.js +1 -0
  138. package/client/dist/assets/common-Dmm1GhdD.js +1 -0
  139. package/client/dist/assets/common-DnjcgkPH.js +1 -0
  140. package/client/dist/assets/common-GbpxfPG8.js +1 -0
  141. package/client/dist/assets/common-wA36jmj1.js +1 -0
  142. package/client/dist/assets/cpp-BVob6BaP.js +1 -0
  143. package/client/dist/assets/csharp-C4fbRuOu.js +1 -0
  144. package/client/dist/assets/csp-DthFP_vT.js +1 -0
  145. package/client/dist/assets/css-CGMH0hcW.js +3 -0
  146. package/client/dist/assets/css.worker-Wv5dxAWO.js +89 -0
  147. package/client/dist/assets/cssMode-Cc6ozl-J.js +1 -0
  148. package/client/dist/assets/cypher-Pnf68BRV.js +1 -0
  149. package/client/dist/assets/dart-PMMOtxZX.js +1 -0
  150. package/client/dist/assets/dashboard-B4ixDVk8.js +1 -0
  151. package/client/dist/assets/dashboard-BZBADHSj.js +1 -0
  152. package/client/dist/assets/dashboard-C1MfeUHs.js +1 -0
  153. package/client/dist/assets/dashboard-C7SK6xu5.js +1 -0
  154. package/client/dist/assets/dashboard-CB6Le1yN.js +1 -0
  155. package/client/dist/assets/dashboard-CoTpMOBM.js +1 -0
  156. package/client/dist/assets/dashboard-Duo4DDCW.js +1 -0
  157. package/client/dist/assets/dashboard-I19DXBxw.js +1 -0
  158. package/client/dist/assets/dist-js-BY-Fv_fg.js +1 -0
  159. package/client/dist/assets/dist-js-Bakc4uxT.js +1 -0
  160. package/client/dist/assets/dockerfile-di1nsJCc.js +1 -0
  161. package/client/dist/assets/ecl-D_WVcB5M.js +1 -0
  162. package/client/dist/assets/editor-Br_kD0ds.css +1 -0
  163. package/client/dist/assets/editor.api2-XLGzZfbc.js +872 -0
  164. package/client/dist/assets/editor.main-CfXxHimg.js +6 -0
  165. package/client/dist/assets/editor.worker-Bd9IXS8d.js +26 -0
  166. package/client/dist/assets/elixir-OAdJEMOn.js +1 -0
  167. package/client/dist/assets/explore-4mFpnrKU.js +1 -0
  168. package/client/dist/assets/explore-A8Ltoblq.js +1 -0
  169. package/client/dist/assets/explore-B9A3iN2W.js +1 -0
  170. package/client/dist/assets/explore-BV5Xxlsn.js +1 -0
  171. package/client/dist/assets/explore-BrBJvfjP.js +1 -0
  172. package/client/dist/assets/explore-C3FSE42C.js +1 -0
  173. package/client/dist/assets/explore-D2EFgt8J.js +1 -0
  174. package/client/dist/assets/explore-hFc3HFcp.js +1 -0
  175. package/client/dist/assets/flow9-D3QEZjgn.js +1 -0
  176. package/client/dist/assets/format-command-CwGuwzGA.js +1 -0
  177. package/client/dist/assets/freemarker2-DP7J1gG3.js +3 -0
  178. package/client/dist/assets/fsharp-BF0k_8N8.js +1 -0
  179. package/client/dist/assets/go-BAQO5Jsz.js +1 -0
  180. package/client/dist/assets/graphql-hdFVFkiV.js +1 -0
  181. package/client/dist/assets/handlebars-BjRlucw6.js +1 -0
  182. package/client/dist/assets/hcl-DWnl1o-X.js +1 -0
  183. package/client/dist/assets/html-OumBQJ-U.js +1 -0
  184. package/client/dist/assets/html.worker-CQP8QQsS.js +502 -0
  185. package/client/dist/assets/htmlMode-CStc3zXM.js +1 -0
  186. package/client/dist/assets/index-CimDRRi7.css +2 -0
  187. package/client/dist/assets/index-XGZaKl_u.js +142 -0
  188. package/client/dist/assets/ini-CB-6OVu3.js +1 -0
  189. package/client/dist/assets/integrations-C3p12Ms6.js +1 -0
  190. package/client/dist/assets/integrations-Cr6hH7XR.js +1 -0
  191. package/client/dist/assets/integrations-Cublz3m6.js +1 -0
  192. package/client/dist/assets/integrations-D28q1kF6.js +1 -0
  193. package/client/dist/assets/integrations-DRdbki5W.js +1 -0
  194. package/client/dist/assets/integrations-DaC4SzzL.js +1 -0
  195. package/client/dist/assets/integrations-DmQYCUvN.js +1 -0
  196. package/client/dist/assets/integrations-HIlUxXVs.js +1 -0
  197. package/client/dist/assets/java-d1CmfiHX.js +1 -0
  198. package/client/dist/assets/javascript-CMk--e7g.js +1 -0
  199. package/client/dist/assets/jobs-BE1siB0M.js +1 -0
  200. package/client/dist/assets/jobs-BHcQ_Faf.js +1 -0
  201. package/client/dist/assets/jobs-CFfc2dNX.js +1 -0
  202. package/client/dist/assets/jobs-CSi5n8X_.js +1 -0
  203. package/client/dist/assets/jobs-Dc3X86PY.js +1 -0
  204. package/client/dist/assets/jobs-De5tASex.js +1 -0
  205. package/client/dist/assets/jobs-DsoXEdo7.js +1 -0
  206. package/client/dist/assets/jobs-Wl-ApPMb.js +1 -0
  207. package/client/dist/assets/json.worker-DzV-CpCQ.js +58 -0
  208. package/client/dist/assets/jsonMode-C2h3ZcjZ.js +7 -0
  209. package/client/dist/assets/julia-Bgv08lKa.js +1 -0
  210. package/client/dist/assets/kotlin-u98kaVTf.js +1 -0
  211. package/client/dist/assets/less-CjYwpgg5.js +2 -0
  212. package/client/dist/assets/lexon-YTjaAFBB.js +1 -0
  213. package/client/dist/assets/lib-CPxTMOAq.js +7 -0
  214. package/client/dist/assets/liquid-mI3KJrBE.js +1 -0
  215. package/client/dist/assets/lspLanguageFeatures-DU09ggWi.js +4 -0
  216. package/client/dist/assets/lua-BzmkWv27.js +1 -0
  217. package/client/dist/assets/m3-CFwk9fw0.js +1 -0
  218. package/client/dist/assets/markdown-CR5iMpSZ.js +1 -0
  219. package/client/dist/assets/mdx-C41VDTR_.js +1 -0
  220. package/client/dist/assets/mips-CcEalc17.js +1 -0
  221. package/client/dist/assets/monaco.contribution-CPObAXMC.js +2 -0
  222. package/client/dist/assets/msdax-BQbkawnr.js +1 -0
  223. package/client/dist/assets/mysql-GTlaaW_P.js +1 -0
  224. package/client/dist/assets/nav-0fwkrgHt.js +1 -0
  225. package/client/dist/assets/nav-BEL3MTwK.js +1 -0
  226. package/client/dist/assets/nav-B_G-TJDW.js +1 -0
  227. package/client/dist/assets/nav-C2YXcbZS.js +1 -0
  228. package/client/dist/assets/nav-ClzOE4mA.js +1 -0
  229. package/client/dist/assets/nav-CtYwmMgu.js +1 -0
  230. package/client/dist/assets/nav-D2bOGSEg.js +1 -0
  231. package/client/dist/assets/nav-iH1V5j6o.js +1 -0
  232. package/client/dist/assets/objective-c-Byu1T5if.js +1 -0
  233. package/client/dist/assets/pascal-BrfzBfRm.js +1 -0
  234. package/client/dist/assets/pascaligo-BXXKFUeo.js +1 -0
  235. package/client/dist/assets/perl-B3OikKq-.js +1 -0
  236. package/client/dist/assets/pgsql-CTsa0Acc.js +1 -0
  237. package/client/dist/assets/php-DiQh3FUW.js +1 -0
  238. package/client/dist/assets/pla-92uH8Fzm.js +1 -0
  239. package/client/dist/assets/postiats-BbeWkKUr.js +1 -0
  240. package/client/dist/assets/powerquery-DgDMzpsm.js +1 -0
  241. package/client/dist/assets/powershell-BfdUUzaG.js +1 -0
  242. package/client/dist/assets/preload-helper-DSXbuxSR.js +1 -0
  243. package/client/dist/assets/protobuf-BojW2ftW.js +2 -0
  244. package/client/dist/assets/pug-BxqTg3IU.js +1 -0
  245. package/client/dist/assets/python-Y27rKQtk.js +1 -0
  246. package/client/dist/assets/qsharp-BX_A-MW9.js +1 -0
  247. package/client/dist/assets/r-D9BMnxvJ.js +1 -0
  248. package/client/dist/assets/razor-Cd5-q9Bp.js +1 -0
  249. package/client/dist/assets/redis-5cJqEQJJ.js +1 -0
  250. package/client/dist/assets/redshift-d8BBqiwb.js +1 -0
  251. package/client/dist/assets/restructuredtext-C8a6yIcZ.js +1 -0
  252. package/client/dist/assets/ruby-egeh-6KX.js +1 -0
  253. package/client/dist/assets/rust-a3r9IInB.js +1 -0
  254. package/client/dist/assets/sb-y8iRIDei.js +1 -0
  255. package/client/dist/assets/scala-BPDK2AmK.js +1 -0
  256. package/client/dist/assets/scheme-BIWUEoOs.js +1 -0
  257. package/client/dist/assets/scss-CA-PSzwg.js +3 -0
  258. package/client/dist/assets/settings-55oDcbSh.js +1 -0
  259. package/client/dist/assets/settings-Bd4Tq1RB.js +1 -0
  260. package/client/dist/assets/settings-CCSM-Fhn.js +1 -0
  261. package/client/dist/assets/settings-D3e_bDoW.js +1 -0
  262. package/client/dist/assets/settings-DKbTkbn7.js +1 -0
  263. package/client/dist/assets/settings-Dxpo6_w7.js +1 -0
  264. package/client/dist/assets/settings-bt84e3Aa.js +1 -0
  265. package/client/dist/assets/settings-nu68QukM.js +1 -0
  266. package/client/dist/assets/setup-BMqwfbW9.js +1 -0
  267. package/client/dist/assets/setup-Bb5LcG28.js +1 -0
  268. package/client/dist/assets/setup-BeEx2_da.js +1 -0
  269. package/client/dist/assets/setup-CCCrB53Q.js +1 -0
  270. package/client/dist/assets/setup-CJA0ATmd.js +1 -0
  271. package/client/dist/assets/setup-CeiDbZcb.js +1 -0
  272. package/client/dist/assets/setup-Cus7TApA.js +1 -0
  273. package/client/dist/assets/setup-D9qOs2Xo.js +1 -0
  274. package/client/dist/assets/shell--LiT1Bja.js +1 -0
  275. package/client/dist/assets/solidity-DdqZccZg.js +1 -0
  276. package/client/dist/assets/sophia-S6-YxNG_.js +1 -0
  277. package/client/dist/assets/sparql-BSf5kMp2.js +1 -0
  278. package/client/dist/assets/specs-BFfu3u-a.js +1 -0
  279. package/client/dist/assets/specs-B__C8-8a.js +1 -0
  280. package/client/dist/assets/specs-CZ1PsXsC.js +1 -0
  281. package/client/dist/assets/specs-D2FzlLn9.js +1 -0
  282. package/client/dist/assets/specs-DaUTrNF9.js +1 -0
  283. package/client/dist/assets/specs-Dyc5hYeE.js +1 -0
  284. package/client/dist/assets/specs-cKEh2LXt.js +1 -0
  285. package/client/dist/assets/specs-k0PyLDVt.js +1 -0
  286. package/client/dist/assets/sql-D7KgjR8G.js +1 -0
  287. package/client/dist/assets/st-BnoDa-Ml.js +1 -0
  288. package/client/dist/assets/swift-DEUHTkUX.js +1 -0
  289. package/client/dist/assets/systemverilog-Tqb_KPnW.js +1 -0
  290. package/client/dist/assets/tcl-BmBFS2qq.js +1 -0
  291. package/client/dist/assets/terminal-80yDMgMF.js +1 -0
  292. package/client/dist/assets/terminal-Bje4ziIa.js +1 -0
  293. package/client/dist/assets/terminal-C2WYcFHF.js +1 -0
  294. package/client/dist/assets/terminal-CSONJOex.js +1 -0
  295. package/client/dist/assets/terminal-DEqzGtcr.js +1 -0
  296. package/client/dist/assets/terminal-DeWzh6ys.js +1 -0
  297. package/client/dist/assets/terminal-YOlsJCQj.js +1 -0
  298. package/client/dist/assets/terminal-lkZYR4wJ.js +1 -0
  299. package/client/dist/assets/tickets-CB7N30gm.js +1 -0
  300. package/client/dist/assets/tickets-CF2PYelu.js +1 -0
  301. package/client/dist/assets/tickets-DNOANUXr.js +1 -0
  302. package/client/dist/assets/tickets-DU1aqsbr.js +1 -0
  303. package/client/dist/assets/tickets-DYvafSaY.js +1 -0
  304. package/client/dist/assets/tickets-DlpC_iTg.js +1 -0
  305. package/client/dist/assets/tickets-DucYgtdl.js +1 -0
  306. package/client/dist/assets/tickets-clefmXLv.js +1 -0
  307. package/client/dist/assets/ts.worker-METxwbDZ.js +67719 -0
  308. package/client/dist/assets/tsMode-B0y_xEci.js +11 -0
  309. package/client/dist/assets/twig-BQV8igWC.js +1 -0
  310. package/client/dist/assets/typescript-BzK0OgwW.js +1 -0
  311. package/client/dist/assets/typespec-DlFroUGY.js +1 -0
  312. package/client/dist/assets/useProjectCache-DSaiGFjV.js +1 -0
  313. package/client/dist/assets/vb-BlrJpIMX.js +1 -0
  314. package/client/dist/assets/wgsl-BWgIc6FZ.js +298 -0
  315. package/client/dist/assets/workers-rt--R2Qy.js +1 -0
  316. package/client/dist/assets/xml-eX9QXAmI.js +1 -0
  317. package/client/dist/assets/yaml-fcsNkpOt.js +1 -0
  318. package/client/dist/index.html +246 -0
  319. package/docs/README.md +54 -0
  320. package/docs/cli.md +198 -0
  321. package/docs/codex.md +210 -0
  322. package/docs/creating-specs.md +197 -0
  323. package/docs/customizing.md +197 -0
  324. package/docs/getting-started.md +140 -0
  325. package/docs/internals/README.md +25 -0
  326. package/docs/internals/adding-a-provider.md +238 -0
  327. package/docs/internals/api-reference.md +634 -0
  328. package/docs/internals/architecture.md +332 -0
  329. package/docs/internals/configuration.md +172 -0
  330. package/docs/internals/openspec-workflow.md +282 -0
  331. package/docs/internals/operations-runbook.md +198 -0
  332. package/docs/internals/profiles.md +152 -0
  333. package/docs/platforms/macos.md +130 -0
  334. package/docs/platforms/windows.md +81 -0
  335. package/docs/running-pipelines.md +240 -0
  336. package/docs/terminal.md +138 -0
  337. package/docs/tracking-cost.md +155 -0
  338. package/package.json +82 -0
  339. package/server/dist/agent-generator.js +232 -0
  340. package/server/dist/agent-refine-db.js +124 -0
  341. package/server/dist/agent-refine-manager.js +526 -0
  342. package/server/dist/ai-invocations.js +111 -0
  343. package/server/dist/attachment-manager.js +299 -0
  344. package/server/dist/auth.js +207 -0
  345. package/server/dist/binary-probe.js +35 -0
  346. package/server/dist/browser-capture-manager.js +576 -0
  347. package/server/dist/browser-capture-types.js +28 -0
  348. package/server/dist/browser-network.js +149 -0
  349. package/server/dist/browser-playwright.js +888 -0
  350. package/server/dist/build-dirs.js +44 -0
  351. package/server/dist/changes-reader.js +120 -0
  352. package/server/dist/chat-manager.js +1060 -0
  353. package/server/dist/chromium-resolver.js +311 -0
  354. package/server/dist/code-explorer-router.js +788 -0
  355. package/server/dist/codex-otel-bridge.js +235 -0
  356. package/server/dist/command-resolver.js +102 -0
  357. package/server/dist/config.js +306 -0
  358. package/server/dist/context-budget.js +113 -0
  359. package/server/dist/context-scope.js +279 -0
  360. package/server/dist/contract-refine-runner.js +521 -0
  361. package/server/dist/core-compat.js +207 -0
  362. package/server/dist/core-package.js +14 -0
  363. package/server/dist/db.js +1034 -0
  364. package/server/dist/desktop-analytics.js +156 -0
  365. package/server/dist/desktop-db.js +456 -0
  366. package/server/dist/desktop-router.js +735 -0
  367. package/server/dist/docs-router.js +207 -0
  368. package/server/dist/explore-contract-refine.js +421 -0
  369. package/server/dist/explore-cwd-manager.js +242 -0
  370. package/server/dist/explore-draft-title.js +47 -0
  371. package/server/dist/explore-smash.js +450 -0
  372. package/server/dist/feature-flags.js +17 -0
  373. package/server/dist/file-provenance.js +382 -0
  374. package/server/dist/file-summary-generator.js +221 -0
  375. package/server/dist/file-summary-manager.js +689 -0
  376. package/server/dist/hooks.js +102 -0
  377. package/server/dist/ids.js +7 -0
  378. package/server/dist/index.js +586 -0
  379. package/server/dist/metrics.js +136 -0
  380. package/server/dist/mobile/index.js +16 -0
  381. package/server/dist/mobile/mobile-admin-router.js +84 -0
  382. package/server/dist/mobile/mobile-auth.js +67 -0
  383. package/server/dist/mobile/mobile-devices.js +80 -0
  384. package/server/dist/mobile/mobile-event-bus.js +39 -0
  385. package/server/dist/mobile/mobile-gateway.js +285 -0
  386. package/server/dist/mobile/mobile-mdns.js +81 -0
  387. package/server/dist/mobile/mobile-pairing.js +179 -0
  388. package/server/dist/mobile/mobile-redact.js +53 -0
  389. package/server/dist/mobile/mobile-router.js +411 -0
  390. package/server/dist/mobile/mobile-tls.js +86 -0
  391. package/server/dist/mobile/mobile-types.js +9 -0
  392. package/server/dist/mobile/mobile-ws.js +275 -0
  393. package/server/dist/path-resolver.js +298 -0
  394. package/server/dist/plugin-manager.js +617 -0
  395. package/server/dist/plugins/claude-approval.js +179 -0
  396. package/server/dist/plugins/claude-md-mutation.js +146 -0
  397. package/server/dist/plugins/codex-mcp.js +108 -0
  398. package/server/dist/plugins/contributors.js +72 -0
  399. package/server/dist/plugins/drift.js +58 -0
  400. package/server/dist/plugins/index.js +14 -0
  401. package/server/dist/plugins/json-mutation.js +120 -0
  402. package/server/dist/plugins/manager.js +32 -0
  403. package/server/dist/plugins/ownership.js +86 -0
  404. package/server/dist/plugins/paths.js +37 -0
  405. package/server/dist/plugins/prereq-installer.js +104 -0
  406. package/server/dist/plugins/rail-integration.js +79 -0
  407. package/server/dist/plugins/serena/index.js +13 -0
  408. package/server/dist/plugins/serena/install.js +91 -0
  409. package/server/dist/plugins/serena/instructions-content.js +21 -0
  410. package/server/dist/plugins/serena/manifest.js +111 -0
  411. package/server/dist/plugins/serena/verify.js +78 -0
  412. package/server/dist/plugins-router.js +215 -0
  413. package/server/dist/pricing.js +89 -0
  414. package/server/dist/profile-manager.js +310 -0
  415. package/server/dist/profiles-router.js +759 -0
  416. package/server/dist/project-registry.js +443 -0
  417. package/server/dist/project-router.js +4016 -0
  418. package/server/dist/proposal-manager.js +291 -0
  419. package/server/dist/provider-selection.js +69 -0
  420. package/server/dist/providers/claude-adapter.js +281 -0
  421. package/server/dist/providers/codex-adapter.js +264 -0
  422. package/server/dist/providers/index.js +23 -0
  423. package/server/dist/providers/registry.js +37 -0
  424. package/server/dist/providers/types.js +22 -0
  425. package/server/dist/queue-manager.js +1511 -0
  426. package/server/dist/rails-router.js +362 -0
  427. package/server/dist/rails-store.js +116 -0
  428. package/server/dist/result-event.js +106 -0
  429. package/server/dist/schemas/profile.v1.json +151 -0
  430. package/server/dist/setup-manager.js +1165 -0
  431. package/server/dist/setup-prerequisites.js +372 -0
  432. package/server/dist/smash-runner.js +663 -0
  433. package/server/dist/spec-draft-parser.js +133 -0
  434. package/server/dist/spec-launcher-manager.js +174 -0
  435. package/server/dist/spec-models.js +32 -0
  436. package/server/dist/specrails-tech-client.js +82 -0
  437. package/server/dist/spending.js +448 -0
  438. package/server/dist/telemetry-compactor.js +180 -0
  439. package/server/dist/telemetry-export.js +317 -0
  440. package/server/dist/telemetry-receiver.js +224 -0
  441. package/server/dist/terminal-manager.js +633 -0
  442. package/server/dist/terminal-marks-store.js +117 -0
  443. package/server/dist/terminal-osc-parser.js +159 -0
  444. package/server/dist/terminal-settings.js +282 -0
  445. package/server/dist/terminal-shell-integration.js +196 -0
  446. package/server/dist/ticket-broadcast.js +47 -0
  447. package/server/dist/ticket-store.js +397 -0
  448. package/server/dist/ticket-watcher.js +117 -0
  449. package/server/dist/types.js +10 -0
  450. package/server/dist/user-mcp-config.js +117 -0
  451. package/server/dist/util/cli-prompt.js +181 -0
  452. package/server/dist/util/secure-fs.js +50 -0
  453. package/server/dist/util/win-spawn.js +43 -0
  454. package/server/dist/webhook-manager.js +89 -0
  455. package/server/dist/ws-routing.js +47 -0
@@ -0,0 +1,448 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSpending = getSpending;
4
+ exports.summariseExplorePrompt = summariseExplorePrompt;
5
+ exports.getInvocations = getInvocations;
6
+ exports.parseSpendingFilters = parseSpendingFilters;
7
+ const ALL_SURFACES = ['job', 'quick-spec', 'explore-spec', 'ai-edit', 'smash', 'file-summary'];
8
+ function resolveRange(filters, now = new Date()) {
9
+ const period = filters.period ?? '30d';
10
+ if (period === 'custom' && filters.from && filters.to) {
11
+ const fromMs = new Date(filters.from).getTime();
12
+ const toMs = new Date(filters.to).getTime();
13
+ const span = toMs - fromMs;
14
+ return {
15
+ from: filters.from,
16
+ to: filters.to,
17
+ prevFrom: new Date(fromMs - span).toISOString(),
18
+ prevTo: filters.from,
19
+ };
20
+ }
21
+ if (period === 'all') {
22
+ return {
23
+ from: '1970-01-01T00:00:00Z',
24
+ to: now.toISOString(),
25
+ prevFrom: '1970-01-01T00:00:00Z',
26
+ prevTo: '1970-01-01T00:00:00Z',
27
+ };
28
+ }
29
+ const days = period === '7d' ? 7 : period === '90d' ? 90 : 30;
30
+ const toMs = now.getTime();
31
+ const fromMs = toMs - days * 86_400_000;
32
+ const prevFromMs = fromMs - days * 86_400_000;
33
+ return {
34
+ from: new Date(fromMs).toISOString(),
35
+ to: new Date(toMs).toISOString(),
36
+ prevFrom: new Date(prevFromMs).toISOString(),
37
+ prevTo: new Date(fromMs).toISOString(),
38
+ };
39
+ }
40
+ function buildWhere(projectId, filters, range, alias = '') {
41
+ const a = alias ? `${alias}.` : '';
42
+ const conditions = [`${a}project_id = ?`];
43
+ const params = [projectId];
44
+ conditions.push(`${a}started_at >= ?`);
45
+ params.push(range.from);
46
+ conditions.push(`${a}started_at <= ?`);
47
+ params.push(range.to);
48
+ if (filters.surface && filters.surface.length > 0) {
49
+ const placeholders = filters.surface.map(() => '?').join(',');
50
+ conditions.push(`${a}surface IN (${placeholders})`);
51
+ params.push(...filters.surface);
52
+ }
53
+ if (filters.model && filters.model.length > 0) {
54
+ const placeholders = filters.model.map(() => '?').join(',');
55
+ conditions.push(`${a}model IN (${placeholders})`);
56
+ params.push(...filters.model);
57
+ }
58
+ if (filters.provider && filters.provider.length > 0) {
59
+ // Coalesce legacy NULL provider rows to 'claude' so a 'claude' filter still
60
+ // surfaces pre-migration invocations.
61
+ const placeholders = filters.provider.map(() => '?').join(',');
62
+ conditions.push(`COALESCE(${a}provider, 'claude') IN (${placeholders})`);
63
+ params.push(...filters.provider);
64
+ }
65
+ if (filters.status) {
66
+ conditions.push(`${a}status = ?`);
67
+ params.push(filters.status);
68
+ }
69
+ if (typeof filters.minCostUsd === 'number') {
70
+ conditions.push(`${a}total_cost_usd >= ?`);
71
+ params.push(filters.minCostUsd);
72
+ }
73
+ if (typeof filters.ticketId === 'number') {
74
+ conditions.push(`${a}ticket_id = ?`);
75
+ params.push(filters.ticketId);
76
+ }
77
+ return { sql: conditions.join(' AND '), params };
78
+ }
79
+ function dateOnly(iso) {
80
+ return iso.slice(0, 10);
81
+ }
82
+ function eachDay(fromIso, toIso) {
83
+ const out = [];
84
+ const fromDay = new Date(dateOnly(fromIso) + 'T00:00:00Z').getTime();
85
+ const toDay = new Date(dateOnly(toIso) + 'T00:00:00Z').getTime();
86
+ for (let t = fromDay; t <= toDay; t += 86_400_000) {
87
+ out.push(new Date(t).toISOString().slice(0, 10));
88
+ }
89
+ return out;
90
+ }
91
+ function getSpending(db, projectId, filters = {}) {
92
+ const range = resolveRange(filters);
93
+ const where = buildWhere(projectId, filters, { from: range.from, to: range.to });
94
+ // summary
95
+ const summaryRow = db.prepare(`
96
+ SELECT
97
+ COALESCE(SUM(total_cost_usd), 0) AS totalCost,
98
+ COALESCE(SUM(CASE WHEN total_cost_usd_estimated = 1 THEN total_cost_usd ELSE 0 END), 0) AS totalEstimatedCost,
99
+ COALESCE(SUM(COALESCE(tokens_in, 0) + COALESCE(tokens_out, 0)
100
+ + COALESCE(tokens_cache_read, 0) + COALESCE(tokens_cache_create, 0)), 0) AS totalTokens,
101
+ COUNT(*) AS totalRuns,
102
+ SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed,
103
+ AVG(CASE WHEN status = 'success' THEN total_cost_usd END) AS avgCost
104
+ FROM ai_invocations WHERE ${where.sql}
105
+ `).get(...where.params);
106
+ // prev period
107
+ const prevWhere = buildWhere(projectId, filters, { from: range.prevFrom, to: range.prevTo });
108
+ const prevRow = db.prepare(`
109
+ SELECT COALESCE(SUM(total_cost_usd), 0) AS totalCost
110
+ FROM ai_invocations WHERE ${prevWhere.sql}
111
+ `).get(...prevWhere.params);
112
+ const deltaPct = prevRow.totalCost > 0
113
+ ? ((summaryRow.totalCost - prevRow.totalCost) / prevRow.totalCost) * 100
114
+ : null;
115
+ // byModel (top 10) + per-mode dominant models — one GROUP BY model, surface
116
+ // query serves both (H24: previously byModel plus two per-mode dominant-model
117
+ // queries re-scanned the same rows).
118
+ const modelRows = db.prepare(`
119
+ SELECT model, surface, COUNT(*) AS cnt, COALESCE(SUM(total_cost_usd), 0) AS cost
120
+ FROM ai_invocations WHERE ${where.sql} AND model IS NOT NULL
121
+ GROUP BY model, surface
122
+ `).all(...where.params);
123
+ const modelTotals = new Map();
124
+ for (const r of modelRows) {
125
+ const agg = modelTotals.get(r.model) ?? { cnt: 0, cost: 0 };
126
+ agg.cnt += r.cnt;
127
+ agg.cost += r.cost ?? 0;
128
+ modelTotals.set(r.model, agg);
129
+ }
130
+ const byModel = Array.from(modelTotals.entries())
131
+ .map(([model, t]) => ({ model, count: t.cnt, costUsd: t.cost }))
132
+ .sort((a, b) => b.costUsd - a.costUsd)
133
+ .slice(0, 10);
134
+ const dominantBySurface = new Map();
135
+ for (const r of modelRows) {
136
+ const cur = dominantBySurface.get(r.surface);
137
+ if (!cur || r.cnt > cur.cnt)
138
+ dominantBySurface.set(r.surface, { model: r.model, cnt: r.cnt });
139
+ }
140
+ // dailyTimeline (zero-filled, stacked by surface)
141
+ const dayRows = db.prepare(`
142
+ SELECT substr(started_at, 1, 10) AS day, surface, COALESCE(SUM(total_cost_usd), 0) AS cost
143
+ FROM ai_invocations WHERE ${where.sql}
144
+ GROUP BY day, surface
145
+ `).all(...where.params);
146
+ // B63: period 'all' resolves range.from to 1970, which would make eachDay emit
147
+ // ~20,000 zero-filled days (a huge payload + 20k-bar chart). Clamp the zero-fill
148
+ // start to the first day that actually has data (or a single day when empty).
149
+ let timelineFrom = range.from;
150
+ if (filters.period === 'all') {
151
+ timelineFrom = dayRows.length > 0
152
+ ? dayRows.reduce((min, r) => (r.day < min ? r.day : min), dayRows[0].day)
153
+ : range.to.slice(0, 10);
154
+ }
155
+ const days = eachDay(timelineFrom, range.to);
156
+ const dayMap = new Map();
157
+ for (const day of days) {
158
+ dayMap.set(day, {
159
+ date: day, jobsCostUsd: 0, quickCostUsd: 0, exploreCostUsd: 0, aiEditCostUsd: 0, smashCostUsd: 0, fileSummaryCostUsd: 0, totalCostUsd: 0,
160
+ });
161
+ }
162
+ for (const r of dayRows) {
163
+ const entry = dayMap.get(r.day);
164
+ if (!entry)
165
+ continue;
166
+ const c = r.cost ?? 0;
167
+ if (r.surface === 'job')
168
+ entry.jobsCostUsd += c;
169
+ else if (r.surface === 'quick-spec')
170
+ entry.quickCostUsd += c;
171
+ else if (r.surface === 'explore-spec')
172
+ entry.exploreCostUsd += c;
173
+ else if (r.surface === 'ai-edit')
174
+ entry.aiEditCostUsd += c;
175
+ else if (r.surface === 'smash')
176
+ entry.smashCostUsd += c;
177
+ else if (r.surface === 'file-summary')
178
+ entry.fileSummaryCostUsd += c; // B58
179
+ entry.totalCostUsd += c;
180
+ }
181
+ const dailyTimeline = Array.from(dayMap.values());
182
+ // byMode (Quick vs Explore) — one GROUP BY surface query for both modes;
183
+ // dominant models come from modelRows and sparklines from dayRows, which
184
+ // already carry exactly these aggregates (H24: previously 3 extra queries
185
+ // per mode re-scanned the same rows).
186
+ // M18: count DISTINCT tickets, not rows. An Explore session writes one
187
+ // ai_invocations row per turn (and contract-refine adds another), all
188
+ // back-filled with the same ticket_id — so SUM(ticket_id IS NOT NULL) counted
189
+ // turns and inflated "N created". avgCostPerSpec is likewise per-spec:
190
+ // total success cost of ticket-bearing rows / distinct successful tickets.
191
+ const modeAggRows = db.prepare(`
192
+ SELECT
193
+ surface,
194
+ COUNT(*) AS totalRuns,
195
+ COUNT(DISTINCT ticket_id) AS ticketsCreated,
196
+ COALESCE(SUM(total_cost_usd), 0) AS totalCost,
197
+ COALESCE(SUM(CASE WHEN status = 'success' AND ticket_id IS NOT NULL THEN total_cost_usd ELSE 0 END), 0) AS specCostSum,
198
+ COUNT(DISTINCT CASE WHEN status = 'success' AND ticket_id IS NOT NULL THEN ticket_id END) AS specCount,
199
+ AVG(CASE WHEN status = 'success' THEN duration_ms END) AS avgDur
200
+ FROM ai_invocations WHERE ${where.sql} AND surface IN ('quick-spec', 'explore-spec')
201
+ GROUP BY surface
202
+ `).all(...where.params);
203
+ const modeAggBySurface = new Map(modeAggRows.map((r) => [r.surface, r]));
204
+ const costByDaySurface = new Map();
205
+ for (const r of dayRows)
206
+ costByDaySurface.set(`${r.day}|${r.surface}`, r.cost ?? 0);
207
+ const byMode = ['quick-spec', 'explore-spec'].map((surface) => {
208
+ const modeKey = surface === 'quick-spec' ? 'quick' : 'explore';
209
+ const r = modeAggBySurface.get(surface);
210
+ const avgCostPerSpec = r && r.specCount > 0 ? r.specCostSum / r.specCount : null;
211
+ const sparkline = days.map((d) => costByDaySurface.get(`${d}|${surface}`) ?? 0);
212
+ return {
213
+ mode: modeKey,
214
+ totalRuns: r?.totalRuns ?? 0,
215
+ ticketsCreated: r?.ticketsCreated ?? 0,
216
+ totalCostUsd: r?.totalCost ?? 0,
217
+ avgCostPerSpec,
218
+ avgDurationMs: r?.avgDur ?? null,
219
+ dominantModel: dominantBySurface.get(surface)?.model ?? null,
220
+ sparkline,
221
+ };
222
+ });
223
+ // scatter (capped at 500 points to avoid heavy payloads)
224
+ const scatterRows = db.prepare(`
225
+ SELECT id, surface, total_cost_usd, num_turns, duration_ms, ticket_id, started_at
226
+ FROM ai_invocations WHERE ${where.sql} AND total_cost_usd IS NOT NULL
227
+ ORDER BY started_at DESC LIMIT 500
228
+ `).all(...where.params);
229
+ const scatter = scatterRows.map((r) => ({
230
+ id: r.id,
231
+ surface: r.surface,
232
+ costUsd: r.total_cost_usd,
233
+ numTurns: r.num_turns,
234
+ durationMs: r.duration_ms,
235
+ ticketId: r.ticket_id,
236
+ startedAt: r.started_at,
237
+ }));
238
+ // topTickets (cross-surface aggregation)
239
+ const ticketRows = db.prepare(`
240
+ SELECT ticket_id, surface, COUNT(*) AS cnt, COALESCE(SUM(total_cost_usd), 0) AS cost
241
+ FROM ai_invocations WHERE ${where.sql}
242
+ GROUP BY ticket_id, surface
243
+ `).all(...where.params);
244
+ const ticketMap = new Map();
245
+ for (const r of ticketRows) {
246
+ const key = r.ticket_id === null ? '__unattributed__' : String(r.ticket_id);
247
+ if (!ticketMap.has(key)) {
248
+ ticketMap.set(key, {
249
+ ticketId: r.ticket_id,
250
+ ticketTitle: null,
251
+ totalCostUsd: 0,
252
+ totalRuns: 0,
253
+ bySurface: {
254
+ job: { count: 0, costUsd: 0 },
255
+ 'quick-spec': { count: 0, costUsd: 0 },
256
+ 'explore-spec': { count: 0, costUsd: 0 },
257
+ 'ai-edit': { count: 0, costUsd: 0 },
258
+ smash: { count: 0, costUsd: 0 },
259
+ 'file-summary': { count: 0, costUsd: 0 },
260
+ },
261
+ isUnattributed: r.ticket_id === null ? true : undefined,
262
+ });
263
+ }
264
+ const entry = ticketMap.get(key);
265
+ entry.bySurface[r.surface].count += r.cnt;
266
+ entry.bySurface[r.surface].costUsd += r.cost ?? 0;
267
+ entry.totalRuns += r.cnt;
268
+ entry.totalCostUsd += r.cost ?? 0;
269
+ }
270
+ const topTickets = Array.from(ticketMap.values())
271
+ .sort((a, b) => b.totalCostUsd - a.totalCostUsd)
272
+ .slice(0, 10);
273
+ // bySurface — derived from ticketRows (same WHERE, grouped by
274
+ // ticket_id+surface): summing across tickets equals a GROUP BY surface
275
+ // (H24: previously a dedicated query re-scanned the same rows).
276
+ const surfaceTotals = new Map();
277
+ for (const r of ticketRows) {
278
+ const agg = surfaceTotals.get(r.surface) ?? { cnt: 0, cost: 0 };
279
+ agg.cnt += r.cnt;
280
+ agg.cost += r.cost ?? 0;
281
+ surfaceTotals.set(r.surface, agg);
282
+ }
283
+ const bySurface = ALL_SURFACES.map((s) => {
284
+ const t = surfaceTotals.get(s);
285
+ return { surface: s, count: t?.cnt ?? 0, costUsd: t?.cost ?? 0 };
286
+ });
287
+ // tracking start (project's first invocation)
288
+ const trackingRow = db.prepare(`
289
+ SELECT MIN(started_at) AS first FROM ai_invocations WHERE project_id = ?
290
+ `).get(projectId);
291
+ // byProvider — split authoritative vs estimated cost so the UI can render
292
+ // the `~` tilde + Hero footnote without re-querying. Rows persisted with
293
+ // NULL provider (pre-migration backfill missed somehow) coalesce to
294
+ // `claude` to match the migration default.
295
+ const providerRows = db.prepare(`
296
+ SELECT
297
+ COALESCE(provider, 'claude') AS provider,
298
+ COUNT(*) AS cnt,
299
+ COALESCE(SUM(CASE WHEN total_cost_usd_estimated = 0 THEN total_cost_usd ELSE 0 END), 0) AS authoritativeCost,
300
+ COALESCE(SUM(CASE WHEN total_cost_usd_estimated = 1 THEN total_cost_usd ELSE 0 END), 0) AS estimatedCost
301
+ FROM ai_invocations WHERE ${where.sql}
302
+ GROUP BY provider
303
+ ORDER BY (authoritativeCost + estimatedCost) DESC
304
+ `).all(...where.params);
305
+ const byProvider = providerRows.map((r) => ({
306
+ provider: r.provider,
307
+ count: r.cnt,
308
+ costUsd: r.authoritativeCost,
309
+ estimatedCostUsd: r.estimatedCost,
310
+ }));
311
+ return {
312
+ summary: {
313
+ totalCostUsd: summaryRow.totalCost,
314
+ totalEstimatedCostUsd: summaryRow.totalEstimatedCost,
315
+ totalTokens: summaryRow.totalTokens,
316
+ totalRuns: summaryRow.totalRuns,
317
+ failureRate: summaryRow.totalRuns > 0 ? (summaryRow.failed ?? 0) / summaryRow.totalRuns : 0,
318
+ prevTotalCostUsd: prevRow.totalCost,
319
+ deltaPct,
320
+ avgCostPerRun: summaryRow.avgCost,
321
+ },
322
+ bySurface,
323
+ byModel,
324
+ byMode,
325
+ byProvider,
326
+ dailyTimeline,
327
+ scatter,
328
+ topTickets,
329
+ trackingStartedAt: trackingRow.first,
330
+ rangeFrom: range.from,
331
+ rangeTo: range.to,
332
+ };
333
+ }
334
+ /**
335
+ * Produce a short identifying label from an Explore conversation's first
336
+ * user message. Strips leading slash-command lines and resolved-command
337
+ * frontmatter, takes the first non-empty line, and truncates to a few
338
+ * words so the analytics TICKET column stays readable.
339
+ */
340
+ function summariseExplorePrompt(raw) {
341
+ if (!raw)
342
+ return null;
343
+ let text = raw;
344
+ // Strip the slash-command head (`/specrails:explore-spec ...` plus any
345
+ // trailing blank lines until the user's content).
346
+ text = text.replace(/^\/[^\n]*\n+/, '');
347
+ // Find the first non-frontmatter, non-empty, non-heading line.
348
+ const lines = text.split('\n').map((l) => l.trim()).filter(Boolean);
349
+ let first = lines.find((l) => !l.startsWith('#') && !l.startsWith('---') && !l.startsWith('//') && !l.startsWith('>')) ?? lines[0] ?? '';
350
+ // Strip markdown emphasis / inline code so the chip reads clean.
351
+ first = first.replace(/[*_`]/g, '').trim();
352
+ if (!first)
353
+ return null;
354
+ const words = first.split(/\s+/).filter(Boolean);
355
+ const top = words.slice(0, 4).join(' ');
356
+ return words.length > 4 ? `${top}…` : top;
357
+ }
358
+ function getInvocations(db, projectId, filters = {}) {
359
+ const range = resolveRange(filters);
360
+ const where = buildWhere(projectId, filters, { from: range.from, to: range.to });
361
+ const totalRow = db.prepare(`
362
+ SELECT COUNT(*) AS total FROM ai_invocations WHERE ${where.sql}
363
+ `).get(...where.params);
364
+ const cap = filters.cap;
365
+ const limit = cap ?? Math.min(filters.limit ?? 50, 200);
366
+ const offset = filters.offset ?? 0;
367
+ const rows = db.prepare(`
368
+ SELECT * FROM ai_invocations WHERE ${where.sql}
369
+ ORDER BY started_at DESC LIMIT ? OFFSET ?
370
+ `).all(...where.params, limit, offset);
371
+ // For Explore rows (conversation_id non-null) without a committed ticket,
372
+ // surface the conversation title as the provisional ticket label so the
373
+ // analytics table is useful before commit.
374
+ const convIds = Array.from(new Set(rows.filter((r) => r.conversation_id).map((r) => r.conversation_id)));
375
+ const titleByConv = new Map();
376
+ if (convIds.length > 0) {
377
+ const placeholders = convIds.map(() => '?').join(',');
378
+ const titleRows = db.prepare(`SELECT id, title FROM chat_conversations WHERE id IN (${placeholders})`).all(...convIds);
379
+ for (const tr of titleRows)
380
+ titleByConv.set(tr.id, tr.title);
381
+ // Fallback: first user message for convs without a title yet (Explore
382
+ // lightweight mode never auto-titles unless saved as draft).
383
+ const missing = convIds.filter((id) => !titleByConv.get(id));
384
+ if (missing.length > 0) {
385
+ const p2 = missing.map(() => '?').join(',');
386
+ const msgRows = db.prepare(`SELECT conversation_id, content FROM chat_messages
387
+ WHERE role = 'user' AND conversation_id IN (${p2})
388
+ ORDER BY conversation_id, id ASC`).all(...missing);
389
+ const seen = new Set();
390
+ for (const mr of msgRows) {
391
+ if (seen.has(mr.conversation_id))
392
+ continue;
393
+ seen.add(mr.conversation_id);
394
+ const summary = summariseExplorePrompt(mr.content);
395
+ if (summary)
396
+ titleByConv.set(mr.conversation_id, summary);
397
+ }
398
+ }
399
+ }
400
+ const enriched = rows.map((r) => ({
401
+ ...r,
402
+ ticket_title: r.conversation_id ? (titleByConv.get(r.conversation_id) ?? null) : null,
403
+ }));
404
+ return {
405
+ rows: enriched,
406
+ total: cap ? Math.min(rows.length, cap) : rows.length,
407
+ truncated: cap !== undefined && totalRow.total > cap,
408
+ totalAvailable: totalRow.total,
409
+ };
410
+ }
411
+ function parseSpendingFilters(query) {
412
+ const f = {};
413
+ if (typeof query.period === 'string')
414
+ f.period = query.period;
415
+ if (typeof query.from === 'string')
416
+ f.from = query.from;
417
+ if (typeof query.to === 'string')
418
+ f.to = query.to;
419
+ if (typeof query.surface === 'string') {
420
+ // M17: validate against the canonical surface list. The old hardcoded subset
421
+ // silently dropped 'smash'/'file-summary' — clicking those chips produced an
422
+ // empty filter array, so buildWhere applied NO surface condition and the UI
423
+ // showed ALL-surface totals while claiming a single-surface filter was active.
424
+ f.surface = query.surface.split(',').filter((s) => ALL_SURFACES.includes(s));
425
+ }
426
+ if (typeof query.model === 'string') {
427
+ f.model = query.model.split(',').filter((s) => s.length > 0);
428
+ }
429
+ if (typeof query.provider === 'string') {
430
+ const provs = query.provider.split(',').filter((s) => s.length > 0);
431
+ if (provs.length > 0)
432
+ f.provider = provs;
433
+ }
434
+ if (typeof query.status === 'string' && ['success', 'failed', 'aborted'].includes(query.status)) {
435
+ f.status = query.status;
436
+ }
437
+ if (typeof query.minCostUsd === 'string') {
438
+ const v = parseFloat(query.minCostUsd);
439
+ if (!Number.isNaN(v))
440
+ f.minCostUsd = v;
441
+ }
442
+ if (typeof query.ticketId === 'string') {
443
+ const v = parseInt(query.ticketId, 10);
444
+ if (!Number.isNaN(v))
445
+ f.ticketId = v;
446
+ }
447
+ return f;
448
+ }
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runCompaction = runCompaction;
7
+ exports.runCompactionForAll = runCompactionForAll;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const zlib_1 = __importDefault(require("zlib"));
10
+ const db_1 = require("./db");
11
+ const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
12
+ function readNdjsonGz(filePath) {
13
+ try {
14
+ const compressed = fs_1.default.readFileSync(filePath);
15
+ // Decompress all concatenated gzip members
16
+ const raw = zlib_1.default.gunzipSync(compressed).toString('utf-8');
17
+ const lines = [];
18
+ for (const line of raw.split('\n')) {
19
+ const trimmed = line.trim();
20
+ if (!trimmed)
21
+ continue;
22
+ try {
23
+ lines.push(JSON.parse(trimmed));
24
+ }
25
+ catch {
26
+ // Skip malformed lines
27
+ }
28
+ }
29
+ return lines;
30
+ }
31
+ catch {
32
+ return [];
33
+ }
34
+ }
35
+ function emptyAgg() {
36
+ return { durationMs: 0, tokensInput: 0, tokensOutput: 0, tokensCache: 0, toolCallCounts: {}, apiErrors: 0, costUsd: 0 };
37
+ }
38
+ /**
39
+ * Extract per-phase aggregates from telemetry lines.
40
+ * We use the `scope.name` or a best-effort fallback to group by phase.
41
+ * If no phase can be determined we bucket everything into "unknown".
42
+ */
43
+ function aggregateByPhase(lines) {
44
+ const phases = new Map();
45
+ function getAgg(phase) {
46
+ let agg = phases.get(phase);
47
+ if (!agg) {
48
+ agg = emptyAgg();
49
+ phases.set(phase, agg);
50
+ }
51
+ return agg;
52
+ }
53
+ for (const line of lines) {
54
+ if (line.signal === 'control')
55
+ continue;
56
+ const payload = line.payload;
57
+ if (!payload)
58
+ continue;
59
+ // ── Traces: extract duration and tool call info ──────────────────────────
60
+ if (line.signal === 'traces') {
61
+ const resourceSpans = payload.resourceSpans;
62
+ for (const rs of resourceSpans ?? []) {
63
+ const scopeSpans = rs.scopeSpans;
64
+ for (const ss of scopeSpans ?? []) {
65
+ const scope = ss.scope;
66
+ const phase = (typeof scope?.name === 'string' ? scope.name : undefined) ?? 'unknown';
67
+ const agg = getAgg(phase);
68
+ const spans = ss.spans;
69
+ for (const span of spans ?? []) {
70
+ // Duration in nanoseconds → convert to ms
71
+ const startNs = typeof span.startTimeUnixNano === 'string' ? BigInt(span.startTimeUnixNano) : null;
72
+ const endNs = typeof span.endTimeUnixNano === 'string' ? BigInt(span.endTimeUnixNano) : null;
73
+ if (startNs && endNs && endNs > startNs) {
74
+ agg.durationMs += Number((endNs - startNs) / BigInt(1_000_000));
75
+ }
76
+ // Count tool calls by name
77
+ const name = typeof span.name === 'string' ? span.name : null;
78
+ if (name) {
79
+ agg.toolCallCounts[name] = (agg.toolCallCounts[name] ?? 0) + 1;
80
+ }
81
+ // API errors: status code != 0 in OTEL spans
82
+ const status = span.status;
83
+ if (status && status.code !== 0 && status.code !== 'STATUS_CODE_OK') {
84
+ agg.apiErrors++;
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ // ── Metrics: extract token counts and cost ────────────────────────────────
91
+ if (line.signal === 'metrics') {
92
+ const resourceMetrics = payload.resourceMetrics;
93
+ for (const rm of resourceMetrics ?? []) {
94
+ const scopeMetrics = rm.scopeMetrics;
95
+ for (const sm of scopeMetrics ?? []) {
96
+ const scope = sm.scope;
97
+ const phase = (typeof scope?.name === 'string' ? scope.name : undefined) ?? 'unknown';
98
+ const agg = getAgg(phase);
99
+ const metrics = sm.metrics;
100
+ for (const metric of metrics ?? []) {
101
+ const metricName = typeof metric.name === 'string' ? metric.name : '';
102
+ const sum = metric.sum;
103
+ const dataPoints = sum?.dataPoints;
104
+ for (const dp of dataPoints ?? []) {
105
+ const value = typeof dp.asInt === 'string' ? parseInt(dp.asInt, 10)
106
+ : typeof dp.asDouble === 'number' ? dp.asDouble : 0;
107
+ if (metricName.includes('input_tokens') || metricName.includes('tokens_in')) {
108
+ agg.tokensInput += value;
109
+ }
110
+ else if (metricName.includes('output_tokens') || metricName.includes('tokens_out')) {
111
+ agg.tokensOutput += value;
112
+ }
113
+ else if (metricName.includes('cache_tokens') || metricName.includes('tokens_cache')) {
114
+ agg.tokensCache += value;
115
+ }
116
+ else if (metricName.includes('cost_usd') || metricName.includes('total_cost')) {
117
+ agg.costUsd += value;
118
+ }
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ // Default to 'unknown' phase if nothing was bucketed
126
+ if (phases.size === 0)
127
+ phases.set('unknown', emptyAgg());
128
+ return phases;
129
+ }
130
+ // ─── Core compaction ──────────────────────────────────────────────────────────
131
+ async function runCompaction(ctx, now = Date.now()) {
132
+ const { db } = ctx;
133
+ const blobs = (0, db_1.listActiveTelemetryBlobs)(db);
134
+ for (const blob of blobs) {
135
+ const age = blob.endedAt ?? blob.startedAt;
136
+ if (!age)
137
+ continue;
138
+ if (now - age < SEVEN_DAYS_MS)
139
+ continue;
140
+ // Blob is older than 7 days — compact it
141
+ const lines = blob.path ? readNdjsonGz(blob.path) : [];
142
+ const phaseAggs = aggregateByPhase(lines);
143
+ for (const [phase, agg] of phaseAggs) {
144
+ (0, db_1.insertTelemetrySummary)(db, {
145
+ jobId: blob.jobId,
146
+ phase,
147
+ durationMs: agg.durationMs || null,
148
+ tokensInput: agg.tokensInput || null,
149
+ tokensOutput: agg.tokensOutput || null,
150
+ tokensCache: agg.tokensCache || null,
151
+ toolCalls: Object.keys(agg.toolCallCounts).length > 0
152
+ ? JSON.stringify(agg.toolCallCounts)
153
+ : null,
154
+ apiErrors: agg.apiErrors || null,
155
+ costUsd: agg.costUsd || null,
156
+ });
157
+ }
158
+ // Delete the blob file
159
+ if (blob.path) {
160
+ try {
161
+ fs_1.default.unlinkSync(blob.path);
162
+ }
163
+ catch { /* already gone */ }
164
+ }
165
+ // Mark the pointer row as compacted
166
+ (0, db_1.setTelemetryBlobCompacted)(db, blob.jobId);
167
+ }
168
+ }
169
+ /** Run compaction for every loaded project. Called at server startup. */
170
+ async function runCompactionForAll(registry) {
171
+ const now = Date.now();
172
+ for (const ctx of registry.listContexts()) {
173
+ try {
174
+ await runCompaction(ctx, now);
175
+ }
176
+ catch (err) {
177
+ console.error(`[telemetry-compactor] compaction failed for project ${ctx.project.id}:`, err);
178
+ }
179
+ }
180
+ }