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,321 @@
1
+ # Jira como fuente de Specs — Estudio e implementación
2
+
3
+ > **Estado: v1 IMPLEMENTADO en specrails-desktop** (Fase 0 + Fase 1). Subsistema `server/jira/` + UI wizard + tests; core y companion sin cambios. Ver la sección "Jira integration" en `CLAUDE.md` para el mapa de código. Las Fases 2–3 (OAuth 3LO, keychain OS, conflictos bidireccionales, sink de comentarios Jira→local, relay de webhooks) siguen pendientes. Producto: Specrails (core + desktop + companion).
4
+ > Objetivo: que una Spec **pueda ser** un ticket de Jira, con **hot‑swap** por proyecto entre tickets locales y tickets de Jira, siguiendo todo el ciclo de vida (crear → To Do → In Progress → Done / revertir), y dejando un comentario en Jira al terminar el job del rail.
5
+ > Conclusión de cabecera: **specrails‑core no necesita ningún cambio**; el 95 % del trabajo vive en specrails‑desktop; specrails‑companion necesita ~0 (campos aditivos opcionales).
6
+
7
+ ---
8
+
9
+ ## 1. Veredicto ejecutivo
10
+
11
+ La feature es viable, de alto valor enterprise, y — crucialmente — se puede construir **sin tocar el contrato congelado de core ni el de la app móvil**. La arquitectura ganadora (validada por un panel de 3 arquitectos + 3 jueces y un barrido adversarial de 87 corner cases) es:
12
+
13
+ > **Desktop es la capa de sincronización. El store local `local-tickets.json` sigue siendo la caché de lectura canónica. Jira es el sistema de registro. El hot‑swap es un *cambio de destino de escritura*, no un refactor del camino de lectura. Toda la escritura a Jira pasa por un *outbox transaccional durable* en SQLite.**
14
+
15
+ Tres decisiones la sostienen:
16
+
17
+ 1. **Core intacto.** Desktop materializa Jira → `.specrails/local-tickets.json` (lo que core ya lee) y escribe `.specrails/backlog-config.json` con `provider:"local"` + `write_access:false`. Eso mete a core en su rama **read‑only**: lee tickets de la caché pero **nunca** muta estado ni habla con Jira. Desktop (`applyJobOutcomeToTickets`) queda como **única autoridad de estado**.
18
+ 2. **No refactorizar el store en v1.** No introducimos todavía una interfaz `TicketProvider` que obligue a reescribir los ~27 importadores de `ticket-store.ts` (el módulo más sensible: advisory‑lock, atomic‑rename, supresión de eco del watcher, regla de no‑resurrección de draft/cancelled). Eso se difiere hasta que exista un **segundo** backend (GitHub Issues/Linear) que justifique la abstracción. En v1, un `JiraSyncManager` *al lado* de los managers existentes en `ProjectContext`.
19
+ 3. **Durabilidad desde el día 1.** Escritura caché + fila de outbox en **una sola transacción SQLite** → es imposible perder una transición o un comentario por crash/offline. Idempotencia en cada operación. Esto es lo verdaderamente "load‑bearing" de cualquier write‑back a Jira y es barato incluirlo desde el principio.
20
+
21
+ Lo que **se descarta para v1** (con motivo): webhooks entrantes (el server liga a `127.0.0.1`, sin ingress público); OAuth 2.0 3LO (el token endpoint de Atlassian exige `client_secret`, sin device‑flow → necesitaría un *token‑broker* hospedado que no existe); resolución de conflictos campo‑a‑campo bidireccional; interfaz `TicketProvider` genérica; sink de comentarios Jira→local.
22
+
23
+ ---
24
+
25
+ ## 2. Respuesta directa a tus preguntas
26
+
27
+ ### ¿Cómo impacta a cada repo?
28
+
29
+ | Repo | Impacto | Detalle |
30
+ |------|---------|---------|
31
+ | **specrails-core** | **Cero cambios** | Core nunca lee tickets por código: cada lectura/escritura es un agente LLM siguiendo instrucciones markdown contra `.specrails/local-tickets.json`, gateado por `.specrails/backlog-config.json` (`{provider, write_access, git_auto}`). La ruta del fichero está *hardcodeada* en cada skill, así que redirigir la **ubicación** sí requeriría tocar core — pero redirigir el **contenido** (materializar Jira en esa ruta fija) no requiere nada. Con `write_access:false` core entra en su rama read‑only (`implement.md:1307`: "Do NOT create, modify, or comment on any issues/tickets") e imprime una tabla de actualización manual en vez de mutar. |
32
+ | **specrails-desktop** | **~95 % del trabajo** | Nuevo subsistema `server/jira/`: cliente HTTP, materializador (poll → `local-tickets.json`), outbox durable, resolver de estados/transiciones, store de credenciales cifrado, router REST + eventos WS, y UI cliente (toggle de fuente, badge Jira, panel de sync/dead‑letter, settings de conexión). Dos *hooks* en código existente (lanzamiento de rail + salida de job). |
33
+ | **specrails-companion** | **~0 (aditivo opcional)** | Los deserializadores Dart son **tolerantes** (`models.dart:1-4`: "parsers are deliberately tolerant … so a schema tweak never crashes the app"). Añadir `jiraKey`/`source`/`externalStatus`/`externalUrl` al JSON REST de `/tickets` es ignorado sin crash. Opción A: cero cambios y la spec Jira ya se ve. Opción B (recomendada): 2 ediciones aditivas para un badge "PROJ‑123" de primera clase. Ningún string del contrato congelado (`hub.*`, `specrailshub` mDNS, campos de pairing) se toca. |
34
+
35
+ ### El ciclo de vida que describiste, mapeado
36
+
37
+ Tu descripción actual:
38
+ - En el listado de Specs = **To Do**; al pasar al rail = **In Progress**; al terminar = **Done**; cancelación/fallo = vuelve a **To Do**.
39
+
40
+ Cómo se implementa hoy en desktop (verificado):
41
+ - `todo → in_progress`: **no se escribe en servidor**. Lo escribe el agente CLI de core en `local-tickets.json`, o lo infiere el tablero por pertenencia al rail. (`rails-router.ts:293-298` solo hace `enqueue` + `railJobs.set` + broadcast `rail.job_started`; `QueueManager` nunca escribe estado de ticket.)
42
+ - `in_progress → done` / `→ todo` / `needs_review`: un único sitio, `applyJobOutcomeToTickets` (`ticket-store.ts:324-353`), invocado solo desde `onJobFinished` en `project-registry.ts:315-317` (envuelto en `mutateStore`).
43
+
44
+ Con Jira, cada transición local se refleja en Jira vía outbox, en **dos chokepoints exactos**:
45
+
46
+ | Transición Specrails | Dónde se emite | Acción Jira (encolada en outbox) |
47
+ |----------------------|----------------|----------------------------------|
48
+ | `todo → in_progress` (lanzar rail) | `rails-router.ts:293-298`, justo tras `enqueue` (con `rail.ticketIds` en scope) | Transición a categoría `indeterminate` + escribir `in_progress` en la caché local (porque `write_access:false` quita la escritura de core) |
49
+ | `in_progress/todo → done` (job OK) | `project-registry.ts:315-327`, tras el broadcast loop | Transición a categoría `done` + **comentario de finalización** |
50
+ | `in_progress → todo` (fallo/cancel/zombie) | mismo sitio | Transición *best‑effort* hacia categoría `new` (puede no existir camino — ver §6) + comentario |
51
+ | `done + needs_review` (murió tras Ship) | mismo sitio | **NO** transicionar a Done; en su lugar comentario ("run terminó anormalmente, revisar") + label `specrails:needs-review` |
52
+
53
+ > Punto crítico verificado: el `in_progress` de lanzamiento **NUNCA** lo emite la app server‑side, y con `write_access:false` el agente CLI **tampoco** lo escribirá. Por eso el push de `In Progress` a Jira **debe** emitirse explícitamente desde el hook de lanzamiento — no se puede depender del file‑watcher ni del agente.
54
+
55
+ ### Crear el ticket y comentar al final
56
+
57
+ - **Crear**: el flujo Add Spec gana un destino. Si la fuente del proyecto es Jira (o el usuario elige "crear en Jira"), `POST /rest/api/3/issue` crea el issue (project + issuetype + summary + descripción en ADF), se mintea un `#id` local estable y se inserta la fila en `jira_links`. Si es local, ruta actual sin cambios.
58
+ - **Comentar al terminar**: el comentario de finalización ("Implementado por Specrails — job N, coste $X, duración Z, PR #…") se compone en el chokepoint de `onJobFinished` (que ya tiene `status`, `costUsd`, `jobRow.duration_ms`, `completedTicketIds` en scope) y se encola como op de outbox **independiente** de la transición (para que si la transición falla por workflow, el comentario igualmente se publique).
59
+
60
+ ---
61
+
62
+ ## 3. Arquitectura
63
+
64
+ ```
65
+ ┌────────────────────────── specrails-desktop ──────────────────────────┐
66
+ │ │
67
+ Jira Cloud/DC ◀──▶│ server/jira/ │
68
+ REST v3 / v2 │ ├─ jira-client.ts (HTTP: Basic Cloud / Bearer DC, ADF vs wiki) │
69
+ (poll + outbox) │ ├─ jira-sync-manager.ts (per-project; poll loop + outbox drainer) │
70
+ │ ├─ jira-outbox.ts (durable SQLite outbox + dead-letter) │
71
+ │ ├─ jira-status-resolver.ts (two-tier map + BFS transition walk) │
72
+ │ ├─ jira-materializer.ts (Jira issues → local-tickets.json, surgical) │
73
+ │ ├─ jira-links.ts (immutable-id ↔ local #id map) │
74
+ │ └─ jira-credential-store.ts (libsodium secretbox behind interface) │
75
+ │ │
76
+ │ Hooks (2): rails-router.ts:293 + project-registry.ts:315 │
77
+ │ Lee/escribe: .specrails/local-tickets.json (vía mutateStore, locked) │
78
+ │ Escribe: .specrails/backlog-config.json {provider:local, write:false} │
79
+ └──────────────────────────────────────────────────────────────────────────┘
80
+ │ lee local-tickets.json (sin saber que viene de Jira)
81
+
82
+ specrails-core (agente LLM, rama read-only, CERO cambios)
83
+ ```
84
+
85
+ **Principio rector — "al lado, no a través" (beside, not through):** el `JiraSyncManager` se monta en `ProjectContext` junto a `chatManager`, `ticketWatcher`, etc. (sitio de construcción en `project-registry.ts:380`). Los ~27 importadores de `ticket-store.ts` quedan intactos. La mutación local en `onJobFinished` sigue siendo **síncrona** (como hoy); solo el efecto secundario hacia Jira se hace **asíncrono y durable**. Esto evita el movimiento más peligroso (async‑ificar `applyJobOutcomeToTickets` en el chokepoint de salida de job, que gobierna la liberación de tickets del rail).
86
+
87
+ ---
88
+
89
+ ## 4. Modelo de datos
90
+
91
+ ### 4.1 Config por proyecto (registro de proyectos, desktop-db)
92
+
93
+ Nueva columna `ticket_source TEXT DEFAULT 'local'` (`'local' | 'jira'`) en la tabla de proyectos (migración aditiva, mismo patrón que `provider`/`providers` migr. 10/11). Más una fila de conexión Jira (ver 4.4). **Invariante** (espejo del multi‑provider): cuando `ticket_source='local'` todo se comporta byte‑idéntico a hoy; ningún selector se renderiza, ninguna credencial se persiste.
94
+
95
+ ### 4.2 `.specrails/backlog-config.json` (lo escribe desktop, lo lee core)
96
+
97
+ ```json
98
+ { "provider": "local", "write_access": false, "git_auto": false }
99
+ ```
100
+
101
+ `provider:"local"` (no `"jira"`) es **deliberado**: mantiene a core fuera del camino `curl`‑a‑Jira; desktop es lo único que autentica contra Jira. `write_access:false` mete a core en read‑only.
102
+
103
+ ### 4.3 `jira_links` (per-project `jobs.sqlite`, nueva migración)
104
+
105
+ La tabla más importante del diseño. **Clave: el id numérico INMUTABLE de Jira**, jamás la key mutable `PROJ-123`.
106
+
107
+ ```sql
108
+ CREATE TABLE jira_links (
109
+ local_id INTEGER PRIMARY KEY, -- el #id que ve core; monotónico, NUNCA reusado
110
+ jira_issue_id TEXT NOT NULL UNIQUE, -- id numérico inmutable de Jira (sobrevive move/rename)
111
+ jira_key TEXT, -- 'PROJ-123' — solo display, re-resuelto por id si 404
112
+ jira_project_id TEXT NOT NULL,
113
+ deployment TEXT NOT NULL, -- 'cloud' | 'dc'
114
+ last_remote_hash TEXT, -- detección barata de divergencia inbound
115
+ status_category TEXT, -- última categoría conocida (new|indeterminate|done)
116
+ state TEXT DEFAULT 'linked', -- linked | orphaned | conflict
117
+ created_at TEXT, updated_at TEXT
118
+ );
119
+ ```
120
+
121
+ Razones (de los corner cases data‑model): un issue **movido** de proyecto cambia la key pero no el id; un **rename de project key** reescribe todas las keys. Si la clave de join fuera la key, cada transición/comentario daría 404 y el poll filtrado por `project=` lo perdería. Los `local_id` se asignan una vez y se **tombstonean** al borrar (nunca se renumeran), para que un `/specrails:implement #42` capturado al lanzar siga resolviendo al mismo issue tras re‑materializaciones y reinicios.
122
+
123
+ ### 4.4 `jira_connection` (per-project) + credencial cifrada
124
+
125
+ ```sql
126
+ CREATE TABLE jira_connection (
127
+ project_id TEXT PRIMARY KEY,
128
+ base_url TEXT NOT NULL, -- https://acme.atlassian.net | https://jira.acme.com
129
+ deployment TEXT NOT NULL, -- 'cloud' | 'dc' (detectado al bind)
130
+ api_version TEXT NOT NULL, -- '3' | '2'
131
+ auth_scheme TEXT NOT NULL, -- 'basic' | 'bearer'
132
+ account_email TEXT, -- solo Cloud (Basic = base64(email:token))
133
+ jira_project_key TEXT NOT NULL,
134
+ jira_project_id TEXT NOT NULL, -- inmutable; la key puede renombrarse
135
+ status_map TEXT, -- JSON: override explícito spec-status → Jira status id
136
+ high_water_ms INTEGER, -- marca de poll (epoch ms; resolución real = minuto)
137
+ created_at TEXT, updated_at TEXT
138
+ );
139
+ ```
140
+
141
+ El **token** NO va en esta tabla en claro. Va en `jira-credential-store.ts` (ver §5): `crypto_secretbox` de libsodium sellado bajo un keyfile `0600`. En lectura se devuelve solo `hasToken: boolean` (espejo de la redacción de `publicWebhook`); el token jamás se devuelve al cliente ni al companion, nunca se loguea.
142
+
143
+ ### 4.5 `jira_outbox` (per-project `jobs.sqlite`) — el corazón de la durabilidad
144
+
145
+ ```sql
146
+ CREATE TABLE jira_outbox (
147
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
148
+ jira_issue_id TEXT NOT NULL, -- FIFO se serializa por este id
149
+ op_type TEXT NOT NULL, -- 'transition' | 'comment' | 'create'
150
+ idempotency_key TEXT NOT NULL UNIQUE, -- (job_id + ticket_id + op_type)
151
+ payload TEXT NOT NULL, -- JSON: target category / comment body / etc.
152
+ issue_version TEXT, -- versión/ETag capturada al encolar (freshness check)
153
+ state TEXT DEFAULT 'pending', -- pending | inflight | done | dead
154
+ attempts INTEGER DEFAULT 0,
155
+ next_attempt_at TEXT, last_error TEXT, dead_reason TEXT,
156
+ created_at TEXT, updated_at TEXT
157
+ );
158
+ ```
159
+
160
+ **El invariante transaccional:** la fila de outbox se inserta en la **misma transacción SQLite** que registra el cambio de estado, *antes* de aplicar la mutación a `local-tickets.json` (que es JSON en disco, no SQLite). El outbox es la fuente de verdad durable de "qué debe llegar a Jira". Si el proceso muere entre escribir `done` en el JSON y encolar, no se pierde nada porque el outbox se persistió primero; al arrancar se drenan las filas no‑acked.
161
+
162
+ ---
163
+
164
+ ## 5. Autenticación
165
+
166
+ ### v1 — token‑paste (recomendado), cero backend
167
+
168
+ | | Cloud | Data Center / Server |
169
+ |---|---|---|
170
+ | Método | API token + email (**Basic**) | Personal Access Token (**Bearer**) |
171
+ | Header | `Authorization: Basic base64(email:token)` | `Authorization: Bearer <PAT>` |
172
+ | Base path | `/rest/api/3` | `/rest/api/2` |
173
+ | Body comentario/desc | **ADF** (JSON) | **wiki‑markup** (string plano) |
174
+ | Versión mínima | — | Jira 8.14+ (si no, basic auth) |
175
+
176
+ Por qué token‑paste y no OAuth en v1:
177
+ - OAuth 3LO de Atlassian **exige `client_secret`** (sin PKCE para public client, sin device flow), que no se puede embeber con seguridad en un binario distribuido → forzaría un **token‑broker hospedado** que veríamos y que dispara revisiones de seguridad enterprise ("¿vuestro servidor ve nuestros tokens?"). Token‑paste mantiene la credencial **en la máquina**.
178
+ - DC **no tiene** OAuth 3LO Cloud; el PAT es el único camino limpio. Token‑paste cubre ambos despliegues con un solo UX.
179
+ - Loopback redirect (`http://localhost`) sí está whitelisteado por Atlassian, así que **OAuth es el v2 correcto** para orgs que deshabilitan API tokens — pero detrás de la misma interfaz de credencial.
180
+
181
+ **Realidades operativas a manejar (de la investigación):**
182
+ - Tras 15‑dic‑2024 todo API token Cloud **expira** (máx 1 año, sin refresh). Tokens legacy se están **forzando a expirar** entre mar‑2025 y may‑2026. ⇒ UX consciente de expiración: detectar 401, pausar el outbox del proyecto, banner "token Jira expirado — re‑pega en Settings", reanudar drenaje tras re‑auth (la idempotencia hace el replay seguro).
183
+ - Muchas enterprises **deshabilitan** la creación de API tokens org‑wide o imponen expiraciones cortas → documentarlo y tener OAuth como ruta v2.
184
+
185
+ ### Cifrado en reposo
186
+
187
+ `jira-credential-store.ts` expone una interfaz de un solo fichero. **v1**: libsodium `crypto_secretbox` bajo keyfile `0600` (estrictamente más fuerte que el listón actual de plaintext del webhook‑HMAC, **sin plugin nativo**). **v2**: swap a Tauri keychain/stronghold = cambio de un fichero (no toca `src-tauri`/Cargo/firma/notarización en v1). En Windows, donde los permisos POSIX de `secure-fs.ts` son no‑op, documentar que una cuenta‑OS compartida no es frontera de seguridad soportada en v1.
188
+
189
+ ---
190
+
191
+ ## 6. Ciclo de vida y mapeo de estados (la parte difícil)
192
+
193
+ Los issues de Jira **no tienen un campo `status` asignable** — hay que **transicionar**, y las transiciones están **gateadas por el workflow** del cliente (que es arbitrario). Las 4 lógicas de Specrails (`todo/in_progress/done/cancelled`) deben caer sobre N estados de cliente repartidos en solo **3 categorías estables**: `new` / `indeterminate` / `done` (`statusCategory.key`).
194
+
195
+ ### Resolver de dos niveles
196
+
197
+ 1. **Mapa explícito por proyecto gana siempre.** En Settings el usuario elige, de la **lista real de estados** de su proyecto (fetched en vivo), el target para cada estado lógico. Esto resuelve la ambigüedad (p.ej. dos estados sobre categoría `done`: `Released` vs `Won't Do`).
198
+ 2. **Fallback por categoría** cuando no hay mapa: anclar en `statusCategory.key` (nunca en el **nombre** localizable del estado). Para `cancelled`, preferir un *cancel‑lexicon* (`won't do`, `cancelled`, `rejected`, `abandoned`, `invalid`, `duplicate`) y fijar `resolution`; para `done`/éxito, preferir un *ship‑lexicon* (`done`, `closed`, `released`, `resolved`, `complete`) y **alejarse** del cancel‑lexicon.
199
+
200
+ ### Camino de transición — BFS por saltos
201
+
202
+ Como solo ves las transiciones salientes del estado **actual**, un workflow `Backlog → Selected for Dev → In Progress → Done` no ofrece arista directa a `Done`. Algoritmo: `GET /transitions` del issue vivo → aplicar la arista que reduce la distancia a la categoría objetivo (orden `new < indeterminate < done`) → re‑`GET` → repetir. Cap ~5 saltos, dedup de estados visitados (evitar bucles), parar si ninguna transición reduce distancia. **Idempotency‑first**: si la categoría actual ya es la objetivo, **no‑op**. Si no hay camino en N saltos → **dead‑letter** no‑fatal ("mover estado manualmente en Jira") y **jamás** se hace error del rail.
203
+
204
+ ### Pantallas de transición / campos requeridos
205
+
206
+ La transición a `done` puede tener `hasScreen:true` con `resolution` requerido. Siempre `GET /transitions?expand=transitions.fields` primero; incluir `resolution`/custom fields **solo** si aparecen en esa pantalla (`required && !hasDefaultValue`). Si un custom field requerido no tiene default programable, abortar **esa** transición con gracia (dead‑letter) en vez de adivinar. El **comentario** va como op separada para que un fallo de transición no se lo lleve por delante.
207
+
208
+ ### `needs_review`
209
+
210
+ Flag app‑only sin equivalente Jira. Mapeo: cuando `needs_review=true`, **no** disparar Done; en su lugar comentario + label `specrails:needs-review`. La transición a Done queda condicionada a `status==='completed' && !needs_review`. Se limpia el label en la siguiente finalización limpia (`clearWarning` en `ticket-store.ts:336-339`).
211
+
212
+ ---
213
+
214
+ ## 7. Hot‑swap local ↔ Jira
215
+
216
+ - **`source` es por‑ticket, no por‑tablero.** Voltear el toggle Local↔Jira cambia solo el **destino de creación** y la **fuente de lectura**; jamás re‑homologa specs existentes.
217
+ - Las specs locales pre‑existentes (sin `jira_links`) siguen siendo locales; ofrecer una acción explícita opt‑in "empujar esta spec a Jira" (crea el issue + fila `jira_links`) en vez de migrar implícitamente.
218
+ - **Write‑back gateado por snapshot‑per‑job:** la op de outbox de un job lleva el `jira_issue_id` capturado **al lanzar** (mismo patrón que el snapshot de profile). Voltear el tablero a mitad de un job no afecta el write‑back en vuelo. Esto también cubre el reinicio del server (se pierde el `railJobs` Map en memoria → re‑parsear `#id` del comando como ya hace el camino local, y re‑resolver `jira_links` por id).
219
+ - **Gating de capacidades:** helper `sourceSupports(source, feature)` (espejo de `sectionVisibleForProviders`) para ocultar Drafts / SMASH / Contract‑Layer en specs de origen Jira, sin construir la interfaz `TicketProvider` completa.
220
+
221
+ ---
222
+
223
+ ## 8. Sincronización
224
+
225
+ ### Inbound — polling (forzado, no es un compromiso)
226
+
227
+ Webhooks descartados: el server liga `127.0.0.1` (`index.ts:552`), sin ingress público; los webhooks dinámicos de Jira Cloud exigen URL HTTPS pública + app Connect/OAuth (Basic no puede registrarlos) y **expiran cada 30 días**.
228
+
229
+ Diseño de poll:
230
+ - `POST /rest/api/3/search/jql` (el `GET /search` legacy fue **deprecado** 2024‑10‑31 y bloqueado fin‑oct‑2025). Enviar `fields` explícito, paginar con `nextPageToken`, parar en token ausente/`isLast`.
231
+ - JQL: `project = KEY AND (labels en filtro specrails) ORDER BY updated ASC`.
232
+ - **High‑water mark con solape de seguridad de 2 min** (`updated >= hw - 2min`), nunca avanzar `hw` más allá de `now - 1min`, dedup por `(issueId, updated)`. **La marca se deriva del `updated` máximo observado en los issues devueltos** (timestamps del *server de Jira*), **no** de `Date.now()` local — esto auto‑corrige el clock skew del desktop.
233
+ - `updated` tiene **resolución de minuto** y la búsqueda es eventualmente consistente → ventana ≥1 min + solape evita perder cambios del mismo minuto.
234
+ - **Reconcile completo horario** (JQL full por project+label) para detectar **borrados/moves** que no bumpean `updated`.
235
+ - **Read‑your‑write**: tras una escritura propia, usar `reconcileIssues:[id]` (consistencia fuerte para ese id) para evitar el flicker done→in_progress→done por lag de réplica.
236
+ - **El outbox es autoritativo:** el poll **no** sobrescribe un campo (sobre todo `status`) de un issue con op de outbox pendiente. Para specs en rail activo, congelar la materialización de `status` (es app‑owned durante el run); solo sincronizar campos no‑estado (description/labels).
237
+
238
+ ### Outbound — outbox durable
239
+
240
+ - Drenaje en worker de fondo: **FIFO por‑issue** (una transición debe aterrizar antes que el comentario que la describe), **paralelo entre issues distintos**, con cap de concurrencia.
241
+ - **Idempotencia:** transiciones por no‑op‑si‑ya‑en‑categoría; **comentarios** con un *self‑marker* invisible embebido en el body ADF (`[specrails:job-<id>]`) — Jira no tiene idempotencia nativa de comentarios, así que antes de re‑postear se hace `GET .../comment` y se salta si el marker ya existe. El marker dobla como filtro de auto‑eco en el poll.
242
+ - **Rate limits:** honrar `Retry-After` en 429 exacto; si ausente, backoff exponencial con jitter (base 2s, cap 30s, ~4 reintentos); respetar el techo ~20 writes/2s por issue. Token‑bucket por debajo de los burst caps.
243
+ - **Clasificación de errores:** `401` = credencial → **pausar** outbox del proyecto + banner re‑auth (no reintentar en bucle); `403` = permiso de operación concreta → dead‑letter nombrando la operación ("tu cuenta no puede transicionar en PROJ"), sin inferir fallo global; `404` sobre issue conocido = terminal (issue borrado/movido) → marcar link `orphaned`, parar la op; solo `429/5xx/timeout` son reintentables.
244
+ - **Dead‑letter visible** con reintento manual: `GET /jira/outbox`, `POST /jira/outbox/:id/retry`, indicador `JiraSyncIndicator` en UI. Un workflow‑gap o un 403 **nunca** es un drop silencioso.
245
+
246
+ ---
247
+
248
+ ## 9. Corner cases (87 catalogados; los críticos)
249
+
250
+ Distribución: **9 critical, 47 high, 29 medium, 2 low**. Categorías: sync (18), workflow‑mapping (16), data‑model (12), lifecycle (10), concurrency (8), auth (6), offline/permissions/rate‑limit (4 c/u), core (3), hot‑swap (2).
251
+
252
+ Los **9 críticos** y su mitigación (todos v1‑must‑handle):
253
+
254
+ 1. **Sin camino de transición a la categoría objetivo** (workflow forward‑only) → BFS por saltos + dead‑letter no‑fatal, nunca error del rail.
255
+ 2. **Transición Done con pantalla + `resolution` requerido** → `expand=transitions.fields`, incluir solo lo que está en pantalla; abortar con gracia si hay custom field sin default.
256
+ 3. **Ambigüedad de categoría `done`** (`Released` vs `Won't Do`) → resolver de 2 niveles, mapa explícito + cancel/ship lexicon.
257
+ 4. **`in_progress` de lanzamiento nunca llega a Jira** (la app no lo emite y `write_access:false` lo quita de core) → push explícito desde `rails-router.ts:293-298` + escribir `in_progress` en caché ahí mismo.
258
+ 5. **Crash entre mutación de caché y enqueue de outbox** → outbox persistido **primero** en la misma txn SQLite; outbox = fuente de verdad; drenar no‑acked al arrancar.
259
+ 6. **Offline en la escritura de estado al salir el job** → side‑effect Jira **nunca** inline en `onJobFinished`; solo el enqueue durable, en try/catch que no puede romper el rail; drena al reconectar.
260
+ 7. **Partial write: caché en `done`, Jira sigue `In Progress`, sin fila de outbox** → mismo invariante transaccional (op registrada antes de mutar JSON) + reconcile de arranque.
261
+ 8. **Proyecto Jira equivocado** (key con typo / multi‑proyecto) → validar `GET /project/{key}` en bind, mostrar nombre+lead+issue‑types para confirmación humana, scope de JQL a `project=KEY` + filtro label, guardar el **id** inmutable del project.
262
+ 9. **Token expira a mitad de un rail multi‑hora** → outbox durable absorbe el 401; estado dead‑letter visible + evento WS `jira.auth_expired` + banner "Reconnect to sync N pending"; al re‑pegar token, auto‑drena (idempotencia = no‑op si ya en categoría).
263
+
264
+ Otros **high** notables: dos editores (Jira UI + TicketDetailModal) → precondición de versión/ETag, 409 no clobbea, banner "editado en Jira"; humano reabre el issue antes de que drene el `done` encolado → freshness check (si la versión cambió desde el enqueue, dead‑letter "superseded", no forzar — "el robot no pelea con el humano"); op de propose‑spec en terminal sobre proyecto Jira → materializador **quirúrgico** que nunca dropea tickets sin `jira_links`, reconcilia los `source:"propose-spec"` hacia Jira; ADF (Cloud v3) vs wiki‑string (DC v2) → branch del serializador en el límite del adapter por deployment detectado.
265
+
266
+ **Guardarraíl explícito:** **no** añadir un valor `'jira-sync'` al enum `ai_invocations.surface` — una op de sync no tiene modelo/tokens/coste y contaminaría el análisis de coste con filas `$0/null`. La telemetría de sync va en una tabla ops dedicada o solo eventos WS.
267
+
268
+ ---
269
+
270
+ ## 10. Plan de implementación por fases
271
+
272
+ ### Fase 0 — Andamiaje y contrato (sin UI)
273
+ - Migraciones desktop‑db (`ticket_source`) y per‑project (`jira_connection`, `jira_links`, `jira_outbox`).
274
+ - `server/jira/jira-credential-store.ts` (interfaz + backend libsodium).
275
+ - `server/jira/jira-client.ts`: detección Cloud/DC en bind, Basic/Bearer, v3/v2, ADF/wiki, `{ok,data,error,status}` (modelado como `specrails-tech-client.ts`, **no** sobre `WebhookManager`).
276
+ - Escritor de `.specrails/backlog-config.json` (`provider:local`, `write_access:false`).
277
+ - Tests: cliente (mock HTTP), credential store, migraciones. Cobertura ≥ umbrales (server 80 %).
278
+
279
+ ### Fase 1 — MVP (read + status round‑trip + comentario) — **el v1 enviable**
280
+ - `jira-materializer.ts`: poll `POST /search/jql` → `local-tickets.json` (merge quirúrgico, vía `mutateStore` con lock), `jira-links` por id inmutable, `#id` monotónico tombstoneado, high‑water + solape + reconcile horario.
281
+ - `jira-status-resolver.ts`: resolver 2 niveles + BFS + manejo de pantalla/resolution.
282
+ - `jira-outbox.ts` + drainer: txn única, idempotencia, FIFO‑por‑issue, Retry‑After, clasificación de errores, dead‑letter.
283
+ - **Hook A** `rails-router.ts:293-298`: encolar transición `in_progress` + escribir caché.
284
+ - **Hook B** `project-registry.ts:315-327`: encolar transición de salida + comentario de finalización.
285
+ - `jira-router.ts` (REST, gateado por flag): bind/validar conexión, `GET/POST /jira/outbox`, reintento, `mypermissions` probe.
286
+ - Eventos WS: `jira.synced`, `jira.auth_expired`, `jira.outbox_changed`, `jira.degraded` (project‑scoped).
287
+ - Cliente: toggle `ticket_source` por proyecto (Settings), badge "PROJ‑123" + estado externo en `SpecCard`/views/`TicketDetailModal`, "Ver en Jira", panel de credenciales + mapa de estados, `JiraSyncIndicator` + dead‑letter. i18n estricto (sin strings hardcoded) + tokens de tema semánticos.
288
+ - **Feature flags**: `SPECRAILS_JIRA_SECTION` (server) + `VITE_FEATURE_JIRA` (cliente), off‑por‑defecto hasta GA.
289
+ - Companion: **nada obligatorio** (pass‑through tolerante).
290
+
291
+ ### Fase 2 — Robustez enterprise
292
+ - OAuth 2.0 3LO (loopback + token‑broker hospedado) detrás de la interfaz de credencial; swap de credential‑store a Tauri keychain/stronghold.
293
+ - Conflictos campo‑a‑campo bidireccionales (precondición de versión, banners de conflicto).
294
+ - Sink de comentarios Jira→local (poblar el `comments[]` dormante).
295
+ - Companion Opción B (badge Jira de primera clase, ~2 ediciones).
296
+ - Probes proactivos de permisos/expiración.
297
+
298
+ ### Fase 3 — Escala / plataforma
299
+ - Relay de ingress público (vía specrails‑tech) para webhooks Jira en tiempo real (sustituye polling donde se pueda).
300
+ - Abstracción `TicketProvider` (extraer `LocalTicketProvider` + `JiraTicketProvider`) **cuando** llegue un tercer backend (GitHub Issues/Linear) — entonces es un refactor mecánico y revisable por separado, no una apuesta especulativa.
301
+
302
+ ---
303
+
304
+ ## 11. Por qué esto aporta alto valor al cliente enterprise
305
+
306
+ - **Adopción sin fricción de seguridad:** token en la máquina, cero backend que vea credenciales, soporta Cloud **y** Data Center (muchas enterprises siguen en DC). Ninguna revisión de "vuestro server ve nuestros tokens".
307
+ - **Respeta el workflow del cliente:** no impone estados; mapea sobre el workflow real con override explícito + fallback por categoría + BFS. Nunca rompe un rail por una transición imposible.
308
+ - **Nunca pierde una actualización ni spamea:** outbox transaccional + idempotencia → exactamente‑una‑vez observable en comentarios y transiciones, incluso con crash/offline/reintentos. El "robot no pelea con el humano".
309
+ - **Visibilidad para PMs:** el issue Jira refleja el ciclo real (In Progress al lanzar, Done al terminar) y recibe un comentario con coste/duración/PR — los stakeholders no técnicos viven en Jira y lo ven sin entrar en Specrails.
310
+ - **Hot‑swap real por proyecto** sin migración forzada ni riesgo a los proyectos locales existentes.
311
+ - **Riesgo de producto mínimo:** core congelado intacto, app móvil en App Review intacta, módulo `ticket-store.ts` crítico sin refactor. Todo detrás de feature flags.
312
+
313
+ ---
314
+
315
+ ## 12. Decisiones abiertas para validar antes de construir
316
+
317
+ 1. **Alcance de despliegue v1:** ¿Cloud‑first (ADF, Basic) y DC detrás del tipo detectado, o DC desde el día 1? (El branch de body ADF/wiki debe existir igual si se promete DC.)
318
+ 2. **Granularidad del mapa de estados:** ¿UI de mapeo explícito obligatoria en el bind, o fallback‑por‑categoría con opción de afinar? (Recomendado: fallback funcional + afinado opcional.)
319
+ 3. **Comportamiento en fallo con workflow forward‑only:** ¿dejar el issue en `In Progress` en Jira por defecto (config) o intentar siempre revertir? (Recomendado: best‑effort revert + dejar‑como‑está configurable.)
320
+ 4. **Companion v1:** ¿cero cambios (pass‑through) o el badge de 2 ediciones?
321
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specrails-desktop",
3
- "version": "2.2.1",
3
+ "version": "2.4.0",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",