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,362 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRailsRouter = createRailsRouter;
4
+ const express_1 = require("express");
5
+ const rails_store_1 = require("./rails-store");
6
+ const queue_manager_1 = require("./queue-manager");
7
+ const provider_selection_1 = require("./provider-selection");
8
+ const VALID_MODES = new Set(['implement', 'batch-implement', 'ultracode']);
9
+ // Models the ultracode picker exposes (Claude aliases). Mirrors the client
10
+ // RailModelSelector options and the project-router orchestrator-model allow-list.
11
+ const VALID_ULTRACODE_MODELS = new Set(['haiku', 'sonnet', 'opus']);
12
+ function createRailsRouter() {
13
+ const router = (0, express_1.Router)({ mergeParams: true });
14
+ function ctx(req) {
15
+ return req.projectCtx;
16
+ }
17
+ // Broadcast the full post-mutation rail snapshot so every connected client
18
+ // (desktop dashboard + mobile companion) reflects non-launch rail changes
19
+ // live — ticket reassignments, renames, mode/profile/engine edits. Re-reads
20
+ // the canonical rail (getRail) so the snapshot always carries the CURRENT
21
+ // name — a mutation return value (e.g. setRailTickets) omits it, which would
22
+ // otherwise broadcast name:null and clobber the rail's label on receivers.
23
+ function broadcastRailUpdated(c, railIndex, changed) {
24
+ const rail = (0, rails_store_1.getRail)(c.db, railIndex);
25
+ const msg = {
26
+ type: 'rail.updated',
27
+ projectId: c.project.id,
28
+ railIndex: rail.railIndex,
29
+ changed,
30
+ ticketIds: rail.ticketIds,
31
+ name: rail.name ?? null,
32
+ mode: rail.mode,
33
+ profileName: rail.profileName ?? null,
34
+ aiEngine: rail.aiEngine ?? null,
35
+ };
36
+ c.broadcast(msg);
37
+ }
38
+ // GET /rails — list all rail assignments + active job info
39
+ router.get('/', (_req, res) => {
40
+ const c = ctx(_req);
41
+ try {
42
+ const rails = (0, rails_store_1.getRails)(c.db);
43
+ // Include which rails have active jobs (so clients can reconcile stale 'running' state)
44
+ const activeJobs = {};
45
+ for (const [jobId, meta] of c.railJobs.entries()) {
46
+ activeJobs[meta.railIndex] = { jobId, mode: meta.mode };
47
+ }
48
+ res.json({ rails, activeJobs });
49
+ }
50
+ catch (err) {
51
+ console.error('[rails-router] get rails error:', err);
52
+ res.status(500).json({ error: 'Failed to fetch rails' });
53
+ }
54
+ });
55
+ // PUT /rails/:railIndex/tickets — set ticket assignments for a rail
56
+ router.put('/:railIndex/tickets', (req, res) => {
57
+ const railIndex = parseInt(req.params.railIndex, 10);
58
+ if (isNaN(railIndex) || railIndex < 0) {
59
+ res.status(400).json({ error: 'Invalid rail index' });
60
+ return;
61
+ }
62
+ const { ticketIds } = req.body ?? {};
63
+ if (!Array.isArray(ticketIds) || ticketIds.some((id) => typeof id !== 'number')) {
64
+ res.status(400).json({ error: 'ticketIds must be an array of numbers' });
65
+ return;
66
+ }
67
+ const c = ctx(req);
68
+ try {
69
+ // setRailTickets does delete-then-reinsert; without forwarding the rail's
70
+ // current mode/profileName they would reset to defaults ('implement' /
71
+ // null) on every ticket reassignment, silently wiping a configured
72
+ // per-rail profile. Preserve them (an explicit body value still wins).
73
+ const current = (0, rails_store_1.getRail)(c.db, railIndex);
74
+ const body = req.body ?? {};
75
+ const mode = typeof body.mode === 'string' ? body.mode : current.mode;
76
+ const profileName = 'profileName' in body ? body.profileName : current.profileName;
77
+ // Preserve the rail's AI engine across ticket reassignment (undefined →
78
+ // setRailTickets re-reads the current value), so it isn't silently wiped.
79
+ const aiEngine = 'aiEngine' in body ? body.aiEngine : undefined;
80
+ const rail = (0, rails_store_1.setRailTickets)(c.db, railIndex, ticketIds, mode, profileName, aiEngine);
81
+ broadcastRailUpdated(c, railIndex, 'tickets');
82
+ res.json({ rail });
83
+ }
84
+ catch (err) {
85
+ console.error('[rails-router] set rail tickets error:', err);
86
+ res.status(500).json({ error: 'Failed to update rail tickets' });
87
+ }
88
+ });
89
+ // PUT /rails/:railIndex/profile — set the default agent profile for a rail
90
+ // Body: { profileName: string | null } (null = force legacy mode for this rail)
91
+ router.put('/:railIndex/profile', (req, res) => {
92
+ const railIndex = parseInt(req.params.railIndex, 10);
93
+ if (isNaN(railIndex) || railIndex < 0) {
94
+ res.status(400).json({ error: 'Invalid rail index' });
95
+ return;
96
+ }
97
+ const body = req.body ?? {};
98
+ if (!('profileName' in body)) {
99
+ res.status(400).json({ error: "body must include 'profileName' (string or null)" });
100
+ return;
101
+ }
102
+ const value = body.profileName;
103
+ if (value !== null && typeof value !== 'string') {
104
+ res.status(400).json({ error: 'profileName must be a string or null' });
105
+ return;
106
+ }
107
+ const c = ctx(req);
108
+ try {
109
+ const rail = (0, rails_store_1.setRailProfile)(c.db, railIndex, value);
110
+ broadcastRailUpdated(c, railIndex, 'profile');
111
+ res.json({ rail });
112
+ }
113
+ catch (err) {
114
+ console.error('[rails-router] set rail profile error:', err);
115
+ res.status(500).json({ error: 'Failed to update rail profile' });
116
+ }
117
+ });
118
+ // PUT /rails/:railIndex/engine — set the AI engine override for a rail
119
+ // Body: { aiEngine: string | null } (null = use the project's primary provider)
120
+ router.put('/:railIndex/engine', (req, res) => {
121
+ const railIndex = parseInt(req.params.railIndex, 10);
122
+ if (isNaN(railIndex) || railIndex < 0) {
123
+ res.status(400).json({ error: 'Invalid rail index' });
124
+ return;
125
+ }
126
+ const body = req.body ?? {};
127
+ if (!('aiEngine' in body)) {
128
+ res.status(400).json({ error: "body must include 'aiEngine' (string or null)" });
129
+ return;
130
+ }
131
+ const value = body.aiEngine;
132
+ const c = ctx(req);
133
+ // null clears the override; a string must be one of the project's providers.
134
+ if (value !== null) {
135
+ const check = (0, provider_selection_1.validateRequestedProvider)(c.project, value);
136
+ if (!check.ok) {
137
+ res.status(400).json({ error: check.error });
138
+ return;
139
+ }
140
+ }
141
+ try {
142
+ const rail = (0, rails_store_1.setRailEngine)(c.db, railIndex, value);
143
+ broadcastRailUpdated(c, railIndex, 'engine');
144
+ res.json({ rail });
145
+ }
146
+ catch (err) {
147
+ console.error('[rails-router] set rail engine error:', err);
148
+ res.status(500).json({ error: 'Failed to update rail engine' });
149
+ }
150
+ });
151
+ // PUT /rails/:railIndex/name — set the rail's display name (the "Rail "-suffix)
152
+ // Body: { name: string | null } (empty/null clears it back to the default label)
153
+ router.put('/:railIndex/name', (req, res) => {
154
+ const railIndex = parseInt(req.params.railIndex, 10);
155
+ if (isNaN(railIndex) || railIndex < 0) {
156
+ res.status(400).json({ error: 'Invalid rail index' });
157
+ return;
158
+ }
159
+ const body = req.body ?? {};
160
+ if (!('name' in body)) {
161
+ res.status(400).json({ error: "body must include 'name' (string or null)" });
162
+ return;
163
+ }
164
+ const value = body.name;
165
+ if (value !== null && typeof value !== 'string') {
166
+ res.status(400).json({ error: 'name must be a string or null' });
167
+ return;
168
+ }
169
+ // Guard against unbounded labels (UI shows a short chip).
170
+ if (typeof value === 'string' && value.length > 60) {
171
+ res.status(400).json({ error: 'name must be 60 characters or fewer' });
172
+ return;
173
+ }
174
+ const c = ctx(req);
175
+ try {
176
+ const rail = (0, rails_store_1.setRailName)(c.db, railIndex, value);
177
+ broadcastRailUpdated(c, railIndex, 'name');
178
+ res.json({ rail });
179
+ }
180
+ catch (err) {
181
+ console.error('[rails-router] set rail name error:', err);
182
+ res.status(500).json({ error: 'Failed to update rail name' });
183
+ }
184
+ });
185
+ // POST /rails/:railIndex/launch — launch job(s) for a rail
186
+ router.post('/:railIndex/launch', (req, res) => {
187
+ const railIndex = parseInt(req.params.railIndex, 10);
188
+ if (isNaN(railIndex) || railIndex < 0) {
189
+ res.status(400).json({ error: 'Invalid rail index' });
190
+ return;
191
+ }
192
+ const { mode = 'implement', profileName, aiEngine, model } = req.body ?? {};
193
+ if (!VALID_MODES.has(mode)) {
194
+ res.status(400).json({ error: 'mode must be "implement", "batch-implement" or "ultracode"' });
195
+ return;
196
+ }
197
+ // Ultracode model picker: optional, validated against the allow-list.
198
+ // Ignored for non-ultracode modes (they use the orchestrator model).
199
+ if (mode === 'ultracode' && model !== undefined && model !== null) {
200
+ if (typeof model !== 'string' || !VALID_ULTRACODE_MODELS.has(model)) {
201
+ res.status(400).json({ error: 'model must be one of: haiku, sonnet, opus' });
202
+ return;
203
+ }
204
+ }
205
+ const c = ctx(req);
206
+ const rail = (0, rails_store_1.getRail)(c.db, railIndex);
207
+ if (rail.ticketIds.length === 0) {
208
+ res.status(400).json({ error: 'Rail has no tickets assigned' });
209
+ return;
210
+ }
211
+ // AI engine precedence: explicit body param > stored rail engine > primary.
212
+ // `undefined`/empty in both means "run on the project's primary provider".
213
+ const requestedEngine = aiEngine === undefined ? (rail.aiEngine ?? undefined) : aiEngine;
214
+ const engineCheck = (0, provider_selection_1.validateRequestedProvider)(c.project, requestedEngine);
215
+ if (!engineCheck.ok) {
216
+ res.status(400).json({ error: engineCheck.error });
217
+ return;
218
+ }
219
+ // Only pass a provider override when one was actually requested (keeps
220
+ // single-provider rails on the legacy code path).
221
+ const railProvider = requestedEngine ? engineCheck.provider : undefined;
222
+ // Ultracode bypasses the OpenSpec pipeline and hands the raw spec to
223
+ // Claude. It is Claude-only — reject when the effective engine is not claude.
224
+ if (mode === 'ultracode' && engineCheck.provider !== 'claude') {
225
+ res.status(400).json({ error: 'Ultracode requires the Claude provider' });
226
+ return;
227
+ }
228
+ // Profile selection precedence: explicit body param > stored rail profile > default resolution.
229
+ // `null` in the body explicitly forces legacy mode. Codex has no agent
230
+ // profiles, so force legacy mode whenever the chosen engine is not claude.
231
+ let resolvedProfile;
232
+ if (mode === 'ultracode') {
233
+ // Ultracode runs no agent pipeline, so profiles do not apply.
234
+ resolvedProfile = null;
235
+ }
236
+ else if (railProvider && railProvider !== 'claude') {
237
+ resolvedProfile = null;
238
+ }
239
+ else if (profileName === null) {
240
+ resolvedProfile = null;
241
+ }
242
+ else if (typeof profileName === 'string' && profileName.trim()) {
243
+ resolvedProfile = profileName.trim();
244
+ }
245
+ else if (rail.profileName) {
246
+ resolvedProfile = rail.profileName;
247
+ }
248
+ else {
249
+ resolvedProfile = undefined; // fall through to QueueManager default resolution
250
+ }
251
+ try {
252
+ let jobId;
253
+ if (mode === 'ultracode') {
254
+ // Ultracode launches ONE independent Claude job per ticket — each gets
255
+ // its own log and runs the spec autonomously (no pipeline). The rail UI
256
+ // tracks the first job as its representative active job; every job is
257
+ // registered so its ticket is marked done on completion.
258
+ // `provider: 'claude'` is explicit so the spawn resolves the claude
259
+ // adapter regardless of the project's primary.
260
+ const ultracodeModel = mode === 'ultracode' && typeof model === 'string' && VALID_ULTRACODE_MODELS.has(model)
261
+ ? model
262
+ : undefined;
263
+ const jobIds = [];
264
+ for (const ticketId of rail.ticketIds) {
265
+ const command = `/specrails:ultracode #${ticketId} --yes`;
266
+ const job = c.queueManager.enqueue(command, 'normal', {
267
+ profileName: null,
268
+ provider: 'claude',
269
+ ...(ultracodeModel ? { model: ultracodeModel } : {}),
270
+ });
271
+ jobIds.push(job.id);
272
+ c.railJobs.set(job.id, { railIndex, mode, ticketIds: [ticketId] });
273
+ }
274
+ jobId = jobIds[0];
275
+ const startMsg = {
276
+ type: 'rail.job_started',
277
+ projectId: c.project.id,
278
+ railIndex,
279
+ jobId,
280
+ mode,
281
+ };
282
+ c.broadcast(startMsg);
283
+ res.status(202).json({ jobId, jobIds, railIndex, mode });
284
+ return;
285
+ }
286
+ // Implement / batch-implement create a single job with all ticket IDs.
287
+ // /specrails:implement handles multiple specs in parallel internally.
288
+ const issueArgs = rail.ticketIds.map((id) => `#${id}`).join(' ');
289
+ const commandName = mode === 'batch-implement' ? 'batch-implement' : 'implement';
290
+ const command = `/specrails:${commandName} ${issueArgs} --yes`;
291
+ const job = c.queueManager.enqueue(command, 'normal', { profileName: resolvedProfile, provider: railProvider });
292
+ jobId = job.id;
293
+ c.railJobs.set(jobId, { railIndex, mode, ticketIds: [...rail.ticketIds] });
294
+ const startMsg = {
295
+ type: 'rail.job_started',
296
+ projectId: c.project.id,
297
+ railIndex,
298
+ jobId,
299
+ mode,
300
+ };
301
+ c.broadcast(startMsg);
302
+ res.status(202).json({ jobId, railIndex, mode });
303
+ }
304
+ catch (err) {
305
+ if (err instanceof queue_manager_1.ClaudeNotFoundError) {
306
+ res.status(503).json({ error: 'Claude CLI not found' });
307
+ return;
308
+ }
309
+ if (err instanceof queue_manager_1.CodexNotFoundError) {
310
+ res.status(503).json({ error: 'Codex CLI not found' });
311
+ return;
312
+ }
313
+ console.error('[rails-router] launch error:', err);
314
+ res.status(500).json({ error: 'Failed to launch rail job' });
315
+ }
316
+ });
317
+ // POST /rails/:railIndex/stop — kill the running job for a rail
318
+ router.post('/:railIndex/stop', (req, res) => {
319
+ const railIndex = parseInt(req.params.railIndex, 10);
320
+ if (isNaN(railIndex) || railIndex < 0) {
321
+ res.status(400).json({ error: 'Invalid rail index' });
322
+ return;
323
+ }
324
+ const c = ctx(req);
325
+ // M19: an Ultracode rail registers ONE queue job per ticket. The old code
326
+ // stopped only the FIRST matching job, so the remaining N-1 jobs kept running
327
+ // and billing while the UI showed the rail stopped. Collect ALL jobs for this
328
+ // rail index and cancel each (running → kill, queued → cancel).
329
+ const targetJobIds = Array.from(c.railJobs.entries())
330
+ .filter(([, meta]) => meta.railIndex === railIndex)
331
+ .map(([jobId]) => jobId);
332
+ if (targetJobIds.length === 0) {
333
+ res.status(404).json({ error: 'No active rail job found for this rail' });
334
+ return;
335
+ }
336
+ let canceledCount = 0;
337
+ for (const jobId of targetJobIds) {
338
+ try {
339
+ c.queueManager.cancel(jobId);
340
+ canceledCount++;
341
+ }
342
+ catch (err) {
343
+ // Already terminal / unknown — clean up the stale entry regardless so the
344
+ // rail card can't get wedged 'running' (this was the unrecoverable case).
345
+ console.warn(`[rails-router] stop: cancel(${jobId}) failed: ${err.message}`);
346
+ }
347
+ c.railJobs.delete(jobId);
348
+ }
349
+ // Broadcast one stop per job so every rail card reconciles.
350
+ for (const jobId of targetJobIds) {
351
+ const stopMsg = {
352
+ type: 'rail.job_stopped',
353
+ projectId: c.project.id,
354
+ railIndex,
355
+ jobId,
356
+ };
357
+ c.broadcast(stopMsg);
358
+ }
359
+ res.json({ ok: true, jobIds: targetJobIds, canceled: canceledCount });
360
+ });
361
+ return router;
362
+ }
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getRails = getRails;
4
+ exports.getRail = getRail;
5
+ exports.setRailTickets = setRailTickets;
6
+ exports.setRailProfile = setRailProfile;
7
+ exports.setRailEngine = setRailEngine;
8
+ exports.setRailName = setRailName;
9
+ /** Read the per-rail display names from rail_meta into a lookup map. */
10
+ function railNames(db) {
11
+ const rows = db.prepare('SELECT rail_index, name FROM rail_meta').all();
12
+ const map = new Map();
13
+ for (const r of rows)
14
+ map.set(r.rail_index, r.name);
15
+ return map;
16
+ }
17
+ // ─── Queries ─────────────────────────────────────────────────────────────────
18
+ function getRails(db) {
19
+ const rows = db
20
+ .prepare('SELECT rail_index, ticket_id, position, mode, profile_name, ai_engine FROM rails ORDER BY rail_index, position')
21
+ .all();
22
+ const map = new Map();
23
+ for (const row of rows) {
24
+ if (!map.has(row.rail_index)) {
25
+ map.set(row.rail_index, {
26
+ ticketIds: [],
27
+ mode: row.mode,
28
+ profileName: row.profile_name,
29
+ aiEngine: row.ai_engine,
30
+ });
31
+ }
32
+ map.get(row.rail_index).ticketIds.push(row.ticket_id);
33
+ }
34
+ const names = railNames(db);
35
+ return [0, 1, 2].map((railIndex) => {
36
+ const rail = map.get(railIndex);
37
+ return {
38
+ railIndex,
39
+ ticketIds: rail?.ticketIds ?? [],
40
+ mode: rail?.mode ?? 'implement',
41
+ profileName: rail?.profileName ?? null,
42
+ aiEngine: rail?.aiEngine ?? null,
43
+ name: names.get(railIndex) ?? null,
44
+ };
45
+ });
46
+ }
47
+ function getRail(db, railIndex) {
48
+ const rows = db
49
+ .prepare('SELECT ticket_id, position, mode, profile_name, ai_engine FROM rails WHERE rail_index = ? ORDER BY position')
50
+ .all(railIndex);
51
+ const meta = db.prepare('SELECT name FROM rail_meta WHERE rail_index = ?').get(railIndex);
52
+ return {
53
+ railIndex,
54
+ ticketIds: rows.map((r) => r.ticket_id),
55
+ mode: rows[0]?.mode ?? 'implement',
56
+ profileName: rows[0]?.profile_name ?? null,
57
+ aiEngine: rows[0]?.ai_engine ?? null,
58
+ name: meta?.name ?? null,
59
+ };
60
+ }
61
+ // ─── Mutations ────────────────────────────────────────────────────────────────
62
+ function setRailTickets(db, railIndex, ticketIds, mode, profileName, aiEngine) {
63
+ const deleteStmt = db.prepare('DELETE FROM rails WHERE rail_index = ?');
64
+ const insertStmt = db.prepare('INSERT INTO rails (rail_index, ticket_id, position, mode, profile_name, ai_engine) VALUES (?, ?, ?, ?, ?, ?)');
65
+ const resolvedMode = mode ?? 'implement';
66
+ const resolvedProfile = profileName === undefined ? null : profileName;
67
+ // undefined → preserve existing engine across re-orders; null → explicit clear.
68
+ const resolvedEngine = aiEngine === undefined ? (getRail(db, railIndex).aiEngine ?? null) : aiEngine;
69
+ db.transaction(() => {
70
+ deleteStmt.run(railIndex);
71
+ for (let i = 0; i < ticketIds.length; i++) {
72
+ insertStmt.run(railIndex, ticketIds[i], i, resolvedMode, resolvedProfile, resolvedEngine);
73
+ }
74
+ })();
75
+ return { railIndex, ticketIds, mode: resolvedMode, profileName: resolvedProfile, aiEngine: resolvedEngine };
76
+ }
77
+ /**
78
+ * Update only the profile for a rail, preserving tickets and mode.
79
+ * No-op (returns current state) when the rail has no tickets yet — the
80
+ * profile is stored as a column on each rail row.
81
+ */
82
+ function setRailProfile(db, railIndex, profileName) {
83
+ const current = getRail(db, railIndex);
84
+ if (current.ticketIds.length === 0) {
85
+ // No rows to update; insert a placeholder row? No — we store profile on
86
+ // each ticket row. Users must assign tickets first. Caller checks.
87
+ return { ...current, profileName };
88
+ }
89
+ db.prepare('UPDATE rails SET profile_name = ? WHERE rail_index = ?').run(profileName, railIndex);
90
+ return { ...current, profileName };
91
+ }
92
+ /**
93
+ * Update only the AI engine for a rail, preserving tickets, mode and profile.
94
+ * Like setRailProfile, no-op (returns current state with the new engine) when
95
+ * the rail has no tickets yet — the engine is stored on each rail row.
96
+ */
97
+ function setRailEngine(db, railIndex, aiEngine) {
98
+ const current = getRail(db, railIndex);
99
+ if (current.ticketIds.length === 0) {
100
+ return { ...current, aiEngine };
101
+ }
102
+ db.prepare('UPDATE rails SET ai_engine = ? WHERE rail_index = ?').run(aiEngine, railIndex);
103
+ return { ...current, aiEngine };
104
+ }
105
+ /**
106
+ * Set a rail's display name (the "Rail "-suffix the user types). Stored in
107
+ * rail_meta (ticket-independent) so even an empty rail keeps its name. Pass
108
+ * an empty/whitespace name to clear it back to the default "Rail N" label.
109
+ */
110
+ function setRailName(db, railIndex, name) {
111
+ const trimmed = typeof name === 'string' ? name.trim() : null;
112
+ const value = trimmed && trimmed.length > 0 ? trimmed : null;
113
+ db.prepare(`INSERT INTO rail_meta (rail_index, name) VALUES (?, ?)
114
+ ON CONFLICT(rail_index) DO UPDATE SET name = excluded.name`).run(railIndex, value);
115
+ return { ...getRail(db, railIndex), name: value };
116
+ }
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ // Result-event normalisation and finalisation for AI CLI invocations.
3
+ //
4
+ // Two coexisting APIs during the multi-provider migration window:
5
+ //
6
+ // - finaliseInvocationResult(adapter, events, opts) — new contract used by
7
+ // post-adapter managers. Walks an AdapterEvent stream, calls
8
+ // `adapter.extractResult`, and falls back to the local pricing table when
9
+ // the adapter declares `capabilities.nativeCostUsd === false`. Returns
10
+ // both the NormalisedResult and an `estimated` flag for the
11
+ // `total_cost_usd_estimated` DB column.
12
+ //
13
+ // - normaliseResultEvent(event, provider) — legacy single-event shape kept
14
+ // so non-migrated managers (queue-manager, chat-manager,
15
+ // agent-refine-manager pre-refactor) keep working. Will be removed after
16
+ // all callsites migrate (tasks §7–10).
17
+ //
18
+ // Spec: openspec/specs/multi-provider-architecture/spec.md
19
+ // Spec: openspec/changes/add-multi-provider-support/specs/project-spending/spec.md
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.finaliseInvocationResult = finaliseInvocationResult;
22
+ exports.normaliseResultEvent = normaliseResultEvent;
23
+ const pricing_1 = require("./pricing");
24
+ /**
25
+ * Finalise an AI invocation by walking the adapter's parsed events, stamping
26
+ * the model when the adapter does not report one, and applying the local
27
+ * pricing-table fallback when the adapter does not report `total_cost_usd`.
28
+ */
29
+ function finaliseInvocationResult(adapter, events, opts = {}) {
30
+ const result = adapter.extractResult(events);
31
+ const cloned = { ...result };
32
+ // Stamp the model from the caller when the adapter did not derive one. The
33
+ // adapter contract permits leaving `model` undefined; the manager knows the
34
+ // model it spawned with.
35
+ if (!cloned.model && opts.fallbackModel) {
36
+ cloned.model = opts.fallbackModel;
37
+ }
38
+ let estimated = false;
39
+ if (!adapter.capabilities.nativeCostUsd) {
40
+ const estimator = opts.estimator ?? pricing_1.estimateCostUsd;
41
+ const computed = estimator(adapter.id, cloned.model, {
42
+ tokens_in: cloned.tokens_in,
43
+ tokens_out: cloned.tokens_out,
44
+ tokens_cache_read: cloned.tokens_cache_read,
45
+ tokens_cache_create: cloned.tokens_cache_create,
46
+ });
47
+ if (computed !== null) {
48
+ cloned.total_cost_usd = computed;
49
+ estimated = true;
50
+ }
51
+ else if (cloned.model) {
52
+ // Non-native-cost provider with a known model id but no pricing-table
53
+ // entry → cost is persisted NULL and silently vanishes from totals.
54
+ // Surface it so a new/renamed/drifted model id is caught fast (the fix
55
+ // is to add the rate to server/pricing.ts, per its quarterly-review
56
+ // contract). We deliberately do NOT fabricate a fallback rate.
57
+ console.warn(`[pricing] no rate-card entry for "${adapter.id}:${cloned.model}" — ` +
58
+ `cost will be persisted NULL. Add it to server/pricing.ts PRICING.`);
59
+ }
60
+ }
61
+ return { result: cloned, estimated };
62
+ }
63
+ // ─── Legacy single-event API ─────────────────────────────────────────────────
64
+ //
65
+ // Pre-adapter managers still call this with the single terminal event payload
66
+ // they captured. Behaviour identical to the implementation that lived here
67
+ // before; provider-specific branches preserved. Once all callsites migrate to
68
+ // `finaliseInvocationResult`, delete this and remove the import.
69
+ function normaliseResultEvent(event, provider = 'claude') {
70
+ if (!event)
71
+ return {};
72
+ if (provider === 'claude') {
73
+ const usage = event.usage;
74
+ return {
75
+ tokens_in: usage?.input_tokens,
76
+ tokens_out: usage?.output_tokens,
77
+ tokens_cache_read: usage?.cache_read_input_tokens,
78
+ tokens_cache_create: usage?.cache_creation_input_tokens,
79
+ total_cost_usd: event.total_cost_usd,
80
+ num_turns: event.num_turns,
81
+ model: event.model,
82
+ duration_ms: event.duration_ms,
83
+ duration_api_ms: event.api_duration_ms,
84
+ session_id: event.session_id,
85
+ };
86
+ }
87
+ // Codex path: callers may pass either a `turn.completed` event (with a
88
+ // nested `usage` block) or a synthesised result-like object with
89
+ // pre-flattened fields. Read whichever shape is present.
90
+ const usage = event.usage;
91
+ // Codex folds reasoning_output_tokens into the billed output token count
92
+ // (OpenAI bills reasoning tokens at output-token rates), so we collapse
93
+ // them here to keep cost estimation consistent with the adapter.
94
+ const outputTokens = usage
95
+ ? (usage.output_tokens ?? 0) + (usage.reasoning_output_tokens ?? 0)
96
+ : undefined;
97
+ return {
98
+ tokens_in: usage?.input_tokens,
99
+ tokens_out: outputTokens,
100
+ tokens_cache_read: usage?.cached_input_tokens,
101
+ total_cost_usd: typeof event.total_cost_usd === 'number' ? event.total_cost_usd : undefined,
102
+ num_turns: typeof event.num_turns === 'number' ? event.num_turns : undefined,
103
+ model: typeof event.model === 'string' ? event.model : undefined,
104
+ duration_ms: typeof event.duration_ms === 'number' ? event.duration_ms : undefined,
105
+ };
106
+ }