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,196 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.NO_SHELL_INTEGRATION = void 0;
7
+ exports.locateBundledShim = locateBundledShim;
8
+ exports.shimDirFor = shimDirFor;
9
+ exports.composeShellIntegrationSpawn = composeShellIntegrationSpawn;
10
+ exports.cleanupSessionShim = cleanupSessionShim;
11
+ exports.cleanupStaleShimDirs = cleanupStaleShimDirs;
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const os_1 = __importDefault(require("os"));
15
+ exports.NO_SHELL_INTEGRATION = {
16
+ args: [],
17
+ env: {},
18
+ shimDir: null,
19
+ shimPath: null,
20
+ };
21
+ const STALE_SHIM_DIR_AGE_MS = 24 * 60 * 60 * 1000;
22
+ /**
23
+ * Resolve the shell basename for our switch logic. We accept full paths or bare
24
+ * basenames (e.g. "/bin/zsh", "C:\\Program Files\\PowerShell\\7\\pwsh.exe").
25
+ */
26
+ function shellBasename(shell) {
27
+ // Use posix.basename after normalising backslashes so this works on Windows paths.
28
+ const normalized = shell.replace(/\\/g, '/');
29
+ return path_1.default.posix.basename(normalized).toLowerCase().replace(/\.exe$/, '');
30
+ }
31
+ /** Source of bundled shims; overridable for tests/packaging. */
32
+ function locateBundledShim(name) {
33
+ const execDir = path_1.default.dirname(process.execPath);
34
+ const candidates = [
35
+ path_1.default.resolve(__dirname, 'shell-integration', name),
36
+ // Desktop bundle: shims ship under binaries/shell-integration (declared in
37
+ // tauri.conf.json resources). Windows/Linux install vs macOS .app layout.
38
+ path_1.default.resolve(execDir, 'binaries', 'shell-integration', name),
39
+ path_1.default.resolve(execDir, '..', 'Resources', 'binaries', 'shell-integration', name),
40
+ // Legacy / sibling layouts (kept as existence-gated fallbacks).
41
+ path_1.default.resolve(execDir, 'shell-integration', name),
42
+ path_1.default.resolve(execDir, '..', 'shell-integration', name),
43
+ path_1.default.resolve(process.cwd(), 'server', 'shell-integration', name),
44
+ ];
45
+ for (const c of candidates) {
46
+ try {
47
+ if (fs_1.default.existsSync(c))
48
+ return c;
49
+ }
50
+ catch { /* ignore */ }
51
+ }
52
+ // Observability: a packaging regression that drops the shims would otherwise
53
+ // silently disable shell integration (no marks/cwd) with no clue why. Warn
54
+ // once per shim name so it's diagnosable from the server log.
55
+ warnShimMissingOnce(name);
56
+ return null;
57
+ }
58
+ const _warnedShims = new Set();
59
+ function warnShimMissingOnce(name) {
60
+ if (_warnedShims.has(name))
61
+ return;
62
+ _warnedShims.add(name);
63
+ console.warn(`[terminal-shell-integration] bundled shim not found: ${name} — shell integration disabled for this shell`);
64
+ }
65
+ function projectsRoot() {
66
+ return path_1.default.join(os_1.default.homedir(), '.specrails', 'projects');
67
+ }
68
+ function shimDirFor(projectSlug, sessionId) {
69
+ return path_1.default.join(projectsRoot(), projectSlug, 'terminals', sessionId);
70
+ }
71
+ function writeFile(target, content) {
72
+ fs_1.default.mkdirSync(path_1.default.dirname(target), { recursive: true });
73
+ fs_1.default.writeFileSync(target, content, { mode: 0o600 });
74
+ }
75
+ /**
76
+ * Compose the env+args needed to spawn the given shell with our shim active.
77
+ * When integration is disabled or the shell is unsupported, returns
78
+ * NO_SHELL_INTEGRATION so the caller's spawn proceeds unchanged.
79
+ */
80
+ function composeShellIntegrationSpawn(shell, sessionId, projectSlug, settings) {
81
+ if (!settings.shellIntegrationEnabled)
82
+ return exports.NO_SHELL_INTEGRATION;
83
+ const base = shellBasename(shell);
84
+ const shimDir = shimDirFor(projectSlug, sessionId);
85
+ if (base === 'zsh') {
86
+ const bundled = locateBundledShim('zsh-shim.zsh');
87
+ if (!bundled)
88
+ return exports.NO_SHELL_INTEGRATION;
89
+ const userZdotdirZshrc = path_1.default.join(shimDir, '.zshrc');
90
+ const shimContent = `# Specrails auto-generated zsh entry — do not edit\nsource '${bundled.replace(/'/g, `'\\''`)}'\n`;
91
+ writeFile(userZdotdirZshrc, shimContent);
92
+ return {
93
+ args: [],
94
+ // Pass the user's real ZDOTDIR (default $HOME) so the shim can still source
95
+ // their ~/.zshenv/.zprofile/.zshrc/.zlogin — the override alone would make
96
+ // zsh skip the login files and lose PATH/Homebrew/nvm setup.
97
+ env: { ZDOTDIR: shimDir, SPECRAILS_REAL_ZDOTDIR: process.env.ZDOTDIR ?? '' },
98
+ shimDir,
99
+ shimPath: userZdotdirZshrc,
100
+ };
101
+ }
102
+ if (base === 'bash') {
103
+ const bundled = locateBundledShim('bash-shim.bash');
104
+ if (!bundled)
105
+ return exports.NO_SHELL_INTEGRATION;
106
+ const shimPath = path_1.default.join(shimDir, 'shim.bash');
107
+ const shimContent = `# Specrails auto-generated bash rcfile — do not edit\nsource '${bundled.replace(/'/g, `'\\''`)}'\n`;
108
+ writeFile(shimPath, shimContent);
109
+ return {
110
+ args: ['--rcfile', shimPath],
111
+ env: {},
112
+ shimDir,
113
+ shimPath,
114
+ };
115
+ }
116
+ if (base === 'fish') {
117
+ const bundled = locateBundledShim('fish-shim.fish');
118
+ if (!bundled)
119
+ return exports.NO_SHELL_INTEGRATION;
120
+ // Use fish's -C/--init-command, which runs AFTER the user's config.fish and
121
+ // conf.d, so the user's real fish config is preserved. The previous approach
122
+ // repointed XDG_CONFIG_HOME at the shim dir, which made fish resolve its
123
+ // ENTIRE config tree (config.fish, functions, completions) from the shim dir
124
+ // and silently dropped the user's configuration.
125
+ return {
126
+ args: ['-C', `source '${bundled.replace(/'/g, `'\\''`)}'`],
127
+ env: {},
128
+ // No per-session dir is written; shimPath (non-null) activates OSC parsing.
129
+ shimDir: null,
130
+ shimPath: bundled,
131
+ };
132
+ }
133
+ if (base === 'powershell' || base === 'pwsh') {
134
+ const bundled = locateBundledShim('powershell-shim.ps1');
135
+ if (!bundled)
136
+ return exports.NO_SHELL_INTEGRATION;
137
+ const shimPath = path_1.default.join(shimDir, 'profile.ps1');
138
+ const shimContent = `# Specrails auto-generated PowerShell profile — do not edit\n. '${bundled.replace(/'/g, "''")}'\n`;
139
+ writeFile(shimPath, shimContent);
140
+ return {
141
+ args: ['-NoLogo', '-NoExit', '-File', shimPath],
142
+ env: {},
143
+ shimDir,
144
+ shimPath,
145
+ };
146
+ }
147
+ // Unsupported shell — degrade silently.
148
+ return exports.NO_SHELL_INTEGRATION;
149
+ }
150
+ function cleanupSessionShim(projectSlug, sessionId) {
151
+ const dir = shimDirFor(projectSlug, sessionId);
152
+ try {
153
+ fs_1.default.rmSync(dir, { recursive: true, force: true });
154
+ }
155
+ catch { /* best effort */ }
156
+ }
157
+ /**
158
+ * Sweep stale shim directories on startup. A directory is "stale" when its
159
+ * mtime is older than 24h. The volatile session registry is empty on cold
160
+ * start, so we cannot match against live sessions; the age cap prevents us
161
+ * from removing a directory that just spawned.
162
+ */
163
+ function cleanupStaleShimDirs(now = Date.now()) {
164
+ let removed = 0;
165
+ let projectsDir = [];
166
+ try {
167
+ projectsDir = fs_1.default.readdirSync(projectsRoot());
168
+ }
169
+ catch {
170
+ return 0;
171
+ }
172
+ for (const slug of projectsDir) {
173
+ const terminalsRoot = path_1.default.join(projectsRoot(), slug, 'terminals');
174
+ let entries = [];
175
+ try {
176
+ entries = fs_1.default.readdirSync(terminalsRoot);
177
+ }
178
+ catch {
179
+ continue;
180
+ }
181
+ for (const sid of entries) {
182
+ const dir = path_1.default.join(terminalsRoot, sid);
183
+ try {
184
+ const stat = fs_1.default.statSync(dir);
185
+ if (!stat.isDirectory())
186
+ continue;
187
+ if (now - stat.mtimeMs >= STALE_SHIM_DIR_AGE_MS) {
188
+ fs_1.default.rmSync(dir, { recursive: true, force: true });
189
+ removed++;
190
+ }
191
+ }
192
+ catch { /* ignore */ }
193
+ }
194
+ }
195
+ return removed;
196
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.broadcastTicketCreated = broadcastTicketCreated;
4
+ exports.broadcastTicketUpdated = broadcastTicketUpdated;
5
+ exports.broadcastTicketDeleted = broadcastTicketDeleted;
6
+ /**
7
+ * Broadcast a ticket_created event and suppress the file-watcher echo.
8
+ * Call this from POST /:projectId/tickets after writing the JSON file.
9
+ */
10
+ function broadcastTicketCreated(ctx, ticket, newRevision) {
11
+ ctx.ticketWatcher.notifyDesktopWrite(newRevision);
12
+ const msg = {
13
+ type: 'ticket_created',
14
+ projectId: ctx.project.id,
15
+ ticket,
16
+ timestamp: new Date().toISOString(),
17
+ };
18
+ ctx.broadcast(msg);
19
+ }
20
+ /**
21
+ * Broadcast a ticket_updated event and suppress the file-watcher echo.
22
+ * Call this from PATCH /:projectId/tickets/:id after writing the JSON file.
23
+ */
24
+ function broadcastTicketUpdated(ctx, ticket, newRevision) {
25
+ ctx.ticketWatcher.notifyDesktopWrite(newRevision);
26
+ const msg = {
27
+ type: 'ticket_updated',
28
+ projectId: ctx.project.id,
29
+ ticket,
30
+ timestamp: new Date().toISOString(),
31
+ };
32
+ ctx.broadcast(msg);
33
+ }
34
+ /**
35
+ * Broadcast a ticket_deleted event and suppress the file-watcher echo.
36
+ * Call this from DELETE /:projectId/tickets/:id after writing the JSON file.
37
+ */
38
+ function broadcastTicketDeleted(ctx, ticketId, newRevision) {
39
+ ctx.ticketWatcher.notifyDesktopWrite(newRevision);
40
+ const msg = {
41
+ type: 'ticket_deleted',
42
+ projectId: ctx.project.id,
43
+ ticketId,
44
+ timestamp: new Date().toISOString(),
45
+ };
46
+ ctx.broadcast(msg);
47
+ }
@@ -0,0 +1,397 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SHORT_SUMMARY_MAX_LEN = exports.CURRENT_SCHEMA_VERSION = void 0;
7
+ exports.clampShortSummary = clampShortSummary;
8
+ exports.resolveTicketStoragePath = resolveTicketStoragePath;
9
+ exports.validateEpicChildIntegrity = validateEpicChildIntegrity;
10
+ exports.readStore = readStore;
11
+ exports.applyJobOutcomeToTickets = applyJobOutcomeToTickets;
12
+ exports.withLock = withLock;
13
+ exports.mutateStore = mutateStore;
14
+ exports.extractTicketIdsFromCommand = extractTicketIdsFromCommand;
15
+ exports.resolveTicketsFromCommand = resolveTicketsFromCommand;
16
+ exports.filterTickets = filterTickets;
17
+ exports.isValidStatus = isValidStatus;
18
+ exports.isValidPriority = isValidPriority;
19
+ exports.validatePriorityForStatus = validatePriorityForStatus;
20
+ const fs_1 = __importDefault(require("fs"));
21
+ const path_1 = __importDefault(require("path"));
22
+ const VALID_STATUSES = new Set(['draft', 'todo', 'in_progress', 'done', 'cancelled']);
23
+ const VALID_PRIORITIES = new Set(['critical', 'high', 'medium', 'low']);
24
+ exports.CURRENT_SCHEMA_VERSION = '1.3';
25
+ exports.SHORT_SUMMARY_MAX_LEN = 240;
26
+ /**
27
+ * Sanitize a `shortSummary` value coming from an AI response or external input.
28
+ * - Returns `null` for nullish, non-string, or empty-after-trim values.
29
+ * - Strips control characters, collapses whitespace, trims.
30
+ * - Hard-caps to `SHORT_SUMMARY_MAX_LEN` characters (server-side safety net).
31
+ */
32
+ function clampShortSummary(raw) {
33
+ if (raw === null || raw === undefined)
34
+ return null;
35
+ if (typeof raw !== 'string')
36
+ return null;
37
+ // Strip ASCII control chars (except common whitespace) and collapse runs.
38
+ // eslint-disable-next-line no-control-regex
39
+ const cleaned = raw.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '').replace(/\s+/g, ' ').trim();
40
+ if (cleaned.length === 0)
41
+ return null;
42
+ if (cleaned.length > exports.SHORT_SUMMARY_MAX_LEN) {
43
+ return cleaned.slice(0, exports.SHORT_SUMMARY_MAX_LEN);
44
+ }
45
+ return cleaned;
46
+ }
47
+ const DEFAULT_STORAGE_PATH = '.specrails/local-tickets.json';
48
+ const LOCK_SUFFIX = '.lock';
49
+ const LOCK_STALE_MS = 10_000; // 10 seconds
50
+ // ─── Path resolution ─────────────────────────────────────────────────────────
51
+ /** True when `candidate` resolves to a path inside (or equal to) `root`. */
52
+ function isContainedIn(root, candidate) {
53
+ const normalizedRoot = path_1.default.resolve(root);
54
+ const rel = path_1.default.relative(normalizedRoot, candidate);
55
+ // Inside the root: relative path is non-empty, does not climb out with '..',
56
+ // and is not absolute (which path.relative returns when on a different drive).
57
+ return rel !== '' && !rel.startsWith('..') && !path_1.default.isAbsolute(rel);
58
+ }
59
+ function resolveTicketStoragePath(projectPath) {
60
+ const fallback = path_1.default.resolve(projectPath, DEFAULT_STORAGE_PATH);
61
+ // Try to read ticketProvider.storagePath from integration-contract.json
62
+ const contractPath = path_1.default.join(projectPath, '.claude', 'integration-contract.json');
63
+ if (fs_1.default.existsSync(contractPath)) {
64
+ try {
65
+ const contract = JSON.parse(fs_1.default.readFileSync(contractPath, 'utf-8'));
66
+ if (contract.ticketProvider?.storagePath) {
67
+ const resolved = path_1.default.resolve(projectPath, contract.ticketProvider.storagePath);
68
+ // A2: integration-contract.json is read FROM THE PROJECT REPO and is
69
+ // therefore untrusted (a hostile repo added as a project). path.resolve
70
+ // lets an absolute or '../../..'-escaping storagePath redirect the ticket
71
+ // store to ANY file on disk — and every ticket mutation then overwrites
72
+ // that file via writeStore(). Reject anything outside the project root
73
+ // and fall back to the default location.
74
+ if (isContainedIn(projectPath, resolved)) {
75
+ return resolved;
76
+ }
77
+ console.warn(`[ticket-store] ignoring out-of-project storagePath from integration-contract.json: ${contract.ticketProvider.storagePath}`);
78
+ }
79
+ }
80
+ catch {
81
+ // Fall through to default
82
+ }
83
+ }
84
+ return fallback;
85
+ }
86
+ // ─── Advisory file locking ───────────────────────────────────────────────────
87
+ function acquireLock(filePath) {
88
+ const lockPath = filePath + LOCK_SUFFIX;
89
+ const maxAttempts = 50;
90
+ const retryDelay = 50; // ms
91
+ // Ensure parent directory exists before attempting lock
92
+ const dir = path_1.default.dirname(lockPath);
93
+ if (!fs_1.default.existsSync(dir)) {
94
+ fs_1.default.mkdirSync(dir, { recursive: true });
95
+ }
96
+ for (let i = 0; i < maxAttempts; i++) {
97
+ try {
98
+ // O_EXCL ensures atomic create-if-not-exists
99
+ const fd = fs_1.default.openSync(lockPath, fs_1.default.constants.O_CREAT | fs_1.default.constants.O_EXCL | fs_1.default.constants.O_WRONLY);
100
+ fs_1.default.writeSync(fd, String(process.pid));
101
+ fs_1.default.closeSync(fd);
102
+ return;
103
+ }
104
+ catch (err) {
105
+ if (err.code === 'EEXIST') {
106
+ // Check for stale lock
107
+ try {
108
+ const stat = fs_1.default.statSync(lockPath);
109
+ if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
110
+ fs_1.default.unlinkSync(lockPath);
111
+ continue;
112
+ }
113
+ }
114
+ catch {
115
+ // Lock file disappeared, retry
116
+ continue;
117
+ }
118
+ // Wait and retry
119
+ const waitUntil = Date.now() + retryDelay;
120
+ while (Date.now() < waitUntil) { /* busy wait for short duration */ }
121
+ continue;
122
+ }
123
+ throw err;
124
+ }
125
+ }
126
+ throw new Error('Could not acquire lock on ticket store');
127
+ }
128
+ function releaseLock(filePath) {
129
+ const lockPath = filePath + LOCK_SUFFIX;
130
+ try {
131
+ fs_1.default.unlinkSync(lockPath);
132
+ }
133
+ catch {
134
+ // Lock already released or missing
135
+ }
136
+ }
137
+ // ─── Store operations ────────────────────────────────────────────────────────
138
+ function emptyStore() {
139
+ return {
140
+ schema_version: exports.CURRENT_SCHEMA_VERSION,
141
+ revision: 0,
142
+ last_updated: new Date().toISOString(),
143
+ next_id: 1,
144
+ tickets: {},
145
+ };
146
+ }
147
+ /**
148
+ * Normalise a ticket loaded from disk so older stores (schema_version < 1.1)
149
+ * surface the new fields with sensible defaults. Mutates the input for speed
150
+ * and returns it.
151
+ */
152
+ function normalizeTicket(t) {
153
+ if (!('origin_conversation_id' in t) || t.origin_conversation_id === undefined) {
154
+ t.origin_conversation_id = null;
155
+ }
156
+ if (t.priority === undefined) {
157
+ // Older stores guaranteed a non-null priority; treat undefined defensively
158
+ // as null only when status is 'draft', otherwise keep undefined → caller
159
+ // sees it as TicketPriority|null which it must handle.
160
+ t.priority = null;
161
+ }
162
+ // Schema 1.2 fields (specs-smash).
163
+ if (!('is_epic' in t) || t.is_epic === undefined) {
164
+ t.is_epic = false;
165
+ }
166
+ if (!('parent_epic_id' in t) || t.parent_epic_id === undefined) {
167
+ t.parent_epic_id = null;
168
+ }
169
+ if (!('execution_order' in t) || t.execution_order === undefined) {
170
+ t.execution_order = null;
171
+ }
172
+ // Schema 1.3 field: AI-generated short summary for postit dashboard view.
173
+ if (!('short_summary' in t) || t.short_summary === undefined) {
174
+ t.short_summary = null;
175
+ }
176
+ return t;
177
+ }
178
+ /**
179
+ * Defensive integrity check used after épica/child mutations: every ticket
180
+ * with parent_epic_id must reference an existing ticket whose is_epic === true.
181
+ * Returns an array of violation messages (empty when the store is consistent).
182
+ */
183
+ function validateEpicChildIntegrity(store) {
184
+ const errors = [];
185
+ for (const id of Object.keys(store.tickets)) {
186
+ const t = store.tickets[id];
187
+ if (t.parent_epic_id === null || t.parent_epic_id === undefined)
188
+ continue;
189
+ const parent = store.tickets[String(t.parent_epic_id)];
190
+ if (!parent) {
191
+ errors.push(`ticket ${t.id} references missing parent_epic_id=${t.parent_epic_id}`);
192
+ continue;
193
+ }
194
+ if (!parent.is_epic) {
195
+ errors.push(`ticket ${t.id} parent ${t.parent_epic_id} is not an epic`);
196
+ }
197
+ }
198
+ return errors;
199
+ }
200
+ function readStore(filePath) {
201
+ if (!fs_1.default.existsSync(filePath)) {
202
+ return emptyStore();
203
+ }
204
+ try {
205
+ const raw = fs_1.default.readFileSync(filePath, 'utf-8');
206
+ const data = JSON.parse(raw);
207
+ // Basic validation
208
+ if (!data.tickets || typeof data.revision !== 'number') {
209
+ return emptyStore();
210
+ }
211
+ // Normalise per-ticket fields added in schema 1.1 without rewriting the
212
+ // file — version bump only happens on next write via writeStore. Guard each
213
+ // entry so a single corrupt/non-object value (hand-edit, partial-write
214
+ // recovery, schema drift) drops only that ticket instead of discarding the
215
+ // ENTIRE store (which the next mutation would then persist permanently).
216
+ for (const id of Object.keys(data.tickets)) {
217
+ const entry = data.tickets[id];
218
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
219
+ delete data.tickets[id];
220
+ continue;
221
+ }
222
+ try {
223
+ data.tickets[id] = normalizeTicket(entry);
224
+ }
225
+ catch {
226
+ delete data.tickets[id];
227
+ }
228
+ }
229
+ return data;
230
+ }
231
+ catch {
232
+ return emptyStore();
233
+ }
234
+ }
235
+ function writeStore(filePath, store) {
236
+ store.last_updated = new Date().toISOString();
237
+ store.revision++;
238
+ // Bump schema_version on first write under the new code so consumers can
239
+ // detect the new shape. Existing 1.0 stores read fine; we only upgrade once
240
+ // we've actually persisted something (which means normalize ran).
241
+ store.schema_version = exports.CURRENT_SCHEMA_VERSION;
242
+ const dir = path_1.default.dirname(filePath);
243
+ if (!fs_1.default.existsSync(dir)) {
244
+ fs_1.default.mkdirSync(dir, { recursive: true });
245
+ }
246
+ // Atomic write: serialise to a sibling temp file then rename over the target.
247
+ // A crash mid-write can only leave the (ignored) temp file truncated — the
248
+ // real store is replaced in one atomic rename, never left half-written. Always
249
+ // runs under the advisory lock (mutateStore/withLock), so the fixed temp name
250
+ // cannot collide with a concurrent writer in this process.
251
+ const tmp = filePath + '.tmp';
252
+ fs_1.default.writeFileSync(tmp, JSON.stringify(store, null, 2), 'utf-8');
253
+ fs_1.default.renameSync(tmp, filePath);
254
+ }
255
+ /**
256
+ * Apply a finished job's outcome to the referenced tickets, in place, and return
257
+ * the ids that actually changed (so the caller can broadcast just those).
258
+ *
259
+ * - `completed`: promote `todo`/`in_progress` → `done` (never resurrect a `draft`
260
+ * or a `cancelled` spec into Done); clear any stale `needs_review` flag.
261
+ * - `failed`/`canceled`/`zombie_terminated`: revert an `in_progress` spec → `todo`
262
+ * (back to the Specs column). If the agent had already marked it `done` (its
263
+ * Ship phase ran, then the process died), keep it `done` but set `needs_review`
264
+ * so the board flags it for review.
265
+ */
266
+ function applyJobOutcomeToTickets(store, ticketIds, outcome, now) {
267
+ const changed = [];
268
+ for (const tid of ticketIds) {
269
+ const ticket = store.tickets[String(tid)];
270
+ if (!ticket)
271
+ continue;
272
+ if (outcome === 'completed') {
273
+ const promotable = ticket.status === 'todo' || ticket.status === 'in_progress';
274
+ const clearWarning = ticket.needs_review === true;
275
+ if (!promotable && !clearWarning)
276
+ continue;
277
+ if (promotable)
278
+ ticket.status = 'done';
279
+ if (clearWarning)
280
+ delete ticket.needs_review;
281
+ ticket.updated_at = now;
282
+ changed.push(tid);
283
+ }
284
+ else if (ticket.status === 'in_progress') {
285
+ ticket.status = 'todo';
286
+ ticket.updated_at = now;
287
+ changed.push(tid);
288
+ }
289
+ else if (ticket.status === 'done' && ticket.needs_review !== true) {
290
+ ticket.needs_review = true;
291
+ ticket.updated_at = now;
292
+ changed.push(tid);
293
+ }
294
+ }
295
+ return changed;
296
+ }
297
+ /** Execute a read-modify-write cycle with advisory locking */
298
+ function withLock(filePath, fn) {
299
+ acquireLock(filePath);
300
+ try {
301
+ return fn(readStore(filePath));
302
+ }
303
+ finally {
304
+ releaseLock(filePath);
305
+ }
306
+ }
307
+ /** Execute a read-modify-write cycle, writing changes back */
308
+ function mutateStore(filePath, fn) {
309
+ acquireLock(filePath);
310
+ try {
311
+ const store = readStore(filePath);
312
+ fn(store);
313
+ writeStore(filePath, store);
314
+ return store;
315
+ }
316
+ finally {
317
+ releaseLock(filePath);
318
+ }
319
+ }
320
+ // ─── Query helpers ───────────────────────────────────────────────────────────
321
+ /**
322
+ * Extract unique ticket ids referenced via `#<digits>` tokens in a command
323
+ * string, preserving first-occurrence order.
324
+ */
325
+ function extractTicketIdsFromCommand(command) {
326
+ const ids = [];
327
+ const seen = new Set();
328
+ for (const match of command.matchAll(/#(\d+)/g)) {
329
+ const id = Number.parseInt(match[1], 10);
330
+ if (Number.isNaN(id) || seen.has(id))
331
+ continue;
332
+ seen.add(id);
333
+ ids.push(id);
334
+ }
335
+ return ids;
336
+ }
337
+ /**
338
+ * Resolve `#<digits>` ticket references in a command to `{ id, title }` pairs
339
+ * by reading the project's local ticket store. Tickets that no longer exist
340
+ * resolve to `title: null`. Returns `[]` when the command has no ticket
341
+ * references.
342
+ */
343
+ function resolveTicketsFromCommand(projectPath, command) {
344
+ const ids = extractTicketIdsFromCommand(command);
345
+ if (ids.length === 0)
346
+ return [];
347
+ const store = readStore(resolveTicketStoragePath(projectPath));
348
+ return ids.map((id) => ({
349
+ id,
350
+ title: store.tickets[String(id)]?.title ?? null,
351
+ }));
352
+ }
353
+ function filterTickets(tickets, filters) {
354
+ let result = tickets;
355
+ if (filters.status) {
356
+ const statuses = filters.status.split(',').map(s => s.trim());
357
+ result = result.filter(t => statuses.includes(t.status));
358
+ }
359
+ if (filters.label) {
360
+ const labels = filters.label.split(',').map(l => l.trim().toLowerCase());
361
+ result = result.filter(t => t.labels.some(tl => labels.includes(tl.toLowerCase())));
362
+ }
363
+ if (filters.q) {
364
+ const query = filters.q.toLowerCase();
365
+ result = result.filter(t => t.title.toLowerCase().includes(query) ||
366
+ t.description.toLowerCase().includes(query));
367
+ }
368
+ return result;
369
+ }
370
+ // ─── Validation helpers ──────────────────────────────────────────────────────
371
+ function isValidStatus(s) {
372
+ return typeof s === 'string' && VALID_STATUSES.has(s);
373
+ }
374
+ function isValidPriority(p) {
375
+ return typeof p === 'string' && VALID_PRIORITIES.has(p);
376
+ }
377
+ /**
378
+ * Single source of truth for the rule: priority MAY be null only while the
379
+ * ticket has status='draft'. Returns an error string (suitable for HTTP 400
380
+ * responses) when the combination is invalid, or `null` when valid.
381
+ */
382
+ function validatePriorityForStatus(status, priority) {
383
+ if (status === 'draft') {
384
+ // null is allowed; non-null must still be a valid priority value.
385
+ if (priority !== null && !isValidPriority(priority)) {
386
+ return 'invalid priority value';
387
+ }
388
+ return null;
389
+ }
390
+ if (priority === null) {
391
+ return `priority is required when status='${status}'`;
392
+ }
393
+ if (!isValidPriority(priority)) {
394
+ return 'invalid priority value';
395
+ }
396
+ return null;
397
+ }