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,1165 @@
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.SetupManager = exports.EMPTY_SUMMARY = exports.QUICK_CHECKPOINTS = exports.CHECKPOINTS = void 0;
7
+ exports.detectCheckpointFromText = detectCheckpointFromText;
8
+ exports.validateInstalledCore = validateInstalledCore;
9
+ exports.computeSummary = computeSummary;
10
+ exports.sweepLegacySrCommands = sweepLegacySrCommands;
11
+ const child_process_1 = require("child_process");
12
+ const readline_1 = require("readline");
13
+ const fs_1 = require("fs");
14
+ const path_1 = require("path");
15
+ const os_1 = require("os");
16
+ const tree_kill_1 = __importDefault(require("tree-kill"));
17
+ const core_compat_1 = require("./core-compat");
18
+ const cli_prompt_1 = require("./util/cli-prompt");
19
+ const win_spawn_1 = require("./util/win-spawn");
20
+ const setup_prerequisites_1 = require("./setup-prerequisites");
21
+ const core_package_1 = require("./core-package");
22
+ const providers_1 = require("./providers");
23
+ /**
24
+ * specrails-core's installer (Node-native from v4.2.0 onward, bash
25
+ * prior) always scaffolds into `.claude/` regardless of which AI
26
+ * CLI the project uses. The provider choice affects which binary
27
+ * runs (claude vs codex), not where the framework files live.
28
+ */
29
+ const SPECRAILS_DIR = '.claude';
30
+ // ─── specrails-core binary resolution ────────────────────────────────────────
31
+ // Default: npx CORE_PACKAGE_SPEC (major-pinned range, see core-package.ts)
32
+ // Override: set SPECRAILS_CORE_BIN to use a local/linked version, e.g.
33
+ // SPECRAILS_CORE_BIN=specrails-core npm run dev
34
+ const WHICH_CMD = process.platform === 'win32' ? 'where' : 'which';
35
+ function resolveCoreBinary(bin) {
36
+ if ((0, path_1.isAbsolute)(bin))
37
+ return bin;
38
+ if (bin.includes('/') || bin.includes('\\'))
39
+ return (0, path_1.resolve)(bin);
40
+ const result = (0, child_process_1.spawnSync)(WHICH_CMD, [bin], {
41
+ env: process.env,
42
+ shell: process.platform === 'win32',
43
+ encoding: 'utf-8',
44
+ timeout: 5_000,
45
+ });
46
+ if (result.error || (result.status ?? 1) !== 0)
47
+ return bin;
48
+ const first = `${result.stdout ?? ''}`.trim().split(/\r?\n/)[0]?.trim();
49
+ return first && first.length > 0 ? first : bin;
50
+ }
51
+ function getCoreCommand() {
52
+ const override = process.env.SPECRAILS_CORE_BIN;
53
+ if (override) {
54
+ return { bin: resolveCoreBinary(override), pkg: '' };
55
+ }
56
+ return { bin: 'npx', pkg: core_package_1.CORE_PACKAGE_SPEC };
57
+ }
58
+ function buildCoreArgs(args) {
59
+ const { bin, pkg } = getCoreCommand();
60
+ const fullArgs = pkg ? ['--yes', '--prefer-online', pkg, ...args] : args;
61
+ return { bin, fullArgs };
62
+ }
63
+ function spawnCoreInit(args, cwd) {
64
+ const { bin, fullArgs } = buildCoreArgs(['init', ...args]);
65
+ console.log(`[SetupManager] spawning core: ${bin} ${fullArgs.join(' ')} (cwd=${cwd}) (SPECRAILS_CORE_BIN=${process.env.SPECRAILS_CORE_BIN ?? '<unset>'})`);
66
+ // M15: use the cross-spawn wrapper instead of `spawn(..., { shell: win32 })`.
67
+ // With shell:true, Node concatenates the argv into one cmd.exe command line and
68
+ // does NOT quote individual args, so `--from-config C:\Users\John Doe\...yaml`
69
+ // (and `--root-dir <path with spaces>`) split on the space and break install on
70
+ // any Windows account/project path containing a space. cross-spawn resolves the
71
+ // .cmd shim AND quotes each arg, so spaces (and newlines) survive intact.
72
+ return (0, win_spawn_1.spawnCli)(bin, fullArgs, {
73
+ cwd,
74
+ env: process.env,
75
+ stdio: ['ignore', 'pipe', 'pipe'],
76
+ });
77
+ }
78
+ // H19: hard cap on the runtime probe. `npx --yes --prefer-online
79
+ // <CORE_PACKAGE_SPEC> version` does a network round-trip to the npm
80
+ // registry, and spawnSync blocks the single event loop — without a timeout a
81
+ // hung network froze the WHOLE app indefinitely during Add Project. On
82
+ // timeout the probe degrades to ok:false, which the install paths surface as
83
+ // a setup_error instead of hanging.
84
+ const CORE_PROBE_TIMEOUT_MS = 60_000;
85
+ function probeCoreRuntimeVersion(cwd) {
86
+ const { bin, fullArgs } = buildCoreArgs(['version']);
87
+ const result = (0, child_process_1.spawnSync)(bin, fullArgs, {
88
+ cwd,
89
+ env: process.env,
90
+ shell: process.platform === 'win32',
91
+ encoding: 'utf-8',
92
+ timeout: CORE_PROBE_TIMEOUT_MS,
93
+ });
94
+ if (result.error) {
95
+ const timedOut = result.error.code === 'ETIMEDOUT';
96
+ return {
97
+ ok: false,
98
+ bin,
99
+ error: timedOut
100
+ ? `probe timed out after ${CORE_PROBE_TIMEOUT_MS / 1000}s — npm registry unreachable?`
101
+ : result.error.message,
102
+ };
103
+ }
104
+ if ((result.status ?? 1) !== 0) {
105
+ const stderr = typeof result.stderr === 'string' ? result.stderr.trim() : '';
106
+ const stdout = typeof result.stdout === 'string' ? result.stdout.trim() : '';
107
+ return { ok: false, bin, error: stderr || stdout || `exit code ${result.status ?? 'unknown'}` };
108
+ }
109
+ const output = `${result.stdout ?? ''}`.trim();
110
+ const match = output.match(/\d+\.\d+\.\d+/);
111
+ if (!match) {
112
+ return { ok: false, bin, error: `could not parse version from output: ${output}` };
113
+ }
114
+ return { ok: true, bin, version: match[0] };
115
+ }
116
+ // ─── YAML helpers ─────────────────────────────────────────────────────────────
117
+ function writeSpawnInstallConfig(projectId, yamlText) {
118
+ const tmpDir = (0, os_1.tmpdir)();
119
+ const tempPath = (0, path_1.join)(tmpDir, `specrails-desktop-install-config-${projectId}-${Date.now()}.yaml`);
120
+ (0, fs_1.writeFileSync)(tempPath, yamlText, 'utf-8');
121
+ return tempPath;
122
+ }
123
+ function readInstallConfig(projectPath) {
124
+ const configPath = (0, path_1.join)(projectPath, '.specrails', 'install-config.yaml');
125
+ try {
126
+ const text = (0, fs_1.readFileSync)(configPath, 'utf-8');
127
+ const tierMatch = text.match(/^tier:\s*(\w+)/m);
128
+ const tier = (tierMatch?.[1] === 'quick' ? 'quick' : 'full');
129
+ let selectedAgents = [];
130
+ // Inline format: selected: [a, b, c]
131
+ const inlineMatch = text.match(/selected:\s*\[([^\]]*)\]/);
132
+ if (inlineMatch) {
133
+ selectedAgents = inlineMatch[1].split(',').map((s) => s.trim()).filter(Boolean);
134
+ }
135
+ else {
136
+ // Multi-line format: selected:\n - a\n - b
137
+ const multilineMatch = text.match(/selected:\s*\n((?:\s+-\s+\S+\n?)+)/);
138
+ if (multilineMatch) {
139
+ selectedAgents = multilineMatch[1].match(/- (\S+)/g)?.map((m) => m.replace('- ', '')) ?? [];
140
+ }
141
+ }
142
+ return { tier, selectedAgents };
143
+ }
144
+ catch {
145
+ return null;
146
+ }
147
+ }
148
+ // ─── Template deployment (post-install) ──────────────────────────────────────
149
+ function deployTemplates(projectPath, selectedAgents) {
150
+ const templatesDir = (0, path_1.join)(projectPath, '.specrails', 'setup-templates');
151
+ const targetDir = (0, path_1.join)(projectPath, SPECRAILS_DIR);
152
+ let agents = 0, commands = 0, personas = 0;
153
+ // Deploy selected agent templates
154
+ const agentTemplatesDir = (0, path_1.join)(templatesDir, 'agents');
155
+ const agentTargetDir = (0, path_1.join)(targetDir, 'agents');
156
+ if ((0, fs_1.existsSync)(agentTemplatesDir)) {
157
+ (0, fs_1.mkdirSync)(agentTargetDir, { recursive: true });
158
+ for (const file of (0, fs_1.readdirSync)(agentTemplatesDir)) {
159
+ if (!file.endsWith('.md'))
160
+ continue;
161
+ const agentId = file.replace(/\.md$/, '');
162
+ if (selectedAgents.length > 0 && !selectedAgents.includes(agentId))
163
+ continue;
164
+ (0, fs_1.copyFileSync)((0, path_1.join)(agentTemplatesDir, file), (0, path_1.join)(agentTargetDir, file));
165
+ agents++;
166
+ }
167
+ }
168
+ // Deploy persona templates
169
+ const personaTemplatesDir = (0, path_1.join)(templatesDir, 'personas');
170
+ const personaTargetDir = (0, path_1.join)(agentTargetDir, 'personas');
171
+ if ((0, fs_1.existsSync)(personaTemplatesDir)) {
172
+ (0, fs_1.mkdirSync)(personaTargetDir, { recursive: true });
173
+ for (const file of (0, fs_1.readdirSync)(personaTemplatesDir)) {
174
+ if (!file.endsWith('.md'))
175
+ continue;
176
+ (0, fs_1.copyFileSync)((0, path_1.join)(personaTemplatesDir, file), (0, path_1.join)(personaTargetDir, file));
177
+ personas++;
178
+ }
179
+ }
180
+ // Deploy command templates
181
+ const cmdTemplatesDir = (0, path_1.join)(templatesDir, 'commands', 'specrails');
182
+ const cmdTargetDir = (0, path_1.join)(targetDir, 'commands', 'specrails');
183
+ if ((0, fs_1.existsSync)(cmdTemplatesDir)) {
184
+ (0, fs_1.mkdirSync)(cmdTargetDir, { recursive: true });
185
+ for (const file of (0, fs_1.readdirSync)(cmdTemplatesDir)) {
186
+ if (!file.endsWith('.md'))
187
+ continue;
188
+ (0, fs_1.copyFileSync)((0, path_1.join)(cmdTemplatesDir, file), (0, path_1.join)(cmdTargetDir, file));
189
+ commands++;
190
+ }
191
+ }
192
+ return { agents, commands, personas };
193
+ }
194
+ // Full install: 7-phase enrichment flow (claude /specrails:enrich)
195
+ exports.CHECKPOINTS = [
196
+ { key: 'base_install', name: 'Base installation' },
197
+ { key: 'repo_analysis', name: 'Repository analysis' },
198
+ { key: 'stack_conventions', name: 'Stack & conventions' },
199
+ { key: 'product_discovery', name: 'Product discovery' },
200
+ { key: 'agent_generation', name: 'Agent generation' },
201
+ { key: 'command_config', name: 'Command configuration' },
202
+ { key: 'final_verification', name: 'Final verification' },
203
+ ];
204
+ // Quick install: 3-phase non-interactive flow (npx init --from-config)
205
+ exports.QUICK_CHECKPOINTS = [
206
+ { key: 'config_written', name: 'Config written' },
207
+ { key: 'base_install', name: 'Base installation' },
208
+ { key: 'quick_complete', name: 'Quick install complete' },
209
+ ];
210
+ function checkFilesystem(projectPath) {
211
+ const dir = SPECRAILS_DIR;
212
+ const hasBaseInstall = (0, fs_1.existsSync)((0, path_1.join)(projectPath, '.specrails', 'specrails-version')) ||
213
+ (0, fs_1.existsSync)((0, path_1.join)(projectPath, '.specrails-version'));
214
+ const hasSetupTemplates = (0, fs_1.existsSync)((0, path_1.join)(projectPath, '.specrails', 'setup-templates')) ||
215
+ (0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'setup-templates'));
216
+ const hasRules = (0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'rules')) &&
217
+ hasFiles((0, path_1.join)(projectPath, dir, 'rules'), /\.md$/);
218
+ const hasPersonas = (0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'agents', 'personas')) &&
219
+ hasFiles((0, path_1.join)(projectPath, dir, 'agents', 'personas'), /\.md$/);
220
+ const hasAgents = (0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'agents')) &&
221
+ hasFiles((0, path_1.join)(projectPath, dir, 'agents'), /^sr-.*\.md$/);
222
+ const hasCommands = (((0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'commands', 'sr')) && hasFiles((0, path_1.join)(projectPath, dir, 'commands', 'sr'), /\.md$/)) ||
223
+ ((0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'commands', 'specrails')) && hasFiles((0, path_1.join)(projectPath, dir, 'commands', 'specrails'), /\.md$/)));
224
+ const hasCLAUDE = (0, fs_1.existsSync)((0, path_1.join)(projectPath, 'CLAUDE.md'));
225
+ return {
226
+ base_install: hasBaseInstall,
227
+ // repo_analysis: detected when setup templates exist and CLAUDE.md is written
228
+ // (Claude writes CLAUDE.md after analyzing the repo)
229
+ repo_analysis: hasBaseInstall && (hasCLAUDE || hasSetupTemplates),
230
+ // stack_conventions: detected when rules files are generated
231
+ stack_conventions: hasRules,
232
+ product_discovery: hasPersonas,
233
+ agent_generation: hasAgents,
234
+ command_config: hasCommands,
235
+ // Final verification: agents + commands must exist (manifest from install.sh is unreliable —
236
+ // it's created during scaffolding before /setup generates the actual artifacts)
237
+ final_verification: hasAgents && hasCommands,
238
+ };
239
+ }
240
+ function hasFiles(dir, pattern) {
241
+ try {
242
+ return (0, fs_1.readdirSync)(dir).some((f) => pattern.test(f));
243
+ }
244
+ catch {
245
+ return false;
246
+ }
247
+ }
248
+ // ─── Enrich.md content resolver (shared by start + resume enrich paths) ─────
249
+ /**
250
+ * Reads the enrich command's body from the project's specrails dir, falling
251
+ * back across the three known locations:
252
+ * 1. `.claude/commands/sr/enrich.md` (modern; written by core ≥ 4.2)
253
+ * 2. `.claude/commands/specrails/enrich.md` (legacy; written by core 4.1.x)
254
+ * 3. `.claude/commands/setup.md` (very legacy; before enrich rename)
255
+ *
256
+ * For codex projects the .codex/skills/<name>/SKILL.md layout is read by the
257
+ * codex CLI directly when the slash command is forwarded — when the legacy
258
+ * codex flow needs the literal content (synthetic-session resume), the same
259
+ * .claude/ paths are consulted because specrails-core scaffolds both trees.
260
+ *
261
+ * Returns an empty string when no file is found; callers fall back to passing
262
+ * the literal slash command and let the CLI surface the missing-command error.
263
+ */
264
+ function readEnrichMdContent(projectPath) {
265
+ const enrichMdPathSr = (0, path_1.join)(projectPath, SPECRAILS_DIR, 'commands', 'sr', 'enrich.md');
266
+ const enrichMdPathSpecrails = (0, path_1.join)(projectPath, SPECRAILS_DIR, 'commands', 'specrails', 'enrich.md');
267
+ const legacyMdPath = (0, path_1.join)(projectPath, SPECRAILS_DIR, 'commands', 'setup.md');
268
+ for (const p of [enrichMdPathSr, enrichMdPathSpecrails, legacyMdPath]) {
269
+ try {
270
+ return (0, fs_1.readFileSync)(p, 'utf-8');
271
+ }
272
+ catch { /* try next */ }
273
+ }
274
+ return '';
275
+ }
276
+ // ─── Stream-based checkpoint detection ───────────────────────────────────────
277
+ function detectCheckpointFromText(text) {
278
+ const hits = [];
279
+ // Match phase headers from Claude's /specrails:enrich output (and legacy /setup)
280
+ if (/phase\s*1|codebase\s*analysis|repository\s*analysis/i.test(text)) {
281
+ hits.push({ key: 'repo_analysis', detail: 'Analyzing codebase...' });
282
+ }
283
+ if (/phase\s*2|user\s*personas|product\s*discovery/i.test(text)) {
284
+ hits.push({ key: 'product_discovery', detail: 'Generating personas...' });
285
+ }
286
+ if (/phase\s*3|configuration|agent\s*selection|backlog\s*provider/i.test(text)) {
287
+ hits.push({ key: 'stack_conventions', detail: 'Configuring stack...' });
288
+ }
289
+ if (/generating\s*all\s*files|writing.*agent|sr-architect|sr-developer|sr-reviewer/i.test(text)) {
290
+ hits.push({ key: 'agent_generation', detail: 'Generating agents...' });
291
+ }
292
+ if (/command\s*selection|installing.*commands|\.claude\/commands\/(sr|specrails)/i.test(text)) {
293
+ hits.push({ key: 'command_config', detail: 'Configuring commands...' });
294
+ }
295
+ // TUI output patterns from specrails-core init --from-config.
296
+ // Covers both the retired bash installer (✓ config loaded, reading
297
+ // install-config.yaml) and the Node installer ≥ v4.2.0 (Loaded
298
+ // install config from <path>, Phase 1 / 2 / 3 step headers, final
299
+ // `init complete` sentinel).
300
+ if (/✓\s*config\s*loaded|reading.*install-config|loaded\s*install\s*config|from-config/i.test(text)) {
301
+ hits.push({ key: 'config_written' });
302
+ }
303
+ if (/installing\s*specrails|phase\s*2\s*&\s*3|placing\s*agents/i.test(text)) {
304
+ hits.push({ key: 'agent_generation', detail: 'Installing specrails artefacts...' });
305
+ }
306
+ if (/writing\s*manifest|wrote\s+.*specrails-manifest/i.test(text)) {
307
+ hits.push({ key: 'final_verification' });
308
+ }
309
+ if (/✓\s*installed|installation\s*complete|init\s*complete|update\s*complete/i.test(text)) {
310
+ hits.push({ key: 'quick_complete' });
311
+ }
312
+ // File path detection in tool_use events
313
+ if (text.includes('.specrails-version') || text.includes('specrails/specrails-version'))
314
+ hits.push({ key: 'base_install' });
315
+ if (text.includes('/agents/personas/') && text.includes('.md')) {
316
+ hits.push({ key: 'product_discovery', detail: 'Writing personas...' });
317
+ }
318
+ // Claude path: .claude/agents/sr-<name>.md
319
+ if (/\/agents\/sr-[^/]+\.md/.test(text)) {
320
+ hits.push({ key: 'agent_generation', detail: 'Writing agents...' });
321
+ }
322
+ // Codex path: .codex/skills/sr-<name>/SKILL.md (rail skills ship in
323
+ // specrails-core 4.6.0+ — see openspec/.../specs/setup-wizard… for the
324
+ // checkpoint protocol shared across providers).
325
+ if (/\.codex\/skills\/sr-[^/]+\/SKILL\.md/.test(text)) {
326
+ hits.push({ key: 'agent_generation', detail: 'Writing agent skills...' });
327
+ }
328
+ if ((text.includes('/commands/sr/') || text.includes('/commands/specrails/')) && text.includes('.md')) {
329
+ hits.push({ key: 'command_config', detail: 'Writing commands...' });
330
+ }
331
+ // Codex enrich/doctor skills (the non-rail commands) also indicate
332
+ // command_config progress.
333
+ if (/\.codex\/skills\/(enrich|doctor)\/SKILL\.md/.test(text)) {
334
+ hits.push({ key: 'command_config', detail: 'Writing codex command skills...' });
335
+ }
336
+ if (text.includes('/rules/') && text.includes('.md')) {
337
+ hits.push({ key: 'stack_conventions', detail: 'Writing conventions...' });
338
+ }
339
+ // Codex sandbox / approval policy lives inside .codex/config.toml
340
+ // (top-level `sandbox_mode` + `approval_policy` keys, per codex
341
+ // 0.128.0+). There is no separate Starlark rules file.
342
+ if (/\.codex\/config\.toml/.test(text)) {
343
+ hits.push({ key: 'stack_conventions', detail: 'Writing codex sandbox config...' });
344
+ }
345
+ if (text.includes('.specrails-manifest.json') || text.includes('specrails/specrails-manifest.json')) {
346
+ hits.push({ key: 'final_verification' });
347
+ }
348
+ return hits;
349
+ }
350
+ exports.EMPTY_SUMMARY = {
351
+ agents: 0,
352
+ specrailsCommands: 0,
353
+ opsxCommands: 0,
354
+ personas: 0,
355
+ legacySrRemoved: 0,
356
+ tier: 'quick',
357
+ provider: 'claude',
358
+ };
359
+ const MIN_NODE_NATIVE_CORE_VERSION = '4.1.0';
360
+ function compareSemver(a, b) {
361
+ const aParts = a.trim().split('.').map((n) => parseInt(n, 10));
362
+ const bParts = b.trim().split('.').map((n) => parseInt(n, 10));
363
+ if (aParts.length < 3 || bParts.length < 3)
364
+ return null;
365
+ if ([...aParts, ...bParts].some((n) => Number.isNaN(n)))
366
+ return null;
367
+ for (let i = 0; i < 3; i++) {
368
+ if (aParts[i] > bParts[i])
369
+ return 1;
370
+ if (aParts[i] < bParts[i])
371
+ return -1;
372
+ }
373
+ return 0;
374
+ }
375
+ function validateInstalledCore(projectPath) {
376
+ const reasons = [];
377
+ const versionCandidates = [
378
+ (0, path_1.join)(projectPath, '.specrails', 'specrails-version'),
379
+ (0, path_1.join)(projectPath, '.specrails-version'),
380
+ ];
381
+ for (const candidate of versionCandidates) {
382
+ if (!(0, fs_1.existsSync)(candidate))
383
+ continue;
384
+ try {
385
+ const raw = (0, fs_1.readFileSync)(candidate, 'utf-8').trim();
386
+ const cmp = compareSemver(raw, MIN_NODE_NATIVE_CORE_VERSION);
387
+ if (cmp !== null && cmp < 0) {
388
+ reasons.push(`installed specrails-core version ${raw} is older than required ${MIN_NODE_NATIVE_CORE_VERSION}`);
389
+ }
390
+ break;
391
+ }
392
+ catch {
393
+ reasons.push(`failed to read installed specrails-core version from ${candidate}`);
394
+ break;
395
+ }
396
+ }
397
+ const legacyMarkers = [
398
+ { path: (0, path_1.join)(projectPath, '.specrails', 'bin', 'doctor.sh'), reason: 'legacy bash doctor detected' },
399
+ {
400
+ path: (0, path_1.join)(projectPath, '.specrails', 'setup-templates', 'settings', 'integration-contract.json'),
401
+ reason: 'legacy integration-contract copy detected in setup-templates',
402
+ },
403
+ ];
404
+ for (const marker of legacyMarkers) {
405
+ if ((0, fs_1.existsSync)(marker.path))
406
+ reasons.push(marker.reason);
407
+ }
408
+ return { ok: reasons.length === 0, reasons };
409
+ }
410
+ function formatLegacyInstallError(reasons) {
411
+ return [
412
+ 'Installed specrails-core is legacy; expected the Node-native installer.',
413
+ '',
414
+ ...reasons.map((reason) => `- ${reason}`),
415
+ ].join('\n');
416
+ }
417
+ function computeSummary(projectPath, tier, provider = 'claude') {
418
+ let agents = 0;
419
+ let personas = 0;
420
+ let specrailsCommands = 0;
421
+ let opsxCommands = 0;
422
+ try {
423
+ if (provider === 'codex') {
424
+ // Codex layout: every artefact ships as a SKILL under `.codex/skills/`.
425
+ // - agents = rail personas (`skills/rails/sr-*/SKILL.md`) + orchestrator
426
+ // skills at the root with an `sr-` prefix (sr-implement,
427
+ // sr-batch-implement, …)
428
+ // - opsxCommands = `skills/openspec-*/SKILL.md`
429
+ // - specrailsCommands = everything else under `skills/` (ported claude
430
+ // slash commands like propose-spec, explore-spec, retry, doctor,
431
+ // enrich, vpc-drift, …)
432
+ // - personas = 0 today; codex VPC pass not implemented yet.
433
+ const skillsDir = (0, path_1.join)(projectPath, '.codex', 'skills');
434
+ if ((0, fs_1.existsSync)(skillsDir)) {
435
+ // Rails (always counted as agents).
436
+ const railsDir = (0, path_1.join)(skillsDir, 'rails');
437
+ if ((0, fs_1.existsSync)(railsDir)) {
438
+ for (const entry of (0, fs_1.readdirSync)(railsDir)) {
439
+ if ((0, fs_1.existsSync)((0, path_1.join)(railsDir, entry, 'SKILL.md')))
440
+ agents++;
441
+ }
442
+ }
443
+ // Top-level skill dirs.
444
+ for (const entry of (0, fs_1.readdirSync)(skillsDir)) {
445
+ if (entry === 'rails')
446
+ continue;
447
+ if (!(0, fs_1.existsSync)((0, path_1.join)(skillsDir, entry, 'SKILL.md')))
448
+ continue;
449
+ if (/^sr-/.test(entry))
450
+ agents++;
451
+ else if (/^openspec-/.test(entry))
452
+ opsxCommands++;
453
+ else
454
+ specrailsCommands++;
455
+ }
456
+ }
457
+ }
458
+ else {
459
+ // Claude layout (unchanged).
460
+ const dir = SPECRAILS_DIR;
461
+ const agentsDir = (0, path_1.join)(projectPath, dir, 'agents');
462
+ if ((0, fs_1.existsSync)(agentsDir)) {
463
+ const files = (0, fs_1.readdirSync)(agentsDir);
464
+ agents = files.filter((f) => /^sr-.*\.md$/.test(f)).length;
465
+ const personasDir = (0, path_1.join)(agentsDir, 'personas');
466
+ if ((0, fs_1.existsSync)(personasDir)) {
467
+ personas = (0, fs_1.readdirSync)(personasDir).filter((f) => f.endsWith('.md')).length;
468
+ }
469
+ }
470
+ const commandsDirSpecrails = (0, path_1.join)(projectPath, dir, 'commands', 'specrails');
471
+ const commandsDirOpsx = (0, path_1.join)(projectPath, dir, 'commands', 'opsx');
472
+ if ((0, fs_1.existsSync)(commandsDirSpecrails)) {
473
+ specrailsCommands = (0, fs_1.readdirSync)(commandsDirSpecrails).filter((f) => f.endsWith('.md')).length;
474
+ }
475
+ if ((0, fs_1.existsSync)(commandsDirOpsx)) {
476
+ opsxCommands = (0, fs_1.readdirSync)(commandsDirOpsx).filter((f) => f.endsWith('.md')).length;
477
+ }
478
+ }
479
+ }
480
+ catch {
481
+ // non-fatal
482
+ }
483
+ return { agents, specrailsCommands, opsxCommands, personas, legacySrRemoved: 0, tier, provider };
484
+ }
485
+ /**
486
+ * Deletes the deprecated `.claude/commands/sr/` directory (if present) and returns
487
+ * the number of `.md` files that were removed. Safe to call even if the directory
488
+ * does not exist. Never throws — errors are logged at info level.
489
+ */
490
+ function sweepLegacySrCommands(projectPath) {
491
+ const srDir = (0, path_1.join)(projectPath, SPECRAILS_DIR, 'commands', 'sr');
492
+ try {
493
+ if (!(0, fs_1.existsSync)(srDir))
494
+ return 0;
495
+ const files = (0, fs_1.readdirSync)(srDir).filter((f) => f.endsWith('.md'));
496
+ const count = files.length;
497
+ (0, fs_1.rmSync)(srDir, { recursive: true, force: true });
498
+ console.info(`[SetupManager] Swept ${count} legacy /specrails:* command(s) from ${srDir}`);
499
+ return count;
500
+ }
501
+ catch (err) {
502
+ console.info(`[SetupManager] sweepLegacySrCommands failed (non-fatal): ${err}`);
503
+ return 0;
504
+ }
505
+ }
506
+ // ─── Core contract validation ────────────────────────────────────────────────
507
+ async function validateCoreContract() {
508
+ const contractPath = await (0, core_compat_1.findCoreContract)();
509
+ if (!contractPath) {
510
+ // specrails-core does not yet ship integration-contract.json (planned in RFC-003).
511
+ // Fall back silently to runtime defaults — Specrails works fine without the contract.
512
+ console.debug('[Specrails] integration-contract.json not found — using runtime defaults');
513
+ return;
514
+ }
515
+ let contract;
516
+ try {
517
+ const raw = require('fs').readFileSync(contractPath, 'utf-8');
518
+ contract = JSON.parse(raw);
519
+ }
520
+ catch {
521
+ console.debug('[Specrails] integration-contract.json failed to parse — using runtime defaults');
522
+ return;
523
+ }
524
+ if (contract.checkpoints) {
525
+ const missingCheckpoints = contract.checkpoints.filter((c) => !exports.CHECKPOINTS.some((cp) => cp.key === c));
526
+ const extraCheckpoints = exports.CHECKPOINTS
527
+ .filter((cp) => !contract.checkpoints.includes(cp.key))
528
+ .map((cp) => cp.key);
529
+ if (missingCheckpoints.length > 0 || extraCheckpoints.length > 0) {
530
+ console.warn('[Specrails] ⚠️ specrails-core contract checkpoint mismatch:');
531
+ if (missingCheckpoints.length > 0)
532
+ console.warn(` Checkpoints in Core but not in the app: ${missingCheckpoints.join(', ')}`);
533
+ if (extraCheckpoints.length > 0)
534
+ console.warn(` Checkpoints in the app but not in Core: ${extraCheckpoints.join(', ')}`);
535
+ }
536
+ }
537
+ }
538
+ // ─── SetupManager ─────────────────────────────────────────────────────────────
539
+ const INSTALL_LOG_BUFFER_MAX = 2000;
540
+ function formatBufferedInstallError(baseMessage, logBuffer) {
541
+ const recentLines = logBuffer
542
+ .map((line) => line.trim())
543
+ .filter(Boolean)
544
+ .slice(-8);
545
+ if (recentLines.length === 0)
546
+ return baseMessage;
547
+ return [
548
+ baseMessage,
549
+ '',
550
+ 'Recent output:',
551
+ ...recentLines.map((line) => `- ${line}`),
552
+ ].join('\n');
553
+ }
554
+ class SetupManager {
555
+ _broadcast;
556
+ _onSessionCaptured;
557
+ _onSetupDone;
558
+ // Map from projectId → active child processes
559
+ _installProcesses;
560
+ _setupProcesses;
561
+ // Track checkpoint states per project
562
+ _checkpoints;
563
+ // Track checkpoint start times
564
+ _checkpointStart;
565
+ // Ring buffer for install log lines — allows clients to recover log on reconnect
566
+ _installLogBuffer;
567
+ // Track each project's chosen AI provider for binary selection
568
+ _projectProviders;
569
+ // Track each project's install tier (quick vs full)
570
+ _projectTiers;
571
+ // Track project names for codex context header injection
572
+ _projectNames;
573
+ constructor(broadcast, onSessionCaptured, onSetupDone) {
574
+ this._broadcast = broadcast;
575
+ this._onSessionCaptured = onSessionCaptured;
576
+ this._onSetupDone = onSetupDone;
577
+ this._installProcesses = new Map();
578
+ this._setupProcesses = new Map();
579
+ this._checkpoints = new Map();
580
+ this._checkpointStart = new Map();
581
+ this._pollTimers = new Map();
582
+ this._installLogBuffer = new Map();
583
+ this._projectProviders = new Map();
584
+ this._projectTiers = new Map();
585
+ this._projectNames = new Map();
586
+ }
587
+ // ─── Full Install: TUI installer (npx specrails-core) ────────────────────────
588
+ startInstall(projectId, projectPath) {
589
+ if (this._installProcesses.has(projectId)) {
590
+ console.warn(`[SetupManager] install already running for ${projectId}`);
591
+ return;
592
+ }
593
+ const configPath = (0, path_1.join)(projectPath, '.specrails', 'install-config.yaml');
594
+ const hasConfig = (0, fs_1.existsSync)(configPath);
595
+ const parsedConfig = hasConfig ? readInstallConfig(projectPath) : null;
596
+ const tier = parsedConfig?.tier ?? 'full';
597
+ this._projectTiers.set(projectId, tier);
598
+ // Pull provider out of the just-written install-config.yaml so the
599
+ // completion-summary path can label tiles correctly (codex → "Skills"
600
+ // etc.). Without this, summary.provider stays undefined and the client
601
+ // renders the claude labels with 0/0/0 counts because the codex skill
602
+ // walker never gets selected.
603
+ if (hasConfig) {
604
+ try {
605
+ const text = (0, fs_1.readFileSync)(configPath, 'utf-8');
606
+ const m = text.match(/^provider:\s*(\w+)/m);
607
+ if (m && (m[1] === 'claude' || m[1] === 'codex')) {
608
+ this._projectProviders.set(projectId, m[1]);
609
+ }
610
+ }
611
+ catch {
612
+ // Ignore — falls back to claude default downstream.
613
+ }
614
+ }
615
+ this._initCheckpoints(projectId);
616
+ const missingPrerequisites = (0, setup_prerequisites_1.formatMissingSetupPrerequisites)();
617
+ if (missingPrerequisites) {
618
+ this._broadcast({
619
+ type: 'setup_error',
620
+ projectId,
621
+ error: missingPrerequisites,
622
+ });
623
+ return;
624
+ }
625
+ const probe = probeCoreRuntimeVersion(projectPath);
626
+ if (!probe.ok) {
627
+ this._broadcast({
628
+ type: 'setup_error',
629
+ projectId,
630
+ error: `Failed to verify specrails-core runtime before install: ${probe.error ?? 'unknown error'}`,
631
+ });
632
+ return;
633
+ }
634
+ console.log(`[SetupManager] core runtime probe: ${probe.bin} -> ${probe.version}`);
635
+ const probeCmp = compareSemver(probe.version, MIN_NODE_NATIVE_CORE_VERSION);
636
+ if (probeCmp !== null && probeCmp < 0) {
637
+ this._broadcast({
638
+ type: 'setup_error',
639
+ projectId,
640
+ error: `Resolved specrails-core@${probe.version} is legacy; expected Node-native >= ${MIN_NODE_NATIVE_CORE_VERSION}.`,
641
+ });
642
+ return;
643
+ }
644
+ let spawnConfigPath = null;
645
+ if (hasConfig) {
646
+ try {
647
+ spawnConfigPath = writeSpawnInstallConfig(projectId, (0, fs_1.readFileSync)(configPath, 'utf-8'));
648
+ }
649
+ catch (err) {
650
+ console.warn(`[SetupManager] Failed to write temp install-config.yaml: ${err}`);
651
+ }
652
+ }
653
+ const initArgs = hasConfig
654
+ ? ['--yes', '--from-config', spawnConfigPath ?? configPath]
655
+ : ['--yes', '--root-dir', projectPath];
656
+ const child = spawnCoreInit(initArgs, projectPath);
657
+ this._installProcesses.set(projectId, child);
658
+ this._installLogBuffer.set(projectId, []);
659
+ // spawnCoreInit uses shell:false on POSIX, so a spawn failure emits 'error'
660
+ // (and NOT 'close') — without this handler the temp config file leaks and
661
+ // the unhandled 'error' event would crash the app.
662
+ /* c8 ignore start -- spawn-failure path; exercised manually, not in CI */
663
+ child.on('error', (err) => {
664
+ console.error(`[SetupManager] core spawn failed for ${projectId}: ${err.message}`);
665
+ this._installProcesses.delete(projectId);
666
+ if (spawnConfigPath) {
667
+ try {
668
+ (0, fs_1.rmSync)(spawnConfigPath, { force: true });
669
+ }
670
+ catch { /* non-fatal */ }
671
+ }
672
+ this._broadcast({
673
+ type: 'setup_error',
674
+ projectId,
675
+ error: `Failed to launch specrails-core: ${err.message}`,
676
+ });
677
+ });
678
+ /* c8 ignore stop */
679
+ const stdoutReader = (0, readline_1.createInterface)({ input: child.stdout, crlfDelay: Infinity });
680
+ const stderrReader = (0, readline_1.createInterface)({ input: child.stderr, crlfDelay: Infinity });
681
+ const appendLog = (line) => {
682
+ const buf = this._installLogBuffer.get(projectId) ?? [];
683
+ buf.push(line);
684
+ if (buf.length > INSTALL_LOG_BUFFER_MAX)
685
+ buf.splice(0, buf.length - INSTALL_LOG_BUFFER_MAX);
686
+ this._installLogBuffer.set(projectId, buf);
687
+ };
688
+ stdoutReader.on('line', (line) => {
689
+ appendLog(line);
690
+ this._broadcast({ type: 'setup_log', projectId, line, stream: 'stdout' });
691
+ const hits = detectCheckpointFromText(line);
692
+ for (const hit of hits) {
693
+ this._advanceCheckpoint(projectId, hit.key, hit.detail);
694
+ }
695
+ });
696
+ stderrReader.on('line', (line) => {
697
+ appendLog(line);
698
+ this._broadcast({ type: 'setup_log', projectId, line, stream: 'stderr' });
699
+ });
700
+ child.on('close', (code) => {
701
+ if (spawnConfigPath) {
702
+ try {
703
+ (0, fs_1.rmSync)(spawnConfigPath, { force: true });
704
+ }
705
+ catch { /* non-fatal */ }
706
+ }
707
+ this._installProcesses.delete(projectId);
708
+ if (code === 0) {
709
+ const validation = validateInstalledCore(projectPath);
710
+ if (!validation.ok) {
711
+ this._broadcast({
712
+ type: 'setup_error',
713
+ projectId,
714
+ error: formatLegacyInstallError(validation.reasons),
715
+ });
716
+ return;
717
+ }
718
+ this._advanceCheckpoint(projectId, 'base_install');
719
+ this._completeCheckpoint(projectId, 'base_install');
720
+ const legacySrRemoved = sweepLegacySrCommands(projectPath);
721
+ const summary = { ...computeSummary(projectPath, tier, this._projectProviders.get(projectId) ?? 'claude'), legacySrRemoved };
722
+ this._broadcast({
723
+ type: 'setup_install_done',
724
+ projectId,
725
+ timestamp: new Date().toISOString(),
726
+ summary,
727
+ });
728
+ validateCoreContract().catch(() => { });
729
+ }
730
+ else {
731
+ const logBuffer = this._installLogBuffer.get(projectId) ?? [];
732
+ this._broadcast({
733
+ type: 'setup_error',
734
+ projectId,
735
+ error: formatBufferedInstallError(`npx specrails-core exited with code ${code ?? 'unknown'}`, logBuffer),
736
+ });
737
+ }
738
+ });
739
+ }
740
+ // ─── Enrich: claude -p "/specrails:enrich --from-config" ────────────────────
741
+ startEnrich(projectId, projectPath, provider, projectName) {
742
+ if (this._setupProcesses.has(projectId)) {
743
+ console.warn(`[SetupManager] enrich already running for ${projectId}`);
744
+ return;
745
+ }
746
+ if (provider)
747
+ this._projectProviders.set(projectId, provider);
748
+ if (projectName)
749
+ this._projectNames.set(projectId, projectName);
750
+ this._projectTiers.set(projectId, 'full');
751
+ this._initCheckpoints(projectId);
752
+ // Pre-create the directory structure that /specrails:enrich will write to.
753
+ // Claude Code's Write tool does not create parent directories automatically —
754
+ // if a target directory doesn't exist the write fails and Claude reports a
755
+ // misleading "write permissions aren't enabled" error. Creating the dirs
756
+ // here ensures enrich runs transparently without any user intervention.
757
+ try {
758
+ (0, fs_1.mkdirSync)((0, path_1.join)(projectPath, SPECRAILS_DIR, 'agents', 'personas'), { recursive: true });
759
+ (0, fs_1.mkdirSync)((0, path_1.join)(projectPath, SPECRAILS_DIR, 'commands', 'sr'), { recursive: true });
760
+ (0, fs_1.mkdirSync)((0, path_1.join)(projectPath, SPECRAILS_DIR, 'commands', 'specrails'), { recursive: true });
761
+ (0, fs_1.mkdirSync)((0, path_1.join)(projectPath, SPECRAILS_DIR, 'rules'), { recursive: true });
762
+ }
763
+ catch (err) {
764
+ console.warn(`[SetupManager] Failed to pre-create enrich directories: ${err}`);
765
+ }
766
+ const configPath = (0, path_1.join)(projectPath, '.specrails', 'install-config.yaml');
767
+ const hasConfig = (0, fs_1.existsSync)(configPath);
768
+ const enrichCmd = hasConfig ? '/specrails:enrich --from-config' : '/specrails:enrich';
769
+ this._spawnSetupWithAdapter(projectId, projectPath, {
770
+ action: 'setup-enrich',
771
+ prompt: enrichCmd,
772
+ provider,
773
+ });
774
+ }
775
+ /** @deprecated Use startEnrich() instead */
776
+ startSetup(projectId, projectPath, provider) {
777
+ return this.startEnrich(projectId, projectPath, provider);
778
+ }
779
+ resumeEnrich(projectId, projectPath, sessionId, userMessage, provider) {
780
+ if (this._setupProcesses.has(projectId)) {
781
+ console.warn(`[SetupManager] enrich already running for ${projectId}`);
782
+ return;
783
+ }
784
+ if (provider)
785
+ this._projectProviders.set(projectId, provider);
786
+ const resolvedProvider = (provider ?? this._projectProviders.get(projectId));
787
+ const adapter = (0, providers_1.getAdapter)(resolvedProvider ?? 'claude');
788
+ // Synthetic codex session ids (from before §10) can't be resumed against
789
+ // a real codex thread — detect and fall back to a fresh enrich respawn
790
+ // that folds enrich.md content + the user reply, matching the legacy UX.
791
+ const isSyntheticSession = sessionId.startsWith('codex-') && !/^[0-9a-f]{8}-[0-9a-f]{4}/i.test(sessionId);
792
+ if (adapter.id === 'codex' && isSyntheticSession) {
793
+ const enrichContent = readEnrichMdContent(projectPath);
794
+ const prompt = enrichContent
795
+ ? `${enrichContent}\n\n---\nIMPORTANT: This is a continuation of a previous enrich run. Check which artifacts already exist in the project before regenerating anything. The user responded to your question with:\n\n${userMessage}`
796
+ : userMessage;
797
+ this._spawnSetupWithAdapter(projectId, projectPath, {
798
+ action: 'setup-enrich',
799
+ prompt,
800
+ provider: resolvedProvider,
801
+ });
802
+ return;
803
+ }
804
+ // Modern path: real session id (claude or post-§10 codex) — use the
805
+ // adapter's resume action.
806
+ this._spawnSetupWithAdapter(projectId, projectPath, {
807
+ action: 'setup-enrich-resume',
808
+ prompt: userMessage,
809
+ sessionId,
810
+ provider: resolvedProvider,
811
+ });
812
+ }
813
+ /** @deprecated Use resumeEnrich() instead */
814
+ resumeSetup(projectId, projectPath, sessionId, userMessage, provider) {
815
+ return this.resumeEnrich(projectId, projectPath, sessionId, userMessage, provider);
816
+ }
817
+ // Active filesystem poll timers per project
818
+ _pollTimers;
819
+ _startFilesystemPoll(projectId, projectPath) {
820
+ this._stopFilesystemPoll(projectId);
821
+ const timer = setInterval(() => {
822
+ this._syncFilesystemCheckpoints(projectId, projectPath);
823
+ }, 3000);
824
+ this._pollTimers.set(projectId, timer);
825
+ }
826
+ _stopFilesystemPoll(projectId) {
827
+ const timer = this._pollTimers.get(projectId);
828
+ if (timer) {
829
+ clearInterval(timer);
830
+ this._pollTimers.delete(projectId);
831
+ }
832
+ }
833
+ /**
834
+ * Adapter-driven enrich spawn. Provider-aware prompt resolution
835
+ * (slash command for claude vs file-content fold for codex), real
836
+ * thread_id capture from `session-started` events (no more synthetic
837
+ * `codex-<id>-<ts>` ids), uniform stream parsing via
838
+ * `adapter.parseStreamLine`.
839
+ */
840
+ _spawnSetupWithAdapter(projectId, projectPath, opts) {
841
+ const resolvedProvider = opts.provider ?? (0, core_compat_1.detectCLISync)();
842
+ if (resolvedProvider === null) {
843
+ console.warn('[SetupManager] No AI CLI detected. Falling back to claude.');
844
+ }
845
+ const adapter = (0, providers_1.getAdapter)(resolvedProvider ?? 'claude');
846
+ // Provider-aware prompt resolution:
847
+ // - claude: pass the slash command unresolved so the CLI looks up
848
+ // `.claude/commands/specrails/enrich.md` natively. Honours the
849
+ // skills-resolution priority over CLAUDE.md.
850
+ // - codex: no slash-command support; fold the enrich.md content into
851
+ // the prompt with the PROJECT context header so codex knows the cwd.
852
+ let effectivePrompt = opts.prompt;
853
+ if (!adapter.capabilities.systemPromptArg &&
854
+ (opts.prompt === '/specrails:enrich' || opts.prompt === '/specrails:enrich --from-config')) {
855
+ const enrichContent = readEnrichMdContent(projectPath);
856
+ if (enrichContent) {
857
+ const projectName = this._projectNames.get(projectId);
858
+ effectivePrompt = projectName
859
+ ? `PROJECT: ${projectName}\nCWD: ${projectPath}\n\n---\n\n${enrichContent}`
860
+ : enrichContent;
861
+ }
862
+ else {
863
+ console.warn(`[SetupManager] Could not read enrich.md or setup.md — falling back to literal prompt`);
864
+ }
865
+ }
866
+ const args = adapter.buildArgs(opts.action, {
867
+ prompt: effectivePrompt,
868
+ model: adapter.defaultModel(),
869
+ sessionId: opts.sessionId,
870
+ });
871
+ // No OTEL env injection here — SetupManager spawns drive the initial project
872
+ // setup wizard, not repeatable pipeline jobs. Telemetry is scoped to
873
+ // QueueManager pipeline runs only.
874
+ const child = (0, cli_prompt_1.spawnAiCli)(adapter.binary, args, {
875
+ cwd: projectPath,
876
+ env: process.env,
877
+ stdio: ['ignore', 'pipe', 'pipe'],
878
+ });
879
+ this._setupProcesses.set(projectId, child);
880
+ /* c8 ignore start -- spawn-failure path; exercised manually, not in CI */
881
+ child.on('error', (err) => {
882
+ console.error(`[SetupManager] ${adapter.binary} spawn failed for ${projectId}: ${err.message}`);
883
+ this._setupProcesses.delete(projectId);
884
+ this._stopFilesystemPoll(projectId);
885
+ this._broadcast({
886
+ type: 'setup_error',
887
+ projectId,
888
+ error: `Failed to launch ${adapter.binary}: ${err.message}`,
889
+ });
890
+ });
891
+ /* c8 ignore stop */
892
+ // Start periodic filesystem polling for checkpoint detection
893
+ this._startFilesystemPoll(projectId, projectPath);
894
+ let capturedSessionId = null;
895
+ const stdoutReader = (0, readline_1.createInterface)({ input: child.stdout, crlfDelay: Infinity });
896
+ const stderrReader = (0, readline_1.createInterface)({ input: child.stderr, crlfDelay: Infinity });
897
+ stdoutReader.on('line', (line) => {
898
+ const ev = adapter.parseStreamLine(line);
899
+ if (!ev) {
900
+ // Non-parseable line — emit as raw log.
901
+ if (line)
902
+ this._broadcast({ type: 'setup_log', projectId, line, stream: 'stdout' });
903
+ return;
904
+ }
905
+ switch (ev.kind) {
906
+ case 'session-started': {
907
+ if (!capturedSessionId) {
908
+ capturedSessionId = ev.sessionId;
909
+ this._onSessionCaptured?.(projectId, ev.sessionId);
910
+ }
911
+ break;
912
+ }
913
+ case 'text-delta': {
914
+ // Run checkpoint detection over the assistant text and surface it
915
+ // both to the collapsible log viewer and the wizard chat panel.
916
+ this._broadcast({ type: 'setup_log', projectId, line: ev.text, stream: 'stdout' });
917
+ this._broadcast({ type: 'setup_chat', projectId, text: ev.text, role: 'assistant' });
918
+ const hits = detectCheckpointFromText(ev.text);
919
+ for (const hit of hits) {
920
+ this._advanceCheckpoint(projectId, hit.key, hit.detail);
921
+ }
922
+ // Also sync filesystem (cheap mtime/exists checks, see helper).
923
+ this._syncFilesystemCheckpoints(projectId, projectPath);
924
+ break;
925
+ }
926
+ case 'tool-use': {
927
+ this._broadcast({ type: 'setup_log', projectId, line: `[tool] ${ev.name}`, stream: 'stdout' });
928
+ // Tool inputs commonly mention the paths being written — feed the
929
+ // input preview into the checkpoint detector so writes to
930
+ // .claude/agents/sr-*.md or .codex/skills/sr-*/SKILL.md advance
931
+ // the checkpoint state immediately.
932
+ const hits = detectCheckpointFromText(ev.inputPreview);
933
+ for (const hit of hits) {
934
+ this._advanceCheckpoint(projectId, hit.key, hit.detail);
935
+ }
936
+ break;
937
+ }
938
+ case 'result': {
939
+ // Claude's `result` event also carries session_id; use it as a
940
+ // backstop if `session-started` wasn't observed earlier.
941
+ const sid = ev.payload.session_id;
942
+ if (sid && !capturedSessionId) {
943
+ capturedSessionId = sid;
944
+ this._onSessionCaptured?.(projectId, sid);
945
+ }
946
+ break;
947
+ }
948
+ case 'other':
949
+ // Other event types (system progress markers) — already broadcast
950
+ // implicitly through the line itself if useful. Skip.
951
+ break;
952
+ }
953
+ });
954
+ stderrReader.on('line', (line) => {
955
+ this._broadcast({ type: 'setup_log', projectId, line, stream: 'stderr' });
956
+ });
957
+ child.on('close', (code) => {
958
+ this._setupProcesses.delete(projectId);
959
+ this._stopFilesystemPoll(projectId);
960
+ // Final filesystem sync
961
+ this._syncFilesystemCheckpoints(projectId, projectPath);
962
+ if (code === 0) {
963
+ // Sync filesystem checkpoints
964
+ this._syncFilesystemCheckpoints(projectId, projectPath);
965
+ // Check if setup is truly complete — real artifacts must exist
966
+ const hasAgents = (0, fs_1.existsSync)((0, path_1.join)(projectPath, SPECRAILS_DIR, 'agents')) &&
967
+ hasFiles((0, path_1.join)(projectPath, SPECRAILS_DIR, 'agents'), /^sr-.*\.md$/);
968
+ const hasCommands = (((0, fs_1.existsSync)((0, path_1.join)(projectPath, SPECRAILS_DIR, 'commands', 'sr')) && hasFiles((0, path_1.join)(projectPath, SPECRAILS_DIR, 'commands', 'sr'), /\.md$/)) ||
969
+ ((0, fs_1.existsSync)((0, path_1.join)(projectPath, SPECRAILS_DIR, 'commands', 'specrails')) && hasFiles((0, path_1.join)(projectPath, SPECRAILS_DIR, 'commands', 'specrails'), /\.md$/)));
970
+ const isComplete = hasAgents && hasCommands;
971
+ if (isComplete) {
972
+ const legacySrRemoved = sweepLegacySrCommands(projectPath);
973
+ const tier = this._projectTiers.get(projectId) ?? 'full';
974
+ const summary = { ...computeSummary(projectPath, tier, this._projectProviders.get(projectId) ?? 'claude'), legacySrRemoved };
975
+ this._onSetupDone?.(projectId);
976
+ this._broadcast({
977
+ type: 'setup_complete',
978
+ projectId,
979
+ sessionId: capturedSessionId ?? undefined,
980
+ summary,
981
+ });
982
+ }
983
+ else {
984
+ // Claude finished one turn but setup isn't done yet.
985
+ // Emit turn_done so the wizard knows to wait for user input.
986
+ this._broadcast({
987
+ type: 'setup_turn_done',
988
+ projectId,
989
+ sessionId: capturedSessionId ?? undefined,
990
+ });
991
+ }
992
+ }
993
+ else {
994
+ this._onSetupDone?.(projectId);
995
+ this._broadcast({
996
+ type: 'setup_error',
997
+ projectId,
998
+ error: `${adapter.binary} enrich exited with code ${code ?? 'unknown'}`,
999
+ });
1000
+ }
1001
+ });
1002
+ }
1003
+ _initCheckpoints(projectId) {
1004
+ const tier = this._projectTiers.get(projectId) ?? 'full';
1005
+ const defs = tier === 'quick' ? exports.QUICK_CHECKPOINTS : exports.CHECKPOINTS;
1006
+ const statuses = new Map();
1007
+ const starts = new Map();
1008
+ for (const def of defs) {
1009
+ statuses.set(def.key, { key: def.key, name: def.name, status: 'pending' });
1010
+ }
1011
+ this._checkpoints.set(projectId, statuses);
1012
+ this._checkpointStart.set(projectId, starts);
1013
+ }
1014
+ _advanceCheckpoint(projectId, key, detail) {
1015
+ const statuses = this._checkpoints.get(projectId);
1016
+ if (!statuses)
1017
+ return;
1018
+ const checkpoint = statuses.get(key);
1019
+ if (!checkpoint || checkpoint.status === 'done')
1020
+ return;
1021
+ const starts = this._checkpointStart.get(projectId);
1022
+ // When a later checkpoint starts, auto-complete all earlier ones
1023
+ const tier = this._projectTiers.get(projectId) ?? 'full';
1024
+ const checkpointDefs = tier === 'quick' ? exports.QUICK_CHECKPOINTS : exports.CHECKPOINTS;
1025
+ const checkpointKeys = checkpointDefs.map((c) => c.key);
1026
+ const targetIdx = checkpointKeys.indexOf(key);
1027
+ for (let i = 0; i < targetIdx; i++) {
1028
+ const prevKey = checkpointKeys[i];
1029
+ const prev = statuses.get(prevKey);
1030
+ if (prev && prev.status !== 'done') {
1031
+ this._completeCheckpoint(projectId, prevKey);
1032
+ }
1033
+ }
1034
+ if (checkpoint.status === 'pending') {
1035
+ checkpoint.status = 'running';
1036
+ starts.set(key, Date.now());
1037
+ if (detail)
1038
+ checkpoint.detail = detail;
1039
+ this._broadcast({ type: 'setup_checkpoint', projectId, checkpoint: key, status: 'running', detail });
1040
+ }
1041
+ }
1042
+ _completeCheckpoint(projectId, key) {
1043
+ const statuses = this._checkpoints.get(projectId);
1044
+ if (!statuses)
1045
+ return;
1046
+ const checkpoint = statuses.get(key);
1047
+ if (!checkpoint || checkpoint.status === 'done')
1048
+ return;
1049
+ const starts = this._checkpointStart.get(projectId);
1050
+ const startTime = starts.get(key) ?? Date.now();
1051
+ const duration_ms = Date.now() - startTime;
1052
+ starts.delete(key);
1053
+ checkpoint.status = 'done';
1054
+ checkpoint.duration_ms = duration_ms;
1055
+ this._broadcast({ type: 'setup_checkpoint', projectId, checkpoint: key, status: 'done', duration_ms });
1056
+ }
1057
+ _syncFilesystemCheckpoints(projectId, projectPath) {
1058
+ const statuses = this._checkpoints.get(projectId);
1059
+ if (!statuses)
1060
+ return;
1061
+ const fsChecks = checkFilesystem(projectPath);
1062
+ for (const [key, exists] of Object.entries(fsChecks)) {
1063
+ if (!exists)
1064
+ continue;
1065
+ const cp = statuses.get(key);
1066
+ if (!cp)
1067
+ continue;
1068
+ if (cp.status === 'pending') {
1069
+ // Fast-path: mark running then done immediately
1070
+ this._advanceCheckpoint(projectId, key);
1071
+ this._completeCheckpoint(projectId, key);
1072
+ }
1073
+ else if (cp.status === 'running') {
1074
+ this._completeCheckpoint(projectId, key);
1075
+ }
1076
+ }
1077
+ }
1078
+ // ─── Checkpoint poll endpoint ─────────────────────────────────────────────────
1079
+ getCheckpointStatus(projectId, projectPath) {
1080
+ // Sync from filesystem before returning
1081
+ this._syncFilesystemCheckpoints(projectId, projectPath);
1082
+ const tier = this._projectTiers.get(projectId) ?? 'full';
1083
+ const defs = tier === 'quick' ? exports.QUICK_CHECKPOINTS : exports.CHECKPOINTS;
1084
+ const statuses = this._checkpoints.get(projectId);
1085
+ if (!statuses) {
1086
+ // Return all-pending if install hasn't started
1087
+ return defs.map((def) => ({ key: def.key, name: def.name, status: 'pending' }));
1088
+ }
1089
+ return defs.map((def) => statuses.get(def.key) ?? { key: def.key, name: def.name, status: 'pending' });
1090
+ }
1091
+ getInstallLog(projectId) {
1092
+ return this._installLogBuffer.get(projectId) ?? [];
1093
+ }
1094
+ // ─── Abort ────────────────────────────────────────────────────────────────────
1095
+ abort(projectId) {
1096
+ this._stopFilesystemPoll(projectId);
1097
+ this._projectProviders.delete(projectId);
1098
+ this._projectTiers.delete(projectId);
1099
+ this._onSetupDone?.(projectId);
1100
+ const installChild = this._installProcesses.get(projectId);
1101
+ if (installChild?.pid) {
1102
+ this._terminateWithEscalation(installChild.pid);
1103
+ this._installProcesses.delete(projectId);
1104
+ }
1105
+ const setupChild = this._setupProcesses.get(projectId);
1106
+ if (setupChild?.pid) {
1107
+ this._terminateWithEscalation(setupChild.pid);
1108
+ this._setupProcesses.delete(projectId);
1109
+ }
1110
+ }
1111
+ /**
1112
+ * SIGTERM a process tree, then escalate to SIGKILL after a grace window if it
1113
+ * is still alive — mirroring QueueManager._kill. The Map entry is deleted
1114
+ * immediately by the caller, so the pid is captured locally here; without the
1115
+ * escalation a child that ignores SIGTERM (npm/npx scaffolding, a hung CLI)
1116
+ * would be orphaned for the host's lifetime with no remaining handle.
1117
+ */
1118
+ _terminateWithEscalation(pid) {
1119
+ try {
1120
+ (0, tree_kill_1.default)(pid, 'SIGTERM');
1121
+ }
1122
+ catch { /* best-effort */ }
1123
+ const grace = setTimeout(() => {
1124
+ try {
1125
+ (0, tree_kill_1.default)(pid, 'SIGKILL', () => { });
1126
+ }
1127
+ catch { /* best-effort */ }
1128
+ }, 5000);
1129
+ if (typeof grace.unref === 'function')
1130
+ grace.unref();
1131
+ }
1132
+ isInstalling(projectId) {
1133
+ return this._installProcesses.has(projectId);
1134
+ }
1135
+ isEnriching(projectId) {
1136
+ return this._setupProcesses.has(projectId);
1137
+ }
1138
+ /** @deprecated Use isEnriching() instead */
1139
+ isSettingUp(projectId) {
1140
+ return this.isEnriching(projectId);
1141
+ }
1142
+ getInstallTier(projectId) {
1143
+ return this._projectTiers.get(projectId);
1144
+ }
1145
+ getSummary(projectPath) {
1146
+ const config = readInstallConfig(projectPath);
1147
+ const tier = config?.tier ?? 'quick';
1148
+ // Provider is authoritative from install-config.yaml when present; we
1149
+ // do NOT fall back to filesystem heuristics because both `.codex/` and
1150
+ // `.claude/` can legitimately coexist (e.g. a project that's been
1151
+ // re-init'd) and a generic `existsSync` probe would mis-route.
1152
+ let provider = 'claude';
1153
+ try {
1154
+ const text = (0, fs_1.readFileSync)((0, path_1.join)(projectPath, '.specrails', 'install-config.yaml'), 'utf-8');
1155
+ const m = text.match(/^provider:\s*(\w+)/m);
1156
+ if (m && m[1] === 'codex')
1157
+ provider = 'codex';
1158
+ }
1159
+ catch {
1160
+ // Missing install-config — stay on claude default.
1161
+ }
1162
+ return computeSummary(projectPath, tier, provider);
1163
+ }
1164
+ }
1165
+ exports.SetupManager = SetupManager;