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,9 @@
1
+ "use strict";
2
+ // Shared types for the per-project Jira integration.
3
+ //
4
+ // Design: Desktop is the sync layer. The local `.specrails/local-tickets.json`
5
+ // store stays the canonical read cache (specrails-core reads it unchanged). Jira
6
+ // is the system of record. Every write to Jira goes through a durable
7
+ // transactional outbox in the per-project `jobs.sqlite`. See
8
+ // docs/jira-integration-plan.md.
9
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,304 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createJiraRouter = createJiraRouter;
37
+ const express_1 = require("express");
38
+ const jira_db_1 = require("./jira/jira-db");
39
+ const feature_flags_1 = require("./feature-flags");
40
+ // req.projectCtx is declared in project-router / rails-router.
41
+ /**
42
+ * Per-project Jira router, mounted at /api/projects/:projectId/jira. Gated by
43
+ * SPECRAILS_JIRA_SECTION; 404s entirely when the flag is off. Endpoints back the
44
+ * step-by-step setup wizard (test → pick project → optional status map → connect)
45
+ * and the sync/outbox management surfaces. The token is never returned to the
46
+ * client (connection responses carry `hasToken` only).
47
+ */
48
+ function createJiraRouter() {
49
+ const router = (0, express_1.Router)({ mergeParams: true });
50
+ function ctx(req) {
51
+ return req.projectCtx;
52
+ }
53
+ // Feature-flag gate for every route under /jira.
54
+ router.use((_req, res, next) => {
55
+ if (!(0, feature_flags_1.isJiraEnabled)()) {
56
+ res.status(404).json({ error: 'Jira integration disabled' });
57
+ return;
58
+ }
59
+ next();
60
+ });
61
+ // ─── Connection ──────────────────────────────────────────────────────────────
62
+ // GET /connection — current connection (token redacted) or { connected: false }.
63
+ router.get('/connection', (req, res) => {
64
+ const c = ctx(req);
65
+ const conn = (0, jira_db_1.getConnectionPublic)(c.db, c.project.id);
66
+ if (!conn) {
67
+ res.json({ connected: false });
68
+ return;
69
+ }
70
+ res.json({ connected: true, connection: conn, outbox: c.jiraSyncManager.outboxCounts() });
71
+ });
72
+ // POST /test — wizard step 1: validate credentials without saving.
73
+ router.post('/test', async (req, res) => {
74
+ const { baseUrl, accountEmail, token } = req.body ?? {};
75
+ if (!isNonEmptyString(baseUrl) || !isNonEmptyString(token)) {
76
+ res.status(400).json({ error: 'baseUrl and token are required' });
77
+ return;
78
+ }
79
+ const result = await ctx(req).jiraSyncManager.probeCredentials({
80
+ baseUrl: baseUrl.trim(),
81
+ accountEmail: isNonEmptyString(accountEmail) ? accountEmail.trim() : null,
82
+ token,
83
+ });
84
+ if (!result.ok) {
85
+ res.status(result.status === 401 ? 401 : 400).json({ error: result.error });
86
+ return;
87
+ }
88
+ res.json(result);
89
+ });
90
+ // POST /discover-projects — wizard step 2: list visible projects.
91
+ router.post('/discover-projects', async (req, res) => {
92
+ const { baseUrl, accountEmail, token, query } = req.body ?? {};
93
+ if (!isNonEmptyString(baseUrl) || !isNonEmptyString(token)) {
94
+ res.status(400).json({ error: 'baseUrl and token are required' });
95
+ return;
96
+ }
97
+ const result = await ctx(req).jiraSyncManager.discoverProjects({
98
+ baseUrl: baseUrl.trim(),
99
+ accountEmail: isNonEmptyString(accountEmail) ? accountEmail.trim() : null,
100
+ token,
101
+ query: isNonEmptyString(query) ? query.trim() : undefined,
102
+ });
103
+ if (!result.ok) {
104
+ res.status(400).json({ error: result.error });
105
+ return;
106
+ }
107
+ res.json({ projects: result.projects });
108
+ });
109
+ // POST /discover-statuses — wizard step 3 (optional): the project's statuses.
110
+ router.post('/discover-statuses', async (req, res) => {
111
+ const { baseUrl, accountEmail, token, projectKey } = req.body ?? {};
112
+ if (!isNonEmptyString(baseUrl) || !isNonEmptyString(token) || !isNonEmptyString(projectKey)) {
113
+ res.status(400).json({ error: 'baseUrl, token and projectKey are required' });
114
+ return;
115
+ }
116
+ const result = await ctx(req).jiraSyncManager.discoverStatuses({
117
+ baseUrl: baseUrl.trim(),
118
+ accountEmail: isNonEmptyString(accountEmail) ? accountEmail.trim() : null,
119
+ token,
120
+ projectKey: projectKey.trim(),
121
+ });
122
+ if (!result.ok) {
123
+ res.status(400).json({ error: result.error });
124
+ return;
125
+ }
126
+ res.json({ statuses: result.statuses });
127
+ });
128
+ // POST /connect — wizard final step: validate + persist + start sync.
129
+ router.post('/connect', async (req, res) => {
130
+ const c = ctx(req);
131
+ const { baseUrl, accountEmail, token, jiraProjectKey, statusMap, discardStatus } = req.body ?? {};
132
+ if (!isNonEmptyString(baseUrl) || !isNonEmptyString(token) || !isNonEmptyString(jiraProjectKey)) {
133
+ res.status(400).json({ error: 'baseUrl, token and jiraProjectKey are required' });
134
+ return;
135
+ }
136
+ const cleanMap = sanitizeStatusMap(statusMap);
137
+ const result = await c.jiraSyncManager.connect({
138
+ baseUrl: baseUrl.trim(),
139
+ accountEmail: isNonEmptyString(accountEmail) ? accountEmail.trim() : null,
140
+ token,
141
+ jiraProjectKey: jiraProjectKey.trim(),
142
+ statusMap: cleanMap,
143
+ discardStatus: isNonEmptyString(discardStatus) ? discardStatus.trim() : null,
144
+ });
145
+ if (!result.ok) {
146
+ res.status(result.status === 401 ? 401 : 400).json({ error: result.error });
147
+ return;
148
+ }
149
+ res.status(201).json({ connection: (0, jira_db_1.getConnectionPublic)(c.db, c.project.id) });
150
+ });
151
+ // PATCH /connection — toggle enabled (hot-swap local↔Jira) and/or update the
152
+ // discard "move-to" status. `discardStatus: ''`/null clears it.
153
+ router.patch('/connection', (req, res) => {
154
+ const c = ctx(req);
155
+ const existing = (0, jira_db_1.getConnectionPublic)(c.db, c.project.id);
156
+ if (!existing) {
157
+ res.status(404).json({ error: 'No Jira connection configured' });
158
+ return;
159
+ }
160
+ const { enabled, discardStatus, statusMap } = req.body ?? {};
161
+ if (typeof enabled === 'boolean') {
162
+ c.jiraSyncManager.setEnabled(enabled);
163
+ }
164
+ if (discardStatus !== undefined) {
165
+ c.jiraSyncManager.setDiscardStatus(isNonEmptyString(discardStatus) ? discardStatus.trim() : null);
166
+ }
167
+ if (statusMap !== undefined) {
168
+ c.jiraSyncManager.setStatusMap(sanitizeStatusMap(statusMap));
169
+ }
170
+ res.json({ connection: (0, jira_db_1.getConnectionPublic)(c.db, c.project.id) });
171
+ });
172
+ // GET /statuses — the connected project's real statuses (post-connect picker
173
+ // for the discard "move-to" status). Uses the stored credentials.
174
+ router.get('/statuses', async (req, res) => {
175
+ const result = await ctx(req).jiraSyncManager.listStatusesForConnection();
176
+ if (!result.ok) {
177
+ res.status(400).json({ error: result.error });
178
+ return;
179
+ }
180
+ res.json({ statuses: result.statuses });
181
+ });
182
+ // DELETE /connection — remove the connection + restore local backlog config.
183
+ router.delete('/connection', (req, res) => {
184
+ const c = ctx(req);
185
+ c.jiraSyncManager.disconnect();
186
+ res.json({ connected: false });
187
+ });
188
+ // POST /resume — re-paste of a fresh token after a 401: drain the parked outbox.
189
+ router.post('/resume', (req, res) => {
190
+ ctx(req).jiraSyncManager.resumeAfterReauth();
191
+ res.json({ ok: true });
192
+ });
193
+ // ─── Sync + outbox management ───────────────────────────────────────────────
194
+ // POST /sync — manual "Sync now": a FULL re-fetch (ignores the high-water) so
195
+ // it back-fills any data the cache is missing (e.g. sprint/epic fields).
196
+ router.post('/sync', async (req, res) => {
197
+ const result = await ctx(req).jiraSyncManager.pollOnce(true);
198
+ res.json({ ok: true, upserted: result?.upserted ?? 0 });
199
+ });
200
+ // GET /outbox?state= — list outbox ops (defaults to all).
201
+ router.get('/outbox', (req, res) => {
202
+ const c = ctx(req);
203
+ const state = req.query.state;
204
+ const valid = ['pending', 'inflight', 'done', 'dead'];
205
+ const filter = valid.includes(state) ? state : undefined;
206
+ res.json({ ops: c.jiraSyncManager.listOutbox(filter), counts: c.jiraSyncManager.outboxCounts() });
207
+ });
208
+ // POST /outbox/:id/retry — re-queue a dead-lettered op for a manual retry.
209
+ router.post('/outbox/:id/retry', async (req, res) => {
210
+ const c = ctx(req);
211
+ const id = parseInt(req.params.id, 10);
212
+ if (Number.isNaN(id)) {
213
+ res.status(400).json({ error: 'Invalid op id' });
214
+ return;
215
+ }
216
+ const { retryDeadOutbox } = await Promise.resolve().then(() => __importStar(require('./jira/jira-db')));
217
+ const ok = retryDeadOutbox(c.db, id);
218
+ if (!ok) {
219
+ res.status(404).json({ error: 'Op not found or not in dead state' });
220
+ return;
221
+ }
222
+ void c.jiraSyncManager.drainOnce().catch(() => undefined);
223
+ res.json({ ok: true });
224
+ });
225
+ // POST /specs — Add Spec when the project source is Jira: create the issue in
226
+ // Jira and materialize it locally. Keeps the generic local ticket-create path
227
+ // untouched.
228
+ router.post('/specs', async (req, res) => {
229
+ const c = ctx(req);
230
+ const { title, description, labels, priority, issueType } = req.body ?? {};
231
+ if (!isNonEmptyString(title)) {
232
+ res.status(400).json({ error: 'title is required' });
233
+ return;
234
+ }
235
+ const result = await c.jiraSyncManager.createSpec({
236
+ title: title.trim(),
237
+ description: isNonEmptyString(description) ? description : undefined,
238
+ labels: Array.isArray(labels) ? labels.filter((l) => typeof l === 'string') : undefined,
239
+ priority: isNonEmptyString(priority) ? priority : undefined,
240
+ issueType: isNonEmptyString(issueType) ? issueType : undefined,
241
+ });
242
+ if (!result.ok) {
243
+ res.status(result.status === 401 ? 401 : 400).json({ error: result.error });
244
+ return;
245
+ }
246
+ res.status(201).json({ localId: result.localId, jiraKey: result.jiraKey });
247
+ });
248
+ // POST /specs/:localId/discard — "Move to <discard status>": transition the
249
+ // linked issue to the configured discard status (+ optional reason comment)
250
+ // instead of a destructive delete. Body: { comment?: string }.
251
+ router.post('/specs/:localId/discard', (req, res) => {
252
+ const c = ctx(req);
253
+ const localId = parseInt(req.params.localId, 10);
254
+ if (Number.isNaN(localId)) {
255
+ res.status(400).json({ error: 'Invalid spec id' });
256
+ return;
257
+ }
258
+ const comment = isNonEmptyString(req.body?.comment) ? req.body.comment : null;
259
+ const result = c.jiraSyncManager.discardSpec(localId, comment);
260
+ if (!result.ok) {
261
+ const status = result.reason === 'no-link' ? 404 : 409;
262
+ res.status(status).json({ error: result.reason });
263
+ return;
264
+ }
265
+ res.status(202).json({ ok: true });
266
+ });
267
+ // GET /specs/:localId/details — read-only "Jira details" + "Development" payload
268
+ // for the spec detail modal. Resilient: dev-status failures still return fields.
269
+ router.get('/specs/:localId/details', async (req, res) => {
270
+ const c = ctx(req);
271
+ const localId = parseInt(req.params.localId, 10);
272
+ if (Number.isNaN(localId)) {
273
+ res.status(400).json({ error: 'Invalid spec id' });
274
+ return;
275
+ }
276
+ const result = await c.jiraSyncManager.getSpecDetails(localId);
277
+ if (!result.ok) {
278
+ const status = result.reason === 'no-link' ? 404 : result.reason === 'not-active' ? 409 : (result.status ?? 502);
279
+ res.status(status).json({ error: result.reason });
280
+ return;
281
+ }
282
+ res.json(result.details);
283
+ });
284
+ // GET /links — the spec↔issue map (for the badge / diagnostics).
285
+ router.get('/links', (req, res) => {
286
+ res.json({ links: ctx(req).jiraSyncManager.listLinks() });
287
+ });
288
+ return router;
289
+ }
290
+ function isNonEmptyString(v) {
291
+ return typeof v === 'string' && v.trim().length > 0;
292
+ }
293
+ function sanitizeStatusMap(raw) {
294
+ if (!raw || typeof raw !== 'object')
295
+ return null;
296
+ const states = ['todo', 'in_progress', 'done', 'cancelled'];
297
+ const out = {};
298
+ for (const s of states) {
299
+ const v = raw[s];
300
+ if (typeof v === 'string' && v.trim())
301
+ out[s] = v.trim();
302
+ }
303
+ return Object.keys(out).length > 0 ? out : null;
304
+ }
@@ -29,6 +29,10 @@ function createMobileAuthMiddleware(opts) {
29
29
  const clock = opts.clock ?? (() => Date.now());
30
30
  const lastTouch = new Map();
31
31
  const ipHits = new Map();
32
+ // Periodically evict stale keys so these maps can't grow unbounded under a
33
+ // multi-source flood (each per-IP timestamp array self-bounds to the 60s
34
+ // window, but the Map KEYS would otherwise live forever, one per distinct IP).
35
+ let lastSweep = clock();
32
36
  return function mobileAuth(req, res, next) {
33
37
  // 1. Refuse browser-origin requests outright.
34
38
  if (req.headers['origin']) {
@@ -38,6 +42,18 @@ function createMobileAuthMiddleware(opts) {
38
42
  // 2. Coarse per-IP rate limit.
39
43
  const ip = req.socket?.remoteAddress ?? 'unknown';
40
44
  const now = clock();
45
+ // Sweep stale tracking at most once per window (cheap, amortized O(1)).
46
+ if (now - lastSweep > 60_000) {
47
+ lastSweep = now;
48
+ for (const [k, v] of ipHits) {
49
+ if (v.length === 0 || now - v[v.length - 1] >= 60_000)
50
+ ipHits.delete(k);
51
+ }
52
+ for (const [k, t] of lastTouch) {
53
+ if (now - t >= 3_600_000)
54
+ lastTouch.delete(k); // drop device touch-cache after 1h idle
55
+ }
56
+ }
41
57
  const hits = (ipHits.get(ip) ?? []).filter((t) => now - t < 60_000);
42
58
  hits.push(now);
43
59
  ipHits.set(ip, hits);
@@ -24,6 +24,7 @@ const terminal_manager_1 = require("./terminal-manager");
24
24
  const browser_capture_manager_1 = require("./browser-capture-manager");
25
25
  const explore_cwd_manager_1 = require("./explore-cwd-manager");
26
26
  const ticket_store_1 = require("./ticket-store");
27
+ const jira_sync_manager_1 = require("./jira/jira-sync-manager");
27
28
  const rails_store_1 = require("./rails-store");
28
29
  const desktop_db_1 = require("./desktop-db");
29
30
  const config_1 = require("./config");
@@ -115,6 +116,11 @@ class ProjectRegistry {
115
116
  catch { /* ignore */ }
116
117
  // Tear down the embedded browser (closes pages + persistent context).
117
118
  void ctx.browserCaptureManager.shutdown().catch(() => { });
119
+ // Stop the Jira sync poll/drain timers (no children, just intervals).
120
+ try {
121
+ ctx.jiraSyncManager.stop();
122
+ }
123
+ catch { /* ignore */ }
118
124
  // Kill any terminal sessions belonging to this project
119
125
  try {
120
126
  (0, terminal_manager_1.getTerminalManager)().killAllForProject(id);
@@ -201,6 +207,10 @@ class ProjectRegistry {
201
207
  }
202
208
  catch { /* ignore */ }
203
209
  void ctx.browserCaptureManager.shutdown().catch(() => { });
210
+ try {
211
+ ctx.jiraSyncManager.stop();
212
+ }
213
+ catch { /* ignore */ }
204
214
  // Release chokidar watchers + abort in-flight generations so a restart
205
215
  // does not leak handles/children — mirror removeProject()'s per-project teardown.
206
216
  try {
@@ -250,6 +260,14 @@ class ProjectRegistry {
250
260
  catch { /* queue_state table may not exist yet */ }
251
261
  const webhookManager = this._webhookManager;
252
262
  const railJobs = new Map();
263
+ // Jira sync (per-project, inert until a connection is configured). Constructed
264
+ // before QueueManager so the onJobFinished closure can reference it.
265
+ const jiraSyncManager = new jira_sync_manager_1.JiraSyncManager({
266
+ db,
267
+ projectId: project.id,
268
+ projectPath: project.path,
269
+ broadcast: boundBroadcast,
270
+ });
253
271
  const queueManager = new queue_manager_1.QueueManager(boundBroadcast, db, undefined, project.path, {
254
272
  zombieTimeoutMs: projectZombieTimeout,
255
273
  provider: project.provider ?? 'claude',
@@ -318,6 +336,26 @@ class ProjectRegistry {
318
336
  timestamp: ticket.updated_at,
319
337
  });
320
338
  }
339
+ // Jira write-back (inert for non-Jira projects): enqueue the status
340
+ // transition + completion comment per linked ticket. The LOCAL mutation
341
+ // above stays synchronous; only the durable outbox enqueue happens here,
342
+ // wrapped so a Jira failure can never break the job-exit handler.
343
+ if (status === 'completed' || status === 'failed' || status === 'canceled' || status === 'zombie_terminated') {
344
+ try {
345
+ const needsReviewIds = completedTicketIds.filter((tid) => store.tickets[String(tid)]?.needs_review === true);
346
+ jiraSyncManager.onJobOutcome({
347
+ ticketIds: completedTicketIds,
348
+ status,
349
+ jobId,
350
+ costUsd: costUsd ?? null,
351
+ durationMs: jobRow?.duration_ms ?? null,
352
+ needsReviewIds,
353
+ });
354
+ }
355
+ catch (err) {
356
+ console.error('[project-registry] jira onJobOutcome failed:', err);
357
+ }
358
+ }
321
359
  }
322
360
  catch (err) {
323
361
  console.error('[project-registry] failed to apply job outcome to tickets:', err);
@@ -425,6 +463,10 @@ class ProjectRegistry {
425
463
  }
426
464
  const ticketWatcher = new ticket_watcher_1.TicketWatcher(project.path, project.id, boundBroadcast);
427
465
  ticketWatcher.start();
466
+ // Suppress the file-watcher echo for the Jira sync's own writes (the every-60s
467
+ // poll would otherwise trigger a full-board refresh = flicker). Late-bound
468
+ // because the JiraSyncManager is constructed before the watcher.
469
+ jiraSyncManager.setLocalWriteNotifier((rev) => ticketWatcher.notifyDesktopWrite(rev));
428
470
  // BrowserCaptureManager — "Add Spec from browser". Constructed for every
429
471
  // project regardless of the feature flag; the routes + WS endpoint 404 when
430
472
  // the flag is off, and the persistent Chromium context is launched lazily on
@@ -435,7 +477,7 @@ class ProjectRegistry {
435
477
  db,
436
478
  broadcast: boundBroadcast,
437
479
  });
438
- const ctx = { project, db, queueManager, chatManager, setupManager, proposalManager, agentRefineManager, fileSummaryManager, specLauncherManager, ticketWatcher, browserCaptureManager, broadcast: boundBroadcast, railJobs };
480
+ const ctx = { project, db, queueManager, chatManager, setupManager, proposalManager, agentRefineManager, fileSummaryManager, specLauncherManager, ticketWatcher, browserCaptureManager, jiraSyncManager, broadcast: boundBroadcast, railJobs };
439
481
  this._contexts.set(project.id, ctx);
440
482
  return ctx;
441
483
  }
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerChatRoutes = registerChatRoutes;
4
+ const ids_1 = require("./ids");
5
+ const db_1 = require("./db");
6
+ const context_scope_1 = require("./context-scope");
7
+ const spec_models_1 = require("./spec-models");
8
+ const provider_selection_1 = require("./provider-selection");
9
+ const ticket_store_1 = require("./ticket-store");
10
+ const project_router_helpers_1 = require("./project-router-helpers");
11
+ function registerChatRoutes(deps) {
12
+ const { router, registry, ctx, ticketPath } = deps;
13
+ // ─── Chat routes ─────────────────────────────────────────────────────────────
14
+ router.get('/:projectId/chat/conversations', (req, res) => {
15
+ const conversations = (0, db_1.listConversations)(ctx(req).db);
16
+ res.json({ conversations });
17
+ });
18
+ router.post('/:projectId/chat/conversations', (req, res) => {
19
+ const { db, project } = ctx(req);
20
+ // Multi-provider: an optional aiEngine (alias: provider) picks which engine
21
+ // this conversation runs on. It must be installed on the project; omitting
22
+ // it uses the project's primary provider. The chosen provider drives model
23
+ // validation and is persisted on the conversation so resume turns and
24
+ // ai_invocations attribute to the right engine.
25
+ const requestedEngine = req.body?.aiEngine ?? req.body?.provider;
26
+ const engineCheck = (0, provider_selection_1.validateRequestedProvider)(project, requestedEngine);
27
+ if (!engineCheck.ok) {
28
+ res.status(400).json({ error: engineCheck.error });
29
+ return;
30
+ }
31
+ const provider = engineCheck.provider;
32
+ const rawModel = req.body?.model;
33
+ let model;
34
+ if (rawModel === undefined || rawModel === null || rawModel === '') {
35
+ model = (0, project_router_helpers_1.resolveDefaultSpecModel)({ projectPath: project.path, provider });
36
+ }
37
+ else if ((0, spec_models_1.isValidModelForProvider)(rawModel, provider)) {
38
+ model = rawModel;
39
+ }
40
+ else {
41
+ res.status(400).json({
42
+ error: `Invalid model "${String(rawModel)}" for provider "${provider}"`,
43
+ allowed: (0, spec_models_1.getModelsForProvider)(provider).map((m) => m.value),
44
+ });
45
+ return;
46
+ }
47
+ const rawKind = req.body?.kind;
48
+ const kind = rawKind === 'explore' ? 'explore' : 'sidebar';
49
+ const id = (0, ids_1.newId)();
50
+ const rawScope = req.body?.contextScope;
51
+ if (rawScope !== undefined && kind !== 'explore') {
52
+ res.status(400).json({ error: 'contextScope is only allowed for kind=explore' });
53
+ return;
54
+ }
55
+ let scope;
56
+ if (kind === 'explore') {
57
+ const fallback = (0, context_scope_1.getLastContextScope)(db, 'explore');
58
+ // Defence-in-depth: SMASH / Contract Layer is Claude-only. Strip
59
+ // contractRefine from the scope when the conversation's resolved provider
60
+ // is non-Claude so no downstream code (Contract Refine Runner, SMASH
61
+ // eligibility) ever sees a mismatched flag.
62
+ const safeRawScope = provider !== 'claude' && rawScope != null
63
+ ? { ...rawScope, contractRefine: false }
64
+ : rawScope;
65
+ scope = (0, context_scope_1.normalizeContextScope)(safeRawScope ?? fallback, fallback);
66
+ (0, context_scope_1.setLastContextScope)(db, scope);
67
+ console.log(`[project-router] new explore conv ${id} provider=${provider} scope=${JSON.stringify(scope)} rawScope=${JSON.stringify(rawScope)}`);
68
+ }
69
+ // Only persist provider when the project is multi-provider; single-provider
70
+ // projects leave it NULL so behaviour is byte-identical to before.
71
+ const persistProvider = (0, provider_selection_1.isMultiProvider)(project) ? provider : null;
72
+ (0, db_1.createConversation)(db, { id, model, kind, contextScope: scope, provider: persistProvider });
73
+ const conversation = (0, db_1.getConversation)(db, id);
74
+ res.status(201).json({ conversation });
75
+ });
76
+ router.get('/:projectId/chat/conversations/:id', (req, res) => {
77
+ const { db } = ctx(req);
78
+ const conversation = (0, db_1.getConversation)(db, req.params.id);
79
+ if (!conversation) {
80
+ res.status(404).json({ error: 'Conversation not found' });
81
+ return;
82
+ }
83
+ const messages = (0, db_1.getMessages)(db, req.params.id);
84
+ res.json({ conversation, messages });
85
+ });
86
+ router.delete('/:projectId/chat/conversations/:id', (req, res) => {
87
+ const { db, chatManager, broadcast, project, ticketWatcher } = ctx(req);
88
+ const convId = req.params.id;
89
+ const conversation = (0, db_1.getConversation)(db, convId);
90
+ if (!conversation) {
91
+ res.status(404).json({ error: 'Conversation not found' });
92
+ return;
93
+ }
94
+ (0, db_1.deleteConversation)(db, convId);
95
+ chatManager?.forgetSpecDraft(convId);
96
+ chatManager?.forgetExploreLifecycle(convId);
97
+ // Cascade-clear origin_conversation_id on any ticket that referenced this
98
+ // conversation (application-level "ON DELETE SET NULL").
99
+ try {
100
+ const filePath = ticketPath(req);
101
+ const store = (0, ticket_store_1.mutateStore)(filePath, (s) => {
102
+ for (const id of Object.keys(s.tickets)) {
103
+ if (s.tickets[id].origin_conversation_id === convId) {
104
+ s.tickets[id].origin_conversation_id = null;
105
+ s.tickets[id].updated_at = new Date().toISOString();
106
+ }
107
+ }
108
+ });
109
+ ticketWatcher.notifyDesktopWrite(store.revision);
110
+ // No per-ticket broadcast: the cleared field is metadata-only and the
111
+ // board card visual treatment doesn't depend on it.
112
+ }
113
+ catch (err) {
114
+ console.error('[project-router] conversation-cascade ticket update error:', err);
115
+ }
116
+ res.json({ ok: true });
117
+ });
118
+ router.patch('/:projectId/chat/conversations/:id', (req, res) => {
119
+ const { db } = ctx(req);
120
+ const conversation = (0, db_1.getConversation)(db, req.params.id);
121
+ if (!conversation) {
122
+ res.status(404).json({ error: 'Conversation not found' });
123
+ return;
124
+ }
125
+ const { title, model } = req.body ?? {};
126
+ const patch = {};
127
+ if (title !== undefined)
128
+ patch.title = title;
129
+ if (model !== undefined)
130
+ patch.model = model;
131
+ (0, db_1.updateConversation)(db, req.params.id, patch);
132
+ const updated = (0, db_1.getConversation)(db, req.params.id);
133
+ res.json({ ok: true, conversation: updated });
134
+ });
135
+ router.get('/:projectId/chat/conversations/:id/messages', (req, res) => {
136
+ const { db } = ctx(req);
137
+ const conversation = (0, db_1.getConversation)(db, req.params.id);
138
+ if (!conversation) {
139
+ res.status(404).json({ error: 'Conversation not found' });
140
+ return;
141
+ }
142
+ const messages = (0, db_1.getMessages)(db, req.params.id);
143
+ res.json({ messages });
144
+ });
145
+ // Returns the in-memory spec-draft state Claude has accumulated for this
146
+ // conversation. Used by useSpecDraftStream on mount to rehydrate updates
147
+ // that were broadcast while the client wasn't subscribed (refresh /
148
+ // minimize-and-restore). Returns 200 with `null` draft when no state yet.
149
+ router.get('/:projectId/chat/conversations/:id/spec-draft', (req, res) => {
150
+ const { db, chatManager } = ctx(req);
151
+ const conversation = (0, db_1.getConversation)(db, req.params.id);
152
+ if (!conversation) {
153
+ res.status(404).json({ error: 'Conversation not found' });
154
+ return;
155
+ }
156
+ const state = chatManager.getSpecDraftState(req.params.id);
157
+ if (!state) {
158
+ res.json({ draft: null, ready: false, chips: [] });
159
+ return;
160
+ }
161
+ res.json({
162
+ draft: state.draft,
163
+ ready: state.ready,
164
+ chips: state.chips,
165
+ });
166
+ });
167
+ router.post('/:projectId/chat/conversations/:id/messages', async (req, res) => {
168
+ const { db, chatManager, project } = ctx(req);
169
+ const conversation = (0, db_1.getConversation)(db, req.params.id);
170
+ if (!conversation) {
171
+ res.status(404).json({ error: 'Conversation not found' });
172
+ return;
173
+ }
174
+ const text = req.body?.text;
175
+ if (!text || !text.trim()) {
176
+ res.status(400).json({ error: 'text is required' });
177
+ return;
178
+ }
179
+ if (chatManager.isActive(req.params.id)) {
180
+ res.status(409).json({ error: 'CONVERSATION_BUSY' });
181
+ return;
182
+ }
183
+ const lightweight = req.body?.lightweight === true;
184
+ const maxTurns = typeof req.body?.maxTurns === 'number' ? req.body.maxTurns : undefined;
185
+ let attachments;
186
+ const rawAtt = req.body?.attachments;
187
+ if (rawAtt && typeof rawAtt === 'object' && typeof rawAtt.ticketKey === 'string'
188
+ && Array.isArray(rawAtt.ids)) {
189
+ const ids = rawAtt.ids.filter((x) => typeof x === 'string');
190
+ if (ids.length > 0) {
191
+ attachments = { slug: project.slug, ticketKey: rawAtt.ticketKey, ids };
192
+ }
193
+ }
194
+ res.status(202).json({ ok: true });
195
+ chatManager.sendMessage(req.params.id, text.trim(), { lightweight, maxTurns, attachments }).catch((err) => {
196
+ console.error('[project-router] chat sendMessage error:', err);
197
+ });
198
+ });
199
+ router.delete('/:projectId/chat/conversations/:id/messages/stream', (req, res) => {
200
+ const { chatManager } = ctx(req);
201
+ if (!chatManager.isActive(req.params.id)) {
202
+ res.status(404).json({ error: 'No active stream for this conversation' });
203
+ return;
204
+ }
205
+ chatManager.abort(req.params.id);
206
+ res.json({ ok: true });
207
+ });
208
+ // Explore Spec lifecycle: minimize-to-toast hint and restore-from-toast hint.
209
+ // Idempotent; does not mutate persistent state. See design.md D7.
210
+ router.post('/:projectId/chat/conversations/:id/minimize', (req, res) => {
211
+ ctx(req).chatManager.notifyMinimized(req.params.id);
212
+ res.json({ ok: true });
213
+ });
214
+ router.post('/:projectId/chat/conversations/:id/restore', (req, res) => {
215
+ ctx(req).chatManager.notifyRestored(req.params.id);
216
+ res.json({ ok: true });
217
+ });
218
+ }