specrails-desktop 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/specrails/batch-implement.md +287 -0
- package/.claude/commands/specrails/compat-check.md +271 -0
- package/.claude/commands/specrails/doctor.md +62 -0
- package/.claude/commands/specrails/enrich.md +1635 -0
- package/.claude/commands/specrails/explore-spec.md +173 -0
- package/.claude/commands/specrails/health-check.md +527 -0
- package/.claude/commands/specrails/implement.md +1457 -0
- package/.claude/commands/specrails/memory-inspect.md +259 -0
- package/.claude/commands/specrails/opsx-diff.md +419 -0
- package/.claude/commands/specrails/propose-spec.md +102 -0
- package/.claude/commands/specrails/reconfig.md +89 -0
- package/.claude/commands/specrails/refactor-recommender.md +212 -0
- package/.claude/commands/specrails/retry.md +363 -0
- package/.claude/commands/specrails/telemetry.md +552 -0
- package/.claude/commands/specrails/why.md +96 -0
- package/LICENSE +21 -0
- package/README.md +290 -0
- package/cli/dist/specrails-desktop.js +1098 -0
- package/client/dist/assets/ActivityFeedPage-Gy4x8dBt.js +1 -0
- package/client/dist/assets/AgentsPage-CPgu--Fb.js +86 -0
- package/client/dist/assets/AnalyticsPage-B5sJEee2.js +1 -0
- package/client/dist/assets/BarChart-7IMQ8HY1.js +33 -0
- package/client/dist/assets/CodePage-CBdFvbwe.js +2 -0
- package/client/dist/assets/DesktopAnalyticsPage-w0rdTq4w.js +1 -0
- package/client/dist/assets/DocsDialog-BZUYM7wm.js +11 -0
- package/client/dist/assets/DocsPage-9QglWl46.js +11 -0
- package/client/dist/assets/ExportDropdown-BLZFXtNi.js +1 -0
- package/client/dist/assets/IntegrationsPage-BxBE4y99.js +3 -0
- package/client/dist/assets/JobDetailPage-DydWx_5S.js +16 -0
- package/client/dist/assets/JobsPage-20ibw0IO.js +1 -0
- package/client/dist/assets/abap-Bw6f2wDG.js +1 -0
- package/client/dist/assets/activity-BEIp_Y1A.js +1 -0
- package/client/dist/assets/activity-BdrPln96.js +1 -0
- package/client/dist/assets/activity-CpkRS8Sx.js +1 -0
- package/client/dist/assets/activity-DKCpESPt.js +1 -0
- package/client/dist/assets/activity-DOUVEjJi.js +1 -0
- package/client/dist/assets/activity-DRwkql_y.js +1 -0
- package/client/dist/assets/activity-DcDQ7tjw.js +1 -0
- package/client/dist/assets/activity-Dv6H7wEr.js +1 -0
- package/client/dist/assets/addon-image-3WCl5Vhd.js +1 -0
- package/client/dist/assets/addon-ligatures-C5OdliKs.js +2 -0
- package/client/dist/assets/addon-webgl-BbX6pSjl.js +44 -0
- package/client/dist/assets/addspec-B5yl4Loj.js +1 -0
- package/client/dist/assets/addspec-BEeF5-zc.js +1 -0
- package/client/dist/assets/addspec-D33ocMxf.js +1 -0
- package/client/dist/assets/addspec-DFswZ0jK.js +1 -0
- package/client/dist/assets/addspec-DRE-jZv7.js +1 -0
- package/client/dist/assets/addspec-DVZ15Jp8.js +1 -0
- package/client/dist/assets/addspec-Fkv91Opc.js +1 -0
- package/client/dist/assets/addspec-GWm4ffKl.js +1 -0
- package/client/dist/assets/agents-1nCDWRmP.js +1 -0
- package/client/dist/assets/agents-Bm9rPqnt.js +1 -0
- package/client/dist/assets/agents-CMxtJMLD.js +1 -0
- package/client/dist/assets/agents-DK-Dlc0i.js +1 -0
- package/client/dist/assets/agents-Q6Ldfpxx.js +1 -0
- package/client/dist/assets/agents-TeOSy-ax.js +1 -0
- package/client/dist/assets/agents-iTqjRajS.js +1 -0
- package/client/dist/assets/agents-s87sMGzL.js +1 -0
- package/client/dist/assets/agentstudio-B6Wb59E7.js +1 -0
- package/client/dist/assets/agentstudio-BADhZ41e.js +1 -0
- package/client/dist/assets/agentstudio-BSnWLR63.js +1 -0
- package/client/dist/assets/agentstudio-BdidyBzZ.js +1 -0
- package/client/dist/assets/agentstudio-CxlUllqI.js +1 -0
- package/client/dist/assets/agentstudio-D3I62TLJ.js +1 -0
- package/client/dist/assets/agentstudio-DuH9TogZ.js +1 -0
- package/client/dist/assets/agentstudio-Kw88_dUF.js +1 -0
- package/client/dist/assets/aiedit-BWxHGsYA.js +1 -0
- package/client/dist/assets/aiedit-D2ji6Qy0.js +1 -0
- package/client/dist/assets/aiedit-DAhZTvtk.js +1 -0
- package/client/dist/assets/aiedit-DJMny-D5.js +1 -0
- package/client/dist/assets/aiedit-DOcxERkU.js +1 -0
- package/client/dist/assets/aiedit-DvrcbwGv.js +1 -0
- package/client/dist/assets/aiedit-TTwzL1TS.js +1 -0
- package/client/dist/assets/aiedit-WBSjT_C1.js +1 -0
- package/client/dist/assets/analytics-BIdr0YfL.js +1 -0
- package/client/dist/assets/analytics-C6EzgtdE.js +1 -0
- package/client/dist/assets/analytics-C9Zc-rkM.js +1 -0
- package/client/dist/assets/analytics-CVx3YOc0.js +1 -0
- package/client/dist/assets/analytics-CYj0tfj7.js +1 -0
- package/client/dist/assets/analytics-CnY4kNG3.js +1 -0
- package/client/dist/assets/analytics-CrPCZRJ-.js +1 -0
- package/client/dist/assets/analytics-DMCto-TF.js +1 -0
- package/client/dist/assets/apex-Cw8_REBo.js +1 -0
- package/client/dist/assets/atom-one-dark-B-oHczHB.css +1 -0
- package/client/dist/assets/attachments-BIsSSnHJ.js +1 -0
- package/client/dist/assets/attachments-BW4L3l2L.js +1 -0
- package/client/dist/assets/attachments-Bcf6BG6V.js +1 -0
- package/client/dist/assets/attachments-Bke8sCU4.js +1 -0
- package/client/dist/assets/attachments-COcrGRFz.js +1 -0
- package/client/dist/assets/attachments-DYHGA2Dj.js +1 -0
- package/client/dist/assets/attachments-Dd92KpUH.js +1 -0
- package/client/dist/assets/attachments-DzdU6DV6.js +1 -0
- package/client/dist/assets/azcli-Cz6HAoOw.js +1 -0
- package/client/dist/assets/bat-CcJ-xyqL.js +1 -0
- package/client/dist/assets/bicep-z1WDCKYz.js +2 -0
- package/client/dist/assets/browser-5ErDlJoR.js +1 -0
- package/client/dist/assets/browser-Bc-YdlVg.js +1 -0
- package/client/dist/assets/browser-BlYF4OOq.js +1 -0
- package/client/dist/assets/browser-CT-ReZGt.js +1 -0
- package/client/dist/assets/browser-DGITz3fC.js +1 -0
- package/client/dist/assets/browser-JsAIGCEW.js +1 -0
- package/client/dist/assets/browser-M5-rbPlw.js +1 -0
- package/client/dist/assets/browser-Qya9cARy.js +1 -0
- package/client/dist/assets/cameligo-BRewOpfa.js +1 -0
- package/client/dist/assets/chat-BEGuC03z.js +1 -0
- package/client/dist/assets/chat-BEW60P_u.js +1 -0
- package/client/dist/assets/chat-BQNMD0PL.js +1 -0
- package/client/dist/assets/chat-BsbNGPW9.js +1 -0
- package/client/dist/assets/chat-CboQguCi.js +1 -0
- package/client/dist/assets/chat-DRCa9pOt.js +1 -0
- package/client/dist/assets/chat-DwUm6W9z.js +1 -0
- package/client/dist/assets/chat-yoXwguQu.js +1 -0
- package/client/dist/assets/chunk-CilyBKbf.js +1 -0
- package/client/dist/assets/clojure-DBjRWN6g.js +1 -0
- package/client/dist/assets/clsx-DnqN-uhr.js +1 -0
- package/client/dist/assets/code-AL1rVIMb.js +1 -0
- package/client/dist/assets/code-C0BKpkht.js +1 -0
- package/client/dist/assets/code-C0FTS3ew.js +1 -0
- package/client/dist/assets/code-CPcHxzxw.js +1 -0
- package/client/dist/assets/code-D3ryDniw.js +1 -0
- package/client/dist/assets/code-D3zVVQTj.js +1 -0
- package/client/dist/assets/code-PCmfS3dn.js +1 -0
- package/client/dist/assets/code-exI0G5Wd.js +1 -0
- package/client/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/client/dist/assets/coffee-Cfk_XHGR.js +1 -0
- package/client/dist/assets/commands-B772IyDa.js +1 -0
- package/client/dist/assets/commands-BDDp6xFG.js +1 -0
- package/client/dist/assets/commands-CJxCry-o.js +1 -0
- package/client/dist/assets/commands-CfgY-_of.js +1 -0
- package/client/dist/assets/commands-DLrvnPNg.js +1 -0
- package/client/dist/assets/commands-IXMOKBYt.js +1 -0
- package/client/dist/assets/commands-UD1NzmwX.js +1 -0
- package/client/dist/assets/commands-sqrqsxyE.js +1 -0
- package/client/dist/assets/common-DCr6VzJ7.js +1 -0
- package/client/dist/assets/common-Dard9UNH.js +1 -0
- package/client/dist/assets/common-DeDELLZJ.js +1 -0
- package/client/dist/assets/common-DltqHaAe.js +1 -0
- package/client/dist/assets/common-Dmm1GhdD.js +1 -0
- package/client/dist/assets/common-DnjcgkPH.js +1 -0
- package/client/dist/assets/common-GbpxfPG8.js +1 -0
- package/client/dist/assets/common-wA36jmj1.js +1 -0
- package/client/dist/assets/cpp-BVob6BaP.js +1 -0
- package/client/dist/assets/csharp-C4fbRuOu.js +1 -0
- package/client/dist/assets/csp-DthFP_vT.js +1 -0
- package/client/dist/assets/css-CGMH0hcW.js +3 -0
- package/client/dist/assets/css.worker-Wv5dxAWO.js +89 -0
- package/client/dist/assets/cssMode-Cc6ozl-J.js +1 -0
- package/client/dist/assets/cypher-Pnf68BRV.js +1 -0
- package/client/dist/assets/dart-PMMOtxZX.js +1 -0
- package/client/dist/assets/dashboard-B4ixDVk8.js +1 -0
- package/client/dist/assets/dashboard-BZBADHSj.js +1 -0
- package/client/dist/assets/dashboard-C1MfeUHs.js +1 -0
- package/client/dist/assets/dashboard-C7SK6xu5.js +1 -0
- package/client/dist/assets/dashboard-CB6Le1yN.js +1 -0
- package/client/dist/assets/dashboard-CoTpMOBM.js +1 -0
- package/client/dist/assets/dashboard-Duo4DDCW.js +1 -0
- package/client/dist/assets/dashboard-I19DXBxw.js +1 -0
- package/client/dist/assets/dist-js-BY-Fv_fg.js +1 -0
- package/client/dist/assets/dist-js-Bakc4uxT.js +1 -0
- package/client/dist/assets/dockerfile-di1nsJCc.js +1 -0
- package/client/dist/assets/ecl-D_WVcB5M.js +1 -0
- package/client/dist/assets/editor-Br_kD0ds.css +1 -0
- package/client/dist/assets/editor.api2-XLGzZfbc.js +872 -0
- package/client/dist/assets/editor.main-CfXxHimg.js +6 -0
- package/client/dist/assets/editor.worker-Bd9IXS8d.js +26 -0
- package/client/dist/assets/elixir-OAdJEMOn.js +1 -0
- package/client/dist/assets/explore-4mFpnrKU.js +1 -0
- package/client/dist/assets/explore-A8Ltoblq.js +1 -0
- package/client/dist/assets/explore-B9A3iN2W.js +1 -0
- package/client/dist/assets/explore-BV5Xxlsn.js +1 -0
- package/client/dist/assets/explore-BrBJvfjP.js +1 -0
- package/client/dist/assets/explore-C3FSE42C.js +1 -0
- package/client/dist/assets/explore-D2EFgt8J.js +1 -0
- package/client/dist/assets/explore-hFc3HFcp.js +1 -0
- package/client/dist/assets/flow9-D3QEZjgn.js +1 -0
- package/client/dist/assets/format-command-CwGuwzGA.js +1 -0
- package/client/dist/assets/freemarker2-DP7J1gG3.js +3 -0
- package/client/dist/assets/fsharp-BF0k_8N8.js +1 -0
- package/client/dist/assets/go-BAQO5Jsz.js +1 -0
- package/client/dist/assets/graphql-hdFVFkiV.js +1 -0
- package/client/dist/assets/handlebars-BjRlucw6.js +1 -0
- package/client/dist/assets/hcl-DWnl1o-X.js +1 -0
- package/client/dist/assets/html-OumBQJ-U.js +1 -0
- package/client/dist/assets/html.worker-CQP8QQsS.js +502 -0
- package/client/dist/assets/htmlMode-CStc3zXM.js +1 -0
- package/client/dist/assets/index-CimDRRi7.css +2 -0
- package/client/dist/assets/index-XGZaKl_u.js +142 -0
- package/client/dist/assets/ini-CB-6OVu3.js +1 -0
- package/client/dist/assets/integrations-C3p12Ms6.js +1 -0
- package/client/dist/assets/integrations-Cr6hH7XR.js +1 -0
- package/client/dist/assets/integrations-Cublz3m6.js +1 -0
- package/client/dist/assets/integrations-D28q1kF6.js +1 -0
- package/client/dist/assets/integrations-DRdbki5W.js +1 -0
- package/client/dist/assets/integrations-DaC4SzzL.js +1 -0
- package/client/dist/assets/integrations-DmQYCUvN.js +1 -0
- package/client/dist/assets/integrations-HIlUxXVs.js +1 -0
- package/client/dist/assets/java-d1CmfiHX.js +1 -0
- package/client/dist/assets/javascript-CMk--e7g.js +1 -0
- package/client/dist/assets/jobs-BE1siB0M.js +1 -0
- package/client/dist/assets/jobs-BHcQ_Faf.js +1 -0
- package/client/dist/assets/jobs-CFfc2dNX.js +1 -0
- package/client/dist/assets/jobs-CSi5n8X_.js +1 -0
- package/client/dist/assets/jobs-Dc3X86PY.js +1 -0
- package/client/dist/assets/jobs-De5tASex.js +1 -0
- package/client/dist/assets/jobs-DsoXEdo7.js +1 -0
- package/client/dist/assets/jobs-Wl-ApPMb.js +1 -0
- package/client/dist/assets/json.worker-DzV-CpCQ.js +58 -0
- package/client/dist/assets/jsonMode-C2h3ZcjZ.js +7 -0
- package/client/dist/assets/julia-Bgv08lKa.js +1 -0
- package/client/dist/assets/kotlin-u98kaVTf.js +1 -0
- package/client/dist/assets/less-CjYwpgg5.js +2 -0
- package/client/dist/assets/lexon-YTjaAFBB.js +1 -0
- package/client/dist/assets/lib-CPxTMOAq.js +7 -0
- package/client/dist/assets/liquid-mI3KJrBE.js +1 -0
- package/client/dist/assets/lspLanguageFeatures-DU09ggWi.js +4 -0
- package/client/dist/assets/lua-BzmkWv27.js +1 -0
- package/client/dist/assets/m3-CFwk9fw0.js +1 -0
- package/client/dist/assets/markdown-CR5iMpSZ.js +1 -0
- package/client/dist/assets/mdx-C41VDTR_.js +1 -0
- package/client/dist/assets/mips-CcEalc17.js +1 -0
- package/client/dist/assets/monaco.contribution-CPObAXMC.js +2 -0
- package/client/dist/assets/msdax-BQbkawnr.js +1 -0
- package/client/dist/assets/mysql-GTlaaW_P.js +1 -0
- package/client/dist/assets/nav-0fwkrgHt.js +1 -0
- package/client/dist/assets/nav-BEL3MTwK.js +1 -0
- package/client/dist/assets/nav-B_G-TJDW.js +1 -0
- package/client/dist/assets/nav-C2YXcbZS.js +1 -0
- package/client/dist/assets/nav-ClzOE4mA.js +1 -0
- package/client/dist/assets/nav-CtYwmMgu.js +1 -0
- package/client/dist/assets/nav-D2bOGSEg.js +1 -0
- package/client/dist/assets/nav-iH1V5j6o.js +1 -0
- package/client/dist/assets/objective-c-Byu1T5if.js +1 -0
- package/client/dist/assets/pascal-BrfzBfRm.js +1 -0
- package/client/dist/assets/pascaligo-BXXKFUeo.js +1 -0
- package/client/dist/assets/perl-B3OikKq-.js +1 -0
- package/client/dist/assets/pgsql-CTsa0Acc.js +1 -0
- package/client/dist/assets/php-DiQh3FUW.js +1 -0
- package/client/dist/assets/pla-92uH8Fzm.js +1 -0
- package/client/dist/assets/postiats-BbeWkKUr.js +1 -0
- package/client/dist/assets/powerquery-DgDMzpsm.js +1 -0
- package/client/dist/assets/powershell-BfdUUzaG.js +1 -0
- package/client/dist/assets/preload-helper-DSXbuxSR.js +1 -0
- package/client/dist/assets/protobuf-BojW2ftW.js +2 -0
- package/client/dist/assets/pug-BxqTg3IU.js +1 -0
- package/client/dist/assets/python-Y27rKQtk.js +1 -0
- package/client/dist/assets/qsharp-BX_A-MW9.js +1 -0
- package/client/dist/assets/r-D9BMnxvJ.js +1 -0
- package/client/dist/assets/razor-Cd5-q9Bp.js +1 -0
- package/client/dist/assets/redis-5cJqEQJJ.js +1 -0
- package/client/dist/assets/redshift-d8BBqiwb.js +1 -0
- package/client/dist/assets/restructuredtext-C8a6yIcZ.js +1 -0
- package/client/dist/assets/ruby-egeh-6KX.js +1 -0
- package/client/dist/assets/rust-a3r9IInB.js +1 -0
- package/client/dist/assets/sb-y8iRIDei.js +1 -0
- package/client/dist/assets/scala-BPDK2AmK.js +1 -0
- package/client/dist/assets/scheme-BIWUEoOs.js +1 -0
- package/client/dist/assets/scss-CA-PSzwg.js +3 -0
- package/client/dist/assets/settings-55oDcbSh.js +1 -0
- package/client/dist/assets/settings-Bd4Tq1RB.js +1 -0
- package/client/dist/assets/settings-CCSM-Fhn.js +1 -0
- package/client/dist/assets/settings-D3e_bDoW.js +1 -0
- package/client/dist/assets/settings-DKbTkbn7.js +1 -0
- package/client/dist/assets/settings-Dxpo6_w7.js +1 -0
- package/client/dist/assets/settings-bt84e3Aa.js +1 -0
- package/client/dist/assets/settings-nu68QukM.js +1 -0
- package/client/dist/assets/setup-BMqwfbW9.js +1 -0
- package/client/dist/assets/setup-Bb5LcG28.js +1 -0
- package/client/dist/assets/setup-BeEx2_da.js +1 -0
- package/client/dist/assets/setup-CCCrB53Q.js +1 -0
- package/client/dist/assets/setup-CJA0ATmd.js +1 -0
- package/client/dist/assets/setup-CeiDbZcb.js +1 -0
- package/client/dist/assets/setup-Cus7TApA.js +1 -0
- package/client/dist/assets/setup-D9qOs2Xo.js +1 -0
- package/client/dist/assets/shell--LiT1Bja.js +1 -0
- package/client/dist/assets/solidity-DdqZccZg.js +1 -0
- package/client/dist/assets/sophia-S6-YxNG_.js +1 -0
- package/client/dist/assets/sparql-BSf5kMp2.js +1 -0
- package/client/dist/assets/specs-BFfu3u-a.js +1 -0
- package/client/dist/assets/specs-B__C8-8a.js +1 -0
- package/client/dist/assets/specs-CZ1PsXsC.js +1 -0
- package/client/dist/assets/specs-D2FzlLn9.js +1 -0
- package/client/dist/assets/specs-DaUTrNF9.js +1 -0
- package/client/dist/assets/specs-Dyc5hYeE.js +1 -0
- package/client/dist/assets/specs-cKEh2LXt.js +1 -0
- package/client/dist/assets/specs-k0PyLDVt.js +1 -0
- package/client/dist/assets/sql-D7KgjR8G.js +1 -0
- package/client/dist/assets/st-BnoDa-Ml.js +1 -0
- package/client/dist/assets/swift-DEUHTkUX.js +1 -0
- package/client/dist/assets/systemverilog-Tqb_KPnW.js +1 -0
- package/client/dist/assets/tcl-BmBFS2qq.js +1 -0
- package/client/dist/assets/terminal-80yDMgMF.js +1 -0
- package/client/dist/assets/terminal-Bje4ziIa.js +1 -0
- package/client/dist/assets/terminal-C2WYcFHF.js +1 -0
- package/client/dist/assets/terminal-CSONJOex.js +1 -0
- package/client/dist/assets/terminal-DEqzGtcr.js +1 -0
- package/client/dist/assets/terminal-DeWzh6ys.js +1 -0
- package/client/dist/assets/terminal-YOlsJCQj.js +1 -0
- package/client/dist/assets/terminal-lkZYR4wJ.js +1 -0
- package/client/dist/assets/tickets-CB7N30gm.js +1 -0
- package/client/dist/assets/tickets-CF2PYelu.js +1 -0
- package/client/dist/assets/tickets-DNOANUXr.js +1 -0
- package/client/dist/assets/tickets-DU1aqsbr.js +1 -0
- package/client/dist/assets/tickets-DYvafSaY.js +1 -0
- package/client/dist/assets/tickets-DlpC_iTg.js +1 -0
- package/client/dist/assets/tickets-DucYgtdl.js +1 -0
- package/client/dist/assets/tickets-clefmXLv.js +1 -0
- package/client/dist/assets/ts.worker-METxwbDZ.js +67719 -0
- package/client/dist/assets/tsMode-B0y_xEci.js +11 -0
- package/client/dist/assets/twig-BQV8igWC.js +1 -0
- package/client/dist/assets/typescript-BzK0OgwW.js +1 -0
- package/client/dist/assets/typespec-DlFroUGY.js +1 -0
- package/client/dist/assets/useProjectCache-DSaiGFjV.js +1 -0
- package/client/dist/assets/vb-BlrJpIMX.js +1 -0
- package/client/dist/assets/wgsl-BWgIc6FZ.js +298 -0
- package/client/dist/assets/workers-rt--R2Qy.js +1 -0
- package/client/dist/assets/xml-eX9QXAmI.js +1 -0
- package/client/dist/assets/yaml-fcsNkpOt.js +1 -0
- package/client/dist/index.html +246 -0
- package/docs/README.md +54 -0
- package/docs/cli.md +198 -0
- package/docs/codex.md +210 -0
- package/docs/creating-specs.md +197 -0
- package/docs/customizing.md +197 -0
- package/docs/getting-started.md +140 -0
- package/docs/internals/README.md +25 -0
- package/docs/internals/adding-a-provider.md +238 -0
- package/docs/internals/api-reference.md +634 -0
- package/docs/internals/architecture.md +332 -0
- package/docs/internals/configuration.md +172 -0
- package/docs/internals/openspec-workflow.md +282 -0
- package/docs/internals/operations-runbook.md +198 -0
- package/docs/internals/profiles.md +152 -0
- package/docs/platforms/macos.md +130 -0
- package/docs/platforms/windows.md +81 -0
- package/docs/running-pipelines.md +240 -0
- package/docs/terminal.md +138 -0
- package/docs/tracking-cost.md +155 -0
- package/package.json +82 -0
- package/server/dist/agent-generator.js +232 -0
- package/server/dist/agent-refine-db.js +124 -0
- package/server/dist/agent-refine-manager.js +526 -0
- package/server/dist/ai-invocations.js +111 -0
- package/server/dist/attachment-manager.js +299 -0
- package/server/dist/auth.js +207 -0
- package/server/dist/binary-probe.js +35 -0
- package/server/dist/browser-capture-manager.js +576 -0
- package/server/dist/browser-capture-types.js +28 -0
- package/server/dist/browser-network.js +149 -0
- package/server/dist/browser-playwright.js +888 -0
- package/server/dist/build-dirs.js +44 -0
- package/server/dist/changes-reader.js +120 -0
- package/server/dist/chat-manager.js +1060 -0
- package/server/dist/chromium-resolver.js +311 -0
- package/server/dist/code-explorer-router.js +788 -0
- package/server/dist/codex-otel-bridge.js +235 -0
- package/server/dist/command-resolver.js +102 -0
- package/server/dist/config.js +306 -0
- package/server/dist/context-budget.js +113 -0
- package/server/dist/context-scope.js +279 -0
- package/server/dist/contract-refine-runner.js +521 -0
- package/server/dist/core-compat.js +207 -0
- package/server/dist/core-package.js +14 -0
- package/server/dist/db.js +1034 -0
- package/server/dist/desktop-analytics.js +156 -0
- package/server/dist/desktop-db.js +456 -0
- package/server/dist/desktop-router.js +735 -0
- package/server/dist/docs-router.js +207 -0
- package/server/dist/explore-contract-refine.js +421 -0
- package/server/dist/explore-cwd-manager.js +242 -0
- package/server/dist/explore-draft-title.js +47 -0
- package/server/dist/explore-smash.js +450 -0
- package/server/dist/feature-flags.js +17 -0
- package/server/dist/file-provenance.js +382 -0
- package/server/dist/file-summary-generator.js +221 -0
- package/server/dist/file-summary-manager.js +689 -0
- package/server/dist/hooks.js +102 -0
- package/server/dist/ids.js +7 -0
- package/server/dist/index.js +586 -0
- package/server/dist/metrics.js +136 -0
- package/server/dist/mobile/index.js +16 -0
- package/server/dist/mobile/mobile-admin-router.js +84 -0
- package/server/dist/mobile/mobile-auth.js +67 -0
- package/server/dist/mobile/mobile-devices.js +80 -0
- package/server/dist/mobile/mobile-event-bus.js +39 -0
- package/server/dist/mobile/mobile-gateway.js +285 -0
- package/server/dist/mobile/mobile-mdns.js +81 -0
- package/server/dist/mobile/mobile-pairing.js +179 -0
- package/server/dist/mobile/mobile-redact.js +53 -0
- package/server/dist/mobile/mobile-router.js +411 -0
- package/server/dist/mobile/mobile-tls.js +86 -0
- package/server/dist/mobile/mobile-types.js +9 -0
- package/server/dist/mobile/mobile-ws.js +275 -0
- package/server/dist/path-resolver.js +298 -0
- package/server/dist/plugin-manager.js +617 -0
- package/server/dist/plugins/claude-approval.js +179 -0
- package/server/dist/plugins/claude-md-mutation.js +146 -0
- package/server/dist/plugins/codex-mcp.js +108 -0
- package/server/dist/plugins/contributors.js +72 -0
- package/server/dist/plugins/drift.js +58 -0
- package/server/dist/plugins/index.js +14 -0
- package/server/dist/plugins/json-mutation.js +120 -0
- package/server/dist/plugins/manager.js +32 -0
- package/server/dist/plugins/ownership.js +86 -0
- package/server/dist/plugins/paths.js +37 -0
- package/server/dist/plugins/prereq-installer.js +104 -0
- package/server/dist/plugins/rail-integration.js +79 -0
- package/server/dist/plugins/serena/index.js +13 -0
- package/server/dist/plugins/serena/install.js +91 -0
- package/server/dist/plugins/serena/instructions-content.js +21 -0
- package/server/dist/plugins/serena/manifest.js +111 -0
- package/server/dist/plugins/serena/verify.js +78 -0
- package/server/dist/plugins-router.js +215 -0
- package/server/dist/pricing.js +89 -0
- package/server/dist/profile-manager.js +310 -0
- package/server/dist/profiles-router.js +759 -0
- package/server/dist/project-registry.js +443 -0
- package/server/dist/project-router.js +4016 -0
- package/server/dist/proposal-manager.js +291 -0
- package/server/dist/provider-selection.js +69 -0
- package/server/dist/providers/claude-adapter.js +281 -0
- package/server/dist/providers/codex-adapter.js +264 -0
- package/server/dist/providers/index.js +23 -0
- package/server/dist/providers/registry.js +37 -0
- package/server/dist/providers/types.js +22 -0
- package/server/dist/queue-manager.js +1511 -0
- package/server/dist/rails-router.js +362 -0
- package/server/dist/rails-store.js +116 -0
- package/server/dist/result-event.js +106 -0
- package/server/dist/schemas/profile.v1.json +151 -0
- package/server/dist/setup-manager.js +1165 -0
- package/server/dist/setup-prerequisites.js +372 -0
- package/server/dist/smash-runner.js +663 -0
- package/server/dist/spec-draft-parser.js +133 -0
- package/server/dist/spec-launcher-manager.js +174 -0
- package/server/dist/spec-models.js +32 -0
- package/server/dist/specrails-tech-client.js +82 -0
- package/server/dist/spending.js +448 -0
- package/server/dist/telemetry-compactor.js +180 -0
- package/server/dist/telemetry-export.js +317 -0
- package/server/dist/telemetry-receiver.js +224 -0
- package/server/dist/terminal-manager.js +633 -0
- package/server/dist/terminal-marks-store.js +117 -0
- package/server/dist/terminal-osc-parser.js +159 -0
- package/server/dist/terminal-settings.js +282 -0
- package/server/dist/terminal-shell-integration.js +196 -0
- package/server/dist/ticket-broadcast.js +47 -0
- package/server/dist/ticket-store.js +397 -0
- package/server/dist/ticket-watcher.js +117 -0
- package/server/dist/types.js +10 -0
- package/server/dist/user-mcp-config.js +117 -0
- package/server/dist/util/cli-prompt.js +181 -0
- package/server/dist/util/secure-fs.js +50 -0
- package/server/dist/util/win-spawn.js +43 -0
- package/server/dist/webhook-manager.js +89 -0
- package/server/dist/ws-routing.js +47 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.withFileLock = withFileLock;
|
|
7
|
+
exports.atomicWriteFileSync = atomicWriteFileSync;
|
|
8
|
+
exports.readJsonOr = readJsonOr;
|
|
9
|
+
exports.surgicalMergeJson = surgicalMergeJson;
|
|
10
|
+
exports.surgicalRemoveKeys = surgicalRemoveKeys;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
/**
|
|
14
|
+
* In-process mutex keyed by absolute file path. The app runs as a single
|
|
15
|
+
* Node process so cross-process locking (proper-lockfile, flock) is not
|
|
16
|
+
* required for v1: every install / uninstall path goes through this module.
|
|
17
|
+
*
|
|
18
|
+
* If the architecture ever moves to multi-process or parallel test workers
|
|
19
|
+
* sharing a project directory, swap this for an advisory file lock. The API
|
|
20
|
+
* surface (`withFileLock`) is intentionally narrow so the swap is mechanical.
|
|
21
|
+
*/
|
|
22
|
+
const _locks = new Map();
|
|
23
|
+
async function withFileLock(filePath, fn) {
|
|
24
|
+
const key = path_1.default.resolve(filePath);
|
|
25
|
+
const previous = _locks.get(key) ?? Promise.resolve();
|
|
26
|
+
let release;
|
|
27
|
+
const next = new Promise((resolve) => { release = resolve; });
|
|
28
|
+
// B74: hold the SAME promise reference we store, so the finally can compare by
|
|
29
|
+
// identity. The old code rebuilt `previous.then(() => next)` in the finally — a
|
|
30
|
+
// fresh promise that never matched, so the cleanup never ran and `_locks` grew
|
|
31
|
+
// one entry per distinct file path forever.
|
|
32
|
+
const chained = previous.then(() => next);
|
|
33
|
+
_locks.set(key, chained);
|
|
34
|
+
try {
|
|
35
|
+
await previous;
|
|
36
|
+
return await fn();
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
release();
|
|
40
|
+
// Drop the entry only if no later caller has chained onto us (i.e. we are
|
|
41
|
+
// still the tail). Otherwise their chain depends on ours — leave it.
|
|
42
|
+
if (_locks.get(key) === chained) {
|
|
43
|
+
_locks.delete(key);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Atomically replace a file's bytes by writing a sibling temp file and renaming.
|
|
48
|
+
* Accepts a raw Buffer for byte-identical restores (rollback of snapshots that
|
|
49
|
+
* may contain non-UTF8 bytes); a string is written as UTF-8. */
|
|
50
|
+
function atomicWriteFileSync(filePath, body, mode) {
|
|
51
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
52
|
+
const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
53
|
+
// Node ignores the encoding option for Buffer payloads; only set it for strings.
|
|
54
|
+
const opts = Buffer.isBuffer(body)
|
|
55
|
+
? (mode != null ? { mode } : {})
|
|
56
|
+
: (mode != null ? { encoding: 'utf8', mode } : { encoding: 'utf8' });
|
|
57
|
+
fs_1.default.writeFileSync(tmp, body, opts);
|
|
58
|
+
fs_1.default.renameSync(tmp, filePath);
|
|
59
|
+
}
|
|
60
|
+
/** Read+parse JSON; returns `defaultValue` when the file is missing. Throws on malformed JSON. */
|
|
61
|
+
function readJsonOr(filePath, defaultValue) {
|
|
62
|
+
if (!fs_1.default.existsSync(filePath))
|
|
63
|
+
return defaultValue;
|
|
64
|
+
const raw = fs_1.default.readFileSync(filePath, 'utf8');
|
|
65
|
+
if (!raw.trim())
|
|
66
|
+
return defaultValue;
|
|
67
|
+
return JSON.parse(raw);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Surgically merge a JSON file by passing the parsed object (or `null` when
|
|
71
|
+
* absent) to `mutator` and atomically writing back the result. The mutator
|
|
72
|
+
* MUST return the new object to write, or `null` to delete the file.
|
|
73
|
+
*
|
|
74
|
+
* Holds an in-process file lock for the whole read-modify-write round trip.
|
|
75
|
+
*/
|
|
76
|
+
async function surgicalMergeJson(filePath, mutator) {
|
|
77
|
+
await withFileLock(filePath, async () => {
|
|
78
|
+
let current = null;
|
|
79
|
+
if (fs_1.default.existsSync(filePath)) {
|
|
80
|
+
const raw = fs_1.default.readFileSync(filePath, 'utf8');
|
|
81
|
+
current = raw.trim() ? JSON.parse(raw) : null;
|
|
82
|
+
}
|
|
83
|
+
const next = mutator(current);
|
|
84
|
+
if (next === null) {
|
|
85
|
+
if (fs_1.default.existsSync(filePath))
|
|
86
|
+
fs_1.default.unlinkSync(filePath);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
atomicWriteFileSync(filePath, JSON.stringify(next, null, 2) + '\n');
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Surgically remove top-level or nested keys from a JSON file. `paths` is a
|
|
94
|
+
* list of dot-separated paths (e.g., "mcpServers.serena"). Missing paths are
|
|
95
|
+
* a no-op. The file is not removed even when emptied — callers preserve the
|
|
96
|
+
* "state file always survives" invariant.
|
|
97
|
+
*/
|
|
98
|
+
async function surgicalRemoveKeys(filePath, paths) {
|
|
99
|
+
if (paths.length === 0)
|
|
100
|
+
return;
|
|
101
|
+
await surgicalMergeJson(filePath, (current) => {
|
|
102
|
+
if (!current)
|
|
103
|
+
return current;
|
|
104
|
+
for (const p of paths) {
|
|
105
|
+
const segments = p.split('.');
|
|
106
|
+
let cursor = current;
|
|
107
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
108
|
+
const next = cursor?.[segments[i]];
|
|
109
|
+
if (typeof next !== 'object' || next === null) {
|
|
110
|
+
cursor = undefined;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
cursor = next;
|
|
114
|
+
}
|
|
115
|
+
if (cursor)
|
|
116
|
+
delete cursor[segments[segments.length - 1]];
|
|
117
|
+
}
|
|
118
|
+
return current;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPluginManager = getPluginManager;
|
|
4
|
+
exports.setPluginManagerForTesting = setPluginManagerForTesting;
|
|
5
|
+
const plugin_manager_1 = require("../plugin-manager");
|
|
6
|
+
const index_1 = require("./index");
|
|
7
|
+
const setup_prerequisites_1 = require("../setup-prerequisites");
|
|
8
|
+
const livePrerequisiteCheck = async (req) => {
|
|
9
|
+
const status = (0, setup_prerequisites_1.getSetupPrerequisitesStatus)({ includeUv: true });
|
|
10
|
+
const match = status.prerequisites.find((p) => p.command === req.name || p.key === req.name);
|
|
11
|
+
if (!match) {
|
|
12
|
+
return { installed: false, executable: false, meetsMinimum: false };
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
installed: match.installed,
|
|
16
|
+
executable: match.executable,
|
|
17
|
+
version: match.version,
|
|
18
|
+
meetsMinimum: match.meetsMinimum,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
let _instance = null;
|
|
22
|
+
/** Lazily-built process-wide PluginManager (driven by BUNDLED_PLUGINS). */
|
|
23
|
+
function getPluginManager() {
|
|
24
|
+
if (!_instance) {
|
|
25
|
+
_instance = new plugin_manager_1.PluginManager(index_1.BUNDLED_PLUGINS, { checkPrerequisite: livePrerequisiteCheck });
|
|
26
|
+
}
|
|
27
|
+
return _instance;
|
|
28
|
+
}
|
|
29
|
+
/** Test helper: replace the singleton. */
|
|
30
|
+
function setPluginManagerForTesting(mgr) {
|
|
31
|
+
_instance = mgr;
|
|
32
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PluginOwnershipConflictError = void 0;
|
|
4
|
+
exports.buildOwnershipMap = buildOwnershipMap;
|
|
5
|
+
class PluginOwnershipConflictError extends Error {
|
|
6
|
+
conflicts;
|
|
7
|
+
constructor(conflicts) {
|
|
8
|
+
const lines = conflicts.map((c) => ` ${c.kind}.${c.key} claimed by: ${c.plugins.join(', ')}`);
|
|
9
|
+
super(`plugin ownership conflict:\n${lines.join('\n')}`);
|
|
10
|
+
this.name = 'PluginOwnershipConflictError';
|
|
11
|
+
this.conflicts = conflicts;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.PluginOwnershipConflictError = PluginOwnershipConflictError;
|
|
15
|
+
/**
|
|
16
|
+
* Build a global ownership index from a registry of plugins. Fails fast when
|
|
17
|
+
* any two plugins claim the same key — that is the central guarantee that
|
|
18
|
+
* additivity holds: install/uninstall of plugin N can never corrupt plugin M
|
|
19
|
+
* because their owned keys are statically known to be disjoint.
|
|
20
|
+
*
|
|
21
|
+
* Also rejects plugins with duplicate names.
|
|
22
|
+
*/
|
|
23
|
+
function buildOwnershipMap(plugins) {
|
|
24
|
+
const byName = new Map();
|
|
25
|
+
const mcpServers = new Map();
|
|
26
|
+
const agentFragments = new Map();
|
|
27
|
+
const configKeys = new Map();
|
|
28
|
+
const conflicts = [];
|
|
29
|
+
// Tracks which plugins claim each key, for richer error reporting.
|
|
30
|
+
const claimMap = {
|
|
31
|
+
mcpServers: new Map(),
|
|
32
|
+
agentFragments: new Map(),
|
|
33
|
+
configKeys: new Map(),
|
|
34
|
+
};
|
|
35
|
+
for (const plugin of plugins) {
|
|
36
|
+
const m = plugin.manifest;
|
|
37
|
+
if (!m || !m.name) {
|
|
38
|
+
throw new Error('plugin manifest is missing required field: name');
|
|
39
|
+
}
|
|
40
|
+
if (!m.version) {
|
|
41
|
+
throw new Error(`plugin '${m.name}' is missing required field: version`);
|
|
42
|
+
}
|
|
43
|
+
if (!m.owns) {
|
|
44
|
+
throw new Error(`plugin '${m.name}' is missing required field: owns`);
|
|
45
|
+
}
|
|
46
|
+
if (byName.has(m.name)) {
|
|
47
|
+
throw new Error(`duplicate plugin name in registry: '${m.name}'`);
|
|
48
|
+
}
|
|
49
|
+
byName.set(m.name, plugin);
|
|
50
|
+
for (const key of m.owns.mcpServers ?? []) {
|
|
51
|
+
const list = claimMap.mcpServers.get(key) ?? [];
|
|
52
|
+
list.push(m.name);
|
|
53
|
+
claimMap.mcpServers.set(key, list);
|
|
54
|
+
}
|
|
55
|
+
for (const key of m.owns.agentFragments ?? []) {
|
|
56
|
+
const list = claimMap.agentFragments.get(key) ?? [];
|
|
57
|
+
list.push(m.name);
|
|
58
|
+
claimMap.agentFragments.set(key, list);
|
|
59
|
+
}
|
|
60
|
+
for (const key of m.owns.configKeys ?? []) {
|
|
61
|
+
const list = claimMap.configKeys.get(key) ?? [];
|
|
62
|
+
list.push(m.name);
|
|
63
|
+
claimMap.configKeys.set(key, list);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Promote single-claimer entries; surface multi-claimer entries as conflicts.
|
|
67
|
+
for (const kind of ['mcpServers', 'agentFragments', 'configKeys']) {
|
|
68
|
+
for (const [key, owners] of claimMap[kind].entries()) {
|
|
69
|
+
if (owners.length === 1) {
|
|
70
|
+
if (kind === 'mcpServers')
|
|
71
|
+
mcpServers.set(key, owners[0]);
|
|
72
|
+
else if (kind === 'agentFragments')
|
|
73
|
+
agentFragments.set(key, owners[0]);
|
|
74
|
+
else
|
|
75
|
+
configKeys.set(key, owners[0]);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
conflicts.push({ kind, key, plugins: owners });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (conflicts.length > 0) {
|
|
83
|
+
throw new PluginOwnershipConflictError(conflicts);
|
|
84
|
+
}
|
|
85
|
+
return { mcpServers, agentFragments, configKeys, byName };
|
|
86
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.pluginsDir = pluginsDir;
|
|
7
|
+
exports.stateFilePath = stateFilePath;
|
|
8
|
+
exports.snapshotsDir = snapshotsDir;
|
|
9
|
+
exports.projectJobSnapshotPath = projectJobSnapshotPath;
|
|
10
|
+
exports.homeJobSnapshotPath = homeJobSnapshotPath;
|
|
11
|
+
exports.mcpJsonPath = mcpJsonPath;
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
/** `<project>/.specrails/plugins/` — base directory for app-managed plugin state. */
|
|
15
|
+
function pluginsDir(projectPath) {
|
|
16
|
+
return path_1.default.join(projectPath, '.specrails', 'plugins');
|
|
17
|
+
}
|
|
18
|
+
/** `<project>/.specrails/plugins/state.json` — per-project plugin registry. */
|
|
19
|
+
function stateFilePath(projectPath) {
|
|
20
|
+
return path_1.default.join(pluginsDir(projectPath), 'state.json');
|
|
21
|
+
}
|
|
22
|
+
/** `<project>/.specrails/plugins/snapshots/` — per-job plugin snapshots (project-local copy). */
|
|
23
|
+
function snapshotsDir(projectPath) {
|
|
24
|
+
return path_1.default.join(pluginsDir(projectPath), 'snapshots');
|
|
25
|
+
}
|
|
26
|
+
/** `<project>/.specrails/plugins/snapshots/<jobId>.json` — per-job project-local snapshot. */
|
|
27
|
+
function projectJobSnapshotPath(projectPath, jobId) {
|
|
28
|
+
return path_1.default.join(snapshotsDir(projectPath), `${jobId}.json`);
|
|
29
|
+
}
|
|
30
|
+
/** `~/.specrails/projects/<slug>/jobs/<jobId>/plugins.json` — chmod-400 spawn-time snapshot. */
|
|
31
|
+
function homeJobSnapshotPath(slug, jobId) {
|
|
32
|
+
return path_1.default.join(os_1.default.homedir(), '.specrails', 'projects', slug, 'jobs', jobId, 'plugins.json');
|
|
33
|
+
}
|
|
34
|
+
/** `<project>/.mcp.json` — Claude CLI MCP config (managed surgically by the app). */
|
|
35
|
+
function mcpJsonPath(projectPath) {
|
|
36
|
+
return path_1.default.join(projectPath, '.mcp.json');
|
|
37
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.installPrerequisite = installPrerequisite;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
/**
|
|
6
|
+
* Returns the official installer command for `name` on the current platform.
|
|
7
|
+
* Returns `null` for unsupported (name, platform) pairs — caller should treat
|
|
8
|
+
* as "tell the user to install manually".
|
|
9
|
+
*/
|
|
10
|
+
function resolveInstallerCommand(name) {
|
|
11
|
+
if (name !== 'uv')
|
|
12
|
+
return null;
|
|
13
|
+
if (process.platform === 'darwin' || process.platform === 'linux') {
|
|
14
|
+
return {
|
|
15
|
+
label: 'Astral uv installer (curl)',
|
|
16
|
+
shell: 'curl -LsSf https://astral.sh/uv/install.sh | sh',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (process.platform === 'win32') {
|
|
20
|
+
// PowerShell installer is the cross-version path. winget would be nicer
|
|
21
|
+
// but isn't always present. The Astral installer adds uv to PATH for the
|
|
22
|
+
// user; the app's PATH augmentation on next start picks it up.
|
|
23
|
+
return {
|
|
24
|
+
label: 'Astral uv installer (PowerShell)',
|
|
25
|
+
shell: 'powershell -ExecutionPolicy ByPass -NoProfile -Command "irm https://astral.sh/uv/install.ps1 | iex"',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Run the platform-appropriate installer for `name` and stream stdout+stderr
|
|
32
|
+
* to `broadcast` as `plugin.prereq_install_progress` events. Resolves once the
|
|
33
|
+
* child exits — never throws.
|
|
34
|
+
*/
|
|
35
|
+
async function installPrerequisite(name, projectId, broadcast) {
|
|
36
|
+
const cmd = resolveInstallerCommand(name);
|
|
37
|
+
if (!cmd) {
|
|
38
|
+
return { ok: false, exitCode: null, reason: `no installer for '${name}' on ${process.platform}` };
|
|
39
|
+
}
|
|
40
|
+
const log = (line) => {
|
|
41
|
+
broadcast({
|
|
42
|
+
type: 'plugin.prereq_install_progress',
|
|
43
|
+
projectId,
|
|
44
|
+
prereq: name,
|
|
45
|
+
line,
|
|
46
|
+
timestamp: new Date().toISOString(),
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
log(`Running: ${cmd.label}`);
|
|
50
|
+
log(`> ${cmd.shell}`);
|
|
51
|
+
// Escape hatch for tests that exercise the router but don't want a real
|
|
52
|
+
// network installer to spawn. Set SPECRAILS_PREREQ_NOOP=1 to short-circuit.
|
|
53
|
+
if (process.env.SPECRAILS_PREREQ_NOOP === '1') {
|
|
54
|
+
log('(SPECRAILS_PREREQ_NOOP=1 — skipping real installer)');
|
|
55
|
+
return { ok: true, exitCode: 0, reason: 'noop' };
|
|
56
|
+
}
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const isWin = process.platform === 'win32';
|
|
59
|
+
// Use shell:true so pipes / irm / iex work. Inputs are not user-controlled.
|
|
60
|
+
const child = (0, child_process_1.spawn)(cmd.shell, [], {
|
|
61
|
+
shell: isWin ? 'powershell.exe' : true,
|
|
62
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
63
|
+
env: process.env,
|
|
64
|
+
});
|
|
65
|
+
let settled = false;
|
|
66
|
+
const finish = (result) => {
|
|
67
|
+
if (settled)
|
|
68
|
+
return;
|
|
69
|
+
settled = true;
|
|
70
|
+
resolve(result);
|
|
71
|
+
};
|
|
72
|
+
child.stdout?.on('data', (b) => { for (const line of b.toString().split(/\r?\n/))
|
|
73
|
+
if (line)
|
|
74
|
+
log(line); });
|
|
75
|
+
child.stderr?.on('data', (b) => { for (const line of b.toString().split(/\r?\n/))
|
|
76
|
+
if (line)
|
|
77
|
+
log(line); });
|
|
78
|
+
child.on('error', (err) => {
|
|
79
|
+
log(`error: ${err.message}`);
|
|
80
|
+
finish({ ok: false, exitCode: null, reason: err.message });
|
|
81
|
+
});
|
|
82
|
+
child.on('close', (code) => {
|
|
83
|
+
if (code === 0) {
|
|
84
|
+
log('Installer finished successfully.');
|
|
85
|
+
finish({ ok: true, exitCode: 0 });
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
log(`Installer exited with code ${code}`);
|
|
89
|
+
finish({ ok: false, exitCode: code, reason: `exit-code-${code}` });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
// Hard cap: 5 minutes. Long enough for slow networks, prevents hangs.
|
|
93
|
+
setTimeout(() => {
|
|
94
|
+
if (settled)
|
|
95
|
+
return;
|
|
96
|
+
try {
|
|
97
|
+
child.kill('SIGKILL');
|
|
98
|
+
}
|
|
99
|
+
catch { /* ignore */ }
|
|
100
|
+
log('Installer timed out after 5 minutes');
|
|
101
|
+
finish({ ok: false, exitCode: null, reason: 'timeout' });
|
|
102
|
+
}, 5 * 60 * 1000).unref?.();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolvePluginsForSpawn = resolvePluginsForSpawn;
|
|
7
|
+
exports.snapshotPluginsForJob = snapshotPluginsForJob;
|
|
8
|
+
exports.snapshotPluginsForJobProjectLocal = snapshotPluginsForJobProjectLocal;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const manager_1 = require("./manager");
|
|
12
|
+
const paths_1 = require("./paths");
|
|
13
|
+
const json_mutation_1 = require("./json-mutation");
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the project's installed plugins for a rail spawn. For each entry
|
|
16
|
+
* in `state.json`, runs `verify` (timeout-bounded) and classifies into
|
|
17
|
+
* `active` (verify ok) or `degraded` (verify failed / timed out / threw).
|
|
18
|
+
*
|
|
19
|
+
* Side effect: `verify` persists a plugin's health to `state.json` only when it
|
|
20
|
+
* actually changed since the last check (no churn on the steady-state path).
|
|
21
|
+
* Otherwise this performs no project-file mutation.
|
|
22
|
+
*/
|
|
23
|
+
async function resolvePluginsForSpawn(projectPath, projectId, _jobId) {
|
|
24
|
+
const mgr = (0, manager_1.getPluginManager)();
|
|
25
|
+
const state = mgr.getProjectState(projectPath);
|
|
26
|
+
const active = [];
|
|
27
|
+
const degraded = [];
|
|
28
|
+
// Run verify in parallel for all installed plugins. Each verify is
|
|
29
|
+
// already timeout-wrapped inside PluginManager.
|
|
30
|
+
const checks = await Promise.all(Object.entries(state.plugins).map(async ([name, entry]) => {
|
|
31
|
+
// Skip orphans — they are not in the registry, so verify cannot run.
|
|
32
|
+
if (!mgr.registry.byName.has(name))
|
|
33
|
+
return { name, version: entry.version, result: { ok: false, reason: 'orphan', checkedAt: new Date().toISOString() } };
|
|
34
|
+
const result = await mgr.verify(projectPath, projectId, name);
|
|
35
|
+
return { name, version: entry.version, result };
|
|
36
|
+
}));
|
|
37
|
+
for (const c of checks) {
|
|
38
|
+
if (c.result.ok)
|
|
39
|
+
active.push({ name: c.name, version: c.version });
|
|
40
|
+
else
|
|
41
|
+
degraded.push({ name: c.name, reason: c.result.reason ?? 'unknown' });
|
|
42
|
+
}
|
|
43
|
+
return { active, degraded };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Writes the per-job plugin snapshot to:
|
|
47
|
+
* - `~/.specrails/projects/<slug>/jobs/<jobId>/plugins.json` (chmod 400)
|
|
48
|
+
* - `<project>/.specrails/plugins/snapshots/<jobId>.json` (project-local mirror)
|
|
49
|
+
* Returns the absolute path of the home-side snapshot (used for env var).
|
|
50
|
+
*/
|
|
51
|
+
function snapshotPluginsForJob(slug, jobId, projectId, active, degraded) {
|
|
52
|
+
const body = JSON.stringify({
|
|
53
|
+
jobId,
|
|
54
|
+
projectId,
|
|
55
|
+
capturedAt: new Date().toISOString(),
|
|
56
|
+
active,
|
|
57
|
+
degraded,
|
|
58
|
+
}, null, 2) + '\n';
|
|
59
|
+
// Home-side snapshot — chmod 400, source of truth for diagnostic export.
|
|
60
|
+
const homeSnap = (0, paths_1.homeJobSnapshotPath)(slug, jobId);
|
|
61
|
+
fs_1.default.mkdirSync(path_1.default.dirname(homeSnap), { recursive: true });
|
|
62
|
+
(0, json_mutation_1.atomicWriteFileSync)(homeSnap, body, 0o400);
|
|
63
|
+
return homeSnap;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Project-local mirror snapshot, kept alongside `state.json`. Best-effort —
|
|
67
|
+
* never fails the rail spawn. Used for in-app inspection.
|
|
68
|
+
*/
|
|
69
|
+
function snapshotPluginsForJobProjectLocal(projectPath, jobId, projectId, active, degraded) {
|
|
70
|
+
try {
|
|
71
|
+
const body = JSON.stringify({ jobId, projectId, capturedAt: new Date().toISOString(), active, degraded }, null, 2) + '\n';
|
|
72
|
+
const p = (0, paths_1.projectJobSnapshotPath)(projectPath, jobId);
|
|
73
|
+
fs_1.default.mkdirSync(path_1.default.dirname(p), { recursive: true });
|
|
74
|
+
(0, json_mutation_1.atomicWriteFileSync)(p, body);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Non-fatal.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serenaPlugin = void 0;
|
|
4
|
+
const manifest_1 = require("./manifest");
|
|
5
|
+
const install_1 = require("./install");
|
|
6
|
+
const verify_1 = require("./verify");
|
|
7
|
+
exports.serenaPlugin = {
|
|
8
|
+
manifest: manifest_1.serenaManifest,
|
|
9
|
+
install: install_1.installSerena,
|
|
10
|
+
uninstall: install_1.uninstallSerena,
|
|
11
|
+
verify: verify_1.verifySerena,
|
|
12
|
+
expectedMcpEntry: () => manifest_1.SERENA_MCP_ENTRY,
|
|
13
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.installSerena = installSerena;
|
|
7
|
+
exports.uninstallSerena = uninstallSerena;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const plugin_manager_1 = require("../../plugin-manager");
|
|
11
|
+
const manifest_1 = require("./manifest");
|
|
12
|
+
const instructions_content_1 = require("./instructions-content");
|
|
13
|
+
const codex_mcp_1 = require("../codex-mcp");
|
|
14
|
+
const providers_1 = require("../../providers");
|
|
15
|
+
/** Claude fragment lives in the core-protected `.claude/agents/custom-*.md`
|
|
16
|
+
* namespace; only written on claude projects. Codex doesn't honour that
|
|
17
|
+
* file convention, and the per-project AGENTS.md block (written by the
|
|
18
|
+
* shared-file contributor in plugin-manager) is the codex equivalent. */
|
|
19
|
+
const CLAUDE_FRAGMENT_REL = '.claude/agents/custom-serena.md';
|
|
20
|
+
function isCodex(ctx) {
|
|
21
|
+
if (!ctx.providerId)
|
|
22
|
+
return false;
|
|
23
|
+
if (!(0, providers_1.hasAdapter)(ctx.providerId))
|
|
24
|
+
return false;
|
|
25
|
+
return (0, providers_1.getAdapter)(ctx.providerId).mcpRegistration === 'cli-add';
|
|
26
|
+
}
|
|
27
|
+
/** Project slug used by codex-mcp helpers — derived from the project path's
|
|
28
|
+
* basename. The app maintains the canonical slug elsewhere (ProjectRegistry);
|
|
29
|
+
* for the install context we don't have the registry, but the basename is a
|
|
30
|
+
* stable per-project identifier sufficient for CODEX_HOME isolation. */
|
|
31
|
+
function slugFromProjectPath(projectPath) {
|
|
32
|
+
return path_1.default.basename(projectPath);
|
|
33
|
+
}
|
|
34
|
+
async function installSerena(ctx) {
|
|
35
|
+
if (isCodex(ctx)) {
|
|
36
|
+
// Codex path: `codex mcp add` against per-project CODEX_HOME. The
|
|
37
|
+
// declarative entry comes from the manifest's providerSupport.codex
|
|
38
|
+
// so future plugins can declare their own without touching this file.
|
|
39
|
+
const entry = manifest_1.serenaManifest.providerSupport?.codex?.mcpEntry;
|
|
40
|
+
if (!entry) {
|
|
41
|
+
throw new Error('serena manifest is missing providerSupport.codex.mcpEntry');
|
|
42
|
+
}
|
|
43
|
+
const slug = slugFromProjectPath(ctx.projectPath);
|
|
44
|
+
ctx.log(`Registering serena MCP via 'codex mcp add' (CODEX_HOME=~/.specrails/projects/${slug}/codex-home/)`);
|
|
45
|
+
const result = (0, codex_mcp_1.codexMcpAdd)(slug, 'serena', entry);
|
|
46
|
+
if (!result.ok) {
|
|
47
|
+
const detail = result.stderr.trim() || result.stdout.trim() || '(no output)';
|
|
48
|
+
throw new Error(`codex mcp add serena failed: ${detail}`);
|
|
49
|
+
}
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Claude path: surgical merge of project's `.mcp.json` + custom agent
|
|
53
|
+
// fragment. Unchanged from the pre-§14 behaviour.
|
|
54
|
+
ctx.log('Adding mcpServers.serena to .mcp.json');
|
|
55
|
+
await plugin_manager_1.PluginManager.mergeMcpServers(ctx.projectPath, { serena: manifest_1.SERENA_MCP_ENTRY });
|
|
56
|
+
ctx.log(`Writing ${CLAUDE_FRAGMENT_REL}`);
|
|
57
|
+
const fragmentDest = path_1.default.join(ctx.projectPath, CLAUDE_FRAGMENT_REL);
|
|
58
|
+
fs_1.default.mkdirSync(path_1.default.dirname(fragmentDest), { recursive: true });
|
|
59
|
+
fs_1.default.writeFileSync(fragmentDest, instructions_content_1.SERENA_INSTRUCTIONS_MD, 'utf8');
|
|
60
|
+
ctx.recordInstalledFile(CLAUDE_FRAGMENT_REL);
|
|
61
|
+
}
|
|
62
|
+
async function uninstallSerena(ctx) {
|
|
63
|
+
if (isCodex(ctx)) {
|
|
64
|
+
const slug = slugFromProjectPath(ctx.projectPath);
|
|
65
|
+
// Probe first so removing an already-removed server doesn't surface as an
|
|
66
|
+
// error (e.g. the user uninstalled via terminal then via the app).
|
|
67
|
+
const listing = (0, codex_mcp_1.codexMcpList)(slug);
|
|
68
|
+
if (listing.ok && !listing.servers.includes('serena')) {
|
|
69
|
+
ctx.log('serena not present in codex mcp list — nothing to remove');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
ctx.log(`Removing serena via 'codex mcp remove' (CODEX_HOME=~/.specrails/projects/${slug}/codex-home/)`);
|
|
73
|
+
const result = (0, codex_mcp_1.codexMcpRemove)(slug, 'serena');
|
|
74
|
+
if (!result.ok) {
|
|
75
|
+
// Removal failures are warnings — the state.json entry is gone either
|
|
76
|
+
// way and a subsequent install will overwrite. Don't block uninstall.
|
|
77
|
+
ctx.log(`codex mcp remove warning: ${result.stderr.trim() || '(no output)'}`);
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
ctx.log('Removing mcpServers.serena from .mcp.json');
|
|
82
|
+
await plugin_manager_1.PluginManager.removeMcpServers(ctx.projectPath, ['serena']);
|
|
83
|
+
const fragmentDest = path_1.default.join(ctx.projectPath, CLAUDE_FRAGMENT_REL);
|
|
84
|
+
if (fs_1.default.existsSync(fragmentDest)) {
|
|
85
|
+
ctx.log(`Removing ${CLAUDE_FRAGMENT_REL}`);
|
|
86
|
+
try {
|
|
87
|
+
fs_1.default.unlinkSync(fragmentDest);
|
|
88
|
+
}
|
|
89
|
+
catch { /* best-effort */ }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SERENA_INSTRUCTIONS_MD = void 0;
|
|
4
|
+
/* Embedded so the plugin survives bundling/packaging without external assets. */
|
|
5
|
+
exports.SERENA_INSTRUCTIONS_MD = `---
|
|
6
|
+
name: serena-helper
|
|
7
|
+
description: "Serena MCP semantic-navigation hints (auto-installed by Specrails). Prefer Serena tools over raw Read/Grep when locating symbols, references, or definitions."
|
|
8
|
+
color: violet
|
|
9
|
+
memory: project
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Serena MCP — usage hints
|
|
13
|
+
|
|
14
|
+
When working in a repository where Serena is installed:
|
|
15
|
+
|
|
16
|
+
- Prefer \`find_symbol\`, \`get_references\`, \`get_definition\`, and \`get_symbols_overview\` over \`Read\` and \`Grep\` when you only need a function, class, or callsites.
|
|
17
|
+
- Use \`replace_symbol_body\` to edit a function or method without re-reading or re-writing the rest of the file.
|
|
18
|
+
- Fall back to \`Read\` / \`Grep\` only when Serena cannot handle the request (e.g., binary files, free-form text, JSON/Markdown without symbols).
|
|
19
|
+
|
|
20
|
+
These tools are added to every agent on this project automatically; no further configuration is needed.
|
|
21
|
+
`;
|