specrails-desktop 2.3.0 → 2.5.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.
- package/client/dist/assets/ActivityFeedPage-BTYWMRwB.js +1 -0
- package/client/dist/assets/AgentsPage-BfOCeHHt.js +86 -0
- package/client/dist/assets/{AnalyticsPage-Dyyz1ht3.js → AnalyticsPage-AbVXKh9v.js} +1 -1
- package/client/dist/assets/{BarChart-CMdLa6Es.js → BarChart-DlshJN3Z.js} +2 -2
- package/client/dist/assets/CodePage-DJCjDG4I.js +2 -0
- package/client/dist/assets/{DesktopAnalyticsPage-CTNwb639.js → DesktopAnalyticsPage-CTqZ9mbB.js} +1 -1
- package/client/dist/assets/DocsDialog-KiJOSRvX.js +11 -0
- package/client/dist/assets/DocsPage-B17CR54A.js +11 -0
- package/client/dist/assets/{ExportDropdown-DuoZcdYN.js → ExportDropdown-BAu6z3b6.js} +1 -1
- package/client/dist/assets/IntegrationsPage-CCG64Q-6.js +3 -0
- package/client/dist/assets/JobDetailPage-BnGJSMiS.js +16 -0
- package/client/dist/assets/JobsPage-B-tn4CIf.js +1 -0
- package/client/dist/assets/{cssMode-Cc6ozl-J.js → cssMode-DzNPAYFh.js} +1 -1
- package/client/dist/assets/dashboard--Ahnvfr3.js +1 -0
- package/client/dist/assets/dashboard-BN1C2pEh.js +1 -0
- package/client/dist/assets/dashboard-BZs_EzAn.js +1 -0
- package/client/dist/assets/dashboard-Bsw44L8_.js +1 -0
- package/client/dist/assets/dashboard-Bw3VECgY.js +1 -0
- package/client/dist/assets/{dashboard-Duo4DDCW.js → dashboard-CuOshSHn.js} +1 -1
- package/client/dist/assets/dashboard-DfouCM3_.js +1 -0
- package/client/dist/assets/dashboard-Pp5hwnZB.js +1 -0
- package/client/dist/assets/{dist-js-H6hyhSuv.js → dist-js-B16c3VyT.js} +1 -1
- package/client/dist/assets/{dist-js-4UEGaKhD.js → dist-js-P2FkJ6fA.js} +1 -1
- package/client/dist/assets/{editor.main-CfXxHimg.js → editor.main-C7Rmw-hR.js} +2 -2
- package/client/dist/assets/{freemarker2-DP7J1gG3.js → freemarker2-Cszs4SVo.js} +1 -1
- package/client/dist/assets/{handlebars-BjRlucw6.js → handlebars-Dp7Lsuym.js} +1 -1
- package/client/dist/assets/{html-OumBQJ-U.js → html-BURidrEm.js} +1 -1
- package/client/dist/assets/{htmlMode-CStc3zXM.js → htmlMode--k5M7GjZ.js} +1 -1
- package/client/dist/assets/index-AfVF6BgE.js +142 -0
- package/client/dist/assets/index-NlH5BbXJ.css +2 -0
- package/client/dist/assets/{integrations-Cublz3m6.js → integrations-2C7MkGT0.js} +1 -1
- package/client/dist/assets/{integrations-HIlUxXVs.js → integrations-BDC670cg.js} +1 -1
- package/client/dist/assets/integrations-BqUmRUef.js +1 -0
- package/client/dist/assets/{integrations-DmQYCUvN.js → integrations-C2jQtv-s.js} +1 -1
- package/client/dist/assets/{integrations-DRdbki5W.js → integrations-CB98NeH5.js} +1 -1
- package/client/dist/assets/{integrations-C3p12Ms6.js → integrations-CX4p_bij.js} +1 -1
- package/client/dist/assets/{integrations-DaC4SzzL.js → integrations-_SuVeQIG.js} +1 -1
- package/client/dist/assets/{integrations-Cr6hH7XR.js → integrations-eQPHAYsE.js} +1 -1
- package/client/dist/assets/{javascript-CMk--e7g.js → javascript-kJQz__44.js} +1 -1
- package/client/dist/assets/jira-C-ATCti0.js +1 -0
- package/client/dist/assets/jira-CmVfRM-b.js +1 -0
- package/client/dist/assets/jira-D7bkKAX8.js +1 -0
- package/client/dist/assets/jira-DKImM1YH.js +1 -0
- package/client/dist/assets/jira-DOw8bkIR.js +1 -0
- package/client/dist/assets/jira-DlA-wGp-.js +1 -0
- package/client/dist/assets/jira-Fob8EGxN.js +1 -0
- package/client/dist/assets/jira-xZA2lixb.js +1 -0
- package/client/dist/assets/jobs-BGkI19S_.js +1 -0
- package/client/dist/assets/jobs-Brp44JDd.js +1 -0
- package/client/dist/assets/jobs-D93lG6If.js +1 -0
- package/client/dist/assets/jobs-DAF8AGy5.js +1 -0
- package/client/dist/assets/jobs-Db3xrsp_.js +1 -0
- package/client/dist/assets/jobs-Do4Ltqdj.js +1 -0
- package/client/dist/assets/jobs-F5PGJwbW.js +1 -0
- package/client/dist/assets/jobs-fYWWxCUV.js +1 -0
- package/client/dist/assets/{jsonMode-C2h3ZcjZ.js → jsonMode-v5JYPpnz.js} +1 -1
- package/client/dist/assets/{lib-Cs5FrUJI.js → lib-rNNmltMb.js} +1 -1
- package/client/dist/assets/{liquid-mI3KJrBE.js → liquid-Dl9I6gWt.js} +1 -1
- package/client/dist/assets/{lspLanguageFeatures-DU09ggWi.js → lspLanguageFeatures-CPlEe0NK.js} +1 -1
- package/client/dist/assets/{mdx-C41VDTR_.js → mdx-Byl7TtzQ.js} +1 -1
- package/client/dist/assets/{monaco.contribution-CPObAXMC.js → monaco.contribution-YMAkHQcQ.js} +2 -2
- package/client/dist/assets/{python-Y27rKQtk.js → python-jWQwT6j2.js} +1 -1
- package/client/dist/assets/{razor-Cd5-q9Bp.js → razor-BWS3sP-E.js} +1 -1
- package/client/dist/assets/setup-C0dzw8j4.js +1 -0
- package/client/dist/assets/setup-C1IA-9YS.js +1 -0
- package/client/dist/assets/setup-CpfjaNut.js +1 -0
- package/client/dist/assets/setup-D3rNZA9A.js +1 -0
- package/client/dist/assets/setup-UD2aanGs.js +1 -0
- package/client/dist/assets/setup-WP6WOYQh.js +1 -0
- package/client/dist/assets/setup-gzLG8T6F.js +1 -0
- package/client/dist/assets/setup-pjgmYHx6.js +1 -0
- package/client/dist/assets/{specs-DaUTrNF9.js → specs-B4GuOzuZ.js} +1 -1
- package/client/dist/assets/{specs-CZ1PsXsC.js → specs-BVLKe2n5.js} +1 -1
- package/client/dist/assets/{specs-k0PyLDVt.js → specs-C62F2CDv.js} +1 -1
- package/client/dist/assets/specs-D-Sb6dre.js +1 -0
- package/client/dist/assets/{specs-B__C8-8a.js → specs-DFSkAeK8.js} +1 -1
- package/client/dist/assets/{specs-BFfu3u-a.js → specs-DfwDeADE.js} +1 -1
- package/client/dist/assets/{specs-D2FzlLn9.js → specs-VK-zXv7x.js} +1 -1
- package/client/dist/assets/{specs-Dyc5hYeE.js → specs-ghyBMnib.js} +1 -1
- package/client/dist/assets/{tsMode-B0y_xEci.js → tsMode-BbOGOuSV.js} +1 -1
- package/client/dist/assets/{typescript-BzK0OgwW.js → typescript-eBtFQJLs.js} +1 -1
- package/client/dist/assets/{useProjectCache-BZWYV-w-.js → useProjectCache-Cid_GxRM.js} +1 -1
- package/client/dist/assets/{workers-rt--R2Qy.js → workers-BvicOoDf.js} +1 -1
- package/client/dist/assets/{xml-eX9QXAmI.js → xml-BJepAPyM.js} +1 -1
- package/client/dist/assets/{yaml-fcsNkpOt.js → yaml-DabgV-eA.js} +1 -1
- package/client/dist/index.html +13 -12
- package/docs/jira-integration-plan.md +321 -0
- package/package.json +1 -1
- package/server/dist/chat-manager.js +19 -7
- package/server/dist/context-scope.js +29 -8
- package/server/dist/db.js +127 -2
- package/server/dist/feature-flags.js +26 -0
- package/server/dist/interactive-job-session.js +363 -0
- package/server/dist/jira/jira-adf.js +113 -0
- package/server/dist/jira/jira-backlog-config.js +58 -0
- package/server/dist/jira/jira-client.js +279 -0
- package/server/dist/jira/jira-credential-store.js +103 -0
- package/server/dist/jira/jira-db.js +341 -0
- package/server/dist/jira/jira-issue-fields.js +428 -0
- package/server/dist/jira/jira-materializer.js +250 -0
- package/server/dist/jira/jira-status-resolver.js +211 -0
- package/server/dist/jira/jira-sync-manager.js +1014 -0
- package/server/dist/jira/types.js +9 -0
- package/server/dist/jira-router.js +304 -0
- package/server/dist/project-registry.js +43 -1
- package/server/dist/project-router-jobs.js +42 -0
- package/server/dist/project-router-tickets.js +49 -1
- package/server/dist/project-router.js +4 -0
- package/server/dist/queue-manager.js +214 -54
- package/server/dist/rails-router.js +27 -1
- package/server/dist/util/stream-display.js +66 -0
- package/client/dist/assets/ActivityFeedPage-3Veccrvk.js +0 -1
- package/client/dist/assets/AgentsPage-2mFPghP4.js +0 -86
- package/client/dist/assets/CodePage-D7Xwjhut.js +0 -2
- package/client/dist/assets/DocsDialog-D8yoyZDD.js +0 -11
- package/client/dist/assets/DocsPage-CeO-fAxy.js +0 -11
- package/client/dist/assets/IntegrationsPage-iIZ0UEzf.js +0 -3
- package/client/dist/assets/JobDetailPage-DgJHAH2m.js +0 -16
- package/client/dist/assets/JobsPage-Bv_RpRAE.js +0 -1
- package/client/dist/assets/dashboard-B4ixDVk8.js +0 -1
- package/client/dist/assets/dashboard-BZBADHSj.js +0 -1
- package/client/dist/assets/dashboard-C1MfeUHs.js +0 -1
- package/client/dist/assets/dashboard-C7SK6xu5.js +0 -1
- package/client/dist/assets/dashboard-CB6Le1yN.js +0 -1
- package/client/dist/assets/dashboard-CoTpMOBM.js +0 -1
- package/client/dist/assets/dashboard-I19DXBxw.js +0 -1
- package/client/dist/assets/index-CGHKpC-N.js +0 -142
- package/client/dist/assets/index-D17R4Cjc.css +0 -2
- package/client/dist/assets/integrations-D28q1kF6.js +0 -1
- package/client/dist/assets/jobs-2N3RXDAM.js +0 -1
- package/client/dist/assets/jobs-2f6Hdc72.js +0 -1
- package/client/dist/assets/jobs-3j3_npyo.js +0 -1
- package/client/dist/assets/jobs-BqEbCCxD.js +0 -1
- package/client/dist/assets/jobs-DPPT6bV6.js +0 -1
- package/client/dist/assets/jobs-DRzjWI9u.js +0 -1
- package/client/dist/assets/jobs-cHYInoau.js +0 -1
- package/client/dist/assets/jobs-vGfzIDQa.js +0 -1
- package/client/dist/assets/setup--FMCsnQS.js +0 -1
- package/client/dist/assets/setup-B19ZpBNi.js +0 -1
- package/client/dist/assets/setup-BZPmkjSN.js +0 -1
- package/client/dist/assets/setup-BqYA02rS.js +0 -1
- package/client/dist/assets/setup-ChKQDHN9.js +0 -1
- package/client/dist/assets/setup-D2n9jMfM.js +0 -1
- package/client/dist/assets/setup-P3r6YP1D.js +0 -1
- package/client/dist/assets/setup-fnfEbwlv.js +0 -1
- package/client/dist/assets/specs-cKEh2LXt.js +0 -1
- /package/client/dist/assets/{abap-Bw6f2wDG.js → abap-s65oMlhi.js} +0 -0
- /package/client/dist/assets/{activity-BdrPln96.js → activity-BqqwnH_h.js} +0 -0
- /package/client/dist/assets/{activity-BEIp_Y1A.js → activity-C8qqEIoP.js} +0 -0
- /package/client/dist/assets/{activity-CpkRS8Sx.js → activity-CZVM4nlJ.js} +0 -0
- /package/client/dist/assets/{activity-DOUVEjJi.js → activity-Cyy07Tgo.js} +0 -0
- /package/client/dist/assets/{activity-DRwkql_y.js → activity-DlbWCa4y.js} +0 -0
- /package/client/dist/assets/{activity-DKCpESPt.js → activity-Dwq0heud.js} +0 -0
- /package/client/dist/assets/{activity-DcDQ7tjw.js → activity-qFTcMyW9.js} +0 -0
- /package/client/dist/assets/{addon-image-3WCl5Vhd.js → addon-image-CpF0L0jM.js} +0 -0
- /package/client/dist/assets/{addon-ligatures-C5OdliKs.js → addon-ligatures-hXysGZrA.js} +0 -0
- /package/client/dist/assets/{addon-webgl-BbX6pSjl.js → addon-webgl-Cn1slavz.js} +0 -0
- /package/client/dist/assets/{addspec-D33ocMxf.js → addspec-B1FTtI2a.js} +0 -0
- /package/client/dist/assets/{addspec-DFswZ0jK.js → addspec-BCT9vm_c.js} +0 -0
- /package/client/dist/assets/{addspec-DVZ15Jp8.js → addspec-DeDOztDr.js} +0 -0
- /package/client/dist/assets/{addspec-Fkv91Opc.js → addspec-DpRgmfmx.js} +0 -0
- /package/client/dist/assets/{addspec-BEeF5-zc.js → addspec-Dw-0Dg-4.js} +0 -0
- /package/client/dist/assets/{addspec-B5yl4Loj.js → addspec-rp496P_F.js} +0 -0
- /package/client/dist/assets/{addspec-DRE-jZv7.js → addspec-v8j6A7CD.js} +0 -0
- /package/client/dist/assets/{agents-DK-Dlc0i.js → agents-23iPejcA.js} +0 -0
- /package/client/dist/assets/{agents-Q6Ldfpxx.js → agents-BDx1RXcl.js} +0 -0
- /package/client/dist/assets/{agents-TeOSy-ax.js → agents-BFr3kUhK.js} +0 -0
- /package/client/dist/assets/{agents-Bm9rPqnt.js → agents-B_1L9xRg.js} +0 -0
- /package/client/dist/assets/{agents-1nCDWRmP.js → agents-BlPnx-mz.js} +0 -0
- /package/client/dist/assets/{agents-iTqjRajS.js → agents-DcxZHzNr.js} +0 -0
- /package/client/dist/assets/{agents-s87sMGzL.js → agents-G3shOewU.js} +0 -0
- /package/client/dist/assets/{agentstudio-B6Wb59E7.js → agentstudio-B-CMAQqy.js} +0 -0
- /package/client/dist/assets/{agentstudio-D3I62TLJ.js → agentstudio-Bk1eZcv4.js} +0 -0
- /package/client/dist/assets/{agentstudio-DuH9TogZ.js → agentstudio-ChxNuGAu.js} +0 -0
- /package/client/dist/assets/{agentstudio-Kw88_dUF.js → agentstudio-DNlme601.js} +0 -0
- /package/client/dist/assets/{agentstudio-BdidyBzZ.js → agentstudio-DpP9caEE.js} +0 -0
- /package/client/dist/assets/{agentstudio-BSnWLR63.js → agentstudio-Y3G0ddJ2.js} +0 -0
- /package/client/dist/assets/{agentstudio-BADhZ41e.js → agentstudio-kk9RB7Se.js} +0 -0
- /package/client/dist/assets/{aiedit-DJMny-D5.js → aiedit-5ETerMK1.js} +0 -0
- /package/client/dist/assets/{aiedit-D2ji6Qy0.js → aiedit-BBCrOpHq.js} +0 -0
- /package/client/dist/assets/{aiedit-DAhZTvtk.js → aiedit-BMtcGYNE.js} +0 -0
- /package/client/dist/assets/{aiedit-DvrcbwGv.js → aiedit-D9ddlgkM.js} +0 -0
- /package/client/dist/assets/{aiedit-WBSjT_C1.js → aiedit-De0SOH6S.js} +0 -0
- /package/client/dist/assets/{aiedit-BWxHGsYA.js → aiedit-DrfzQroF.js} +0 -0
- /package/client/dist/assets/{aiedit-DOcxERkU.js → aiedit-fMltW101.js} +0 -0
- /package/client/dist/assets/{analytics-C9Zc-rkM.js → analytics-BeTyviO8.js} +0 -0
- /package/client/dist/assets/{analytics-CrPCZRJ-.js → analytics-C4eEO260.js} +0 -0
- /package/client/dist/assets/{analytics-CYj0tfj7.js → analytics-C67cIA1b.js} +0 -0
- /package/client/dist/assets/{analytics-C6EzgtdE.js → analytics-CAguvW28.js} +0 -0
- /package/client/dist/assets/{analytics-CVx3YOc0.js → analytics-DBtt8Mgk.js} +0 -0
- /package/client/dist/assets/{analytics-CnY4kNG3.js → analytics-DUPtODxX.js} +0 -0
- /package/client/dist/assets/{analytics-BIdr0YfL.js → analytics-YIpQvPAc.js} +0 -0
- /package/client/dist/assets/{apex-Cw8_REBo.js → apex-BLUBIldB.js} +0 -0
- /package/client/dist/assets/{attachments-DYHGA2Dj.js → attachments-CCWasu-P.js} +0 -0
- /package/client/dist/assets/{attachments-Dd92KpUH.js → attachments-CHaDUfjB.js} +0 -0
- /package/client/dist/assets/{attachments-DzdU6DV6.js → attachments-CVSAbGNl.js} +0 -0
- /package/client/dist/assets/{attachments-Bcf6BG6V.js → attachments-Chg5poG1.js} +0 -0
- /package/client/dist/assets/{attachments-BW4L3l2L.js → attachments-DazTVJbH.js} +0 -0
- /package/client/dist/assets/{attachments-COcrGRFz.js → attachments-Dn-JImAK.js} +0 -0
- /package/client/dist/assets/{attachments-Bke8sCU4.js → attachments-LDA9kp2X.js} +0 -0
- /package/client/dist/assets/{azcli-Cz6HAoOw.js → azcli-DuWxh9mO.js} +0 -0
- /package/client/dist/assets/{bat-CcJ-xyqL.js → bat-UKoTejQm.js} +0 -0
- /package/client/dist/assets/{bicep-z1WDCKYz.js → bicep-4sTT4B3D.js} +0 -0
- /package/client/dist/assets/{browser-DGITz3fC.js → browser-BDd1dbFa.js} +0 -0
- /package/client/dist/assets/{browser-JsAIGCEW.js → browser-BWSgbfdX.js} +0 -0
- /package/client/dist/assets/{browser-M5-rbPlw.js → browser-D2Y_UAKA.js} +0 -0
- /package/client/dist/assets/{browser-BlYF4OOq.js → browser-DH9SGVfM.js} +0 -0
- /package/client/dist/assets/{browser-Bc-YdlVg.js → browser-DWOVYMlg.js} +0 -0
- /package/client/dist/assets/{browser-CT-ReZGt.js → browser-Dxc_VIRK.js} +0 -0
- /package/client/dist/assets/{browser-5ErDlJoR.js → browser-lTQwcDCI.js} +0 -0
- /package/client/dist/assets/{cameligo-BRewOpfa.js → cameligo-CAAryRYO.js} +0 -0
- /package/client/dist/assets/{chat-DwUm6W9z.js → chat-BO9MvVID.js} +0 -0
- /package/client/dist/assets/{chat-BEGuC03z.js → chat-CPgmgZOj.js} +0 -0
- /package/client/dist/assets/{chat-CboQguCi.js → chat-CUrG1eVg.js} +0 -0
- /package/client/dist/assets/{chat-DRCa9pOt.js → chat-CvOOKB2s.js} +0 -0
- /package/client/dist/assets/{chat-BEW60P_u.js → chat-DIh3hr6y.js} +0 -0
- /package/client/dist/assets/{chat-yoXwguQu.js → chat-UVVZqA57.js} +0 -0
- /package/client/dist/assets/{chat-BQNMD0PL.js → chat-mPn3UlMl.js} +0 -0
- /package/client/dist/assets/{clojure-DBjRWN6g.js → clojure-BlMERO1w.js} +0 -0
- /package/client/dist/assets/{clsx-DnqN-uhr.js → clsx-CnH-HMk3.js} +0 -0
- /package/client/dist/assets/{code-zCwBt3Uu.js → code-BwIz8agY.js} +0 -0
- /package/client/dist/assets/{code-g0qFMzyg.js → code-CD7yNSK0.js} +0 -0
- /package/client/dist/assets/{code-DDU0CRS0.js → code-CDFlxUFC.js} +0 -0
- /package/client/dist/assets/{code-L35Loak_.js → code-Cp3Fdng-.js} +0 -0
- /package/client/dist/assets/{code-D1z-YDt-.js → code-D24e1Crx.js} +0 -0
- /package/client/dist/assets/{code-BtsmPQLV.js → code-DtZBQTi9.js} +0 -0
- /package/client/dist/assets/{code-Coa8f2Sh.js → code-nKa0fkm_.js} +0 -0
- /package/client/dist/assets/{coffee-Cfk_XHGR.js → coffee-Cj8D-Wl1.js} +0 -0
- /package/client/dist/assets/{commands-sqrqsxyE.js → commands-B-MVT-2F.js} +0 -0
- /package/client/dist/assets/{commands-UD1NzmwX.js → commands-B0yFTp7e.js} +0 -0
- /package/client/dist/assets/{commands-DLrvnPNg.js → commands-BR1kDkHQ.js} +0 -0
- /package/client/dist/assets/{commands-CJxCry-o.js → commands-Cb21pDlG.js} +0 -0
- /package/client/dist/assets/{commands-CfgY-_of.js → commands-DWgp-8W1.js} +0 -0
- /package/client/dist/assets/{commands-B772IyDa.js → commands-ddsl1V91.js} +0 -0
- /package/client/dist/assets/{commands-BDDp6xFG.js → commands-t4frzhB0.js} +0 -0
- /package/client/dist/assets/{common-Dmm1GhdD.js → common-5ilvMOcH.js} +0 -0
- /package/client/dist/assets/{common-DltqHaAe.js → common-B4sqsKp7.js} +0 -0
- /package/client/dist/assets/{common-GbpxfPG8.js → common-BKpVwUIf.js} +0 -0
- /package/client/dist/assets/{common-DeDELLZJ.js → common-BzEC3kJU.js} +0 -0
- /package/client/dist/assets/{common-DnjcgkPH.js → common-CALKUpYm.js} +0 -0
- /package/client/dist/assets/{common-Dard9UNH.js → common-CTEbWVZS.js} +0 -0
- /package/client/dist/assets/{common-DCr6VzJ7.js → common-DQiza2Xp.js} +0 -0
- /package/client/dist/assets/{cpp-BVob6BaP.js → cpp-BPfKnaj_.js} +0 -0
- /package/client/dist/assets/{csharp-C4fbRuOu.js → csharp-gX-x5uD6.js} +0 -0
- /package/client/dist/assets/{csp-DthFP_vT.js → csp-DKGVt8SM.js} +0 -0
- /package/client/dist/assets/{css-CGMH0hcW.js → css-CPMdnAVq.js} +0 -0
- /package/client/dist/assets/{cypher-Pnf68BRV.js → cypher-ClMDrj9S.js} +0 -0
- /package/client/dist/assets/{dart-PMMOtxZX.js → dart-C4zbzpVv.js} +0 -0
- /package/client/dist/assets/{dockerfile-di1nsJCc.js → dockerfile-D9xw73D1.js} +0 -0
- /package/client/dist/assets/{ecl-D_WVcB5M.js → ecl-gqO8tIR9.js} +0 -0
- /package/client/dist/assets/{editor.api2-XLGzZfbc.js → editor.api2-BPnIxMjz.js} +0 -0
- /package/client/dist/assets/{elixir-OAdJEMOn.js → elixir-DSAhVF3_.js} +0 -0
- /package/client/dist/assets/{explore-D2EFgt8J.js → explore-BE5UmlbD.js} +0 -0
- /package/client/dist/assets/{explore-BV5Xxlsn.js → explore-BmTaI8dX.js} +0 -0
- /package/client/dist/assets/{explore-A8Ltoblq.js → explore-CCwkqoWq.js} +0 -0
- /package/client/dist/assets/{explore-4mFpnrKU.js → explore-CMdEoPDx.js} +0 -0
- /package/client/dist/assets/{explore-C3FSE42C.js → explore-CtdCL4QU.js} +0 -0
- /package/client/dist/assets/{explore-B9A3iN2W.js → explore-DHjxSkqQ.js} +0 -0
- /package/client/dist/assets/{explore-BrBJvfjP.js → explore-__BeALjE.js} +0 -0
- /package/client/dist/assets/{flow9-D3QEZjgn.js → flow9-DeQCSPOd.js} +0 -0
- /package/client/dist/assets/{format-command-CwGuwzGA.js → format-command-2VNoNnMv.js} +0 -0
- /package/client/dist/assets/{fsharp-BF0k_8N8.js → fsharp-CEfaXL-S.js} +0 -0
- /package/client/dist/assets/{go-BAQO5Jsz.js → go-Xp1OkZCh.js} +0 -0
- /package/client/dist/assets/{graphql-hdFVFkiV.js → graphql-BwRXrUwe.js} +0 -0
- /package/client/dist/assets/{hcl-DWnl1o-X.js → hcl-u06DtVFk.js} +0 -0
- /package/client/dist/assets/{ini-CB-6OVu3.js → ini-AmeIpFND.js} +0 -0
- /package/client/dist/assets/{java-d1CmfiHX.js → java-CyDbRQjX.js} +0 -0
- /package/client/dist/assets/{julia-Bgv08lKa.js → julia-BqialFRG.js} +0 -0
- /package/client/dist/assets/{kotlin-u98kaVTf.js → kotlin-Dzz8TWAt.js} +0 -0
- /package/client/dist/assets/{less-CjYwpgg5.js → less-DHRJD3TR.js} +0 -0
- /package/client/dist/assets/{lexon-YTjaAFBB.js → lexon-5Y3QgTmT.js} +0 -0
- /package/client/dist/assets/{lua-BzmkWv27.js → lua-sKvhfPn5.js} +0 -0
- /package/client/dist/assets/{m3-CFwk9fw0.js → m3-DWDVwkFG.js} +0 -0
- /package/client/dist/assets/{markdown-CR5iMpSZ.js → markdown-CD_aSBxW.js} +0 -0
- /package/client/dist/assets/{mips-CcEalc17.js → mips-687T03hg.js} +0 -0
- /package/client/dist/assets/{msdax-BQbkawnr.js → msdax-C1St-dIV.js} +0 -0
- /package/client/dist/assets/{mysql-GTlaaW_P.js → mysql-BG7r8oBS.js} +0 -0
- /package/client/dist/assets/{nav-C2YXcbZS.js → nav-B05EYB0b.js} +0 -0
- /package/client/dist/assets/{nav-D2bOGSEg.js → nav-BNGCq-0y.js} +0 -0
- /package/client/dist/assets/{nav-BEL3MTwK.js → nav-BRInPX8a.js} +0 -0
- /package/client/dist/assets/{nav-CtYwmMgu.js → nav-Bf87DRHD.js} +0 -0
- /package/client/dist/assets/{nav-iH1V5j6o.js → nav-BkVzzFpc.js} +0 -0
- /package/client/dist/assets/{nav-0fwkrgHt.js → nav-BzFLtS1W.js} +0 -0
- /package/client/dist/assets/{nav-ClzOE4mA.js → nav-DBDbQOYn.js} +0 -0
- /package/client/dist/assets/{nav-B_G-TJDW.js → nav-X9sVtUWC.js} +0 -0
- /package/client/dist/assets/{objective-c-Byu1T5if.js → objective-c-Ds1-m05L.js} +0 -0
- /package/client/dist/assets/{pascal-BrfzBfRm.js → pascal-BKK9FpIi.js} +0 -0
- /package/client/dist/assets/{pascaligo-BXXKFUeo.js → pascaligo-SRS3nwtO.js} +0 -0
- /package/client/dist/assets/{perl-B3OikKq-.js → perl-B2hTOlrF.js} +0 -0
- /package/client/dist/assets/{pgsql-CTsa0Acc.js → pgsql-DIQJYNpL.js} +0 -0
- /package/client/dist/assets/{php-DiQh3FUW.js → php-BEaVe8X2.js} +0 -0
- /package/client/dist/assets/{pla-92uH8Fzm.js → pla-oPLHpZ-Q.js} +0 -0
- /package/client/dist/assets/{postiats-BbeWkKUr.js → postiats-D_vzrAzD.js} +0 -0
- /package/client/dist/assets/{powerquery-DgDMzpsm.js → powerquery-BKG6w-FH.js} +0 -0
- /package/client/dist/assets/{powershell-BfdUUzaG.js → powershell-B3dLhDt4.js} +0 -0
- /package/client/dist/assets/{protobuf-BojW2ftW.js → protobuf-DC8SGjcl.js} +0 -0
- /package/client/dist/assets/{pug-BxqTg3IU.js → pug-D5E-4fI0.js} +0 -0
- /package/client/dist/assets/{qsharp-BX_A-MW9.js → qsharp-6vJAWv0x.js} +0 -0
- /package/client/dist/assets/{r-D9BMnxvJ.js → r-CDwsEcbM.js} +0 -0
- /package/client/dist/assets/{redis-5cJqEQJJ.js → redis-CuQbbESS.js} +0 -0
- /package/client/dist/assets/{redshift-d8BBqiwb.js → redshift-B9e1k-qI.js} +0 -0
- /package/client/dist/assets/{restructuredtext-C8a6yIcZ.js → restructuredtext-BiJ5IwaU.js} +0 -0
- /package/client/dist/assets/{ruby-egeh-6KX.js → ruby-B0UAHY9b.js} +0 -0
- /package/client/dist/assets/{rust-a3r9IInB.js → rust-Dg_spmFr.js} +0 -0
- /package/client/dist/assets/{sb-y8iRIDei.js → sb-DjU66I8Q.js} +0 -0
- /package/client/dist/assets/{scala-BPDK2AmK.js → scala-qvStIdfG.js} +0 -0
- /package/client/dist/assets/{scheme-BIWUEoOs.js → scheme-FstEk5Rh.js} +0 -0
- /package/client/dist/assets/{scss-CA-PSzwg.js → scss-w0U3rQLK.js} +0 -0
- /package/client/dist/assets/{settings-CTcwN9RE.js → settings-5tzo0Rn3.js} +0 -0
- /package/client/dist/assets/{settings-D_dujJZI.js → settings-BDAW3trC.js} +0 -0
- /package/client/dist/assets/{settings-Bg0A3zoS.js → settings-BEWv3VEu.js} +0 -0
- /package/client/dist/assets/{settings-BgPqg2nv.js → settings-BORg56um.js} +0 -0
- /package/client/dist/assets/{settings-BSze3_9q.js → settings-D3LurcR5.js} +0 -0
- /package/client/dist/assets/{settings-CSJ0ahZ8.js → settings-DcqWIEM6.js} +0 -0
- /package/client/dist/assets/{settings-DYIV89nV.js → settings-Dfz8QbZS.js} +0 -0
- /package/client/dist/assets/{settings-DDcfx_ca.js → settings-yMubjqYw.js} +0 -0
- /package/client/dist/assets/{shell--LiT1Bja.js → shell-DJ78wREd.js} +0 -0
- /package/client/dist/assets/{solidity-DdqZccZg.js → solidity-1aGIVsdX.js} +0 -0
- /package/client/dist/assets/{sophia-S6-YxNG_.js → sophia-40LqcGjB.js} +0 -0
- /package/client/dist/assets/{sparql-BSf5kMp2.js → sparql-Cz5dqG_g.js} +0 -0
- /package/client/dist/assets/{sql-D7KgjR8G.js → sql-64f62Ni4.js} +0 -0
- /package/client/dist/assets/{st-BnoDa-Ml.js → st-gJe2yG8J.js} +0 -0
- /package/client/dist/assets/{swift-DEUHTkUX.js → swift-C6ME22mv.js} +0 -0
- /package/client/dist/assets/{systemverilog-Tqb_KPnW.js → systemverilog-CEWz259w.js} +0 -0
- /package/client/dist/assets/{tcl-BmBFS2qq.js → tcl-CcLVIi3m.js} +0 -0
- /package/client/dist/assets/{terminal-Bje4ziIa.js → terminal-BYtreaaF.js} +0 -0
- /package/client/dist/assets/{terminal-CSONJOex.js → terminal-C0xx0SjA.js} +0 -0
- /package/client/dist/assets/{terminal-DeWzh6ys.js → terminal-CPpK58RC.js} +0 -0
- /package/client/dist/assets/{terminal-C2WYcFHF.js → terminal-CdxkpafL.js} +0 -0
- /package/client/dist/assets/{terminal-DEqzGtcr.js → terminal-Ciia0wh2.js} +0 -0
- /package/client/dist/assets/{terminal-80yDMgMF.js → terminal-DHIkiWcs.js} +0 -0
- /package/client/dist/assets/{terminal-lkZYR4wJ.js → terminal-DY42QANg.js} +0 -0
- /package/client/dist/assets/{terminal-YOlsJCQj.js → terminal-DoxtVdma.js} +0 -0
- /package/client/dist/assets/{tickets-DYvafSaY.js → tickets-0rM0lIXd.js} +0 -0
- /package/client/dist/assets/{tickets-DNOANUXr.js → tickets-1UIGf_oA.js} +0 -0
- /package/client/dist/assets/{tickets-DlpC_iTg.js → tickets-9kdPXInd.js} +0 -0
- /package/client/dist/assets/{tickets-CF2PYelu.js → tickets-C6pwZwt4.js} +0 -0
- /package/client/dist/assets/{tickets-CB7N30gm.js → tickets-DAjtxAVb.js} +0 -0
- /package/client/dist/assets/{tickets-DU1aqsbr.js → tickets-DNmXcAwu.js} +0 -0
- /package/client/dist/assets/{tickets-clefmXLv.js → tickets-n23kDqJT.js} +0 -0
- /package/client/dist/assets/{tickets-DucYgtdl.js → tickets-tGx5AR5b.js} +0 -0
- /package/client/dist/assets/{twig-BQV8igWC.js → twig-DvsO-WjW.js} +0 -0
- /package/client/dist/assets/{typespec-DlFroUGY.js → typespec-Brkt3IAA.js} +0 -0
- /package/client/dist/assets/{vb-BlrJpIMX.js → vb-r121Uzxt.js} +0 -0
- /package/client/dist/assets/{wgsl-BWgIc6FZ.js → wgsl-BRX8uYh4.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{h as e}from"./editor.api2-
|
|
1
|
+
import{h as e}from"./editor.api2-BPnIxMjz.js";var t={comments:{lineComment:`#`},brackets:[[`{`,`}`],[`[`,`]`],[`(`,`)`]],autoClosingPairs:[{open:`{`,close:`}`},{open:`[`,close:`]`},{open:`(`,close:`)`},{open:`"`,close:`"`},{open:`'`,close:`'`}],surroundingPairs:[{open:`{`,close:`}`},{open:`[`,close:`]`},{open:`(`,close:`)`},{open:`"`,close:`"`},{open:`'`,close:`'`}],folding:{offSide:!0},onEnterRules:[{beforeText:/:\s*$/,action:{indentAction:e.IndentAction.Indent}}]},n={tokenPostfix:`.yaml`,brackets:[{token:`delimiter.bracket`,open:`{`,close:`}`},{token:`delimiter.square`,open:`[`,close:`]`}],keywords:[`true`,`True`,`TRUE`,`false`,`False`,`FALSE`,`null`,`Null`,`Null`,`~`],numberInteger:/(?:0|[+-]?[0-9]+)/,numberFloat:/(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/,numberOctal:/0o[0-7]+/,numberHex:/0x[0-9a-fA-F]+/,numberInfinity:/[+-]?\.(?:inf|Inf|INF)/,numberNaN:/\.(?:nan|Nan|NAN)/,numberDate:/\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/,escapes:/\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,tokenizer:{root:[{include:`@whitespace`},{include:`@comment`},[/%[^ ]+.*$/,`meta.directive`],[/---/,`operators.directivesEnd`],[/\.{3}/,`operators.documentEnd`],[/[-?:](?= )/,`operators`],{include:`@anchor`},{include:`@tagHandle`},{include:`@flowCollections`},{include:`@blockStyle`},[/@numberInteger(?![ \t]*\S+)/,`number`],[/@numberFloat(?![ \t]*\S+)/,`number.float`],[/@numberOctal(?![ \t]*\S+)/,`number.octal`],[/@numberHex(?![ \t]*\S+)/,`number.hex`],[/@numberInfinity(?![ \t]*\S+)/,`number.infinity`],[/@numberNaN(?![ \t]*\S+)/,`number.nan`],[/@numberDate(?![ \t]*\S+)/,`number.date`],[/(".*?"|'.*?'|[^#'"]*?)([ \t]*)(:)( |$)/,[`type`,`white`,`operators`,`white`]],{include:`@flowScalars`},[/.+?(?=(\s+#|$))/,{cases:{"@keywords":`keyword`,"@default":`string`}}]],object:[{include:`@whitespace`},{include:`@comment`},[/\}/,`@brackets`,`@pop`],[/,/,`delimiter.comma`],[/:(?= )/,`operators`],[/(?:".*?"|'.*?'|[^,\{\[]+?)(?=: )/,`type`],{include:`@flowCollections`},{include:`@flowScalars`},{include:`@tagHandle`},{include:`@anchor`},{include:`@flowNumber`},[/[^\},]+/,{cases:{"@keywords":`keyword`,"@default":`string`}}]],array:[{include:`@whitespace`},{include:`@comment`},[/\]/,`@brackets`,`@pop`],[/,/,`delimiter.comma`],{include:`@flowCollections`},{include:`@flowScalars`},{include:`@tagHandle`},{include:`@anchor`},{include:`@flowNumber`},[/[^\],]+/,{cases:{"@keywords":`keyword`,"@default":`string`}}]],multiString:[[/^( +).+$/,`string`,`@multiStringContinued.$1`]],multiStringContinued:[[/^( *).+$/,{cases:{"$1==$S2":`string`,"@default":{token:`@rematch`,next:`@popall`}}}]],whitespace:[[/[ \t\r\n]+/,`white`]],comment:[[/#.*$/,`comment`]],flowCollections:[[/\[/,`@brackets`,`@array`],[/\{/,`@brackets`,`@object`]],flowScalars:[[/"([^"\\]|\\.)*$/,`string.invalid`],[/'([^'\\]|\\.)*$/,`string.invalid`],[/'[^']*'/,`string`],[/"/,`string`,`@doubleQuotedString`]],doubleQuotedString:[[/[^\\"]+/,`string`],[/@escapes/,`string.escape`],[/\\./,`string.escape.invalid`],[/"/,`string`,`@pop`]],blockStyle:[[/[>|][0-9]*[+-]?$/,`operators`,`@multiString`]],flowNumber:[[/@numberInteger(?=[ \t]*[,\]\}])/,`number`],[/@numberFloat(?=[ \t]*[,\]\}])/,`number.float`],[/@numberOctal(?=[ \t]*[,\]\}])/,`number.octal`],[/@numberHex(?=[ \t]*[,\]\}])/,`number.hex`],[/@numberInfinity(?=[ \t]*[,\]\}])/,`number.infinity`],[/@numberNaN(?=[ \t]*[,\]\}])/,`number.nan`],[/@numberDate(?=[ \t]*[,\]\}])/,`number.date`]],tagHandle:[[/\![^ ]*/,`tag`]],anchor:[[/[&*][^ ]+/,`namespace`]]}};export{t as conf,n as language};
|
package/client/dist/index.html
CHANGED
|
@@ -191,10 +191,10 @@
|
|
|
191
191
|
.specrails-splash__mark .pill { opacity: 1; }
|
|
192
192
|
}
|
|
193
193
|
</style>
|
|
194
|
-
<script type="module" crossorigin src="/assets/index-
|
|
194
|
+
<script type="module" crossorigin src="/assets/index-AfVF6BgE.js"></script>
|
|
195
195
|
<link rel="modulepreload" crossorigin href="/assets/chunk-CilyBKbf.js">
|
|
196
196
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-DSXbuxSR.js">
|
|
197
|
-
<link rel="modulepreload" crossorigin href="/assets/clsx-
|
|
197
|
+
<link rel="modulepreload" crossorigin href="/assets/clsx-CnH-HMk3.js">
|
|
198
198
|
<link rel="modulepreload" crossorigin href="/assets/activity-Dv6H7wEr.js">
|
|
199
199
|
<link rel="modulepreload" crossorigin href="/assets/addspec-GWm4ffKl.js">
|
|
200
200
|
<link rel="modulepreload" crossorigin href="/assets/agents-CMxtJMLD.js">
|
|
@@ -207,17 +207,18 @@
|
|
|
207
207
|
<link rel="modulepreload" crossorigin href="/assets/code-CY85RXZU.js">
|
|
208
208
|
<link rel="modulepreload" crossorigin href="/assets/commands-IXMOKBYt.js">
|
|
209
209
|
<link rel="modulepreload" crossorigin href="/assets/common-wA36jmj1.js">
|
|
210
|
-
<link rel="modulepreload" crossorigin href="/assets/dashboard-
|
|
210
|
+
<link rel="modulepreload" crossorigin href="/assets/dashboard-CuOshSHn.js">
|
|
211
211
|
<link rel="modulepreload" crossorigin href="/assets/explore-hFc3HFcp.js">
|
|
212
|
-
<link rel="modulepreload" crossorigin href="/assets/integrations-
|
|
213
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
214
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
215
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
216
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
217
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
218
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
219
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
220
|
-
<link rel="
|
|
212
|
+
<link rel="modulepreload" crossorigin href="/assets/integrations-BqUmRUef.js">
|
|
213
|
+
<link rel="modulepreload" crossorigin href="/assets/jira-C-ATCti0.js">
|
|
214
|
+
<link rel="modulepreload" crossorigin href="/assets/jobs-Db3xrsp_.js">
|
|
215
|
+
<link rel="modulepreload" crossorigin href="/assets/nav-BRInPX8a.js">
|
|
216
|
+
<link rel="modulepreload" crossorigin href="/assets/settings-yMubjqYw.js">
|
|
217
|
+
<link rel="modulepreload" crossorigin href="/assets/setup-C0dzw8j4.js">
|
|
218
|
+
<link rel="modulepreload" crossorigin href="/assets/specs-D-Sb6dre.js">
|
|
219
|
+
<link rel="modulepreload" crossorigin href="/assets/terminal-C0xx0SjA.js">
|
|
220
|
+
<link rel="modulepreload" crossorigin href="/assets/tickets-tGx5AR5b.js">
|
|
221
|
+
<link rel="stylesheet" crossorigin href="/assets/index-NlH5BbXJ.css">
|
|
221
222
|
</head>
|
|
222
223
|
<body>
|
|
223
224
|
<div id="specrails-splash" aria-hidden="false" role="status" aria-label="Loading Specrails">
|
|
@@ -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
|
@@ -391,13 +391,23 @@ class ChatManager {
|
|
|
391
391
|
*/
|
|
392
392
|
_buildLightweightSystemPrompt(scope) {
|
|
393
393
|
const name = this._projectName ?? 'this project';
|
|
394
|
-
|
|
394
|
+
// High tier = the user opted into MCP/connectors (Max/Desktop presets). At
|
|
395
|
+
// that tier the agent also has Bash (gh + repo inspection) and MCP tools, so
|
|
396
|
+
// its stance flips from "be minimal" to "verify against the real code before
|
|
397
|
+
// recommending" — this is deterministic per scope, so the prompt stays
|
|
398
|
+
// byte-stable for prompt caching within a given scope.
|
|
399
|
+
const highTier = !!(scope && scope.full && (scope.mcp || scope.userMcp));
|
|
400
|
+
const intro = `You are a focused assistant for the "${name}" specrails project. ` +
|
|
395
401
|
`You have explicit permission to read and write .specrails/local-tickets.json — ` +
|
|
396
402
|
`this is the project's local ticket store managed by Specrails. It is NOT sensitive. ` +
|
|
397
|
-
`When creating or updating tickets, write directly to this JSON file
|
|
398
|
-
|
|
399
|
-
`
|
|
400
|
-
|
|
403
|
+
`When creating or updating tickets, write directly to this JSON file.`;
|
|
404
|
+
const stance = highTier
|
|
405
|
+
? `IMPORTANT: You have read/search tools (Read, Grep, Glob), the GitHub CLI (\`gh\`, already authenticated for this machine — use it to inspect issues, PRs, and CI), and any MCP servers the user enabled. ` +
|
|
406
|
+
`Before recommending a spec, doing gap analysis, or claiming a feature is missing, you MUST INVESTIGATE THE ACTUAL CODEBASE first — grep and read the relevant source (and check \`gh\`/MCP where useful) to confirm the real implementation status. ` +
|
|
407
|
+
`NEVER recommend a spec for something that is already implemented: if the backlog or another spec references a feature, verify it in code before proposing it. Reading code thoroughly is expected and encouraged at this tier — do not guess from ticket titles alone.`
|
|
408
|
+
: `IMPORTANT: Be efficient. Minimize tool calls. Only read files that are directly relevant. ` +
|
|
409
|
+
`Do not explore broadly — focus on the specific task.`;
|
|
410
|
+
const scopedBase = `${intro}\n\n${stance}\n\n` +
|
|
401
411
|
`When "Specrails Tickets" or "OpenSpec Specs" sections are present below, treat them as authoritative project context. ` +
|
|
402
412
|
`For roadmap-style requests like "suggest the next best spec", ground the answer in that context, avoid duplicates, and propose one concrete next spec instead of generic directions.`;
|
|
403
413
|
if (!scope || !this._cwd)
|
|
@@ -495,8 +505,10 @@ class ChatManager {
|
|
|
495
505
|
: 'chat-turn';
|
|
496
506
|
// Translate the per-conversation Explore scope into provider-native
|
|
497
507
|
// tool-gating flags. `toolFlagsForScope` emits claude-shape argv
|
|
498
|
-
// (`--
|
|
499
|
-
//
|
|
508
|
+
// (`--tools …` to restrict the read-only tiers, or `--disallowedTools …` at
|
|
509
|
+
// the high MCP tier where Bash + MCP tools must stay callable); codex's
|
|
510
|
+
// `exec` would reject those with an "unexpected argument" error and crash
|
|
511
|
+
// the turn. The scope's tool
|
|
500
512
|
// gating is therefore claude-only today — codex inherits its sandbox
|
|
501
513
|
// and approval policy from the project's `.codex/config.toml` (or the
|
|
502
514
|
// `-c sandbox_mode=` override the adapter already attaches on resume).
|
|
@@ -262,16 +262,37 @@ function buildScopedSystemPromptPrefix(scope, projectPath) {
|
|
|
262
262
|
return sections.join('\n\n');
|
|
263
263
|
}
|
|
264
264
|
// Compute the claude CLI tool flags for the given scope.
|
|
265
|
-
// We use `--tools` (whitelist) instead of `--disallowedTools` because
|
|
266
|
-
// `--dangerously-skip-permissions` can bypass disallow filters in some CLI
|
|
267
|
-
// versions; an explicit whitelist is the most reliable lockdown.
|
|
268
265
|
//
|
|
269
|
-
// -
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
266
|
+
// The base read-only Explore tiers use `--tools Read,Grep,Glob` (a built-in
|
|
267
|
+
// toolkit RESTRICTION — NOT a permission allow-list). This is the most reliable
|
|
268
|
+
// read-only lockdown because `--dangerously-skip-permissions` (in COMMON_FLAGS)
|
|
269
|
+
// can bypass `--disallowedTools` filters. Verified against claude 2.1.177:
|
|
270
|
+
// - `--tools Read,Grep,Glob` removes Bash AND MCP tools from the toolkit, so
|
|
271
|
+
// `gh` and any `mcp__*` tools are uncallable (the "only local read access"
|
|
272
|
+
// symptom users hit when they enable approved MCPs at this tier).
|
|
273
|
+
//
|
|
274
|
+
// HIGH TIER (full AND an MCP toggle on — the Max/Desktop presets, i.e. the user
|
|
275
|
+
// explicitly opted into MCP/connectors): we cannot enumerate Claude.ai connector
|
|
276
|
+
// servers to put them in an allow-list, so we keep the full `--tools default`
|
|
277
|
+
// toolkit + skip-permissions (which makes ALL MCP servers — file, plugin, and
|
|
278
|
+
// connector — callable) and merely DISALLOW the GUI file-writers. This is a
|
|
279
|
+
// deliberate capability bump: it also exposes Bash (so the agent can run `gh`
|
|
280
|
+
// with the user's local auth, and inspect the repo via shell). Bash can write
|
|
281
|
+
// to disk, so this tier is NOT a hard read-only sandbox — the Explore system
|
|
282
|
+
// prompt carries the non-destructive stance, and the tier is opt-in.
|
|
283
|
+
//
|
|
284
|
+
// - full && (mcp || userMcp) → --disallowedTools Write,Edit,NotebookEdit
|
|
285
|
+
// (default toolkit: Read/Grep/Glob/Bash/WebFetch +
|
|
286
|
+
// all loaded MCP tools; file-writer tools removed)
|
|
287
|
+
// - full (no MCP toggle) → --tools Read,Grep,Glob (read-only, no Bash/MCP)
|
|
288
|
+
// - !full → --tools __none__ (a non-existent tool name; the
|
|
289
|
+
// empty string `""` is silently dropped by some CLI
|
|
290
|
+
// versions and falls back to the default set, so we
|
|
291
|
+
// use a sentinel to disable all tools).
|
|
274
292
|
function toolFlagsForScope(scope) {
|
|
293
|
+
if (scope.full && (scope.mcp || scope.userMcp)) {
|
|
294
|
+
return { args: ['--disallowedTools', 'Write,Edit,NotebookEdit'] };
|
|
295
|
+
}
|
|
275
296
|
if (scope.full) {
|
|
276
297
|
return { args: ['--tools', 'Read,Grep,Glob'] };
|
|
277
298
|
}
|