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,689 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.FileSummaryManager = void 0;
40
+ exports.summariesDir = summariesDir;
41
+ exports.pathHash = pathHash;
42
+ exports.summaryFilePath = summaryFilePath;
43
+ exports.computeFileHash = computeFileHash;
44
+ exports.readSummary = readSummary;
45
+ exports.writeSummary = writeSummary;
46
+ exports.ensureGitignoreLine = ensureGitignoreLine;
47
+ exports.sweepOrphans = sweepOrphans;
48
+ exports.__resetDesktopSummaryStateForTests = __resetDesktopSummaryStateForTests;
49
+ const crypto_1 = require("crypto");
50
+ const fs = __importStar(require("fs"));
51
+ const path = __importStar(require("path"));
52
+ const chokidar_1 = __importDefault(require("chokidar"));
53
+ const build_dirs_1 = require("./build-dirs");
54
+ const ai_invocations_1 = require("./ai-invocations");
55
+ const SUMMARIES_REL = path.join('.specrails', 'file-summaries');
56
+ // Current summary prompt version. Bump when buildSystemPrompt changes materially
57
+ // so existing summaries are treated as stale and regenerated on next request.
58
+ const CURRENT_PROMPT_VERSION = 1;
59
+ // Defensive bound on the per-job counter map so it cannot grow without limit
60
+ // across a long-lived app session (the per-job cap is best-effort).
61
+ const MAX_JOB_COUNTERS = 2000;
62
+ const TOKEN_CHARS_PER_TOKEN = 4;
63
+ const TOKEN_LIMIT = 8000;
64
+ const TRUNCATE_HEAD_CHARS = 16000;
65
+ const TRUNCATE_TAIL_CHARS = 8000;
66
+ const TRUNCATE_MARKER = '\n// … truncated … //\n';
67
+ function summariesDir(projectPath) {
68
+ return path.join(projectPath, SUMMARIES_REL);
69
+ }
70
+ function pathHash(relPath) {
71
+ return (0, crypto_1.createHash)('sha256').update(Buffer.from(relPath, 'utf8')).digest('hex');
72
+ }
73
+ function summaryFilePath(projectPath, relPath) {
74
+ return path.join(summariesDir(projectPath), `${pathHash(relPath)}.json`);
75
+ }
76
+ async function computeFileHash(absolutePath) {
77
+ // Use streaming hash so very large files do not balloon memory.
78
+ return await new Promise((resolve, reject) => {
79
+ const hash = (0, crypto_1.createHash)('sha256');
80
+ const stream = fs.createReadStream(absolutePath);
81
+ stream.on('error', reject);
82
+ stream.on('data', (chunk) => hash.update(chunk));
83
+ stream.on('end', () => resolve(hash.digest('hex')));
84
+ });
85
+ }
86
+ function readSummary(projectPath, relPath) {
87
+ const file = summaryFilePath(projectPath, relPath);
88
+ try {
89
+ const raw = fs.readFileSync(file, 'utf8');
90
+ return JSON.parse(raw);
91
+ }
92
+ catch (err) {
93
+ if (err.code === 'ENOENT')
94
+ return null;
95
+ return null;
96
+ }
97
+ }
98
+ function writeSummary(projectPath, relPath, payload) {
99
+ const dir = summariesDir(projectPath);
100
+ const firstWrite = !fs.existsSync(dir);
101
+ fs.mkdirSync(dir, { recursive: true });
102
+ const final = summaryFilePath(projectPath, relPath);
103
+ // Atomic write: temp file in the same directory, then rename.
104
+ const tmp = `${final}.tmp.${(0, crypto_1.randomBytes)(6).toString('hex')}`;
105
+ fs.writeFileSync(tmp, JSON.stringify(payload), { encoding: 'utf8', mode: 0o600 });
106
+ fs.renameSync(tmp, final);
107
+ if (firstWrite) {
108
+ // The app appends `.specrails/file-summaries/` to the project `.gitignore`
109
+ // on first write. Idempotent: only appends when the line is absent.
110
+ try {
111
+ ensureGitignoreLine(projectPath, '.specrails/file-summaries/');
112
+ }
113
+ catch { /* non-fatal */ }
114
+ }
115
+ }
116
+ function ensureGitignoreLine(projectPath, line) {
117
+ const gi = path.join(projectPath, '.gitignore');
118
+ let existing = '';
119
+ try {
120
+ existing = fs.readFileSync(gi, 'utf8');
121
+ }
122
+ catch (err) {
123
+ if (err.code !== 'ENOENT')
124
+ throw err;
125
+ }
126
+ const hasLine = existing.split(/\r?\n/).some((l) => l.trim() === line.trim());
127
+ if (hasLine)
128
+ return false;
129
+ const sep = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
130
+ fs.writeFileSync(gi, `${existing}${sep}${line}\n`, 'utf8');
131
+ return true;
132
+ }
133
+ function sweepOrphans(projectPath, cap = 200) {
134
+ const dir = summariesDir(projectPath);
135
+ let deleted = 0;
136
+ let remaining = 0;
137
+ let entries;
138
+ try {
139
+ entries = fs.readdirSync(dir);
140
+ }
141
+ catch (err) {
142
+ if (err.code === 'ENOENT')
143
+ return { deleted: 0, remaining: 0 };
144
+ throw err;
145
+ }
146
+ for (const entry of entries) {
147
+ if (!entry.endsWith('.json'))
148
+ continue;
149
+ const full = path.join(dir, entry);
150
+ let payload;
151
+ try {
152
+ payload = JSON.parse(fs.readFileSync(full, 'utf8'));
153
+ }
154
+ catch {
155
+ continue;
156
+ }
157
+ const sourceAbs = path.join(projectPath, payload.path);
158
+ if (fs.existsSync(sourceAbs))
159
+ continue;
160
+ if (deleted >= cap) {
161
+ remaining += 1;
162
+ continue;
163
+ }
164
+ try {
165
+ fs.unlinkSync(full);
166
+ deleted += 1;
167
+ }
168
+ catch {
169
+ // best-effort sweep
170
+ }
171
+ }
172
+ return { deleted, remaining };
173
+ }
174
+ // App-wide concurrency is shared across EVERY FileSummaryManager instance.
175
+ // Production constructs one manager per project (each with its own db/broadcast/
176
+ // generate), so a per-instance counter would only ever cap per-project. This
177
+ // module-level state makes the documented "app-wide 8" ceiling real: all live
178
+ // managers share one in-flight count, and freeing a slot re-pumps every live
179
+ // manager so a blocked project's queue advances.
180
+ const DESKTOP = {
181
+ inFlight: 0,
182
+ managers: new Set(),
183
+ };
184
+ /** Test-only: reset the shared app-wide counter/registry between unit tests so
185
+ * module-level state never leaks across cases. */
186
+ function __resetDesktopSummaryStateForTests() {
187
+ DESKTOP.inFlight = 0;
188
+ DESKTOP.managers.clear();
189
+ }
190
+ class FileSummaryManager {
191
+ deps;
192
+ perProjectConcurrency;
193
+ desktopConcurrency;
194
+ perJobCap;
195
+ queueTtlMs;
196
+ maxJobCounters;
197
+ // Per-project queue, per-project in-flight count. App-wide in-flight lives in
198
+ // the module-level DESKTOP so it is shared across all per-project instances.
199
+ queues = new Map();
200
+ inFlightPerProject = new Map();
201
+ jobCounter = new Map();
202
+ watchers = new Map();
203
+ // Dedupe key (`projectId:relPath`) → in-flight enqueue promise. A second
204
+ // enqueue for the same file rides the first instead of double-spawning the
205
+ // provider and double-billing ai_invocations.
206
+ inFlightByKey = new Map();
207
+ // AbortControllers for in-flight generations so dispose() can tear down the
208
+ // provider child instead of orphaning it past project removal.
209
+ activeControllers = new Set();
210
+ _disposed = false;
211
+ // Per-project SUPERSET of relPaths that have a summary on disk. Seeded from
212
+ // the summaries dir at attachWatcher and only ever added to (on write), so a
213
+ // path absent from the set provably has no summary — letting the chokidar
214
+ // 'change' handler skip the readSummary disk hit for the common no-summary
215
+ // file. A stale entry (after sweep) just costs one harmless failed read.
216
+ knownSummaries = new Map();
217
+ // Tracks pending generation promises so flush() can await them in tests.
218
+ pending = new Set();
219
+ constructor(deps, opts = {}) {
220
+ this.deps = deps;
221
+ this.perProjectConcurrency = opts.perProjectConcurrency ?? 2;
222
+ this.desktopConcurrency = opts.desktopConcurrency ?? 8;
223
+ this.perJobCap = opts.perJobCap ?? 50;
224
+ this.queueTtlMs = opts.queueTtlMs ?? 5 * 60 * 1000;
225
+ this.maxJobCounters = opts.maxJobCounters ?? MAX_JOB_COUNTERS;
226
+ DESKTOP.managers.add(this);
227
+ }
228
+ // NOT async: returning the in-flight promise verbatim is what makes the dedupe
229
+ // a true coalesce (the second caller gets the SAME promise, not a wrapper).
230
+ enqueue(req) {
231
+ // Per-(project,relPath) in-flight dedupe: a second enqueue for the same file
232
+ // while one is still running coalesces onto the first promise, so the
233
+ // provider is spawned once and ai_invocations is billed once (fixes
234
+ // concurrent-regenerate double-billing across tabs/clients).
235
+ const dedupeKey = `${req.projectId}:${req.relPath}`;
236
+ const existing = this.inFlightByKey.get(dedupeKey);
237
+ if (existing)
238
+ return existing;
239
+ const p = this._enqueueInner(req);
240
+ this.inFlightByKey.set(dedupeKey, p);
241
+ void p.catch(() => undefined).finally(() => {
242
+ if (this.inFlightByKey.get(dedupeKey) === p)
243
+ this.inFlightByKey.delete(dedupeKey);
244
+ });
245
+ return p;
246
+ }
247
+ async _enqueueInner(req) {
248
+ const absolutePath = path.join(req.projectPath, req.relPath);
249
+ // Step 1: file readability check.
250
+ let newHash;
251
+ try {
252
+ const stat = fs.statSync(absolutePath);
253
+ if (!stat.isFile()) {
254
+ this.emitSkipped(req, 'not-found');
255
+ return 'skipped:not-found';
256
+ }
257
+ newHash = await computeFileHash(absolutePath);
258
+ }
259
+ catch {
260
+ this.emitSkipped(req, 'not-found');
261
+ return 'skipped:not-found';
262
+ }
263
+ // Step 2: hash gate. Skip regeneration only when content, language AND prompt
264
+ // version all match — and never when the caller forced it. Without the
265
+ // language check an app language switch (en↔es) would never refresh existing
266
+ // summaries; without `force` an explicit "Regenerate" of an unchanged file
267
+ // would be a silent no-op.
268
+ const currentLang = this.deps.language?.() ?? 'en';
269
+ const existing = readSummary(req.projectPath, req.relPath);
270
+ if (!req.force &&
271
+ existing &&
272
+ existing.fileHash === newHash &&
273
+ existing.language === currentLang &&
274
+ existing.generatedBy?.promptVersion === CURRENT_PROMPT_VERSION) {
275
+ this.deps.broadcast(buildSummaryUpdated(req.projectId, existing, false));
276
+ return 'skipped:hash';
277
+ }
278
+ // Step 3: per-job cap — PRE-CHECK ONLY. The counter is incremented in pump()
279
+ // when a generation actually STARTS, so budget-skipped / TTL-dropped / failed
280
+ // requests never consume a per-job slot (the cap counts generations, not
281
+ // attempts). pump() re-checks the cap at start to catch the case where
282
+ // several requests passed this pre-check before any started.
283
+ if (req.jobId) {
284
+ const count = this.jobCounter.get(req.jobId) ?? 0;
285
+ if (count >= this.perJobCap) {
286
+ this.emitSkipped(req, 'per-job-cap');
287
+ return 'skipped:per-job-cap';
288
+ }
289
+ }
290
+ // Step 4: budget cap. Applies to job- AND user-triggered requests; the only
291
+ // bypass is an explicit overrideBudget (the "Override the budget cap?"
292
+ // confirmation in the UI). Previously only job-triggered requests were
293
+ // gated, which left the manual-regenerate budget prompt unreachable.
294
+ if (!req.overrideBudget) {
295
+ const spend = this.deps.monthToDateSpend(req.projectId);
296
+ const budget = this.deps.monthlyBudgetUsd();
297
+ if (spend >= budget) {
298
+ this.emitSkipped(req, 'budget');
299
+ return 'skipped:budget';
300
+ }
301
+ }
302
+ // Step 5: enqueue or run.
303
+ const result = await new Promise((resolve, reject) => {
304
+ const entry = {
305
+ req: { ...req },
306
+ enqueuedAt: (this.deps.now ?? Date.now)(),
307
+ newHash,
308
+ resolve,
309
+ reject,
310
+ };
311
+ const queue = this.queues.get(req.projectId) ?? [];
312
+ queue.push(entry);
313
+ this.queues.set(req.projectId, queue);
314
+ this.pump(req.projectId);
315
+ });
316
+ return result;
317
+ }
318
+ pump(projectId) {
319
+ if (this._disposed)
320
+ return;
321
+ const queue = this.queues.get(projectId) ?? [];
322
+ while (queue.length > 0) {
323
+ if (DESKTOP.inFlight >= this.desktopConcurrency)
324
+ break;
325
+ const perProject = this.inFlightPerProject.get(projectId) ?? 0;
326
+ if (perProject >= this.perProjectConcurrency)
327
+ break;
328
+ const entry = queue.shift();
329
+ const now = (this.deps.now ?? Date.now)();
330
+ // TTL drop before starting. Distinct 'skipped:ttl' (not 'skipped:hash') so
331
+ // the regenerate route can tell the user it was dropped, not silently 202.
332
+ if (now - entry.enqueuedAt > this.queueTtlMs) {
333
+ this.emitSkipped(entry.req, 'ttl');
334
+ entry.resolve('skipped:ttl');
335
+ continue;
336
+ }
337
+ // Budget re-check at dequeue: an entry that crossed the monthly cap while
338
+ // waiting in the queue is skipped instead of spending, bounding the
339
+ // concurrent-overshoot window to the in-flight set.
340
+ if (!entry.req.overrideBudget) {
341
+ const spend = this.deps.monthToDateSpend(entry.req.projectId);
342
+ const budget = this.deps.monthlyBudgetUsd();
343
+ if (spend >= budget) {
344
+ this.emitSkipped(entry.req, 'budget');
345
+ entry.resolve('skipped:budget');
346
+ continue;
347
+ }
348
+ }
349
+ // Per-job cap re-check + increment at START, so the counter measures
350
+ // generations actually run (not enqueue attempts) and several requests that
351
+ // passed the enqueue pre-check can't collectively exceed the cap.
352
+ if (entry.req.jobId) {
353
+ const count = this.jobCounter.get(entry.req.jobId) ?? 0;
354
+ if (count >= this.perJobCap) {
355
+ this.emitSkipped(entry.req, 'per-job-cap');
356
+ entry.resolve('skipped:per-job-cap');
357
+ continue;
358
+ }
359
+ if (!this.jobCounter.has(entry.req.jobId) && this.jobCounter.size >= this.maxJobCounters) {
360
+ const oldest = this.jobCounter.keys().next().value;
361
+ if (oldest !== undefined)
362
+ this.jobCounter.delete(oldest);
363
+ }
364
+ this.jobCounter.set(entry.req.jobId, count + 1);
365
+ }
366
+ this.inFlightPerProject.set(projectId, perProject + 1);
367
+ DESKTOP.inFlight += 1;
368
+ const p = this.runOne(entry)
369
+ .catch((err) => entry.reject(err))
370
+ .finally(() => {
371
+ this.inFlightPerProject.set(projectId, (this.inFlightPerProject.get(projectId) ?? 1) - 1);
372
+ DESKTOP.inFlight -= 1;
373
+ this.pending.delete(p);
374
+ this.pump(projectId);
375
+ // A freed app-wide slot may unblock OTHER projects' managers.
376
+ for (const m of DESKTOP.managers)
377
+ if (m !== this)
378
+ m._drainAll();
379
+ });
380
+ this.pending.add(p);
381
+ }
382
+ if (queue.length === 0)
383
+ this.queues.delete(projectId);
384
+ else
385
+ this.queues.set(projectId, queue);
386
+ }
387
+ /** Pump every project queue this manager owns (used when an app-wide slot frees
388
+ * up in a different manager). */
389
+ _drainAll() {
390
+ for (const pid of [...this.queues.keys()])
391
+ this.pump(pid);
392
+ }
393
+ async runOne(entry) {
394
+ const { req } = entry;
395
+ const absolutePath = path.join(req.projectPath, req.relPath);
396
+ const startedIso = new Date((this.deps.now ?? Date.now)()).toISOString();
397
+ let contents;
398
+ let fileHash;
399
+ try {
400
+ contents = fs.readFileSync(absolutePath, 'utf8');
401
+ // Use the hash captured for THIS entry at enqueue time (never another
402
+ // entry's). Fall back to a recompute only if it was somehow not set.
403
+ fileHash = entry.newHash || (await computeFileHash(absolutePath));
404
+ }
405
+ catch {
406
+ this.emitSkipped(req, 'not-found');
407
+ entry.resolve('skipped:not-found');
408
+ return;
409
+ }
410
+ const tokens = Math.ceil(contents.length / TOKEN_CHARS_PER_TOKEN);
411
+ let truncated = false;
412
+ let promptContents = contents;
413
+ if (tokens > TOKEN_LIMIT) {
414
+ truncated = true;
415
+ const head = contents.slice(0, TRUNCATE_HEAD_CHARS);
416
+ const tail = contents.slice(contents.length - TRUNCATE_TAIL_CHARS);
417
+ promptContents = head + TRUNCATE_MARKER + tail;
418
+ }
419
+ const lang = (this.deps.language?.() ?? 'en');
420
+ const controller = new AbortController();
421
+ this.activeControllers.add(controller);
422
+ try {
423
+ const out = await this.deps.generate({
424
+ relPath: req.relPath,
425
+ contents: promptContents,
426
+ truncated,
427
+ language: lang,
428
+ }, controller.signal);
429
+ // The project may have been removed (and its DB closed) while the provider
430
+ // ran. Skip all DB/disk/broadcast work so we never touch a closed handle.
431
+ if (this._disposed) {
432
+ entry.resolve('failed');
433
+ return;
434
+ }
435
+ const payload = {
436
+ schemaVersion: 1,
437
+ path: req.relPath,
438
+ fileHash,
439
+ summary: out.summary,
440
+ language: lang,
441
+ generatedAt: new Date((this.deps.now ?? Date.now)()).toISOString(),
442
+ generatedBy: { model: out.model, promptVersion: CURRENT_PROMPT_VERSION, truncated },
443
+ triggeredBy: req.triggeredBy,
444
+ };
445
+ writeSummary(req.projectPath, req.relPath, payload);
446
+ // Keep the watcher's negative-cache a correct superset.
447
+ this.knownSummaries.get(req.projectId)?.add(req.relPath);
448
+ try {
449
+ (0, ai_invocations_1.recordInvocation)(this.deps.db, {
450
+ id: (0, crypto_1.randomUUID)(),
451
+ project_id: req.projectId,
452
+ provider: out.provider,
453
+ surface: 'file-summary',
454
+ surface_ref_id: req.jobId ?? null,
455
+ ticket_id: req.triggeredBy.ticketId,
456
+ status: 'success',
457
+ started_at: startedIso,
458
+ finished_at: new Date((this.deps.now ?? Date.now)()).toISOString(),
459
+ model: out.model,
460
+ total_cost_usd: out.costUsd,
461
+ tokens_in: out.tokensIn,
462
+ tokens_out: out.tokensOut,
463
+ tokens_cache_read: out.tokensCacheRead,
464
+ tokens_cache_create: out.tokensCacheCreate,
465
+ duration_ms: out.durationMs,
466
+ num_turns: 1,
467
+ total_cost_usd_estimated: !!out.costEstimated,
468
+ });
469
+ }
470
+ catch (err) {
471
+ // An ai_invocations write failure must never crash the summary queue,
472
+ // but it should not be silent either — a swallowed failure here means
473
+ // spending under-counts with no trace.
474
+ console.error('[file-summary] recordInvocation (success) failed:', err);
475
+ }
476
+ this.deps.broadcast(buildSummaryUpdated(req.projectId, payload, false));
477
+ this.deps.broadcast({ type: 'spending.invalidated', projectId: req.projectId });
478
+ entry.resolve('enqueued');
479
+ }
480
+ catch (err) {
481
+ // Disposed mid-flight (project removed) — DB is closed; skip all writes.
482
+ if (this._disposed) {
483
+ entry.resolve('failed');
484
+ return;
485
+ }
486
+ const reason = err instanceof Error ? err.message : String(err);
487
+ // A timeout/abort kills the child AFTER the provider may have billed tokens.
488
+ // The generator attaches whatever usage it captured so the failed row (and
489
+ // therefore the monthly-budget reader) accounts for that real spend instead
490
+ // of recording a misleading $0.
491
+ const partial = err.partial;
492
+ try {
493
+ (0, ai_invocations_1.recordInvocation)(this.deps.db, {
494
+ id: (0, crypto_1.randomUUID)(),
495
+ project_id: req.projectId,
496
+ provider: partial?.provider ?? this.deps.providerId?.() ?? 'claude',
497
+ surface: 'file-summary',
498
+ surface_ref_id: req.jobId ?? null,
499
+ ticket_id: req.triggeredBy.ticketId,
500
+ status: 'failed',
501
+ started_at: startedIso,
502
+ finished_at: new Date((this.deps.now ?? Date.now)()).toISOString(),
503
+ model: partial?.model,
504
+ total_cost_usd: partial?.costUsd ?? 0,
505
+ tokens_in: partial?.tokensIn ?? 0,
506
+ tokens_out: partial?.tokensOut ?? 0,
507
+ tokens_cache_read: partial?.tokensCacheRead,
508
+ tokens_cache_create: partial?.tokensCacheCreate,
509
+ duration_ms: partial?.durationMs ?? 0,
510
+ num_turns: 1,
511
+ total_cost_usd_estimated: !!partial?.costEstimated,
512
+ });
513
+ }
514
+ catch (recErr) {
515
+ // ai_invocations write failures must not crash the manager, but log so
516
+ // a persistent DB problem is visible.
517
+ console.error('[file-summary] recordInvocation (failure) failed:', recErr);
518
+ }
519
+ const failedMsg = {
520
+ type: 'file.summary_failed',
521
+ projectId: req.projectId,
522
+ path: req.relPath,
523
+ reason,
524
+ };
525
+ this.deps.broadcast(failedMsg);
526
+ // Resolve with 'failed' (not 'enqueued') so a caller awaiting enqueue()
527
+ // can distinguish a failed generation from a successful one.
528
+ entry.resolve('failed');
529
+ }
530
+ finally {
531
+ this.activeControllers.delete(controller);
532
+ }
533
+ }
534
+ markStale(projectPath, projectId, relPath) {
535
+ const existing = readSummary(projectPath, relPath);
536
+ if (!existing)
537
+ return;
538
+ this.deps.broadcast(buildSummaryUpdated(projectId, existing, true));
539
+ }
540
+ attachWatcher(projectId, projectPath) {
541
+ if (this.watchers.has(projectId))
542
+ return;
543
+ this.knownSummaries.set(projectId, this.scanKnownSummaries(projectPath));
544
+ // Reclaim summary JSON files whose source file was renamed/deleted since the
545
+ // last session. Runs once per project per session (attachWatcher is
546
+ // idempotent) and is capped at 200/pass inside sweepOrphans. The chokidar
547
+ // watcher only sees 'change', never 'unlink', so this is the only reaper.
548
+ try {
549
+ sweepOrphans(projectPath);
550
+ }
551
+ catch { /* best effort */ }
552
+ // CRITICAL: prune build/dep trees (node_modules, dist, target, src-tauri/target,
553
+ // dot-dirs, …) from the recursive watch. Watching a Rust/Tauri `target/` tree
554
+ // opened ~10k file descriptors and broke terminal spawning under fd pressure.
555
+ // The predicate is tested against each path relative to the project root so a
556
+ // dot-segment in the absolute prefix (the user's home dir) can't false-positive.
557
+ const watcher = chokidar_1.default.watch(projectPath, {
558
+ ignored: (p) => {
559
+ const rel = path.relative(projectPath, p);
560
+ if (!rel || rel.startsWith('..'))
561
+ return false; // the root itself — never ignore
562
+ return (0, build_dirs_1.isInBuildDir)(rel);
563
+ },
564
+ ignoreInitial: true,
565
+ persistent: true,
566
+ // Coalesce partial-write churn so one logical edit fires one event.
567
+ awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 100 },
568
+ });
569
+ watcher.on('change', (changed) => {
570
+ const rel = path.relative(projectPath, changed);
571
+ if (!rel || rel.startsWith('..'))
572
+ return;
573
+ // Skip the readSummary disk hit when this file provably has no summary.
574
+ const known = this.knownSummaries.get(projectId);
575
+ if (known && !known.has(rel))
576
+ return;
577
+ this.markStale(projectPath, projectId, rel);
578
+ });
579
+ this.watchers.set(projectId, { projectPath, watcher });
580
+ }
581
+ /** Read the relPaths of every summary on disk into a set (one-time at attach). */
582
+ scanKnownSummaries(projectPath) {
583
+ const set = new Set();
584
+ const dir = summariesDir(projectPath);
585
+ let files = [];
586
+ try {
587
+ files = fs.readdirSync(dir);
588
+ }
589
+ catch {
590
+ return set;
591
+ }
592
+ for (const f of files) {
593
+ if (!f.endsWith('.json'))
594
+ continue;
595
+ try {
596
+ const payload = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8'));
597
+ if (payload?.path)
598
+ set.add(payload.path);
599
+ }
600
+ catch { /* skip unreadable/partial files */ }
601
+ }
602
+ return set;
603
+ }
604
+ detachWatcher(projectId) {
605
+ const state = this.watchers.get(projectId);
606
+ if (!state)
607
+ return;
608
+ void state.watcher.close();
609
+ this.watchers.delete(projectId);
610
+ this.knownSummaries.delete(projectId);
611
+ }
612
+ /** Full teardown for a single manager: stop accepting work, abort any in-flight
613
+ * provider child (so it is not orphaned past project removal), reject queued
614
+ * entries, close watchers, and leave the shared app-wide registry. Call from
615
+ * ProjectRegistry.removeProject (before db.close) and from shutdown(). After
616
+ * this, runOne's `_disposed` guard skips all DB/disk writes. Idempotent. */
617
+ dispose() {
618
+ if (this._disposed)
619
+ return;
620
+ this._disposed = true;
621
+ DESKTOP.managers.delete(this);
622
+ // Abort in-flight generations → generator treeKills the provider child.
623
+ for (const c of this.activeControllers) {
624
+ try {
625
+ c.abort();
626
+ }
627
+ catch { /* best effort */ }
628
+ }
629
+ this.activeControllers.clear();
630
+ // Reject still-queued entries so awaiting callers settle (skipped, not hung).
631
+ for (const [, queue] of this.queues) {
632
+ for (const entry of queue) {
633
+ try {
634
+ this.emitSkipped(entry.req, 'not-found');
635
+ entry.resolve('skipped:not-found');
636
+ }
637
+ catch { /* best effort */ }
638
+ }
639
+ }
640
+ this.queues.clear();
641
+ this.inFlightByKey.clear();
642
+ for (const [, state] of this.watchers) {
643
+ try {
644
+ void state.watcher.close();
645
+ }
646
+ catch { /* best effort */ }
647
+ }
648
+ this.watchers.clear();
649
+ this.knownSummaries.clear();
650
+ }
651
+ /** Close every watcher. Called on graceful server shutdown so chokidar's
652
+ * underlying fsevents/inotify handles are released, never leaked. */
653
+ disposeAll() {
654
+ this.dispose();
655
+ }
656
+ async flush() {
657
+ // Drain until no pending work remains.
658
+ while (this.pending.size > 0 || this.hasQueued()) {
659
+ await Promise.allSettled(Array.from(this.pending));
660
+ }
661
+ }
662
+ hasQueued() {
663
+ for (const q of this.queues.values())
664
+ if (q.length > 0)
665
+ return true;
666
+ return false;
667
+ }
668
+ emitSkipped(req, reason) {
669
+ const msg = {
670
+ type: 'file.summary_skipped',
671
+ projectId: req.projectId,
672
+ path: req.relPath,
673
+ reason,
674
+ };
675
+ this.deps.broadcast(msg);
676
+ }
677
+ }
678
+ exports.FileSummaryManager = FileSummaryManager;
679
+ function buildSummaryUpdated(projectId, payload, stale) {
680
+ const msg = {
681
+ type: 'file.summary_updated',
682
+ projectId,
683
+ path: payload.path,
684
+ summaryAvailable: true,
685
+ stale,
686
+ generatedAt: payload.generatedAt,
687
+ };
688
+ return msg;
689
+ }