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,663 @@
1
+ "use strict";
2
+ /**
3
+ * SPECs SMASH — Runner
4
+ *
5
+ * Standalone orchestrator for a single SMASH turn. Spawns a fresh `claude`
6
+ * with the byte-stable SMASH system prompt, parses the JSON response, then
7
+ * atomically flips the parent ticket to `is_epic` and inserts N child
8
+ * tickets inside a single `mutateStore` callback.
9
+ *
10
+ * See openspec/changes/add-specs-smash.
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.checkSmashEligibility = checkSmashEligibility;
47
+ exports.prepareSmashSpawn = prepareSmashSpawn;
48
+ exports.readSmashChildOutput = readSmashChildOutput;
49
+ exports.composeChildDescription = composeChildDescription;
50
+ exports.applySmashToStore = applySmashToStore;
51
+ exports.applySmashUndo = applySmashUndo;
52
+ exports.applyDeleteEpicChildren = applyDeleteEpicChildren;
53
+ exports.runSmash = runSmash;
54
+ exports.runSmashUndo = runSmashUndo;
55
+ const node_crypto_1 = require("node:crypto");
56
+ const node_readline_1 = require("node:readline");
57
+ const cli_prompt_1 = require("./util/cli-prompt");
58
+ const explore_smash_1 = require("./explore-smash");
59
+ const explore_cwd_manager_1 = require("./explore-cwd-manager");
60
+ const ai_invocations_1 = require("./ai-invocations");
61
+ const result_event_1 = require("./result-event");
62
+ const ticket_store_1 = require("./ticket-store");
63
+ const ticket_store_2 = require("./ticket-store");
64
+ const SMASH_TIMEOUT_MS_SIMPLE = 60_000;
65
+ const SMASH_TIMEOUT_MS_FULL = 900_000; // 15 min — super-spec mode, no rush
66
+ const SMASH_MAX_TURNS_SIMPLE = 1;
67
+ const SMASH_MAX_TURNS_FULL = 30; // plenty of room for codebase exploration
68
+ // ─── Pre-flight gating ───────────────────────────────────────────────────────
69
+ const CONTRACT_LAYER_MARKER = '## Contract Layer';
70
+ /**
71
+ * Check whether the ticket is eligible to be SMASHed. Pure function for ease
72
+ * of testing — does not mutate state, does not depend on env.
73
+ */
74
+ function checkSmashEligibility(store, ticketId) {
75
+ const ticket = store.tickets[String(ticketId)];
76
+ if (!ticket)
77
+ return { ok: false, reason: 'ticket-not-found' };
78
+ if (ticket.status === 'draft')
79
+ return { ok: false, reason: 'is-draft' };
80
+ if (ticket.parent_epic_id !== null)
81
+ return { ok: false, reason: 'is-child' };
82
+ if (!ticket.description.includes(CONTRACT_LAYER_MARKER)) {
83
+ return { ok: false, reason: 'no-contract-layer' };
84
+ }
85
+ // Already-épica with children → must delete first.
86
+ let childCount = 0;
87
+ for (const id of Object.keys(store.tickets)) {
88
+ if (store.tickets[id].parent_epic_id === ticketId)
89
+ childCount += 1;
90
+ }
91
+ if (ticket.is_epic && childCount > 0)
92
+ return { ok: false, reason: 'has-children' };
93
+ return { ok: true, context: { ticket, childCount } };
94
+ }
95
+ // ─── Spawn helpers ───────────────────────────────────────────────────────────
96
+ function buildSmashArgs(model, systemPrompt, userPrompt, mode) {
97
+ // Simple: deny all tools, single turn — fast, no codebase access.
98
+ // Full: allow Read/Grep/Glob (read-only), multi-turn — slower, grounded.
99
+ const disallowed = mode === 'full' ? 'Bash,Edit,Write,NotebookEdit' : 'Read,Grep,Glob,Bash';
100
+ const maxTurns = mode === 'full' ? SMASH_MAX_TURNS_FULL : SMASH_MAX_TURNS_SIMPLE;
101
+ return [
102
+ '--model', model,
103
+ '--dangerously-skip-permissions',
104
+ '--output-format', 'stream-json',
105
+ '--verbose',
106
+ '--system-prompt', systemPrompt,
107
+ '--disallowedTools', disallowed,
108
+ '--max-turns', String(maxTurns),
109
+ '-p', userPrompt,
110
+ ];
111
+ }
112
+ /**
113
+ * Build the spawn argv + cwd. Exported for tests.
114
+ */
115
+ function prepareSmashSpawn(deps, ticket) {
116
+ const mode = deps.mode === 'full' ? 'full' : 'simple';
117
+ const systemPrompt = (0, explore_smash_1.buildSmashSystemPrompt)(mode);
118
+ const userPrompt = `${ticket.title}\n\n${ticket.description}`;
119
+ let cwd;
120
+ // Full mode needs access to the project tree (so Read/Grep/Glob hit the
121
+ // real repo). Simple mode keeps the app-managed dir for a clean scope.
122
+ if (mode === 'full') {
123
+ cwd = deps.projectPath;
124
+ }
125
+ else {
126
+ try {
127
+ cwd = (0, explore_cwd_manager_1.ensureExploreCwd)({
128
+ slug: deps.projectSlug,
129
+ projectPath: deps.projectPath,
130
+ projectName: deps.projectName,
131
+ });
132
+ }
133
+ catch {
134
+ cwd = deps.projectPath;
135
+ }
136
+ }
137
+ const model = deps.model ?? 'sonnet';
138
+ return { args: buildSmashArgs(model, systemPrompt, userPrompt, mode), cwd, systemPrompt, userPrompt, mode };
139
+ }
140
+ /**
141
+ * Read stream-json output from a child process. Exported for tests.
142
+ */
143
+ function readSmashChildOutput(child, timeoutMs) {
144
+ return new Promise((resolve) => {
145
+ let fullText = '';
146
+ let resultEvent = null;
147
+ let timedOut = false;
148
+ let settled = false;
149
+ if (!child.stdout) {
150
+ resolve({ fullText, resultEvent, code: -1, timedOut: false });
151
+ return;
152
+ }
153
+ const reader = (0, node_readline_1.createInterface)({ input: child.stdout, crlfDelay: Infinity });
154
+ reader.on('line', (line) => {
155
+ let parsed = null;
156
+ try {
157
+ parsed = JSON.parse(line);
158
+ }
159
+ catch {
160
+ return;
161
+ }
162
+ if (!parsed)
163
+ return;
164
+ const type = parsed.type;
165
+ if (type === 'result') {
166
+ resultEvent = parsed;
167
+ }
168
+ else if (type === 'assistant') {
169
+ const message = parsed.message;
170
+ const blocks = message?.content ?? [];
171
+ for (const b of blocks) {
172
+ if (b.type === 'text' && typeof b.text === 'string')
173
+ fullText += b.text;
174
+ }
175
+ }
176
+ });
177
+ const timer = setTimeout(() => {
178
+ timedOut = true;
179
+ try {
180
+ child.kill('SIGTERM');
181
+ }
182
+ catch { /* best effort */ }
183
+ if (!settled) {
184
+ settled = true;
185
+ resolve({ fullText, resultEvent, code: null, timedOut: true });
186
+ }
187
+ }, timeoutMs);
188
+ child.on('close', (code) => {
189
+ clearTimeout(timer);
190
+ if (settled)
191
+ return;
192
+ settled = true;
193
+ resolve({ fullText, resultEvent, code, timedOut });
194
+ });
195
+ child.on('error', () => {
196
+ clearTimeout(timer);
197
+ if (settled)
198
+ return;
199
+ settled = true;
200
+ resolve({ fullText, resultEvent, code: -1, timedOut });
201
+ });
202
+ });
203
+ }
204
+ /**
205
+ * Compose the final markdown description persisted for a Sub-Spec.
206
+ * Appends "Why this Sub-Spec" (rationale) and "Acceptance Criteria" sections
207
+ * when those fields are populated (full mode). Simple mode leaves the agent's
208
+ * description as-is.
209
+ */
210
+ function composeChildDescription(child) {
211
+ const parts = [child.description.trim()];
212
+ if (child.rationale && child.rationale.trim().length > 0) {
213
+ parts.push('', '## Why this Sub-Spec', '', child.rationale.trim());
214
+ }
215
+ if (child.acceptanceCriteria && child.acceptanceCriteria.length > 0) {
216
+ parts.push('', '## Acceptance Criteria', '');
217
+ for (const ac of child.acceptanceCriteria) {
218
+ parts.push(`- ${ac}`);
219
+ }
220
+ }
221
+ return parts.join('\n');
222
+ }
223
+ /**
224
+ * Inside a single `mutateStore` callback, flip the parent to épica and
225
+ * insert N child tickets. Returns the updated épica plus the inserted
226
+ * children. Throws if the ticket disappears between the gate check and
227
+ * the lock (rare race).
228
+ */
229
+ function applySmashToStore(filePath, epicId, children, nowIso, createdBy) {
230
+ let epic = null;
231
+ const inserted = [];
232
+ (0, ticket_store_2.mutateStore)(filePath, (s) => {
233
+ const target = s.tickets[String(epicId)];
234
+ if (!target)
235
+ throw new Error(`épica ${epicId} not found inside mutation`);
236
+ target.is_epic = true;
237
+ // Stash the pre-SMASH status so undo can restore it. Only stash on first
238
+ // SMASH (re-SMASH already done) — if already done, leave the original
239
+ // stash in place.
240
+ if (!target.metadata)
241
+ target.metadata = {};
242
+ const md = target.metadata;
243
+ if (!md.pre_smash_status) {
244
+ md.pre_smash_status = target.status;
245
+ }
246
+ // Mark the épica as done so it leaves the active backlog and only the
247
+ // Sub-Specs remain as actionable work.
248
+ target.status = 'done';
249
+ target.updated_at = nowIso;
250
+ epic = target;
251
+ for (const child of children) {
252
+ const id = s.next_id++;
253
+ const description = composeChildDescription(child);
254
+ const ticket = {
255
+ id,
256
+ title: child.title,
257
+ description,
258
+ status: 'todo',
259
+ priority: child.priority,
260
+ labels: [],
261
+ assignee: null,
262
+ prerequisites: [],
263
+ metadata: {},
264
+ comments: [],
265
+ attachments: [],
266
+ origin_conversation_id: null,
267
+ is_epic: false,
268
+ parent_epic_id: epicId,
269
+ execution_order: child.executionOrder,
270
+ short_summary: (0, ticket_store_1.clampShortSummary)(child.shortSummary),
271
+ created_at: nowIso,
272
+ updated_at: nowIso,
273
+ created_by: createdBy,
274
+ source: 'specs-smash',
275
+ };
276
+ s.tickets[String(id)] = ticket;
277
+ inserted.push(ticket);
278
+ }
279
+ });
280
+ if (!epic)
281
+ throw new Error('mutation did not yield épica');
282
+ return { epic, children: inserted, smashedAt: nowIso };
283
+ }
284
+ /**
285
+ * Undo a SMASH: clear `is_epic` on the parent and delete every child whose
286
+ * `parent_epic_id` matches AND whose `created_at >= smashedAt`. Returns the
287
+ * ids of the deleted children plus the updated épica (or null when no épica
288
+ * exists / no longer flagged).
289
+ */
290
+ function applySmashUndo(filePath, epicId, smashedAt, nowIso) {
291
+ let epic = null;
292
+ const deletedChildren = [];
293
+ (0, ticket_store_2.mutateStore)(filePath, (s) => {
294
+ const target = s.tickets[String(epicId)];
295
+ if (!target)
296
+ return;
297
+ if (!target.is_epic)
298
+ return;
299
+ for (const idStr of Object.keys(s.tickets)) {
300
+ const t = s.tickets[idStr];
301
+ if (t.parent_epic_id === epicId && t.created_at >= smashedAt) {
302
+ deletedChildren.push(t.id);
303
+ delete s.tickets[idStr];
304
+ }
305
+ }
306
+ target.is_epic = false;
307
+ // Restore the pre-SMASH status from metadata if available.
308
+ // B61: 'done' was missing from this whitelist, so undoing a SMASH on a ticket
309
+ // that was 'done' before silently demoted it to 'todo'. Include every valid
310
+ // non-epic status.
311
+ const md = (target.metadata ?? {});
312
+ if (md.pre_smash_status === 'todo' || md.pre_smash_status === 'in_progress'
313
+ || md.pre_smash_status === 'cancelled' || md.pre_smash_status === 'draft'
314
+ || md.pre_smash_status === 'done') {
315
+ target.status = md.pre_smash_status;
316
+ }
317
+ else {
318
+ target.status = 'todo';
319
+ }
320
+ delete md.pre_smash_status;
321
+ target.updated_at = nowIso;
322
+ epic = target;
323
+ });
324
+ return { epic, deletedChildren };
325
+ }
326
+ /**
327
+ * Delete all children of an épica without undoing the épica flip itself.
328
+ * Used by the Re-SMASH flow.
329
+ */
330
+ function applyDeleteEpicChildren(filePath, epicId) {
331
+ const deletedChildren = [];
332
+ const store = (0, ticket_store_2.mutateStore)(filePath, (s) => {
333
+ for (const idStr of Object.keys(s.tickets)) {
334
+ const t = s.tickets[idStr];
335
+ if (t.parent_epic_id === epicId) {
336
+ deletedChildren.push(t.id);
337
+ delete s.tickets[idStr];
338
+ }
339
+ }
340
+ });
341
+ return { deletedChildren, revision: store.revision };
342
+ }
343
+ // ─── recordInvocation wrapper ────────────────────────────────────────────────
344
+ function recordSafely(deps, ticketId, runId, startedAt, finishedAt, status, resultEvent, model) {
345
+ try {
346
+ const normalised = resultEvent ? (0, result_event_1.normaliseResultEvent)(resultEvent, 'claude') : {};
347
+ (0, ai_invocations_1.recordInvocation)(deps.db, {
348
+ id: (0, node_crypto_1.randomUUID)(),
349
+ project_id: deps.projectId,
350
+ provider: 'claude',
351
+ surface: 'smash',
352
+ surface_ref_id: `smash:${runId}`,
353
+ conversation_id: null,
354
+ ticket_id: ticketId,
355
+ status,
356
+ started_at: startedAt,
357
+ finished_at: finishedAt,
358
+ ...normalised,
359
+ model: resultEvent?.model ?? model ?? undefined,
360
+ });
361
+ }
362
+ catch (err) {
363
+ console.error('[smash-runner] recordInvocation failed:', err);
364
+ }
365
+ }
366
+ /**
367
+ * On a successful SMASH, attribute the spawn cost/tokens proportionally to
368
+ * each Sub-Spec so the per-child spending line is populated immediately.
369
+ * Cost / tokens / duration are split evenly across N children; num_turns is
370
+ * floor(turns/N) per row (small loss of precision, acceptable).
371
+ */
372
+ function recordChildrenInvocations(deps, childrenIds, runId, startedAt, finishedAt, resultEvent, model) {
373
+ if (childrenIds.length === 0 || !resultEvent)
374
+ return;
375
+ try {
376
+ const normalised = (0, result_event_1.normaliseResultEvent)(resultEvent, 'claude');
377
+ const n = childrenIds.length;
378
+ const split = (v) => {
379
+ if (v === null || v === undefined)
380
+ return undefined;
381
+ return v / n;
382
+ };
383
+ const splitInt = (v) => {
384
+ if (v === null || v === undefined)
385
+ return undefined;
386
+ return Math.floor(v / n);
387
+ };
388
+ for (const childId of childrenIds) {
389
+ (0, ai_invocations_1.recordInvocation)(deps.db, {
390
+ id: (0, node_crypto_1.randomUUID)(),
391
+ project_id: deps.projectId,
392
+ provider: 'claude',
393
+ surface: 'smash',
394
+ surface_ref_id: `smash:${runId}:child:${childId}`,
395
+ conversation_id: null,
396
+ ticket_id: childId,
397
+ status: 'success',
398
+ started_at: startedAt,
399
+ finished_at: finishedAt,
400
+ duration_ms: split(normalised.duration_ms),
401
+ duration_api_ms: split(normalised.duration_api_ms),
402
+ tokens_in: splitInt(normalised.tokens_in),
403
+ tokens_out: splitInt(normalised.tokens_out),
404
+ tokens_cache_read: splitInt(normalised.tokens_cache_read),
405
+ tokens_cache_create: splitInt(normalised.tokens_cache_create),
406
+ total_cost_usd: split(normalised.total_cost_usd),
407
+ num_turns: splitInt(normalised.num_turns),
408
+ session_id: normalised.session_id,
409
+ model: resultEvent.model ?? model ?? undefined,
410
+ });
411
+ }
412
+ }
413
+ catch (err) {
414
+ console.error('[smash-runner] recordChildrenInvocations failed:', err);
415
+ }
416
+ }
417
+ // ─── Public runner ───────────────────────────────────────────────────────────
418
+ /**
419
+ * Fire a SMASH attempt for a single ticket.
420
+ */
421
+ // B60: in-process guard against concurrent SMASH of the same ticket. Eligibility
422
+ // is checked pre-spawn (not under the store lock), so two near-simultaneous
423
+ // requests would both pass the gate and each spawn a child set → duplicates.
424
+ // Keyed by `${projectId}:${ticketId}` so distinct projects don't collide.
425
+ const _smashInFlight = new Set();
426
+ async function runSmash(deps, ticketId) {
427
+ const now = deps.now ?? (() => new Date());
428
+ const mode = deps.mode === 'full' ? 'full' : 'simple';
429
+ const defaultTimeout = mode === 'full' ? SMASH_TIMEOUT_MS_FULL : SMASH_TIMEOUT_MS_SIMPLE;
430
+ const timeoutMs = deps.timeoutMs ?? defaultTimeout;
431
+ const spawn = deps.spawn ?? cli_prompt_1.spawnAiCli;
432
+ const runId = (0, node_crypto_1.randomUUID)();
433
+ console.log(`[smash-runner] entry ticket=${ticketId} run=${runId}`);
434
+ if ((0, explore_smash_1.isSpecsSmashKillSwitchActive)()) {
435
+ return { ok: false, reason: 'disabled', ticketId, runId };
436
+ }
437
+ // B60: reject a concurrent SMASH of the same ticket. The has-check + add are
438
+ // synchronous (no await between them), so they close the TOCTOU window the
439
+ // pre-flight eligibility check leaves open. Released in the finally below.
440
+ const inFlightKey = `${deps.projectId}:${ticketId}`;
441
+ if (_smashInFlight.has(inFlightKey)) {
442
+ return { ok: false, reason: 'in-progress', ticketId, runId };
443
+ }
444
+ _smashInFlight.add(inFlightKey);
445
+ try {
446
+ // Pre-flight: read store and check eligibility.
447
+ const filePath = (0, ticket_store_2.resolveTicketStoragePath)(deps.projectPath);
448
+ let ticket;
449
+ try {
450
+ const { readStore } = await Promise.resolve().then(() => __importStar(require('./ticket-store')));
451
+ const store = readStore(filePath);
452
+ const gate = checkSmashEligibility(store, ticketId);
453
+ if (!gate.ok) {
454
+ return { ok: false, reason: gate.reason, ticketId, runId };
455
+ }
456
+ ticket = gate.context.ticket;
457
+ }
458
+ catch (err) {
459
+ console.error('[smash-runner] pre-flight read failed:', err);
460
+ return { ok: false, reason: 'crashed', ticketId, runId };
461
+ }
462
+ // Broadcast start.
463
+ const startedAt = now().toISOString();
464
+ deps.broadcast({
465
+ type: 'smash.started',
466
+ projectId: deps.projectId,
467
+ ticketId,
468
+ runId,
469
+ ticketTitle: ticket.title,
470
+ timestamp: startedAt,
471
+ });
472
+ const { args, cwd } = prepareSmashSpawn({
473
+ projectSlug: deps.projectSlug,
474
+ projectPath: deps.projectPath,
475
+ projectName: deps.projectName,
476
+ model: deps.model,
477
+ }, ticket);
478
+ let child;
479
+ try {
480
+ child = spawn('claude', args, {
481
+ env: process.env,
482
+ stdio: ['ignore', 'pipe', 'pipe'],
483
+ cwd,
484
+ });
485
+ }
486
+ catch (err) {
487
+ const finishedAt = now().toISOString();
488
+ recordSafely(deps, ticketId, runId, startedAt, finishedAt, 'failed', null, deps.model);
489
+ deps.broadcast({
490
+ type: 'smash.failed',
491
+ projectId: deps.projectId,
492
+ ticketId,
493
+ runId,
494
+ reason: 'crashed',
495
+ timestamp: finishedAt,
496
+ });
497
+ return { ok: false, reason: 'crashed', ticketId, runId };
498
+ }
499
+ // Synthesize progress pills as the spawn proceeds. The runner uses fixed
500
+ // offsets — cheap and deterministic vs trying to read tool-use events.
501
+ const progressTimers = [];
502
+ const stages = ['analyzing', 'identifying', 'ordering'];
503
+ stages.forEach((stage, i) => {
504
+ progressTimers.push(setTimeout(() => {
505
+ deps.broadcast({
506
+ type: 'smash.progress',
507
+ projectId: deps.projectId,
508
+ ticketId,
509
+ runId,
510
+ stage,
511
+ timestamp: now().toISOString(),
512
+ });
513
+ }, 200 + i * 1500));
514
+ });
515
+ const result = await readSmashChildOutput(child, timeoutMs);
516
+ progressTimers.forEach((t) => clearTimeout(t));
517
+ const finishedAt = now().toISOString();
518
+ console.log(`[smash-runner] spawn done code=${result.code} timedOut=${result.timedOut} hasResult=${!!result.resultEvent} textBytes=${result.fullText.length}`);
519
+ if (result.timedOut) {
520
+ recordSafely(deps, ticketId, runId, startedAt, finishedAt, 'aborted', result.resultEvent, deps.model);
521
+ deps.broadcast({
522
+ type: 'smash.failed',
523
+ projectId: deps.projectId,
524
+ ticketId,
525
+ runId,
526
+ reason: 'timeout',
527
+ timestamp: finishedAt,
528
+ });
529
+ return { ok: false, reason: 'timeout', ticketId, runId };
530
+ }
531
+ if (result.code !== 0 || !result.resultEvent) {
532
+ recordSafely(deps, ticketId, runId, startedAt, finishedAt, 'failed', result.resultEvent, deps.model);
533
+ const reason = result.resultEvent ? 'model_error' : 'crashed';
534
+ deps.broadcast({
535
+ type: 'smash.failed',
536
+ projectId: deps.projectId,
537
+ ticketId,
538
+ runId,
539
+ reason,
540
+ timestamp: finishedAt,
541
+ });
542
+ return { ok: false, reason, ticketId, runId };
543
+ }
544
+ const parse = (0, explore_smash_1.parseSmashOutput)(result.fullText);
545
+ if (!parse.ok) {
546
+ console.log(`[smash-runner] parse failed reason=${parse.reason} detail=${parse.detail ?? '-'}`);
547
+ recordSafely(deps, ticketId, runId, startedAt, finishedAt, 'failed', result.resultEvent, deps.model);
548
+ deps.broadcast({
549
+ type: 'smash.failed',
550
+ projectId: deps.projectId,
551
+ ticketId,
552
+ runId,
553
+ reason: 'invalid-output',
554
+ detail: parse.reason,
555
+ timestamp: finishedAt,
556
+ });
557
+ return { ok: false, reason: 'invalid-output', ticketId, runId };
558
+ }
559
+ // Atomic flip + insert.
560
+ let applied;
561
+ try {
562
+ applied = applySmashToStore(filePath, ticketId, parse.value.children, finishedAt, 'sr-specs-smash');
563
+ }
564
+ catch (err) {
565
+ console.error('[smash-runner] mutation failed:', err);
566
+ recordSafely(deps, ticketId, runId, startedAt, finishedAt, 'failed', result.resultEvent, deps.model);
567
+ deps.broadcast({
568
+ type: 'smash.failed',
569
+ projectId: deps.projectId,
570
+ ticketId,
571
+ runId,
572
+ reason: 'mutation-failed',
573
+ timestamp: finishedAt,
574
+ });
575
+ return { ok: false, reason: 'mutation-failed', ticketId, runId };
576
+ }
577
+ // On a successful SMASH, attribute the spawn cost ONLY to the children
578
+ // (cost split evenly). The Epic itself does not accrue cost here — the
579
+ // operation logically birthed the Sub-Specs and the cost belongs to them.
580
+ // Total project cost remains the original spawn cost (sum of N × X/N = X).
581
+ recordChildrenInvocations(deps, applied.children.map((c) => c.id), runId, startedAt, finishedAt, result.resultEvent, deps.model);
582
+ // Broadcast underlying state changes first, then the SMASH-specific event.
583
+ deps.broadcast({
584
+ type: 'ticket_updated',
585
+ ticket: applied.epic,
586
+ projectId: deps.projectId,
587
+ timestamp: finishedAt,
588
+ });
589
+ for (const child of applied.children) {
590
+ deps.broadcast({
591
+ type: 'ticket_created',
592
+ ticket: child,
593
+ projectId: deps.projectId,
594
+ timestamp: finishedAt,
595
+ });
596
+ }
597
+ deps.broadcast({
598
+ type: 'smash.completed',
599
+ projectId: deps.projectId,
600
+ ticketId,
601
+ runId,
602
+ smashedAt: finishedAt,
603
+ childrenIds: applied.children.map((c) => c.id),
604
+ timestamp: finishedAt,
605
+ });
606
+ deps.broadcast({ type: 'spending.invalidated', projectId: deps.projectId });
607
+ return {
608
+ ok: true,
609
+ ticketId,
610
+ runId,
611
+ childrenIds: applied.children.map((c) => c.id),
612
+ };
613
+ }
614
+ finally {
615
+ // B60: always release the in-flight guard (success, failure, or throw).
616
+ _smashInFlight.delete(inFlightKey);
617
+ }
618
+ }
619
+ /**
620
+ * Undo a prior SMASH: deletes any child created since `smashedAt` and clears
621
+ * `is_epic` on the parent ticket.
622
+ */
623
+ async function runSmashUndo(deps, ticketId, smashedAt) {
624
+ if ((0, explore_smash_1.isSpecsSmashKillSwitchActive)()) {
625
+ return { ok: false, deletedChildren: [], reason: 'disabled' };
626
+ }
627
+ const now = deps.now ?? (() => new Date());
628
+ const filePath = (0, ticket_store_2.resolveTicketStoragePath)(deps.projectPath);
629
+ const finishedAt = now().toISOString();
630
+ let undone;
631
+ try {
632
+ undone = applySmashUndo(filePath, ticketId, smashedAt, finishedAt);
633
+ }
634
+ catch (err) {
635
+ console.error('[smash-runner] undo failed:', err);
636
+ return { ok: false, deletedChildren: [], reason: 'mutation-failed' };
637
+ }
638
+ if (!undone.epic) {
639
+ return { ok: false, deletedChildren: [], reason: 'ticket-not-found' };
640
+ }
641
+ for (const id of undone.deletedChildren) {
642
+ deps.broadcast({
643
+ type: 'ticket_deleted',
644
+ ticketId: id,
645
+ projectId: deps.projectId,
646
+ timestamp: finishedAt,
647
+ });
648
+ }
649
+ deps.broadcast({
650
+ type: 'ticket_updated',
651
+ ticket: undone.epic,
652
+ projectId: deps.projectId,
653
+ timestamp: finishedAt,
654
+ });
655
+ deps.broadcast({
656
+ type: 'smash.undone',
657
+ projectId: deps.projectId,
658
+ ticketId,
659
+ childrenIds: undone.deletedChildren,
660
+ timestamp: finishedAt,
661
+ });
662
+ return { ok: true, deletedChildren: undone.deletedChildren };
663
+ }