specrails-desktop 2.2.1 → 2.4.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 (350) hide show
  1. package/client/dist/assets/ActivityFeedPage-DJJlZ3mF.js +1 -0
  2. package/client/dist/assets/AgentsPage-49JaEDjR.js +86 -0
  3. package/client/dist/assets/{AnalyticsPage-BD0paa75.js → AnalyticsPage-BUd3gWYC.js} +1 -1
  4. package/client/dist/assets/{BarChart-D8ZZRab3.js → BarChart-HDe_YoUD.js} +1 -1
  5. package/client/dist/assets/CodePage-CqPPND47.js +2 -0
  6. package/client/dist/assets/{DesktopAnalyticsPage-mwd8460_.js → DesktopAnalyticsPage-CgvmSvF0.js} +1 -1
  7. package/client/dist/assets/DocsDialog-hHFd3Ejs.js +11 -0
  8. package/client/dist/assets/DocsPage-B4R1aksg.js +11 -0
  9. package/client/dist/assets/{ExportDropdown-CLYmQhic.js → ExportDropdown-f4dwQjlT.js} +1 -1
  10. package/client/dist/assets/IntegrationsPage-CX2Ybxx0.js +3 -0
  11. package/client/dist/assets/JobDetailPage-DN2Jc8Ti.js +16 -0
  12. package/client/dist/assets/JobsPage-DmdpqijT.js +1 -0
  13. package/client/dist/assets/code-BwIz8agY.js +1 -0
  14. package/client/dist/assets/code-CD7yNSK0.js +1 -0
  15. package/client/dist/assets/code-CDFlxUFC.js +1 -0
  16. package/client/dist/assets/code-CY85RXZU.js +1 -0
  17. package/client/dist/assets/code-Cp3Fdng-.js +1 -0
  18. package/client/dist/assets/code-D24e1Crx.js +1 -0
  19. package/client/dist/assets/code-DtZBQTi9.js +1 -0
  20. package/client/dist/assets/code-nKa0fkm_.js +1 -0
  21. package/client/dist/assets/{cssMode-Cc6ozl-J.js → cssMode-DzNPAYFh.js} +1 -1
  22. package/client/dist/assets/{dist-js-D3MxtOYa.js → dist-js-COfIfLRE.js} +1 -1
  23. package/client/dist/assets/{dist-js-BOu_cXw3.js → dist-js-CvScGQU_.js} +1 -1
  24. package/client/dist/assets/{editor.main-CfXxHimg.js → editor.main-C7Rmw-hR.js} +2 -2
  25. package/client/dist/assets/{freemarker2-DP7J1gG3.js → freemarker2-Cszs4SVo.js} +1 -1
  26. package/client/dist/assets/{handlebars-BjRlucw6.js → handlebars-Dp7Lsuym.js} +1 -1
  27. package/client/dist/assets/{html-OumBQJ-U.js → html-BURidrEm.js} +1 -1
  28. package/client/dist/assets/{htmlMode-CStc3zXM.js → htmlMode--k5M7GjZ.js} +1 -1
  29. package/client/dist/assets/index-DBpvYrDK.css +2 -0
  30. package/client/dist/assets/index-DGIXKRHE.js +142 -0
  31. package/client/dist/assets/{integrations-Cublz3m6.js → integrations-2C7MkGT0.js} +1 -1
  32. package/client/dist/assets/{integrations-HIlUxXVs.js → integrations-BDC670cg.js} +1 -1
  33. package/client/dist/assets/integrations-BqUmRUef.js +1 -0
  34. package/client/dist/assets/{integrations-DmQYCUvN.js → integrations-C2jQtv-s.js} +1 -1
  35. package/client/dist/assets/{integrations-DRdbki5W.js → integrations-CB98NeH5.js} +1 -1
  36. package/client/dist/assets/{integrations-C3p12Ms6.js → integrations-CX4p_bij.js} +1 -1
  37. package/client/dist/assets/{integrations-DaC4SzzL.js → integrations-_SuVeQIG.js} +1 -1
  38. package/client/dist/assets/{integrations-Cr6hH7XR.js → integrations-eQPHAYsE.js} +1 -1
  39. package/client/dist/assets/{javascript-CMk--e7g.js → javascript-kJQz__44.js} +1 -1
  40. package/client/dist/assets/jira-C-ATCti0.js +1 -0
  41. package/client/dist/assets/jira-CmVfRM-b.js +1 -0
  42. package/client/dist/assets/jira-D7bkKAX8.js +1 -0
  43. package/client/dist/assets/jira-DKImM1YH.js +1 -0
  44. package/client/dist/assets/jira-DOw8bkIR.js +1 -0
  45. package/client/dist/assets/jira-DlA-wGp-.js +1 -0
  46. package/client/dist/assets/jira-Fob8EGxN.js +1 -0
  47. package/client/dist/assets/jira-xZA2lixb.js +1 -0
  48. package/client/dist/assets/{jsonMode-C2h3ZcjZ.js → jsonMode-v5JYPpnz.js} +1 -1
  49. package/client/dist/assets/{lib-DQ2hrj8m.js → lib-Bro9Z0gp.js} +1 -1
  50. package/client/dist/assets/{liquid-mI3KJrBE.js → liquid-Dl9I6gWt.js} +1 -1
  51. package/client/dist/assets/{lspLanguageFeatures-DU09ggWi.js → lspLanguageFeatures-CPlEe0NK.js} +1 -1
  52. package/client/dist/assets/{mdx-C41VDTR_.js → mdx-Byl7TtzQ.js} +1 -1
  53. package/client/dist/assets/{monaco.contribution-CPObAXMC.js → monaco.contribution-YMAkHQcQ.js} +2 -2
  54. package/client/dist/assets/{python-Y27rKQtk.js → python-jWQwT6j2.js} +1 -1
  55. package/client/dist/assets/{razor-Cd5-q9Bp.js → razor-BWS3sP-E.js} +1 -1
  56. package/client/dist/assets/setup-C0dzw8j4.js +1 -0
  57. package/client/dist/assets/setup-C1IA-9YS.js +1 -0
  58. package/client/dist/assets/setup-CpfjaNut.js +1 -0
  59. package/client/dist/assets/setup-D3rNZA9A.js +1 -0
  60. package/client/dist/assets/setup-UD2aanGs.js +1 -0
  61. package/client/dist/assets/setup-WP6WOYQh.js +1 -0
  62. package/client/dist/assets/setup-gzLG8T6F.js +1 -0
  63. package/client/dist/assets/setup-pjgmYHx6.js +1 -0
  64. package/client/dist/assets/specs-4lA_u79w.js +1 -0
  65. package/client/dist/assets/{specs-D2FzlLn9.js → specs-BHjxcjOf.js} +1 -1
  66. package/client/dist/assets/{specs-CZ1PsXsC.js → specs-CXNQzPk9.js} +1 -1
  67. package/client/dist/assets/{specs-Dyc5hYeE.js → specs-DFnc2Huj.js} +1 -1
  68. package/client/dist/assets/{specs-BFfu3u-a.js → specs-DZCLH2-l.js} +1 -1
  69. package/client/dist/assets/{specs-B__C8-8a.js → specs-DgmyAE3N.js} +1 -1
  70. package/client/dist/assets/{specs-DaUTrNF9.js → specs-DicWhvwi.js} +1 -1
  71. package/client/dist/assets/{specs-k0PyLDVt.js → specs-dkro6lSM.js} +1 -1
  72. package/client/dist/assets/{tsMode-B0y_xEci.js → tsMode-BbOGOuSV.js} +1 -1
  73. package/client/dist/assets/{typescript-BzK0OgwW.js → typescript-eBtFQJLs.js} +1 -1
  74. package/client/dist/assets/{useProjectCache-BxY4aTjd.js → useProjectCache-D9juBhsO.js} +1 -1
  75. package/client/dist/assets/{workers-rt--R2Qy.js → workers-BvicOoDf.js} +1 -1
  76. package/client/dist/assets/{xml-eX9QXAmI.js → xml-BJepAPyM.js} +1 -1
  77. package/client/dist/assets/{yaml-fcsNkpOt.js → yaml-DabgV-eA.js} +1 -1
  78. package/client/dist/index.html +13 -12
  79. package/docs/jira-integration-plan.md +321 -0
  80. package/package.json +1 -1
  81. package/server/dist/agent-refine-manager.js +128 -153
  82. package/server/dist/chat-manager.js +242 -0
  83. package/server/dist/code-explorer-router.js +78 -0
  84. package/server/dist/command-resolver.js +17 -0
  85. package/server/dist/contract-refine-runner.js +42 -10
  86. package/server/dist/db.js +86 -0
  87. package/server/dist/desktop-db.js +3 -0
  88. package/server/dist/explore-stdin-session.js +129 -0
  89. package/server/dist/feature-flags.js +11 -0
  90. package/server/dist/jira/jira-adf.js +113 -0
  91. package/server/dist/jira/jira-backlog-config.js +58 -0
  92. package/server/dist/jira/jira-client.js +279 -0
  93. package/server/dist/jira/jira-credential-store.js +103 -0
  94. package/server/dist/jira/jira-db.js +341 -0
  95. package/server/dist/jira/jira-issue-fields.js +428 -0
  96. package/server/dist/jira/jira-materializer.js +250 -0
  97. package/server/dist/jira/jira-status-resolver.js +211 -0
  98. package/server/dist/jira/jira-sync-manager.js +1014 -0
  99. package/server/dist/jira/types.js +9 -0
  100. package/server/dist/jira-router.js +304 -0
  101. package/server/dist/mobile/mobile-auth.js +16 -0
  102. package/server/dist/project-registry.js +43 -1
  103. package/server/dist/project-router-chat.js +218 -0
  104. package/server/dist/project-router-helpers.js +275 -0
  105. package/server/dist/project-router-jobs.js +389 -0
  106. package/server/dist/project-router-settings.js +312 -0
  107. package/server/dist/project-router-setup.js +456 -0
  108. package/server/dist/project-router-spending.js +320 -0
  109. package/server/dist/project-router-terminals.js +312 -0
  110. package/server/dist/project-router-tickets.js +1815 -0
  111. package/server/dist/project-router.js +31 -3950
  112. package/server/dist/providers/claude-adapter.js +23 -0
  113. package/server/dist/providers/codex-adapter.js +6 -0
  114. package/server/dist/rails-router.js +12 -0
  115. package/server/dist/spawn-lifecycle.js +117 -0
  116. package/client/dist/assets/ActivityFeedPage-BpjXuX2H.js +0 -1
  117. package/client/dist/assets/AgentsPage-D-7fDbTc.js +0 -86
  118. package/client/dist/assets/CodePage-B6q6CiYJ.js +0 -2
  119. package/client/dist/assets/DocsDialog-D_dyF2h9.js +0 -11
  120. package/client/dist/assets/DocsPage-C9-Ru8wE.js +0 -11
  121. package/client/dist/assets/IntegrationsPage-3WWtx9hi.js +0 -3
  122. package/client/dist/assets/JobDetailPage-DgN-79s-.js +0 -16
  123. package/client/dist/assets/JobsPage-Du8_w1ob.js +0 -1
  124. package/client/dist/assets/code-AL1rVIMb.js +0 -1
  125. package/client/dist/assets/code-C0BKpkht.js +0 -1
  126. package/client/dist/assets/code-C0FTS3ew.js +0 -1
  127. package/client/dist/assets/code-CPcHxzxw.js +0 -1
  128. package/client/dist/assets/code-D3ryDniw.js +0 -1
  129. package/client/dist/assets/code-D3zVVQTj.js +0 -1
  130. package/client/dist/assets/code-PCmfS3dn.js +0 -1
  131. package/client/dist/assets/code-exI0G5Wd.js +0 -1
  132. package/client/dist/assets/index-D17R4Cjc.css +0 -2
  133. package/client/dist/assets/index-D9G_K4L-.js +0 -142
  134. package/client/dist/assets/integrations-D28q1kF6.js +0 -1
  135. package/client/dist/assets/setup--FMCsnQS.js +0 -1
  136. package/client/dist/assets/setup-B19ZpBNi.js +0 -1
  137. package/client/dist/assets/setup-BZPmkjSN.js +0 -1
  138. package/client/dist/assets/setup-BqYA02rS.js +0 -1
  139. package/client/dist/assets/setup-ChKQDHN9.js +0 -1
  140. package/client/dist/assets/setup-D2n9jMfM.js +0 -1
  141. package/client/dist/assets/setup-P3r6YP1D.js +0 -1
  142. package/client/dist/assets/setup-fnfEbwlv.js +0 -1
  143. package/client/dist/assets/specs-cKEh2LXt.js +0 -1
  144. /package/client/dist/assets/{abap-Bw6f2wDG.js → abap-s65oMlhi.js} +0 -0
  145. /package/client/dist/assets/{activity-BdrPln96.js → activity-BqqwnH_h.js} +0 -0
  146. /package/client/dist/assets/{activity-BEIp_Y1A.js → activity-C8qqEIoP.js} +0 -0
  147. /package/client/dist/assets/{activity-CpkRS8Sx.js → activity-CZVM4nlJ.js} +0 -0
  148. /package/client/dist/assets/{activity-DOUVEjJi.js → activity-Cyy07Tgo.js} +0 -0
  149. /package/client/dist/assets/{activity-DRwkql_y.js → activity-DlbWCa4y.js} +0 -0
  150. /package/client/dist/assets/{activity-DKCpESPt.js → activity-Dwq0heud.js} +0 -0
  151. /package/client/dist/assets/{activity-DcDQ7tjw.js → activity-qFTcMyW9.js} +0 -0
  152. /package/client/dist/assets/{addon-image-3WCl5Vhd.js → addon-image-CpF0L0jM.js} +0 -0
  153. /package/client/dist/assets/{addon-ligatures-C5OdliKs.js → addon-ligatures-hXysGZrA.js} +0 -0
  154. /package/client/dist/assets/{addon-webgl-BbX6pSjl.js → addon-webgl-Cn1slavz.js} +0 -0
  155. /package/client/dist/assets/{addspec-D33ocMxf.js → addspec-B1FTtI2a.js} +0 -0
  156. /package/client/dist/assets/{addspec-DFswZ0jK.js → addspec-BCT9vm_c.js} +0 -0
  157. /package/client/dist/assets/{addspec-DVZ15Jp8.js → addspec-DeDOztDr.js} +0 -0
  158. /package/client/dist/assets/{addspec-Fkv91Opc.js → addspec-DpRgmfmx.js} +0 -0
  159. /package/client/dist/assets/{addspec-BEeF5-zc.js → addspec-Dw-0Dg-4.js} +0 -0
  160. /package/client/dist/assets/{addspec-B5yl4Loj.js → addspec-rp496P_F.js} +0 -0
  161. /package/client/dist/assets/{addspec-DRE-jZv7.js → addspec-v8j6A7CD.js} +0 -0
  162. /package/client/dist/assets/{agents-DK-Dlc0i.js → agents-23iPejcA.js} +0 -0
  163. /package/client/dist/assets/{agents-Q6Ldfpxx.js → agents-BDx1RXcl.js} +0 -0
  164. /package/client/dist/assets/{agents-TeOSy-ax.js → agents-BFr3kUhK.js} +0 -0
  165. /package/client/dist/assets/{agents-Bm9rPqnt.js → agents-B_1L9xRg.js} +0 -0
  166. /package/client/dist/assets/{agents-1nCDWRmP.js → agents-BlPnx-mz.js} +0 -0
  167. /package/client/dist/assets/{agents-iTqjRajS.js → agents-DcxZHzNr.js} +0 -0
  168. /package/client/dist/assets/{agents-s87sMGzL.js → agents-G3shOewU.js} +0 -0
  169. /package/client/dist/assets/{agentstudio-B6Wb59E7.js → agentstudio-B-CMAQqy.js} +0 -0
  170. /package/client/dist/assets/{agentstudio-D3I62TLJ.js → agentstudio-Bk1eZcv4.js} +0 -0
  171. /package/client/dist/assets/{agentstudio-DuH9TogZ.js → agentstudio-ChxNuGAu.js} +0 -0
  172. /package/client/dist/assets/{agentstudio-Kw88_dUF.js → agentstudio-DNlme601.js} +0 -0
  173. /package/client/dist/assets/{agentstudio-BdidyBzZ.js → agentstudio-DpP9caEE.js} +0 -0
  174. /package/client/dist/assets/{agentstudio-BSnWLR63.js → agentstudio-Y3G0ddJ2.js} +0 -0
  175. /package/client/dist/assets/{agentstudio-BADhZ41e.js → agentstudio-kk9RB7Se.js} +0 -0
  176. /package/client/dist/assets/{aiedit-DJMny-D5.js → aiedit-5ETerMK1.js} +0 -0
  177. /package/client/dist/assets/{aiedit-D2ji6Qy0.js → aiedit-BBCrOpHq.js} +0 -0
  178. /package/client/dist/assets/{aiedit-DAhZTvtk.js → aiedit-BMtcGYNE.js} +0 -0
  179. /package/client/dist/assets/{aiedit-DvrcbwGv.js → aiedit-D9ddlgkM.js} +0 -0
  180. /package/client/dist/assets/{aiedit-WBSjT_C1.js → aiedit-De0SOH6S.js} +0 -0
  181. /package/client/dist/assets/{aiedit-BWxHGsYA.js → aiedit-DrfzQroF.js} +0 -0
  182. /package/client/dist/assets/{aiedit-DOcxERkU.js → aiedit-fMltW101.js} +0 -0
  183. /package/client/dist/assets/{analytics-C9Zc-rkM.js → analytics-BeTyviO8.js} +0 -0
  184. /package/client/dist/assets/{analytics-CrPCZRJ-.js → analytics-C4eEO260.js} +0 -0
  185. /package/client/dist/assets/{analytics-CYj0tfj7.js → analytics-C67cIA1b.js} +0 -0
  186. /package/client/dist/assets/{analytics-C6EzgtdE.js → analytics-CAguvW28.js} +0 -0
  187. /package/client/dist/assets/{analytics-CVx3YOc0.js → analytics-DBtt8Mgk.js} +0 -0
  188. /package/client/dist/assets/{analytics-CnY4kNG3.js → analytics-DUPtODxX.js} +0 -0
  189. /package/client/dist/assets/{analytics-BIdr0YfL.js → analytics-YIpQvPAc.js} +0 -0
  190. /package/client/dist/assets/{apex-Cw8_REBo.js → apex-BLUBIldB.js} +0 -0
  191. /package/client/dist/assets/{attachments-DYHGA2Dj.js → attachments-CCWasu-P.js} +0 -0
  192. /package/client/dist/assets/{attachments-Dd92KpUH.js → attachments-CHaDUfjB.js} +0 -0
  193. /package/client/dist/assets/{attachments-DzdU6DV6.js → attachments-CVSAbGNl.js} +0 -0
  194. /package/client/dist/assets/{attachments-Bcf6BG6V.js → attachments-Chg5poG1.js} +0 -0
  195. /package/client/dist/assets/{attachments-BW4L3l2L.js → attachments-DazTVJbH.js} +0 -0
  196. /package/client/dist/assets/{attachments-COcrGRFz.js → attachments-Dn-JImAK.js} +0 -0
  197. /package/client/dist/assets/{attachments-Bke8sCU4.js → attachments-LDA9kp2X.js} +0 -0
  198. /package/client/dist/assets/{azcli-Cz6HAoOw.js → azcli-DuWxh9mO.js} +0 -0
  199. /package/client/dist/assets/{bat-CcJ-xyqL.js → bat-UKoTejQm.js} +0 -0
  200. /package/client/dist/assets/{bicep-z1WDCKYz.js → bicep-4sTT4B3D.js} +0 -0
  201. /package/client/dist/assets/{browser-DGITz3fC.js → browser-BDd1dbFa.js} +0 -0
  202. /package/client/dist/assets/{browser-JsAIGCEW.js → browser-BWSgbfdX.js} +0 -0
  203. /package/client/dist/assets/{browser-M5-rbPlw.js → browser-D2Y_UAKA.js} +0 -0
  204. /package/client/dist/assets/{browser-BlYF4OOq.js → browser-DH9SGVfM.js} +0 -0
  205. /package/client/dist/assets/{browser-Bc-YdlVg.js → browser-DWOVYMlg.js} +0 -0
  206. /package/client/dist/assets/{browser-CT-ReZGt.js → browser-Dxc_VIRK.js} +0 -0
  207. /package/client/dist/assets/{browser-5ErDlJoR.js → browser-lTQwcDCI.js} +0 -0
  208. /package/client/dist/assets/{cameligo-BRewOpfa.js → cameligo-CAAryRYO.js} +0 -0
  209. /package/client/dist/assets/{chat-DwUm6W9z.js → chat-BO9MvVID.js} +0 -0
  210. /package/client/dist/assets/{chat-BEGuC03z.js → chat-CPgmgZOj.js} +0 -0
  211. /package/client/dist/assets/{chat-CboQguCi.js → chat-CUrG1eVg.js} +0 -0
  212. /package/client/dist/assets/{chat-DRCa9pOt.js → chat-CvOOKB2s.js} +0 -0
  213. /package/client/dist/assets/{chat-BEW60P_u.js → chat-DIh3hr6y.js} +0 -0
  214. /package/client/dist/assets/{chat-yoXwguQu.js → chat-UVVZqA57.js} +0 -0
  215. /package/client/dist/assets/{chat-BQNMD0PL.js → chat-mPn3UlMl.js} +0 -0
  216. /package/client/dist/assets/{clojure-DBjRWN6g.js → clojure-BlMERO1w.js} +0 -0
  217. /package/client/dist/assets/{clsx-DnqN-uhr.js → clsx-CnH-HMk3.js} +0 -0
  218. /package/client/dist/assets/{coffee-Cfk_XHGR.js → coffee-Cj8D-Wl1.js} +0 -0
  219. /package/client/dist/assets/{commands-sqrqsxyE.js → commands-B-MVT-2F.js} +0 -0
  220. /package/client/dist/assets/{commands-UD1NzmwX.js → commands-B0yFTp7e.js} +0 -0
  221. /package/client/dist/assets/{commands-DLrvnPNg.js → commands-BR1kDkHQ.js} +0 -0
  222. /package/client/dist/assets/{commands-CJxCry-o.js → commands-Cb21pDlG.js} +0 -0
  223. /package/client/dist/assets/{commands-CfgY-_of.js → commands-DWgp-8W1.js} +0 -0
  224. /package/client/dist/assets/{commands-B772IyDa.js → commands-ddsl1V91.js} +0 -0
  225. /package/client/dist/assets/{commands-BDDp6xFG.js → commands-t4frzhB0.js} +0 -0
  226. /package/client/dist/assets/{common-Dmm1GhdD.js → common-5ilvMOcH.js} +0 -0
  227. /package/client/dist/assets/{common-DltqHaAe.js → common-B4sqsKp7.js} +0 -0
  228. /package/client/dist/assets/{common-GbpxfPG8.js → common-BKpVwUIf.js} +0 -0
  229. /package/client/dist/assets/{common-DeDELLZJ.js → common-BzEC3kJU.js} +0 -0
  230. /package/client/dist/assets/{common-DnjcgkPH.js → common-CALKUpYm.js} +0 -0
  231. /package/client/dist/assets/{common-Dard9UNH.js → common-CTEbWVZS.js} +0 -0
  232. /package/client/dist/assets/{common-DCr6VzJ7.js → common-DQiza2Xp.js} +0 -0
  233. /package/client/dist/assets/{cpp-BVob6BaP.js → cpp-BPfKnaj_.js} +0 -0
  234. /package/client/dist/assets/{csharp-C4fbRuOu.js → csharp-gX-x5uD6.js} +0 -0
  235. /package/client/dist/assets/{csp-DthFP_vT.js → csp-DKGVt8SM.js} +0 -0
  236. /package/client/dist/assets/{css-CGMH0hcW.js → css-CPMdnAVq.js} +0 -0
  237. /package/client/dist/assets/{cypher-Pnf68BRV.js → cypher-ClMDrj9S.js} +0 -0
  238. /package/client/dist/assets/{dart-PMMOtxZX.js → dart-C4zbzpVv.js} +0 -0
  239. /package/client/dist/assets/{dashboard-BZBADHSj.js → dashboard--Y6yzMlf.js} +0 -0
  240. /package/client/dist/assets/{dashboard-I19DXBxw.js → dashboard--a4-6oYE.js} +0 -0
  241. /package/client/dist/assets/{dashboard-CB6Le1yN.js → dashboard-BiJ3CDTG.js} +0 -0
  242. /package/client/dist/assets/{dashboard-B4ixDVk8.js → dashboard-CiXjk63Z.js} +0 -0
  243. /package/client/dist/assets/{dashboard-C1MfeUHs.js → dashboard-Cx5VjCea.js} +0 -0
  244. /package/client/dist/assets/{dashboard-C7SK6xu5.js → dashboard-D7jg25XR.js} +0 -0
  245. /package/client/dist/assets/{dashboard-CoTpMOBM.js → dashboard-DpGYK2s1.js} +0 -0
  246. /package/client/dist/assets/{dockerfile-di1nsJCc.js → dockerfile-D9xw73D1.js} +0 -0
  247. /package/client/dist/assets/{ecl-D_WVcB5M.js → ecl-gqO8tIR9.js} +0 -0
  248. /package/client/dist/assets/{editor.api2-XLGzZfbc.js → editor.api2-BPnIxMjz.js} +0 -0
  249. /package/client/dist/assets/{elixir-OAdJEMOn.js → elixir-DSAhVF3_.js} +0 -0
  250. /package/client/dist/assets/{explore-D2EFgt8J.js → explore-BE5UmlbD.js} +0 -0
  251. /package/client/dist/assets/{explore-BV5Xxlsn.js → explore-BmTaI8dX.js} +0 -0
  252. /package/client/dist/assets/{explore-A8Ltoblq.js → explore-CCwkqoWq.js} +0 -0
  253. /package/client/dist/assets/{explore-4mFpnrKU.js → explore-CMdEoPDx.js} +0 -0
  254. /package/client/dist/assets/{explore-C3FSE42C.js → explore-CtdCL4QU.js} +0 -0
  255. /package/client/dist/assets/{explore-B9A3iN2W.js → explore-DHjxSkqQ.js} +0 -0
  256. /package/client/dist/assets/{explore-BrBJvfjP.js → explore-__BeALjE.js} +0 -0
  257. /package/client/dist/assets/{flow9-D3QEZjgn.js → flow9-DeQCSPOd.js} +0 -0
  258. /package/client/dist/assets/{format-command-CwGuwzGA.js → format-command-2VNoNnMv.js} +0 -0
  259. /package/client/dist/assets/{fsharp-BF0k_8N8.js → fsharp-CEfaXL-S.js} +0 -0
  260. /package/client/dist/assets/{go-BAQO5Jsz.js → go-Xp1OkZCh.js} +0 -0
  261. /package/client/dist/assets/{graphql-hdFVFkiV.js → graphql-BwRXrUwe.js} +0 -0
  262. /package/client/dist/assets/{hcl-DWnl1o-X.js → hcl-u06DtVFk.js} +0 -0
  263. /package/client/dist/assets/{ini-CB-6OVu3.js → ini-AmeIpFND.js} +0 -0
  264. /package/client/dist/assets/{java-d1CmfiHX.js → java-CyDbRQjX.js} +0 -0
  265. /package/client/dist/assets/{jobs-DPPT6bV6.js → jobs-8viuHLDV.js} +0 -0
  266. /package/client/dist/assets/{jobs-3j3_npyo.js → jobs-AW2eB5D-.js} +0 -0
  267. /package/client/dist/assets/{jobs-2N3RXDAM.js → jobs-BSm89DL5.js} +0 -0
  268. /package/client/dist/assets/{jobs-BqEbCCxD.js → jobs-BZ3sQHjZ.js} +0 -0
  269. /package/client/dist/assets/{jobs-cHYInoau.js → jobs-Bd8AdOTb.js} +0 -0
  270. /package/client/dist/assets/{jobs-DRzjWI9u.js → jobs-CRtsq_u0.js} +0 -0
  271. /package/client/dist/assets/{jobs-2f6Hdc72.js → jobs-CSRwFQ6K.js} +0 -0
  272. /package/client/dist/assets/{jobs-vGfzIDQa.js → jobs-CbEl7WMI.js} +0 -0
  273. /package/client/dist/assets/{julia-Bgv08lKa.js → julia-BqialFRG.js} +0 -0
  274. /package/client/dist/assets/{kotlin-u98kaVTf.js → kotlin-Dzz8TWAt.js} +0 -0
  275. /package/client/dist/assets/{less-CjYwpgg5.js → less-DHRJD3TR.js} +0 -0
  276. /package/client/dist/assets/{lexon-YTjaAFBB.js → lexon-5Y3QgTmT.js} +0 -0
  277. /package/client/dist/assets/{lua-BzmkWv27.js → lua-sKvhfPn5.js} +0 -0
  278. /package/client/dist/assets/{m3-CFwk9fw0.js → m3-DWDVwkFG.js} +0 -0
  279. /package/client/dist/assets/{markdown-CR5iMpSZ.js → markdown-CD_aSBxW.js} +0 -0
  280. /package/client/dist/assets/{mips-CcEalc17.js → mips-687T03hg.js} +0 -0
  281. /package/client/dist/assets/{msdax-BQbkawnr.js → msdax-C1St-dIV.js} +0 -0
  282. /package/client/dist/assets/{mysql-GTlaaW_P.js → mysql-BG7r8oBS.js} +0 -0
  283. /package/client/dist/assets/{nav-C2YXcbZS.js → nav-B05EYB0b.js} +0 -0
  284. /package/client/dist/assets/{nav-D2bOGSEg.js → nav-BNGCq-0y.js} +0 -0
  285. /package/client/dist/assets/{nav-BEL3MTwK.js → nav-BRInPX8a.js} +0 -0
  286. /package/client/dist/assets/{nav-CtYwmMgu.js → nav-Bf87DRHD.js} +0 -0
  287. /package/client/dist/assets/{nav-iH1V5j6o.js → nav-BkVzzFpc.js} +0 -0
  288. /package/client/dist/assets/{nav-0fwkrgHt.js → nav-BzFLtS1W.js} +0 -0
  289. /package/client/dist/assets/{nav-ClzOE4mA.js → nav-DBDbQOYn.js} +0 -0
  290. /package/client/dist/assets/{nav-B_G-TJDW.js → nav-X9sVtUWC.js} +0 -0
  291. /package/client/dist/assets/{objective-c-Byu1T5if.js → objective-c-Ds1-m05L.js} +0 -0
  292. /package/client/dist/assets/{pascal-BrfzBfRm.js → pascal-BKK9FpIi.js} +0 -0
  293. /package/client/dist/assets/{pascaligo-BXXKFUeo.js → pascaligo-SRS3nwtO.js} +0 -0
  294. /package/client/dist/assets/{perl-B3OikKq-.js → perl-B2hTOlrF.js} +0 -0
  295. /package/client/dist/assets/{pgsql-CTsa0Acc.js → pgsql-DIQJYNpL.js} +0 -0
  296. /package/client/dist/assets/{php-DiQh3FUW.js → php-BEaVe8X2.js} +0 -0
  297. /package/client/dist/assets/{pla-92uH8Fzm.js → pla-oPLHpZ-Q.js} +0 -0
  298. /package/client/dist/assets/{postiats-BbeWkKUr.js → postiats-D_vzrAzD.js} +0 -0
  299. /package/client/dist/assets/{powerquery-DgDMzpsm.js → powerquery-BKG6w-FH.js} +0 -0
  300. /package/client/dist/assets/{powershell-BfdUUzaG.js → powershell-B3dLhDt4.js} +0 -0
  301. /package/client/dist/assets/{protobuf-BojW2ftW.js → protobuf-DC8SGjcl.js} +0 -0
  302. /package/client/dist/assets/{pug-BxqTg3IU.js → pug-D5E-4fI0.js} +0 -0
  303. /package/client/dist/assets/{qsharp-BX_A-MW9.js → qsharp-6vJAWv0x.js} +0 -0
  304. /package/client/dist/assets/{r-D9BMnxvJ.js → r-CDwsEcbM.js} +0 -0
  305. /package/client/dist/assets/{redis-5cJqEQJJ.js → redis-CuQbbESS.js} +0 -0
  306. /package/client/dist/assets/{redshift-d8BBqiwb.js → redshift-B9e1k-qI.js} +0 -0
  307. /package/client/dist/assets/{restructuredtext-C8a6yIcZ.js → restructuredtext-BiJ5IwaU.js} +0 -0
  308. /package/client/dist/assets/{ruby-egeh-6KX.js → ruby-B0UAHY9b.js} +0 -0
  309. /package/client/dist/assets/{rust-a3r9IInB.js → rust-Dg_spmFr.js} +0 -0
  310. /package/client/dist/assets/{sb-y8iRIDei.js → sb-DjU66I8Q.js} +0 -0
  311. /package/client/dist/assets/{scala-BPDK2AmK.js → scala-qvStIdfG.js} +0 -0
  312. /package/client/dist/assets/{scheme-BIWUEoOs.js → scheme-FstEk5Rh.js} +0 -0
  313. /package/client/dist/assets/{scss-CA-PSzwg.js → scss-w0U3rQLK.js} +0 -0
  314. /package/client/dist/assets/{settings-CTcwN9RE.js → settings-5tzo0Rn3.js} +0 -0
  315. /package/client/dist/assets/{settings-D_dujJZI.js → settings-BDAW3trC.js} +0 -0
  316. /package/client/dist/assets/{settings-Bg0A3zoS.js → settings-BEWv3VEu.js} +0 -0
  317. /package/client/dist/assets/{settings-BgPqg2nv.js → settings-BORg56um.js} +0 -0
  318. /package/client/dist/assets/{settings-BSze3_9q.js → settings-D3LurcR5.js} +0 -0
  319. /package/client/dist/assets/{settings-CSJ0ahZ8.js → settings-DcqWIEM6.js} +0 -0
  320. /package/client/dist/assets/{settings-DYIV89nV.js → settings-Dfz8QbZS.js} +0 -0
  321. /package/client/dist/assets/{settings-DDcfx_ca.js → settings-yMubjqYw.js} +0 -0
  322. /package/client/dist/assets/{shell--LiT1Bja.js → shell-DJ78wREd.js} +0 -0
  323. /package/client/dist/assets/{solidity-DdqZccZg.js → solidity-1aGIVsdX.js} +0 -0
  324. /package/client/dist/assets/{sophia-S6-YxNG_.js → sophia-40LqcGjB.js} +0 -0
  325. /package/client/dist/assets/{sparql-BSf5kMp2.js → sparql-Cz5dqG_g.js} +0 -0
  326. /package/client/dist/assets/{sql-D7KgjR8G.js → sql-64f62Ni4.js} +0 -0
  327. /package/client/dist/assets/{st-BnoDa-Ml.js → st-gJe2yG8J.js} +0 -0
  328. /package/client/dist/assets/{swift-DEUHTkUX.js → swift-C6ME22mv.js} +0 -0
  329. /package/client/dist/assets/{systemverilog-Tqb_KPnW.js → systemverilog-CEWz259w.js} +0 -0
  330. /package/client/dist/assets/{tcl-BmBFS2qq.js → tcl-CcLVIi3m.js} +0 -0
  331. /package/client/dist/assets/{terminal-Bje4ziIa.js → terminal-BYtreaaF.js} +0 -0
  332. /package/client/dist/assets/{terminal-CSONJOex.js → terminal-C0xx0SjA.js} +0 -0
  333. /package/client/dist/assets/{terminal-DeWzh6ys.js → terminal-CPpK58RC.js} +0 -0
  334. /package/client/dist/assets/{terminal-C2WYcFHF.js → terminal-CdxkpafL.js} +0 -0
  335. /package/client/dist/assets/{terminal-DEqzGtcr.js → terminal-Ciia0wh2.js} +0 -0
  336. /package/client/dist/assets/{terminal-80yDMgMF.js → terminal-DHIkiWcs.js} +0 -0
  337. /package/client/dist/assets/{terminal-lkZYR4wJ.js → terminal-DY42QANg.js} +0 -0
  338. /package/client/dist/assets/{terminal-YOlsJCQj.js → terminal-DoxtVdma.js} +0 -0
  339. /package/client/dist/assets/{tickets-DYvafSaY.js → tickets-0rM0lIXd.js} +0 -0
  340. /package/client/dist/assets/{tickets-DNOANUXr.js → tickets-1UIGf_oA.js} +0 -0
  341. /package/client/dist/assets/{tickets-DlpC_iTg.js → tickets-9kdPXInd.js} +0 -0
  342. /package/client/dist/assets/{tickets-CF2PYelu.js → tickets-C6pwZwt4.js} +0 -0
  343. /package/client/dist/assets/{tickets-CB7N30gm.js → tickets-DAjtxAVb.js} +0 -0
  344. /package/client/dist/assets/{tickets-DU1aqsbr.js → tickets-DNmXcAwu.js} +0 -0
  345. /package/client/dist/assets/{tickets-clefmXLv.js → tickets-n23kDqJT.js} +0 -0
  346. /package/client/dist/assets/{tickets-DucYgtdl.js → tickets-tGx5AR5b.js} +0 -0
  347. /package/client/dist/assets/{twig-BQV8igWC.js → twig-DvsO-WjW.js} +0 -0
  348. /package/client/dist/assets/{typespec-DlFroUGY.js → typespec-Brkt3IAA.js} +0 -0
  349. /package/client/dist/assets/{vb-BlrJpIMX.js → vb-r121Uzxt.js} +0 -0
  350. /package/client/dist/assets/{wgsl-BWgIc6FZ.js → wgsl-BRX8uYh4.js} +0 -0
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ // Shared helpers and the deps contract for the project-router domain modules.
3
+ // Extracted verbatim from project-router.ts when that monolith was split into
4
+ // per-domain register functions. Behaviour-preserving — same exports, same code.
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.VALID_MODEL_ALIASES = exports.TERMINAL_PANEL_ENABLED = void 0;
10
+ exports.stripSpecMetadataSections = stripSpecMetadataSections;
11
+ exports.extractShortSummary = extractShortSummary;
12
+ exports.deriveFallbackShortSummary = deriveFallbackShortSummary;
13
+ exports.lightlyStructurePrompt = lightlyStructurePrompt;
14
+ exports.formatDescriptionWithCriteria = formatDescriptionWithCriteria;
15
+ exports.resolveDefaultSpecModel = resolveDefaultSpecModel;
16
+ exports.readAgentModels = readAgentModels;
17
+ exports.applyModelConfig = applyModelConfig;
18
+ exports.serializeInstallConfigYaml = serializeInstallConfigYaml;
19
+ const fs_1 = __importDefault(require("fs"));
20
+ const path_1 = __importDefault(require("path"));
21
+ const core_package_1 = require("./core-package");
22
+ const spec_models_1 = require("./spec-models");
23
+ const ticket_store_1 = require("./ticket-store");
24
+ const TERMINAL_PANEL_ENABLED = process.env.SPECRAILS_TERMINAL_PANEL !== 'false';
25
+ exports.TERMINAL_PANEL_ENABLED = TERMINAL_PANEL_ENABLED;
26
+ // ─── YAML helpers ─────────────────────────────────────────────────────────────
27
+ /**
28
+ * Serialize install config to YAML matching specrails-core's tui-installer format exactly.
29
+ */
30
+ function serializeInstallConfigYaml(config) {
31
+ const c = config;
32
+ const overrides = c.models?.overrides ?? {};
33
+ const overridesEntries = Object.entries(overrides);
34
+ const overridesYaml = overridesEntries.length > 0
35
+ ? '\n' + overridesEntries.map(([k, v]) => ` ${k}: ${v}`).join('\n')
36
+ : ' {}';
37
+ const lines = [
38
+ '# specrails install config — generated by Specrails',
39
+ `# Re-run: npx ${core_package_1.CORE_PACKAGE_SPEC} init to regenerate`,
40
+ `version: ${c.version ?? 1}`,
41
+ `provider: ${c.provider ?? 'claude'}`,
42
+ `tier: ${c.tier ?? 'quick'}`,
43
+ `agents:`,
44
+ ` selected: [${(c.agents?.selected ?? []).join(', ')}]`,
45
+ ` excluded: [${(c.agents?.excluded ?? []).join(', ')}]`,
46
+ `models:`,
47
+ ` preset: ${c.models?.preset ?? 'balanced'}`,
48
+ ` defaults: { model: ${c.models?.defaults?.model ?? 'sonnet'} }`,
49
+ ` overrides:${overridesYaml}`,
50
+ `agent_teams: ${c.agent_teams ?? false}`,
51
+ '',
52
+ ];
53
+ return lines.join('\n');
54
+ }
55
+ // ─── Agent model helpers ──────────────────────────────────────────────────────
56
+ const VALID_MODEL_ALIASES = ['sonnet', 'opus', 'haiku'];
57
+ exports.VALID_MODEL_ALIASES = VALID_MODEL_ALIASES;
58
+ /**
59
+ * Read installed agents from `.claude/agents/*.md` (top-level only, no subdirs).
60
+ * Extracts the `model:` field from YAML frontmatter.
61
+ */
62
+ function readAgentModels(projectPath) {
63
+ const agentsDir = path_1.default.join(projectPath, '.claude', 'agents');
64
+ if (!fs_1.default.existsSync(agentsDir))
65
+ return [];
66
+ let entries;
67
+ try {
68
+ entries = fs_1.default.readdirSync(agentsDir);
69
+ }
70
+ catch {
71
+ return [];
72
+ }
73
+ const agents = [];
74
+ for (const entry of entries) {
75
+ // Top-level .md files only — skip subdirs
76
+ if (!entry.endsWith('.md'))
77
+ continue;
78
+ const filePath = path_1.default.join(agentsDir, entry);
79
+ try {
80
+ const stat = fs_1.default.statSync(filePath);
81
+ if (!stat.isFile())
82
+ continue;
83
+ const content = fs_1.default.readFileSync(filePath, 'utf-8');
84
+ // Extract model from YAML frontmatter between --- markers
85
+ const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
86
+ if (!frontmatterMatch)
87
+ continue;
88
+ const frontmatter = frontmatterMatch[1];
89
+ const modelMatch = frontmatter.match(/^model:\s*(.+)$/m);
90
+ const model = modelMatch ? modelMatch[1].trim() : 'sonnet';
91
+ agents.push({ name: entry.slice(0, -3), model });
92
+ }
93
+ catch {
94
+ // skip unreadable files
95
+ }
96
+ }
97
+ return agents;
98
+ }
99
+ /**
100
+ * Read `.specrails/install-config.yaml` and patch the `model:` line in each
101
+ * `.claude/agents/*.md` frontmatter to match the config's defaults/overrides.
102
+ * No-op if the config file does not exist.
103
+ */
104
+ function applyModelConfig(projectPath) {
105
+ const configPath = path_1.default.join(projectPath, '.specrails', 'install-config.yaml');
106
+ if (!fs_1.default.existsSync(configPath))
107
+ return;
108
+ let configText;
109
+ try {
110
+ configText = fs_1.default.readFileSync(configPath, 'utf-8');
111
+ }
112
+ catch {
113
+ return;
114
+ }
115
+ // Parse defaults.model
116
+ const defaultsMatch = configText.match(/defaults:\s*\{\s*model:\s*(\S+)\s*\}/);
117
+ const defaultModel = defaultsMatch ? defaultsMatch[1] : 'sonnet';
118
+ // Parse overrides block — lines like ` agentname: alias`
119
+ const overrides = {};
120
+ const overridesBlockMatch = configText.match(/overrides:([\s\S]*?)(?:\n\S|$)/);
121
+ if (overridesBlockMatch) {
122
+ const block = overridesBlockMatch[1];
123
+ const overrideLines = block.match(/^ {2,}(\S+):\s*(\S+)/gm) ?? [];
124
+ for (const line of overrideLines) {
125
+ const m = line.match(/^\s+(\S+):\s*(\S+)/);
126
+ if (m)
127
+ overrides[m[1]] = m[2];
128
+ }
129
+ }
130
+ const agentsDir = path_1.default.join(projectPath, '.claude', 'agents');
131
+ if (!fs_1.default.existsSync(agentsDir))
132
+ return;
133
+ let entries;
134
+ try {
135
+ entries = fs_1.default.readdirSync(agentsDir);
136
+ }
137
+ catch {
138
+ return;
139
+ }
140
+ for (const entry of entries) {
141
+ if (!entry.endsWith('.md'))
142
+ continue;
143
+ const filePath = path_1.default.join(agentsDir, entry);
144
+ try {
145
+ const stat = fs_1.default.statSync(filePath);
146
+ if (!stat.isFile())
147
+ continue;
148
+ const agentName = entry.slice(0, -3);
149
+ const targetModel = overrides[agentName] ?? defaultModel;
150
+ const content = fs_1.default.readFileSync(filePath, 'utf-8');
151
+ const patched = content.replace(/^model: .+$/m, `model: ${targetModel}`);
152
+ if (patched !== content) {
153
+ fs_1.default.writeFileSync(filePath, patched, 'utf-8');
154
+ }
155
+ }
156
+ catch {
157
+ // skip unwritable files
158
+ }
159
+ }
160
+ }
161
+ /**
162
+ * Strip the metadata sections (Spec Title, Labels, Estimated Complexity,
163
+ * Short Summary) from a generate-spec LLM response so they don't end up
164
+ * duplicated inside the ticket's description — they're already parsed into
165
+ * dedicated fields and re-rendered by the UI.
166
+ */
167
+ function stripSpecMetadataSections(buffer) {
168
+ return buffer
169
+ .replace(/##\s*Spec Title\s*\n+[^\n]*\n*/i, '')
170
+ .replace(/##\s*Labels\s*\n+(?:[^\n]+(?:\n(?!##)[^\n]+)*)\n*/i, '')
171
+ .replace(/##\s*Estimated Complexity\s*\n+(?:[^\n]+(?:\n(?!##)[^\n]+)*)\n*/i, '')
172
+ .replace(/##\s*Short Summary\s*\n+(?:[^\n]+(?:\n(?!##)[^\n]+)*)\n*/i, '')
173
+ .trim();
174
+ }
175
+ /**
176
+ * Extract the `## Short Summary` section body from a generate-spec response.
177
+ * Returns the raw multi-line string (the ticket-store `clampShortSummary`
178
+ * helper applies trim, control-strip, and the 240-char hard cap before
179
+ * persistence). Returns `null` when the section is missing or empty.
180
+ */
181
+ function extractShortSummary(buffer) {
182
+ const m = buffer.match(/##\s*Short Summary\s*\n+((?:(?!##)[^\n]+(?:\n(?!##)[^\n]+)*))/i);
183
+ if (!m)
184
+ return null;
185
+ const body = m[1].trim();
186
+ return body.length > 0 ? body : null;
187
+ }
188
+ function deriveFallbackShortSummary(title, description) {
189
+ const plain = description
190
+ .replace(/```[\s\S]*?```/g, ' ')
191
+ .replace(/^#{1,6}\s+.*$/gm, ' ')
192
+ .replace(/^\s*[-*]\s+/gm, '')
193
+ .replace(/\[[^\]]+\]\([^)]+\)/g, (m) => m.match(/\[([^\]]+)\]/)?.[1] ?? '')
194
+ .replace(/[`*_>]/g, '')
195
+ .replace(/\s+/g, ' ')
196
+ .trim();
197
+ const source = plain || title.trim();
198
+ if (!source)
199
+ return null;
200
+ const sentence = source.match(/^(.{24,}?[.!?])(?:\s|$)/)?.[1] ?? source;
201
+ const capped = sentence.length > 160 ? `${sentence.slice(0, 157).trimEnd()}...` : sentence;
202
+ return (0, ticket_store_1.clampShortSummary)(capped);
203
+ }
204
+ /**
205
+ * Lightly structure a raw free-form prompt (the "Raw" Add-Spec mode) when the
206
+ * user opts in. We deliberately do NOT fabricate spec sections — a raw prompt
207
+ * stays the user's own words. The only transform: when the body has no leading
208
+ * markdown heading, prefix a single neutral `## Overview` heading so the spec
209
+ * renders with a section title downstream. Returns the (trimmed) input
210
+ * unchanged when it already starts with a heading or is empty.
211
+ */
212
+ function lightlyStructurePrompt(text) {
213
+ const trimmed = text.trim();
214
+ if (!trimmed)
215
+ return trimmed;
216
+ if (/^#{1,6}\s+/.test(trimmed))
217
+ return trimmed;
218
+ return `## Overview\n\n${trimmed}`;
219
+ }
220
+ /**
221
+ * Fold an `acceptanceCriteria` array into a ticket description body, writing
222
+ * (or replacing) a `## Acceptance Criteria` section.
223
+ *
224
+ * - `criteria.length > 0` → append/replace the section with one `- bullet` per item
225
+ * - `criteria.length === 0` → strip any existing `## Acceptance Criteria` section
226
+ *
227
+ * The match is case-insensitive on the heading text but requires `##` exactly
228
+ * to avoid matching other heading levels.
229
+ *
230
+ * Shared by `POST /tickets/from-draft` and `PATCH /tickets/:id`. See
231
+ * openspec/changes/replace-ai-edit-with-continue-editing/design.md D3+D4.
232
+ */
233
+ function formatDescriptionWithCriteria(body, criteria) {
234
+ const sectionRegex = /\n*##\s*Acceptance Criteria\s*\n[\s\S]*?(?=\n##\s|\n*$)/i;
235
+ const withoutExisting = body.replace(sectionRegex, '').replace(/\s+$/, '');
236
+ if (criteria.length === 0)
237
+ return withoutExisting;
238
+ const section = `## Acceptance Criteria\n\n${criteria.map((c) => `- ${c}`).join('\n')}`;
239
+ if (withoutExisting === '')
240
+ return section;
241
+ return `${withoutExisting}\n\n${section}`;
242
+ }
243
+ /**
244
+ * Resolve the default model used by Add Spec for a project.
245
+ *
246
+ * Order:
247
+ * 1. `models.defaults.model` from `<project>/.specrails/install-config.yaml`,
248
+ * if it parses AND is in the provider allow-list.
249
+ * 2. Provider default from `PROVIDER_DEFAULT_MODEL` (`sonnet` / `gpt-5.5`).
250
+ *
251
+ * Logs a warning when the configured value exists but is not valid for the
252
+ * project's provider.
253
+ */
254
+ function resolveDefaultSpecModel(args) {
255
+ const { projectPath, provider } = args;
256
+ const configPath = path_1.default.join(projectPath, '.specrails', 'install-config.yaml');
257
+ if (!fs_1.default.existsSync(configPath))
258
+ return (0, spec_models_1.getProviderDefault)(provider);
259
+ let configText;
260
+ try {
261
+ configText = fs_1.default.readFileSync(configPath, 'utf-8');
262
+ }
263
+ catch {
264
+ return (0, spec_models_1.getProviderDefault)(provider);
265
+ }
266
+ const defaultsMatch = configText.match(/defaults:\s*\{\s*model:\s*(\S+?)\s*\}/);
267
+ const configured = defaultsMatch ? defaultsMatch[1] : null;
268
+ if (!configured)
269
+ return (0, spec_models_1.getProviderDefault)(provider);
270
+ if (!(0, spec_models_1.isValidModelForProvider)(configured, provider)) {
271
+ console.warn(`[project-router] resolveDefaultSpecModel: configured model "${configured}" is not valid for provider "${provider}" — falling back to provider default`);
272
+ return (0, spec_models_1.getProviderDefault)(provider);
273
+ }
274
+ return configured;
275
+ }
@@ -0,0 +1,389 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerJobsRoutes = registerJobsRoutes;
4
+ const db_1 = require("./db");
5
+ const queue_manager_1 = require("./queue-manager");
6
+ const types_1 = require("./types");
7
+ const hooks_1 = require("./hooks");
8
+ const explore_smash_1 = require("./explore-smash");
9
+ const spec_models_1 = require("./spec-models");
10
+ const provider_selection_1 = require("./provider-selection");
11
+ const metrics_1 = require("./metrics");
12
+ const ticket_store_1 = require("./ticket-store");
13
+ const project_router_helpers_1 = require("./project-router-helpers");
14
+ function registerJobsRoutes(deps) {
15
+ const { router, registry, ctx, ticketPath } = deps;
16
+ // ─── Queue / Spawn routes ────────────────────────────────────────────────────
17
+ router.post('/:projectId/spawn', (req, res) => {
18
+ const { command, priority, dependsOnJobId, pipelineId, profileName, aiEngine } = req.body ?? {};
19
+ if (!command || typeof command !== 'string' || !command.trim()) {
20
+ res.status(400).json({ error: 'command is required' });
21
+ return;
22
+ }
23
+ if (priority !== undefined && !types_1.VALID_PRIORITIES.has(priority)) {
24
+ res.status(400).json({ error: 'priority must be one of: low, normal, high, critical' });
25
+ return;
26
+ }
27
+ // profileName accepts: undefined (default resolution), null (force legacy), string (explicit)
28
+ const normalizedProfileName = profileName === null ? null
29
+ : typeof profileName === 'string' && profileName.trim() ? profileName.trim()
30
+ : undefined;
31
+ // aiEngine: optional per-job provider override; must be installed on the
32
+ // project. Omitting it runs on the project's primary provider.
33
+ const engineCheck = (0, provider_selection_1.validateRequestedProvider)(ctx(req).project, aiEngine);
34
+ if (!engineCheck.ok) {
35
+ res.status(400).json({ error: engineCheck.error });
36
+ return;
37
+ }
38
+ try {
39
+ const job = ctx(req).queueManager.enqueue(command, priority ?? 'normal', {
40
+ dependsOnJobId: dependsOnJobId || undefined,
41
+ pipelineId: pipelineId || undefined,
42
+ profileName: normalizedProfileName,
43
+ provider: aiEngine ? engineCheck.provider : undefined,
44
+ });
45
+ const position = job.queuePosition ?? 0;
46
+ res.status(202).json({ jobId: job.id, position });
47
+ }
48
+ catch (err) {
49
+ if (err instanceof queue_manager_1.ClaudeNotFoundError) {
50
+ res.status(400).json({ error: err.message });
51
+ }
52
+ else {
53
+ console.error('[project-router] spawn error:', err);
54
+ res.status(500).json({ error: 'Internal server error' });
55
+ }
56
+ }
57
+ });
58
+ // ─── Pipeline routes ──────────────────────────────────────────────────────────
59
+ // NOTE: Ad-hoc pipeline creation removed — use rails (templates) instead.
60
+ // The GET route remains for viewing existing pipeline status.
61
+ router.get('/:projectId/pipelines/:pipelineId', (req, res) => {
62
+ const { db } = ctx(req);
63
+ const pipelineId = req.params.pipelineId;
64
+ const jobs = (0, db_1.getPipelineJobs)(db, pipelineId);
65
+ if (jobs.length === 0) {
66
+ res.status(404).json({ error: 'Pipeline not found' });
67
+ return;
68
+ }
69
+ const allCompleted = jobs.every(j => j.status === 'completed');
70
+ const anyFailed = jobs.some(j => ['failed', 'skipped', 'canceled', 'zombie_terminated'].includes(j.status));
71
+ const status = allCompleted ? 'completed' : anyFailed ? 'failed' : 'running';
72
+ res.json({ pipelineId, status, jobs });
73
+ });
74
+ router.get('/:projectId/state', (req, res) => {
75
+ const { queueManager, project } = ctx(req);
76
+ res.json({
77
+ projectName: project.name,
78
+ projectId: project.id,
79
+ phases: (0, hooks_1.getPhaseStates)(),
80
+ busy: queueManager.getActiveJobId() !== null,
81
+ currentJobId: queueManager.getActiveJobId(),
82
+ featureFlags: {
83
+ smash: !(0, explore_smash_1.isSpecsSmashKillSwitchActive)(),
84
+ },
85
+ });
86
+ });
87
+ // Returns the resolved default model for Add Spec + the full provider
88
+ // allow-list so the modal can render its picker without maintaining its
89
+ // own copy of the model lists. Source of truth is `server/spec-models.ts`.
90
+ router.get('/:projectId/default-spec-model', (req, res) => {
91
+ const { project } = ctx(req);
92
+ // Multi-provider: an optional ?provider= query selects which engine's models
93
+ // to return. It must be one the project actually has installed; an invalid
94
+ // or omitted value falls back to the project's primary provider. The
95
+ // response also lists every installed provider so the Add Spec modal can
96
+ // render its AI Engine selector without a second round-trip.
97
+ const provider = (0, provider_selection_1.resolveProvider)(project, typeof req.query.provider === 'string' ? req.query.provider : undefined);
98
+ const model = (0, project_router_helpers_1.resolveDefaultSpecModel)({ projectPath: project.path, provider });
99
+ const allowed = (0, spec_models_1.getModelsForProvider)(provider);
100
+ res.json({ model, provider, allowed, providers: project.providers });
101
+ });
102
+ router.delete('/:projectId/jobs/:id', (req, res) => {
103
+ try {
104
+ const result = ctx(req).queueManager.cancel(req.params.id);
105
+ res.json({ ok: true, status: result });
106
+ }
107
+ catch (err) {
108
+ if (err instanceof queue_manager_1.JobNotFoundError) {
109
+ res.status(404).json({ error: 'Job not found' });
110
+ }
111
+ else if (err instanceof queue_manager_1.JobAlreadyTerminalError) {
112
+ // Job already finished — delete it from the DB
113
+ (0, db_1.deleteJob)(ctx(req).db, req.params.id);
114
+ res.json({ ok: true, status: 'deleted' });
115
+ }
116
+ else {
117
+ res.status(500).json({ error: 'Internal server error' });
118
+ }
119
+ }
120
+ });
121
+ router.patch('/:projectId/jobs/:id/priority', (req, res) => {
122
+ const { priority } = req.body ?? {};
123
+ if (!priority || !types_1.VALID_PRIORITIES.has(priority)) {
124
+ res.status(400).json({ error: 'priority must be one of: low, normal, high, critical' });
125
+ return;
126
+ }
127
+ try {
128
+ ctx(req).queueManager.updatePriority(req.params.id, priority);
129
+ res.json({ ok: true });
130
+ }
131
+ catch (err) {
132
+ if (err instanceof queue_manager_1.JobNotFoundError) {
133
+ res.status(404).json({ error: 'Job not found' });
134
+ }
135
+ else {
136
+ res.status(400).json({ error: err.message });
137
+ }
138
+ }
139
+ });
140
+ router.post('/:projectId/queue/pause', (req, res) => {
141
+ ctx(req).queueManager.pause();
142
+ res.json({ ok: true, paused: true });
143
+ });
144
+ router.post('/:projectId/queue/resume', (req, res) => {
145
+ ctx(req).queueManager.resume();
146
+ res.json({ ok: true, paused: false });
147
+ });
148
+ router.put('/:projectId/queue/reorder', (req, res) => {
149
+ const { jobIds } = req.body ?? {};
150
+ if (!Array.isArray(jobIds)) {
151
+ res.status(400).json({ error: 'jobIds must be an array' });
152
+ return;
153
+ }
154
+ try {
155
+ ctx(req).queueManager.reorder(jobIds);
156
+ res.json({ ok: true, queue: jobIds });
157
+ }
158
+ catch (err) {
159
+ res.status(400).json({ error: err.message });
160
+ }
161
+ });
162
+ router.get('/:projectId/queue', (req, res) => {
163
+ const { queueManager } = ctx(req);
164
+ res.json({
165
+ jobs: queueManager.getJobs(),
166
+ paused: queueManager.isPaused(),
167
+ activeJobId: queueManager.getActiveJobId(),
168
+ });
169
+ });
170
+ router.get('/:projectId/jobs', (req, res) => {
171
+ // Clamp to [1, 200] (H-11): a negative limit is LIMIT -1 in SQLite, which
172
+ // means UNLIMITED — without the lower bound `?limit=-1` dumps the whole table.
173
+ const limit = Math.max(1, Math.min(parseInt(String(req.query.limit ?? '50'), 10) || 50, 200));
174
+ const offset = parseInt(String(req.query.offset ?? '0'), 10) || 0;
175
+ const status = req.query.status;
176
+ const from = req.query.from;
177
+ const to = req.query.to;
178
+ const { db } = ctx(req);
179
+ const result = (0, db_1.listJobs)(db, { limit, offset, status, from, to });
180
+ // Merge in-memory queued jobs that haven't been persisted to DB yet
181
+ const { queueManager } = ctx(req);
182
+ const dbIds = new Set(result.jobs.map((j) => j.id));
183
+ const queuedRows = queueManager
184
+ .getJobs()
185
+ .filter((j) => j.status === 'queued' && !dbIds.has(j.id))
186
+ .filter((j) => !status || j.status === status)
187
+ .map((j) => ({
188
+ id: j.id,
189
+ command: j.command,
190
+ started_at: j.startedAt ?? new Date().toISOString(),
191
+ finished_at: j.finishedAt,
192
+ status: j.status,
193
+ exit_code: j.exitCode,
194
+ queue_position: j.queuePosition,
195
+ priority: j.priority,
196
+ tokens_in: null,
197
+ tokens_out: null,
198
+ tokens_cache_read: null,
199
+ tokens_cache_create: null,
200
+ total_cost_usd: null,
201
+ num_turns: null,
202
+ model: null,
203
+ duration_ms: null,
204
+ duration_api_ms: null,
205
+ session_id: null,
206
+ depends_on_job_id: j.dependsOnJobId,
207
+ pipeline_id: j.pipelineId,
208
+ skip_reason: j.skipReason,
209
+ }));
210
+ if (queuedRows.length > 0) {
211
+ result.jobs = [...queuedRows, ...result.jobs];
212
+ result.total += queuedRows.length;
213
+ }
214
+ // Annotate each job with hasTelemetry so the client can show the
215
+ // Export diagnostic button without an extra round trip.
216
+ const jobsWithTelemetry = (0, db_1.getJobsWithTelemetry)(db);
217
+ const annotatedJobs = result.jobs.map((j) => ({
218
+ ...j,
219
+ hasTelemetry: jobsWithTelemetry.has(j.id),
220
+ }));
221
+ res.json({ jobs: annotatedJobs, total: result.total });
222
+ });
223
+ // ─── CSV helper ──────────────────────────────────────────────────────────────
224
+ const toCsv = (headers, rows) => {
225
+ const escape = (v) => {
226
+ const s = v == null ? '' : String(v);
227
+ return s.includes(',') || s.includes('"') || s.includes('\n') ? `"${s.replace(/"/g, '""')}"` : s;
228
+ };
229
+ const lines = [headers.join(',')];
230
+ for (const row of rows) {
231
+ lines.push(headers.map(h => escape(row[h])).join(','));
232
+ }
233
+ return lines.join('\n');
234
+ };
235
+ // ─── Jobs export (must be before /:projectId/jobs/:id) ─────────────────────
236
+ router.get('/:projectId/jobs/export', (req, res) => {
237
+ const format = req.query.format || 'json';
238
+ if (format !== 'json' && format !== 'csv') {
239
+ res.status(400).json({ error: 'Invalid format. Must be json or csv' });
240
+ return;
241
+ }
242
+ const from = req.query.from;
243
+ const to = req.query.to;
244
+ const { db } = ctx(req);
245
+ const conditions = [];
246
+ const params = [];
247
+ if (from) {
248
+ conditions.push('started_at >= ?');
249
+ params.push(from);
250
+ }
251
+ if (to) {
252
+ conditions.push('started_at <= ?');
253
+ params.push(to);
254
+ }
255
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
256
+ const jobs = db
257
+ .prepare(`SELECT * FROM jobs ${where} ORDER BY started_at DESC LIMIT 10000`)
258
+ .all(...params);
259
+ if (format === 'csv') {
260
+ const headers = ['id', 'command', 'status', 'started_at', 'finished_at', 'duration_ms', 'tokens_in', 'tokens_out', 'tokens_cache_read', 'total_cost_usd', 'model'];
261
+ const csv = toCsv(headers, jobs);
262
+ res.setHeader('Content-Type', 'text/csv');
263
+ res.setHeader('Content-Disposition', 'attachment; filename="jobs-export.csv"');
264
+ res.send(csv);
265
+ }
266
+ else {
267
+ res.json({ jobs });
268
+ }
269
+ });
270
+ // Must be registered BEFORE /:projectId/jobs/:id, otherwise Express matches
271
+ // the parameterized route first with id='compare' and this never runs (the
272
+ // Job Comparison feature would always 404).
273
+ router.get('/:projectId/jobs/compare', (req, res) => {
274
+ const raw = req.query.jobIds;
275
+ if (!raw) {
276
+ res.status(400).json({ error: 'jobIds query param required (comma-separated, exactly 2)' });
277
+ return;
278
+ }
279
+ const ids = raw.split(',').map((s) => s.trim()).filter(Boolean);
280
+ if (ids.length !== 2) {
281
+ res.status(400).json({ error: 'Exactly 2 jobIds are required' });
282
+ return;
283
+ }
284
+ const { db } = ctx(req);
285
+ const rows = ids.map((id) => {
286
+ const job = db.prepare('SELECT * FROM jobs WHERE id = ?').get(id);
287
+ if (!job)
288
+ return null;
289
+ const phases = db.prepare("SELECT phase FROM job_phases WHERE job_id = ? AND state = 'done' ORDER BY updated_at ASC").all(id);
290
+ return {
291
+ id: job.id,
292
+ command: job.command,
293
+ status: job.status,
294
+ startedAt: job.started_at,
295
+ finishedAt: job.finished_at,
296
+ durationMs: job.duration_ms,
297
+ tokensIn: job.tokens_in,
298
+ tokensOut: job.tokens_out,
299
+ tokensCacheRead: job.tokens_cache_read,
300
+ totalCostUsd: job.total_cost_usd,
301
+ model: job.model,
302
+ phasesCompleted: phases.map((p) => p.phase),
303
+ };
304
+ });
305
+ const missing = ids.filter((_, i) => rows[i] === null);
306
+ if (missing.length > 0) {
307
+ res.status(404).json({ error: `Jobs not found: ${missing.join(', ')}` });
308
+ return;
309
+ }
310
+ res.json({ jobs: rows });
311
+ });
312
+ router.get('/:projectId/jobs/:id', (req, res) => {
313
+ const { db, queueManager, project } = ctx(req);
314
+ const jobId = req.params.id;
315
+ const job = (0, db_1.getJob)(db, jobId);
316
+ if (!job) {
317
+ // Queued jobs live only in memory until spawn time (createJob runs on spawn,
318
+ // not enqueue). Fall back to the in-memory queue so /jobs/:id returns a
319
+ // usable payload instead of 404 — the detail page then renders a "queued"
320
+ // state and flips to live logs via WS once the job starts.
321
+ const inMemory = queueManager.getJobs().find((j) => j.id === jobId);
322
+ if (!inMemory) {
323
+ res.status(404).json({ error: 'Job not found' });
324
+ return;
325
+ }
326
+ const synthetic = {
327
+ id: inMemory.id,
328
+ command: inMemory.command,
329
+ started_at: inMemory.startedAt ?? '',
330
+ finished_at: inMemory.finishedAt,
331
+ status: inMemory.status,
332
+ exit_code: inMemory.exitCode,
333
+ queue_position: inMemory.queuePosition,
334
+ priority: inMemory.priority,
335
+ tokens_in: null,
336
+ tokens_out: null,
337
+ tokens_cache_read: null,
338
+ tokens_cache_create: null,
339
+ total_cost_usd: null,
340
+ num_turns: null,
341
+ model: null,
342
+ duration_ms: null,
343
+ duration_api_ms: null,
344
+ session_id: null,
345
+ depends_on_job_id: inMemory.dependsOnJobId,
346
+ pipeline_id: inMemory.pipelineId,
347
+ skip_reason: inMemory.skipReason,
348
+ };
349
+ const phaseDefinitions = queueManager.phasesForCommand(synthetic.command);
350
+ const tickets = (0, ticket_store_1.resolveTicketsFromCommand)(project.path, synthetic.command);
351
+ res.json({ job: { ...synthetic, hasTelemetry: false, tickets }, events: [], phaseDefinitions });
352
+ return;
353
+ }
354
+ const events = (0, db_1.getJobEvents)(db, jobId);
355
+ const phaseDefinitions = queueManager.phasesForCommand(job.command);
356
+ const tickets = (0, ticket_store_1.resolveTicketsFromCommand)(project.path, job.command);
357
+ const annotated = { ...job, hasTelemetry: (0, db_1.hasJobTelemetry)(db, jobId), tickets };
358
+ res.json({ job: annotated, events, phaseDefinitions });
359
+ });
360
+ router.delete('/:projectId/jobs', (req, res) => {
361
+ try {
362
+ const { from, to } = req.body ?? {};
363
+ const deleted = (0, db_1.purgeJobs)(ctx(req).db, { from, to });
364
+ res.json({ ok: true, deleted });
365
+ }
366
+ catch (err) {
367
+ console.error('[project-router] purge error:', err);
368
+ res.status(500).json({ error: 'Failed to purge jobs' });
369
+ }
370
+ });
371
+ router.get('/:projectId/activity', (req, res) => {
372
+ const limit = Math.min(Math.max(1, parseInt(String(req.query.limit ?? '50'), 10) || 50), 100);
373
+ const before = req.query.before;
374
+ res.json((0, db_1.getProjectActivity)(ctx(req).db, { limit, before }));
375
+ });
376
+ router.get('/:projectId/stats', (req, res) => {
377
+ res.json((0, db_1.getStats)(ctx(req).db));
378
+ });
379
+ router.get('/:projectId/metrics', (req, res) => {
380
+ const { project, db } = ctx(req);
381
+ try {
382
+ res.json((0, metrics_1.getProjectMetrics)(project.path, db));
383
+ }
384
+ catch (err) {
385
+ console.error('[project-router] metrics error:', err);
386
+ res.status(500).json({ error: 'Failed to compute metrics' });
387
+ }
388
+ });
389
+ }