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,448 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSpending = getSpending;
|
|
4
|
+
exports.summariseExplorePrompt = summariseExplorePrompt;
|
|
5
|
+
exports.getInvocations = getInvocations;
|
|
6
|
+
exports.parseSpendingFilters = parseSpendingFilters;
|
|
7
|
+
const ALL_SURFACES = ['job', 'quick-spec', 'explore-spec', 'ai-edit', 'smash', 'file-summary'];
|
|
8
|
+
function resolveRange(filters, now = new Date()) {
|
|
9
|
+
const period = filters.period ?? '30d';
|
|
10
|
+
if (period === 'custom' && filters.from && filters.to) {
|
|
11
|
+
const fromMs = new Date(filters.from).getTime();
|
|
12
|
+
const toMs = new Date(filters.to).getTime();
|
|
13
|
+
const span = toMs - fromMs;
|
|
14
|
+
return {
|
|
15
|
+
from: filters.from,
|
|
16
|
+
to: filters.to,
|
|
17
|
+
prevFrom: new Date(fromMs - span).toISOString(),
|
|
18
|
+
prevTo: filters.from,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (period === 'all') {
|
|
22
|
+
return {
|
|
23
|
+
from: '1970-01-01T00:00:00Z',
|
|
24
|
+
to: now.toISOString(),
|
|
25
|
+
prevFrom: '1970-01-01T00:00:00Z',
|
|
26
|
+
prevTo: '1970-01-01T00:00:00Z',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const days = period === '7d' ? 7 : period === '90d' ? 90 : 30;
|
|
30
|
+
const toMs = now.getTime();
|
|
31
|
+
const fromMs = toMs - days * 86_400_000;
|
|
32
|
+
const prevFromMs = fromMs - days * 86_400_000;
|
|
33
|
+
return {
|
|
34
|
+
from: new Date(fromMs).toISOString(),
|
|
35
|
+
to: new Date(toMs).toISOString(),
|
|
36
|
+
prevFrom: new Date(prevFromMs).toISOString(),
|
|
37
|
+
prevTo: new Date(fromMs).toISOString(),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function buildWhere(projectId, filters, range, alias = '') {
|
|
41
|
+
const a = alias ? `${alias}.` : '';
|
|
42
|
+
const conditions = [`${a}project_id = ?`];
|
|
43
|
+
const params = [projectId];
|
|
44
|
+
conditions.push(`${a}started_at >= ?`);
|
|
45
|
+
params.push(range.from);
|
|
46
|
+
conditions.push(`${a}started_at <= ?`);
|
|
47
|
+
params.push(range.to);
|
|
48
|
+
if (filters.surface && filters.surface.length > 0) {
|
|
49
|
+
const placeholders = filters.surface.map(() => '?').join(',');
|
|
50
|
+
conditions.push(`${a}surface IN (${placeholders})`);
|
|
51
|
+
params.push(...filters.surface);
|
|
52
|
+
}
|
|
53
|
+
if (filters.model && filters.model.length > 0) {
|
|
54
|
+
const placeholders = filters.model.map(() => '?').join(',');
|
|
55
|
+
conditions.push(`${a}model IN (${placeholders})`);
|
|
56
|
+
params.push(...filters.model);
|
|
57
|
+
}
|
|
58
|
+
if (filters.provider && filters.provider.length > 0) {
|
|
59
|
+
// Coalesce legacy NULL provider rows to 'claude' so a 'claude' filter still
|
|
60
|
+
// surfaces pre-migration invocations.
|
|
61
|
+
const placeholders = filters.provider.map(() => '?').join(',');
|
|
62
|
+
conditions.push(`COALESCE(${a}provider, 'claude') IN (${placeholders})`);
|
|
63
|
+
params.push(...filters.provider);
|
|
64
|
+
}
|
|
65
|
+
if (filters.status) {
|
|
66
|
+
conditions.push(`${a}status = ?`);
|
|
67
|
+
params.push(filters.status);
|
|
68
|
+
}
|
|
69
|
+
if (typeof filters.minCostUsd === 'number') {
|
|
70
|
+
conditions.push(`${a}total_cost_usd >= ?`);
|
|
71
|
+
params.push(filters.minCostUsd);
|
|
72
|
+
}
|
|
73
|
+
if (typeof filters.ticketId === 'number') {
|
|
74
|
+
conditions.push(`${a}ticket_id = ?`);
|
|
75
|
+
params.push(filters.ticketId);
|
|
76
|
+
}
|
|
77
|
+
return { sql: conditions.join(' AND '), params };
|
|
78
|
+
}
|
|
79
|
+
function dateOnly(iso) {
|
|
80
|
+
return iso.slice(0, 10);
|
|
81
|
+
}
|
|
82
|
+
function eachDay(fromIso, toIso) {
|
|
83
|
+
const out = [];
|
|
84
|
+
const fromDay = new Date(dateOnly(fromIso) + 'T00:00:00Z').getTime();
|
|
85
|
+
const toDay = new Date(dateOnly(toIso) + 'T00:00:00Z').getTime();
|
|
86
|
+
for (let t = fromDay; t <= toDay; t += 86_400_000) {
|
|
87
|
+
out.push(new Date(t).toISOString().slice(0, 10));
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
function getSpending(db, projectId, filters = {}) {
|
|
92
|
+
const range = resolveRange(filters);
|
|
93
|
+
const where = buildWhere(projectId, filters, { from: range.from, to: range.to });
|
|
94
|
+
// summary
|
|
95
|
+
const summaryRow = db.prepare(`
|
|
96
|
+
SELECT
|
|
97
|
+
COALESCE(SUM(total_cost_usd), 0) AS totalCost,
|
|
98
|
+
COALESCE(SUM(CASE WHEN total_cost_usd_estimated = 1 THEN total_cost_usd ELSE 0 END), 0) AS totalEstimatedCost,
|
|
99
|
+
COALESCE(SUM(COALESCE(tokens_in, 0) + COALESCE(tokens_out, 0)
|
|
100
|
+
+ COALESCE(tokens_cache_read, 0) + COALESCE(tokens_cache_create, 0)), 0) AS totalTokens,
|
|
101
|
+
COUNT(*) AS totalRuns,
|
|
102
|
+
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
103
|
+
AVG(CASE WHEN status = 'success' THEN total_cost_usd END) AS avgCost
|
|
104
|
+
FROM ai_invocations WHERE ${where.sql}
|
|
105
|
+
`).get(...where.params);
|
|
106
|
+
// prev period
|
|
107
|
+
const prevWhere = buildWhere(projectId, filters, { from: range.prevFrom, to: range.prevTo });
|
|
108
|
+
const prevRow = db.prepare(`
|
|
109
|
+
SELECT COALESCE(SUM(total_cost_usd), 0) AS totalCost
|
|
110
|
+
FROM ai_invocations WHERE ${prevWhere.sql}
|
|
111
|
+
`).get(...prevWhere.params);
|
|
112
|
+
const deltaPct = prevRow.totalCost > 0
|
|
113
|
+
? ((summaryRow.totalCost - prevRow.totalCost) / prevRow.totalCost) * 100
|
|
114
|
+
: null;
|
|
115
|
+
// byModel (top 10) + per-mode dominant models — one GROUP BY model, surface
|
|
116
|
+
// query serves both (H24: previously byModel plus two per-mode dominant-model
|
|
117
|
+
// queries re-scanned the same rows).
|
|
118
|
+
const modelRows = db.prepare(`
|
|
119
|
+
SELECT model, surface, COUNT(*) AS cnt, COALESCE(SUM(total_cost_usd), 0) AS cost
|
|
120
|
+
FROM ai_invocations WHERE ${where.sql} AND model IS NOT NULL
|
|
121
|
+
GROUP BY model, surface
|
|
122
|
+
`).all(...where.params);
|
|
123
|
+
const modelTotals = new Map();
|
|
124
|
+
for (const r of modelRows) {
|
|
125
|
+
const agg = modelTotals.get(r.model) ?? { cnt: 0, cost: 0 };
|
|
126
|
+
agg.cnt += r.cnt;
|
|
127
|
+
agg.cost += r.cost ?? 0;
|
|
128
|
+
modelTotals.set(r.model, agg);
|
|
129
|
+
}
|
|
130
|
+
const byModel = Array.from(modelTotals.entries())
|
|
131
|
+
.map(([model, t]) => ({ model, count: t.cnt, costUsd: t.cost }))
|
|
132
|
+
.sort((a, b) => b.costUsd - a.costUsd)
|
|
133
|
+
.slice(0, 10);
|
|
134
|
+
const dominantBySurface = new Map();
|
|
135
|
+
for (const r of modelRows) {
|
|
136
|
+
const cur = dominantBySurface.get(r.surface);
|
|
137
|
+
if (!cur || r.cnt > cur.cnt)
|
|
138
|
+
dominantBySurface.set(r.surface, { model: r.model, cnt: r.cnt });
|
|
139
|
+
}
|
|
140
|
+
// dailyTimeline (zero-filled, stacked by surface)
|
|
141
|
+
const dayRows = db.prepare(`
|
|
142
|
+
SELECT substr(started_at, 1, 10) AS day, surface, COALESCE(SUM(total_cost_usd), 0) AS cost
|
|
143
|
+
FROM ai_invocations WHERE ${where.sql}
|
|
144
|
+
GROUP BY day, surface
|
|
145
|
+
`).all(...where.params);
|
|
146
|
+
// B63: period 'all' resolves range.from to 1970, which would make eachDay emit
|
|
147
|
+
// ~20,000 zero-filled days (a huge payload + 20k-bar chart). Clamp the zero-fill
|
|
148
|
+
// start to the first day that actually has data (or a single day when empty).
|
|
149
|
+
let timelineFrom = range.from;
|
|
150
|
+
if (filters.period === 'all') {
|
|
151
|
+
timelineFrom = dayRows.length > 0
|
|
152
|
+
? dayRows.reduce((min, r) => (r.day < min ? r.day : min), dayRows[0].day)
|
|
153
|
+
: range.to.slice(0, 10);
|
|
154
|
+
}
|
|
155
|
+
const days = eachDay(timelineFrom, range.to);
|
|
156
|
+
const dayMap = new Map();
|
|
157
|
+
for (const day of days) {
|
|
158
|
+
dayMap.set(day, {
|
|
159
|
+
date: day, jobsCostUsd: 0, quickCostUsd: 0, exploreCostUsd: 0, aiEditCostUsd: 0, smashCostUsd: 0, fileSummaryCostUsd: 0, totalCostUsd: 0,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
for (const r of dayRows) {
|
|
163
|
+
const entry = dayMap.get(r.day);
|
|
164
|
+
if (!entry)
|
|
165
|
+
continue;
|
|
166
|
+
const c = r.cost ?? 0;
|
|
167
|
+
if (r.surface === 'job')
|
|
168
|
+
entry.jobsCostUsd += c;
|
|
169
|
+
else if (r.surface === 'quick-spec')
|
|
170
|
+
entry.quickCostUsd += c;
|
|
171
|
+
else if (r.surface === 'explore-spec')
|
|
172
|
+
entry.exploreCostUsd += c;
|
|
173
|
+
else if (r.surface === 'ai-edit')
|
|
174
|
+
entry.aiEditCostUsd += c;
|
|
175
|
+
else if (r.surface === 'smash')
|
|
176
|
+
entry.smashCostUsd += c;
|
|
177
|
+
else if (r.surface === 'file-summary')
|
|
178
|
+
entry.fileSummaryCostUsd += c; // B58
|
|
179
|
+
entry.totalCostUsd += c;
|
|
180
|
+
}
|
|
181
|
+
const dailyTimeline = Array.from(dayMap.values());
|
|
182
|
+
// byMode (Quick vs Explore) — one GROUP BY surface query for both modes;
|
|
183
|
+
// dominant models come from modelRows and sparklines from dayRows, which
|
|
184
|
+
// already carry exactly these aggregates (H24: previously 3 extra queries
|
|
185
|
+
// per mode re-scanned the same rows).
|
|
186
|
+
// M18: count DISTINCT tickets, not rows. An Explore session writes one
|
|
187
|
+
// ai_invocations row per turn (and contract-refine adds another), all
|
|
188
|
+
// back-filled with the same ticket_id — so SUM(ticket_id IS NOT NULL) counted
|
|
189
|
+
// turns and inflated "N created". avgCostPerSpec is likewise per-spec:
|
|
190
|
+
// total success cost of ticket-bearing rows / distinct successful tickets.
|
|
191
|
+
const modeAggRows = db.prepare(`
|
|
192
|
+
SELECT
|
|
193
|
+
surface,
|
|
194
|
+
COUNT(*) AS totalRuns,
|
|
195
|
+
COUNT(DISTINCT ticket_id) AS ticketsCreated,
|
|
196
|
+
COALESCE(SUM(total_cost_usd), 0) AS totalCost,
|
|
197
|
+
COALESCE(SUM(CASE WHEN status = 'success' AND ticket_id IS NOT NULL THEN total_cost_usd ELSE 0 END), 0) AS specCostSum,
|
|
198
|
+
COUNT(DISTINCT CASE WHEN status = 'success' AND ticket_id IS NOT NULL THEN ticket_id END) AS specCount,
|
|
199
|
+
AVG(CASE WHEN status = 'success' THEN duration_ms END) AS avgDur
|
|
200
|
+
FROM ai_invocations WHERE ${where.sql} AND surface IN ('quick-spec', 'explore-spec')
|
|
201
|
+
GROUP BY surface
|
|
202
|
+
`).all(...where.params);
|
|
203
|
+
const modeAggBySurface = new Map(modeAggRows.map((r) => [r.surface, r]));
|
|
204
|
+
const costByDaySurface = new Map();
|
|
205
|
+
for (const r of dayRows)
|
|
206
|
+
costByDaySurface.set(`${r.day}|${r.surface}`, r.cost ?? 0);
|
|
207
|
+
const byMode = ['quick-spec', 'explore-spec'].map((surface) => {
|
|
208
|
+
const modeKey = surface === 'quick-spec' ? 'quick' : 'explore';
|
|
209
|
+
const r = modeAggBySurface.get(surface);
|
|
210
|
+
const avgCostPerSpec = r && r.specCount > 0 ? r.specCostSum / r.specCount : null;
|
|
211
|
+
const sparkline = days.map((d) => costByDaySurface.get(`${d}|${surface}`) ?? 0);
|
|
212
|
+
return {
|
|
213
|
+
mode: modeKey,
|
|
214
|
+
totalRuns: r?.totalRuns ?? 0,
|
|
215
|
+
ticketsCreated: r?.ticketsCreated ?? 0,
|
|
216
|
+
totalCostUsd: r?.totalCost ?? 0,
|
|
217
|
+
avgCostPerSpec,
|
|
218
|
+
avgDurationMs: r?.avgDur ?? null,
|
|
219
|
+
dominantModel: dominantBySurface.get(surface)?.model ?? null,
|
|
220
|
+
sparkline,
|
|
221
|
+
};
|
|
222
|
+
});
|
|
223
|
+
// scatter (capped at 500 points to avoid heavy payloads)
|
|
224
|
+
const scatterRows = db.prepare(`
|
|
225
|
+
SELECT id, surface, total_cost_usd, num_turns, duration_ms, ticket_id, started_at
|
|
226
|
+
FROM ai_invocations WHERE ${where.sql} AND total_cost_usd IS NOT NULL
|
|
227
|
+
ORDER BY started_at DESC LIMIT 500
|
|
228
|
+
`).all(...where.params);
|
|
229
|
+
const scatter = scatterRows.map((r) => ({
|
|
230
|
+
id: r.id,
|
|
231
|
+
surface: r.surface,
|
|
232
|
+
costUsd: r.total_cost_usd,
|
|
233
|
+
numTurns: r.num_turns,
|
|
234
|
+
durationMs: r.duration_ms,
|
|
235
|
+
ticketId: r.ticket_id,
|
|
236
|
+
startedAt: r.started_at,
|
|
237
|
+
}));
|
|
238
|
+
// topTickets (cross-surface aggregation)
|
|
239
|
+
const ticketRows = db.prepare(`
|
|
240
|
+
SELECT ticket_id, surface, COUNT(*) AS cnt, COALESCE(SUM(total_cost_usd), 0) AS cost
|
|
241
|
+
FROM ai_invocations WHERE ${where.sql}
|
|
242
|
+
GROUP BY ticket_id, surface
|
|
243
|
+
`).all(...where.params);
|
|
244
|
+
const ticketMap = new Map();
|
|
245
|
+
for (const r of ticketRows) {
|
|
246
|
+
const key = r.ticket_id === null ? '__unattributed__' : String(r.ticket_id);
|
|
247
|
+
if (!ticketMap.has(key)) {
|
|
248
|
+
ticketMap.set(key, {
|
|
249
|
+
ticketId: r.ticket_id,
|
|
250
|
+
ticketTitle: null,
|
|
251
|
+
totalCostUsd: 0,
|
|
252
|
+
totalRuns: 0,
|
|
253
|
+
bySurface: {
|
|
254
|
+
job: { count: 0, costUsd: 0 },
|
|
255
|
+
'quick-spec': { count: 0, costUsd: 0 },
|
|
256
|
+
'explore-spec': { count: 0, costUsd: 0 },
|
|
257
|
+
'ai-edit': { count: 0, costUsd: 0 },
|
|
258
|
+
smash: { count: 0, costUsd: 0 },
|
|
259
|
+
'file-summary': { count: 0, costUsd: 0 },
|
|
260
|
+
},
|
|
261
|
+
isUnattributed: r.ticket_id === null ? true : undefined,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
const entry = ticketMap.get(key);
|
|
265
|
+
entry.bySurface[r.surface].count += r.cnt;
|
|
266
|
+
entry.bySurface[r.surface].costUsd += r.cost ?? 0;
|
|
267
|
+
entry.totalRuns += r.cnt;
|
|
268
|
+
entry.totalCostUsd += r.cost ?? 0;
|
|
269
|
+
}
|
|
270
|
+
const topTickets = Array.from(ticketMap.values())
|
|
271
|
+
.sort((a, b) => b.totalCostUsd - a.totalCostUsd)
|
|
272
|
+
.slice(0, 10);
|
|
273
|
+
// bySurface — derived from ticketRows (same WHERE, grouped by
|
|
274
|
+
// ticket_id+surface): summing across tickets equals a GROUP BY surface
|
|
275
|
+
// (H24: previously a dedicated query re-scanned the same rows).
|
|
276
|
+
const surfaceTotals = new Map();
|
|
277
|
+
for (const r of ticketRows) {
|
|
278
|
+
const agg = surfaceTotals.get(r.surface) ?? { cnt: 0, cost: 0 };
|
|
279
|
+
agg.cnt += r.cnt;
|
|
280
|
+
agg.cost += r.cost ?? 0;
|
|
281
|
+
surfaceTotals.set(r.surface, agg);
|
|
282
|
+
}
|
|
283
|
+
const bySurface = ALL_SURFACES.map((s) => {
|
|
284
|
+
const t = surfaceTotals.get(s);
|
|
285
|
+
return { surface: s, count: t?.cnt ?? 0, costUsd: t?.cost ?? 0 };
|
|
286
|
+
});
|
|
287
|
+
// tracking start (project's first invocation)
|
|
288
|
+
const trackingRow = db.prepare(`
|
|
289
|
+
SELECT MIN(started_at) AS first FROM ai_invocations WHERE project_id = ?
|
|
290
|
+
`).get(projectId);
|
|
291
|
+
// byProvider — split authoritative vs estimated cost so the UI can render
|
|
292
|
+
// the `~` tilde + Hero footnote without re-querying. Rows persisted with
|
|
293
|
+
// NULL provider (pre-migration backfill missed somehow) coalesce to
|
|
294
|
+
// `claude` to match the migration default.
|
|
295
|
+
const providerRows = db.prepare(`
|
|
296
|
+
SELECT
|
|
297
|
+
COALESCE(provider, 'claude') AS provider,
|
|
298
|
+
COUNT(*) AS cnt,
|
|
299
|
+
COALESCE(SUM(CASE WHEN total_cost_usd_estimated = 0 THEN total_cost_usd ELSE 0 END), 0) AS authoritativeCost,
|
|
300
|
+
COALESCE(SUM(CASE WHEN total_cost_usd_estimated = 1 THEN total_cost_usd ELSE 0 END), 0) AS estimatedCost
|
|
301
|
+
FROM ai_invocations WHERE ${where.sql}
|
|
302
|
+
GROUP BY provider
|
|
303
|
+
ORDER BY (authoritativeCost + estimatedCost) DESC
|
|
304
|
+
`).all(...where.params);
|
|
305
|
+
const byProvider = providerRows.map((r) => ({
|
|
306
|
+
provider: r.provider,
|
|
307
|
+
count: r.cnt,
|
|
308
|
+
costUsd: r.authoritativeCost,
|
|
309
|
+
estimatedCostUsd: r.estimatedCost,
|
|
310
|
+
}));
|
|
311
|
+
return {
|
|
312
|
+
summary: {
|
|
313
|
+
totalCostUsd: summaryRow.totalCost,
|
|
314
|
+
totalEstimatedCostUsd: summaryRow.totalEstimatedCost,
|
|
315
|
+
totalTokens: summaryRow.totalTokens,
|
|
316
|
+
totalRuns: summaryRow.totalRuns,
|
|
317
|
+
failureRate: summaryRow.totalRuns > 0 ? (summaryRow.failed ?? 0) / summaryRow.totalRuns : 0,
|
|
318
|
+
prevTotalCostUsd: prevRow.totalCost,
|
|
319
|
+
deltaPct,
|
|
320
|
+
avgCostPerRun: summaryRow.avgCost,
|
|
321
|
+
},
|
|
322
|
+
bySurface,
|
|
323
|
+
byModel,
|
|
324
|
+
byMode,
|
|
325
|
+
byProvider,
|
|
326
|
+
dailyTimeline,
|
|
327
|
+
scatter,
|
|
328
|
+
topTickets,
|
|
329
|
+
trackingStartedAt: trackingRow.first,
|
|
330
|
+
rangeFrom: range.from,
|
|
331
|
+
rangeTo: range.to,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Produce a short identifying label from an Explore conversation's first
|
|
336
|
+
* user message. Strips leading slash-command lines and resolved-command
|
|
337
|
+
* frontmatter, takes the first non-empty line, and truncates to a few
|
|
338
|
+
* words so the analytics TICKET column stays readable.
|
|
339
|
+
*/
|
|
340
|
+
function summariseExplorePrompt(raw) {
|
|
341
|
+
if (!raw)
|
|
342
|
+
return null;
|
|
343
|
+
let text = raw;
|
|
344
|
+
// Strip the slash-command head (`/specrails:explore-spec ...` plus any
|
|
345
|
+
// trailing blank lines until the user's content).
|
|
346
|
+
text = text.replace(/^\/[^\n]*\n+/, '');
|
|
347
|
+
// Find the first non-frontmatter, non-empty, non-heading line.
|
|
348
|
+
const lines = text.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
349
|
+
let first = lines.find((l) => !l.startsWith('#') && !l.startsWith('---') && !l.startsWith('//') && !l.startsWith('>')) ?? lines[0] ?? '';
|
|
350
|
+
// Strip markdown emphasis / inline code so the chip reads clean.
|
|
351
|
+
first = first.replace(/[*_`]/g, '').trim();
|
|
352
|
+
if (!first)
|
|
353
|
+
return null;
|
|
354
|
+
const words = first.split(/\s+/).filter(Boolean);
|
|
355
|
+
const top = words.slice(0, 4).join(' ');
|
|
356
|
+
return words.length > 4 ? `${top}…` : top;
|
|
357
|
+
}
|
|
358
|
+
function getInvocations(db, projectId, filters = {}) {
|
|
359
|
+
const range = resolveRange(filters);
|
|
360
|
+
const where = buildWhere(projectId, filters, { from: range.from, to: range.to });
|
|
361
|
+
const totalRow = db.prepare(`
|
|
362
|
+
SELECT COUNT(*) AS total FROM ai_invocations WHERE ${where.sql}
|
|
363
|
+
`).get(...where.params);
|
|
364
|
+
const cap = filters.cap;
|
|
365
|
+
const limit = cap ?? Math.min(filters.limit ?? 50, 200);
|
|
366
|
+
const offset = filters.offset ?? 0;
|
|
367
|
+
const rows = db.prepare(`
|
|
368
|
+
SELECT * FROM ai_invocations WHERE ${where.sql}
|
|
369
|
+
ORDER BY started_at DESC LIMIT ? OFFSET ?
|
|
370
|
+
`).all(...where.params, limit, offset);
|
|
371
|
+
// For Explore rows (conversation_id non-null) without a committed ticket,
|
|
372
|
+
// surface the conversation title as the provisional ticket label so the
|
|
373
|
+
// analytics table is useful before commit.
|
|
374
|
+
const convIds = Array.from(new Set(rows.filter((r) => r.conversation_id).map((r) => r.conversation_id)));
|
|
375
|
+
const titleByConv = new Map();
|
|
376
|
+
if (convIds.length > 0) {
|
|
377
|
+
const placeholders = convIds.map(() => '?').join(',');
|
|
378
|
+
const titleRows = db.prepare(`SELECT id, title FROM chat_conversations WHERE id IN (${placeholders})`).all(...convIds);
|
|
379
|
+
for (const tr of titleRows)
|
|
380
|
+
titleByConv.set(tr.id, tr.title);
|
|
381
|
+
// Fallback: first user message for convs without a title yet (Explore
|
|
382
|
+
// lightweight mode never auto-titles unless saved as draft).
|
|
383
|
+
const missing = convIds.filter((id) => !titleByConv.get(id));
|
|
384
|
+
if (missing.length > 0) {
|
|
385
|
+
const p2 = missing.map(() => '?').join(',');
|
|
386
|
+
const msgRows = db.prepare(`SELECT conversation_id, content FROM chat_messages
|
|
387
|
+
WHERE role = 'user' AND conversation_id IN (${p2})
|
|
388
|
+
ORDER BY conversation_id, id ASC`).all(...missing);
|
|
389
|
+
const seen = new Set();
|
|
390
|
+
for (const mr of msgRows) {
|
|
391
|
+
if (seen.has(mr.conversation_id))
|
|
392
|
+
continue;
|
|
393
|
+
seen.add(mr.conversation_id);
|
|
394
|
+
const summary = summariseExplorePrompt(mr.content);
|
|
395
|
+
if (summary)
|
|
396
|
+
titleByConv.set(mr.conversation_id, summary);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const enriched = rows.map((r) => ({
|
|
401
|
+
...r,
|
|
402
|
+
ticket_title: r.conversation_id ? (titleByConv.get(r.conversation_id) ?? null) : null,
|
|
403
|
+
}));
|
|
404
|
+
return {
|
|
405
|
+
rows: enriched,
|
|
406
|
+
total: cap ? Math.min(rows.length, cap) : rows.length,
|
|
407
|
+
truncated: cap !== undefined && totalRow.total > cap,
|
|
408
|
+
totalAvailable: totalRow.total,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function parseSpendingFilters(query) {
|
|
412
|
+
const f = {};
|
|
413
|
+
if (typeof query.period === 'string')
|
|
414
|
+
f.period = query.period;
|
|
415
|
+
if (typeof query.from === 'string')
|
|
416
|
+
f.from = query.from;
|
|
417
|
+
if (typeof query.to === 'string')
|
|
418
|
+
f.to = query.to;
|
|
419
|
+
if (typeof query.surface === 'string') {
|
|
420
|
+
// M17: validate against the canonical surface list. The old hardcoded subset
|
|
421
|
+
// silently dropped 'smash'/'file-summary' — clicking those chips produced an
|
|
422
|
+
// empty filter array, so buildWhere applied NO surface condition and the UI
|
|
423
|
+
// showed ALL-surface totals while claiming a single-surface filter was active.
|
|
424
|
+
f.surface = query.surface.split(',').filter((s) => ALL_SURFACES.includes(s));
|
|
425
|
+
}
|
|
426
|
+
if (typeof query.model === 'string') {
|
|
427
|
+
f.model = query.model.split(',').filter((s) => s.length > 0);
|
|
428
|
+
}
|
|
429
|
+
if (typeof query.provider === 'string') {
|
|
430
|
+
const provs = query.provider.split(',').filter((s) => s.length > 0);
|
|
431
|
+
if (provs.length > 0)
|
|
432
|
+
f.provider = provs;
|
|
433
|
+
}
|
|
434
|
+
if (typeof query.status === 'string' && ['success', 'failed', 'aborted'].includes(query.status)) {
|
|
435
|
+
f.status = query.status;
|
|
436
|
+
}
|
|
437
|
+
if (typeof query.minCostUsd === 'string') {
|
|
438
|
+
const v = parseFloat(query.minCostUsd);
|
|
439
|
+
if (!Number.isNaN(v))
|
|
440
|
+
f.minCostUsd = v;
|
|
441
|
+
}
|
|
442
|
+
if (typeof query.ticketId === 'string') {
|
|
443
|
+
const v = parseInt(query.ticketId, 10);
|
|
444
|
+
if (!Number.isNaN(v))
|
|
445
|
+
f.ticketId = v;
|
|
446
|
+
}
|
|
447
|
+
return f;
|
|
448
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
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.runCompaction = runCompaction;
|
|
7
|
+
exports.runCompactionForAll = runCompactionForAll;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const zlib_1 = __importDefault(require("zlib"));
|
|
10
|
+
const db_1 = require("./db");
|
|
11
|
+
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
|
|
12
|
+
function readNdjsonGz(filePath) {
|
|
13
|
+
try {
|
|
14
|
+
const compressed = fs_1.default.readFileSync(filePath);
|
|
15
|
+
// Decompress all concatenated gzip members
|
|
16
|
+
const raw = zlib_1.default.gunzipSync(compressed).toString('utf-8');
|
|
17
|
+
const lines = [];
|
|
18
|
+
for (const line of raw.split('\n')) {
|
|
19
|
+
const trimmed = line.trim();
|
|
20
|
+
if (!trimmed)
|
|
21
|
+
continue;
|
|
22
|
+
try {
|
|
23
|
+
lines.push(JSON.parse(trimmed));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Skip malformed lines
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return lines;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function emptyAgg() {
|
|
36
|
+
return { durationMs: 0, tokensInput: 0, tokensOutput: 0, tokensCache: 0, toolCallCounts: {}, apiErrors: 0, costUsd: 0 };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Extract per-phase aggregates from telemetry lines.
|
|
40
|
+
* We use the `scope.name` or a best-effort fallback to group by phase.
|
|
41
|
+
* If no phase can be determined we bucket everything into "unknown".
|
|
42
|
+
*/
|
|
43
|
+
function aggregateByPhase(lines) {
|
|
44
|
+
const phases = new Map();
|
|
45
|
+
function getAgg(phase) {
|
|
46
|
+
let agg = phases.get(phase);
|
|
47
|
+
if (!agg) {
|
|
48
|
+
agg = emptyAgg();
|
|
49
|
+
phases.set(phase, agg);
|
|
50
|
+
}
|
|
51
|
+
return agg;
|
|
52
|
+
}
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
if (line.signal === 'control')
|
|
55
|
+
continue;
|
|
56
|
+
const payload = line.payload;
|
|
57
|
+
if (!payload)
|
|
58
|
+
continue;
|
|
59
|
+
// ── Traces: extract duration and tool call info ──────────────────────────
|
|
60
|
+
if (line.signal === 'traces') {
|
|
61
|
+
const resourceSpans = payload.resourceSpans;
|
|
62
|
+
for (const rs of resourceSpans ?? []) {
|
|
63
|
+
const scopeSpans = rs.scopeSpans;
|
|
64
|
+
for (const ss of scopeSpans ?? []) {
|
|
65
|
+
const scope = ss.scope;
|
|
66
|
+
const phase = (typeof scope?.name === 'string' ? scope.name : undefined) ?? 'unknown';
|
|
67
|
+
const agg = getAgg(phase);
|
|
68
|
+
const spans = ss.spans;
|
|
69
|
+
for (const span of spans ?? []) {
|
|
70
|
+
// Duration in nanoseconds → convert to ms
|
|
71
|
+
const startNs = typeof span.startTimeUnixNano === 'string' ? BigInt(span.startTimeUnixNano) : null;
|
|
72
|
+
const endNs = typeof span.endTimeUnixNano === 'string' ? BigInt(span.endTimeUnixNano) : null;
|
|
73
|
+
if (startNs && endNs && endNs > startNs) {
|
|
74
|
+
agg.durationMs += Number((endNs - startNs) / BigInt(1_000_000));
|
|
75
|
+
}
|
|
76
|
+
// Count tool calls by name
|
|
77
|
+
const name = typeof span.name === 'string' ? span.name : null;
|
|
78
|
+
if (name) {
|
|
79
|
+
agg.toolCallCounts[name] = (agg.toolCallCounts[name] ?? 0) + 1;
|
|
80
|
+
}
|
|
81
|
+
// API errors: status code != 0 in OTEL spans
|
|
82
|
+
const status = span.status;
|
|
83
|
+
if (status && status.code !== 0 && status.code !== 'STATUS_CODE_OK') {
|
|
84
|
+
agg.apiErrors++;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// ── Metrics: extract token counts and cost ────────────────────────────────
|
|
91
|
+
if (line.signal === 'metrics') {
|
|
92
|
+
const resourceMetrics = payload.resourceMetrics;
|
|
93
|
+
for (const rm of resourceMetrics ?? []) {
|
|
94
|
+
const scopeMetrics = rm.scopeMetrics;
|
|
95
|
+
for (const sm of scopeMetrics ?? []) {
|
|
96
|
+
const scope = sm.scope;
|
|
97
|
+
const phase = (typeof scope?.name === 'string' ? scope.name : undefined) ?? 'unknown';
|
|
98
|
+
const agg = getAgg(phase);
|
|
99
|
+
const metrics = sm.metrics;
|
|
100
|
+
for (const metric of metrics ?? []) {
|
|
101
|
+
const metricName = typeof metric.name === 'string' ? metric.name : '';
|
|
102
|
+
const sum = metric.sum;
|
|
103
|
+
const dataPoints = sum?.dataPoints;
|
|
104
|
+
for (const dp of dataPoints ?? []) {
|
|
105
|
+
const value = typeof dp.asInt === 'string' ? parseInt(dp.asInt, 10)
|
|
106
|
+
: typeof dp.asDouble === 'number' ? dp.asDouble : 0;
|
|
107
|
+
if (metricName.includes('input_tokens') || metricName.includes('tokens_in')) {
|
|
108
|
+
agg.tokensInput += value;
|
|
109
|
+
}
|
|
110
|
+
else if (metricName.includes('output_tokens') || metricName.includes('tokens_out')) {
|
|
111
|
+
agg.tokensOutput += value;
|
|
112
|
+
}
|
|
113
|
+
else if (metricName.includes('cache_tokens') || metricName.includes('tokens_cache')) {
|
|
114
|
+
agg.tokensCache += value;
|
|
115
|
+
}
|
|
116
|
+
else if (metricName.includes('cost_usd') || metricName.includes('total_cost')) {
|
|
117
|
+
agg.costUsd += value;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Default to 'unknown' phase if nothing was bucketed
|
|
126
|
+
if (phases.size === 0)
|
|
127
|
+
phases.set('unknown', emptyAgg());
|
|
128
|
+
return phases;
|
|
129
|
+
}
|
|
130
|
+
// ─── Core compaction ──────────────────────────────────────────────────────────
|
|
131
|
+
async function runCompaction(ctx, now = Date.now()) {
|
|
132
|
+
const { db } = ctx;
|
|
133
|
+
const blobs = (0, db_1.listActiveTelemetryBlobs)(db);
|
|
134
|
+
for (const blob of blobs) {
|
|
135
|
+
const age = blob.endedAt ?? blob.startedAt;
|
|
136
|
+
if (!age)
|
|
137
|
+
continue;
|
|
138
|
+
if (now - age < SEVEN_DAYS_MS)
|
|
139
|
+
continue;
|
|
140
|
+
// Blob is older than 7 days — compact it
|
|
141
|
+
const lines = blob.path ? readNdjsonGz(blob.path) : [];
|
|
142
|
+
const phaseAggs = aggregateByPhase(lines);
|
|
143
|
+
for (const [phase, agg] of phaseAggs) {
|
|
144
|
+
(0, db_1.insertTelemetrySummary)(db, {
|
|
145
|
+
jobId: blob.jobId,
|
|
146
|
+
phase,
|
|
147
|
+
durationMs: agg.durationMs || null,
|
|
148
|
+
tokensInput: agg.tokensInput || null,
|
|
149
|
+
tokensOutput: agg.tokensOutput || null,
|
|
150
|
+
tokensCache: agg.tokensCache || null,
|
|
151
|
+
toolCalls: Object.keys(agg.toolCallCounts).length > 0
|
|
152
|
+
? JSON.stringify(agg.toolCallCounts)
|
|
153
|
+
: null,
|
|
154
|
+
apiErrors: agg.apiErrors || null,
|
|
155
|
+
costUsd: agg.costUsd || null,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// Delete the blob file
|
|
159
|
+
if (blob.path) {
|
|
160
|
+
try {
|
|
161
|
+
fs_1.default.unlinkSync(blob.path);
|
|
162
|
+
}
|
|
163
|
+
catch { /* already gone */ }
|
|
164
|
+
}
|
|
165
|
+
// Mark the pointer row as compacted
|
|
166
|
+
(0, db_1.setTelemetryBlobCompacted)(db, blob.jobId);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** Run compaction for every loaded project. Called at server startup. */
|
|
170
|
+
async function runCompactionForAll(registry) {
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
for (const ctx of registry.listContexts()) {
|
|
173
|
+
try {
|
|
174
|
+
await runCompaction(ctx, now);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
console.error(`[telemetry-compactor] compaction failed for project ${ctx.project.id}:`, err);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|