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,663 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SPECs SMASH — Runner
|
|
4
|
+
*
|
|
5
|
+
* Standalone orchestrator for a single SMASH turn. Spawns a fresh `claude`
|
|
6
|
+
* with the byte-stable SMASH system prompt, parses the JSON response, then
|
|
7
|
+
* atomically flips the parent ticket to `is_epic` and inserts N child
|
|
8
|
+
* tickets inside a single `mutateStore` callback.
|
|
9
|
+
*
|
|
10
|
+
* See openspec/changes/add-specs-smash.
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.checkSmashEligibility = checkSmashEligibility;
|
|
47
|
+
exports.prepareSmashSpawn = prepareSmashSpawn;
|
|
48
|
+
exports.readSmashChildOutput = readSmashChildOutput;
|
|
49
|
+
exports.composeChildDescription = composeChildDescription;
|
|
50
|
+
exports.applySmashToStore = applySmashToStore;
|
|
51
|
+
exports.applySmashUndo = applySmashUndo;
|
|
52
|
+
exports.applyDeleteEpicChildren = applyDeleteEpicChildren;
|
|
53
|
+
exports.runSmash = runSmash;
|
|
54
|
+
exports.runSmashUndo = runSmashUndo;
|
|
55
|
+
const node_crypto_1 = require("node:crypto");
|
|
56
|
+
const node_readline_1 = require("node:readline");
|
|
57
|
+
const cli_prompt_1 = require("./util/cli-prompt");
|
|
58
|
+
const explore_smash_1 = require("./explore-smash");
|
|
59
|
+
const explore_cwd_manager_1 = require("./explore-cwd-manager");
|
|
60
|
+
const ai_invocations_1 = require("./ai-invocations");
|
|
61
|
+
const result_event_1 = require("./result-event");
|
|
62
|
+
const ticket_store_1 = require("./ticket-store");
|
|
63
|
+
const ticket_store_2 = require("./ticket-store");
|
|
64
|
+
const SMASH_TIMEOUT_MS_SIMPLE = 60_000;
|
|
65
|
+
const SMASH_TIMEOUT_MS_FULL = 900_000; // 15 min — super-spec mode, no rush
|
|
66
|
+
const SMASH_MAX_TURNS_SIMPLE = 1;
|
|
67
|
+
const SMASH_MAX_TURNS_FULL = 30; // plenty of room for codebase exploration
|
|
68
|
+
// ─── Pre-flight gating ───────────────────────────────────────────────────────
|
|
69
|
+
const CONTRACT_LAYER_MARKER = '## Contract Layer';
|
|
70
|
+
/**
|
|
71
|
+
* Check whether the ticket is eligible to be SMASHed. Pure function for ease
|
|
72
|
+
* of testing — does not mutate state, does not depend on env.
|
|
73
|
+
*/
|
|
74
|
+
function checkSmashEligibility(store, ticketId) {
|
|
75
|
+
const ticket = store.tickets[String(ticketId)];
|
|
76
|
+
if (!ticket)
|
|
77
|
+
return { ok: false, reason: 'ticket-not-found' };
|
|
78
|
+
if (ticket.status === 'draft')
|
|
79
|
+
return { ok: false, reason: 'is-draft' };
|
|
80
|
+
if (ticket.parent_epic_id !== null)
|
|
81
|
+
return { ok: false, reason: 'is-child' };
|
|
82
|
+
if (!ticket.description.includes(CONTRACT_LAYER_MARKER)) {
|
|
83
|
+
return { ok: false, reason: 'no-contract-layer' };
|
|
84
|
+
}
|
|
85
|
+
// Already-épica with children → must delete first.
|
|
86
|
+
let childCount = 0;
|
|
87
|
+
for (const id of Object.keys(store.tickets)) {
|
|
88
|
+
if (store.tickets[id].parent_epic_id === ticketId)
|
|
89
|
+
childCount += 1;
|
|
90
|
+
}
|
|
91
|
+
if (ticket.is_epic && childCount > 0)
|
|
92
|
+
return { ok: false, reason: 'has-children' };
|
|
93
|
+
return { ok: true, context: { ticket, childCount } };
|
|
94
|
+
}
|
|
95
|
+
// ─── Spawn helpers ───────────────────────────────────────────────────────────
|
|
96
|
+
function buildSmashArgs(model, systemPrompt, userPrompt, mode) {
|
|
97
|
+
// Simple: deny all tools, single turn — fast, no codebase access.
|
|
98
|
+
// Full: allow Read/Grep/Glob (read-only), multi-turn — slower, grounded.
|
|
99
|
+
const disallowed = mode === 'full' ? 'Bash,Edit,Write,NotebookEdit' : 'Read,Grep,Glob,Bash';
|
|
100
|
+
const maxTurns = mode === 'full' ? SMASH_MAX_TURNS_FULL : SMASH_MAX_TURNS_SIMPLE;
|
|
101
|
+
return [
|
|
102
|
+
'--model', model,
|
|
103
|
+
'--dangerously-skip-permissions',
|
|
104
|
+
'--output-format', 'stream-json',
|
|
105
|
+
'--verbose',
|
|
106
|
+
'--system-prompt', systemPrompt,
|
|
107
|
+
'--disallowedTools', disallowed,
|
|
108
|
+
'--max-turns', String(maxTurns),
|
|
109
|
+
'-p', userPrompt,
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Build the spawn argv + cwd. Exported for tests.
|
|
114
|
+
*/
|
|
115
|
+
function prepareSmashSpawn(deps, ticket) {
|
|
116
|
+
const mode = deps.mode === 'full' ? 'full' : 'simple';
|
|
117
|
+
const systemPrompt = (0, explore_smash_1.buildSmashSystemPrompt)(mode);
|
|
118
|
+
const userPrompt = `${ticket.title}\n\n${ticket.description}`;
|
|
119
|
+
let cwd;
|
|
120
|
+
// Full mode needs access to the project tree (so Read/Grep/Glob hit the
|
|
121
|
+
// real repo). Simple mode keeps the app-managed dir for a clean scope.
|
|
122
|
+
if (mode === 'full') {
|
|
123
|
+
cwd = deps.projectPath;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
try {
|
|
127
|
+
cwd = (0, explore_cwd_manager_1.ensureExploreCwd)({
|
|
128
|
+
slug: deps.projectSlug,
|
|
129
|
+
projectPath: deps.projectPath,
|
|
130
|
+
projectName: deps.projectName,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
cwd = deps.projectPath;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const model = deps.model ?? 'sonnet';
|
|
138
|
+
return { args: buildSmashArgs(model, systemPrompt, userPrompt, mode), cwd, systemPrompt, userPrompt, mode };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Read stream-json output from a child process. Exported for tests.
|
|
142
|
+
*/
|
|
143
|
+
function readSmashChildOutput(child, timeoutMs) {
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
let fullText = '';
|
|
146
|
+
let resultEvent = null;
|
|
147
|
+
let timedOut = false;
|
|
148
|
+
let settled = false;
|
|
149
|
+
if (!child.stdout) {
|
|
150
|
+
resolve({ fullText, resultEvent, code: -1, timedOut: false });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const reader = (0, node_readline_1.createInterface)({ input: child.stdout, crlfDelay: Infinity });
|
|
154
|
+
reader.on('line', (line) => {
|
|
155
|
+
let parsed = null;
|
|
156
|
+
try {
|
|
157
|
+
parsed = JSON.parse(line);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (!parsed)
|
|
163
|
+
return;
|
|
164
|
+
const type = parsed.type;
|
|
165
|
+
if (type === 'result') {
|
|
166
|
+
resultEvent = parsed;
|
|
167
|
+
}
|
|
168
|
+
else if (type === 'assistant') {
|
|
169
|
+
const message = parsed.message;
|
|
170
|
+
const blocks = message?.content ?? [];
|
|
171
|
+
for (const b of blocks) {
|
|
172
|
+
if (b.type === 'text' && typeof b.text === 'string')
|
|
173
|
+
fullText += b.text;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
const timer = setTimeout(() => {
|
|
178
|
+
timedOut = true;
|
|
179
|
+
try {
|
|
180
|
+
child.kill('SIGTERM');
|
|
181
|
+
}
|
|
182
|
+
catch { /* best effort */ }
|
|
183
|
+
if (!settled) {
|
|
184
|
+
settled = true;
|
|
185
|
+
resolve({ fullText, resultEvent, code: null, timedOut: true });
|
|
186
|
+
}
|
|
187
|
+
}, timeoutMs);
|
|
188
|
+
child.on('close', (code) => {
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
if (settled)
|
|
191
|
+
return;
|
|
192
|
+
settled = true;
|
|
193
|
+
resolve({ fullText, resultEvent, code, timedOut });
|
|
194
|
+
});
|
|
195
|
+
child.on('error', () => {
|
|
196
|
+
clearTimeout(timer);
|
|
197
|
+
if (settled)
|
|
198
|
+
return;
|
|
199
|
+
settled = true;
|
|
200
|
+
resolve({ fullText, resultEvent, code: -1, timedOut });
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Compose the final markdown description persisted for a Sub-Spec.
|
|
206
|
+
* Appends "Why this Sub-Spec" (rationale) and "Acceptance Criteria" sections
|
|
207
|
+
* when those fields are populated (full mode). Simple mode leaves the agent's
|
|
208
|
+
* description as-is.
|
|
209
|
+
*/
|
|
210
|
+
function composeChildDescription(child) {
|
|
211
|
+
const parts = [child.description.trim()];
|
|
212
|
+
if (child.rationale && child.rationale.trim().length > 0) {
|
|
213
|
+
parts.push('', '## Why this Sub-Spec', '', child.rationale.trim());
|
|
214
|
+
}
|
|
215
|
+
if (child.acceptanceCriteria && child.acceptanceCriteria.length > 0) {
|
|
216
|
+
parts.push('', '## Acceptance Criteria', '');
|
|
217
|
+
for (const ac of child.acceptanceCriteria) {
|
|
218
|
+
parts.push(`- ${ac}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return parts.join('\n');
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Inside a single `mutateStore` callback, flip the parent to épica and
|
|
225
|
+
* insert N child tickets. Returns the updated épica plus the inserted
|
|
226
|
+
* children. Throws if the ticket disappears between the gate check and
|
|
227
|
+
* the lock (rare race).
|
|
228
|
+
*/
|
|
229
|
+
function applySmashToStore(filePath, epicId, children, nowIso, createdBy) {
|
|
230
|
+
let epic = null;
|
|
231
|
+
const inserted = [];
|
|
232
|
+
(0, ticket_store_2.mutateStore)(filePath, (s) => {
|
|
233
|
+
const target = s.tickets[String(epicId)];
|
|
234
|
+
if (!target)
|
|
235
|
+
throw new Error(`épica ${epicId} not found inside mutation`);
|
|
236
|
+
target.is_epic = true;
|
|
237
|
+
// Stash the pre-SMASH status so undo can restore it. Only stash on first
|
|
238
|
+
// SMASH (re-SMASH already done) — if already done, leave the original
|
|
239
|
+
// stash in place.
|
|
240
|
+
if (!target.metadata)
|
|
241
|
+
target.metadata = {};
|
|
242
|
+
const md = target.metadata;
|
|
243
|
+
if (!md.pre_smash_status) {
|
|
244
|
+
md.pre_smash_status = target.status;
|
|
245
|
+
}
|
|
246
|
+
// Mark the épica as done so it leaves the active backlog and only the
|
|
247
|
+
// Sub-Specs remain as actionable work.
|
|
248
|
+
target.status = 'done';
|
|
249
|
+
target.updated_at = nowIso;
|
|
250
|
+
epic = target;
|
|
251
|
+
for (const child of children) {
|
|
252
|
+
const id = s.next_id++;
|
|
253
|
+
const description = composeChildDescription(child);
|
|
254
|
+
const ticket = {
|
|
255
|
+
id,
|
|
256
|
+
title: child.title,
|
|
257
|
+
description,
|
|
258
|
+
status: 'todo',
|
|
259
|
+
priority: child.priority,
|
|
260
|
+
labels: [],
|
|
261
|
+
assignee: null,
|
|
262
|
+
prerequisites: [],
|
|
263
|
+
metadata: {},
|
|
264
|
+
comments: [],
|
|
265
|
+
attachments: [],
|
|
266
|
+
origin_conversation_id: null,
|
|
267
|
+
is_epic: false,
|
|
268
|
+
parent_epic_id: epicId,
|
|
269
|
+
execution_order: child.executionOrder,
|
|
270
|
+
short_summary: (0, ticket_store_1.clampShortSummary)(child.shortSummary),
|
|
271
|
+
created_at: nowIso,
|
|
272
|
+
updated_at: nowIso,
|
|
273
|
+
created_by: createdBy,
|
|
274
|
+
source: 'specs-smash',
|
|
275
|
+
};
|
|
276
|
+
s.tickets[String(id)] = ticket;
|
|
277
|
+
inserted.push(ticket);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
if (!epic)
|
|
281
|
+
throw new Error('mutation did not yield épica');
|
|
282
|
+
return { epic, children: inserted, smashedAt: nowIso };
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Undo a SMASH: clear `is_epic` on the parent and delete every child whose
|
|
286
|
+
* `parent_epic_id` matches AND whose `created_at >= smashedAt`. Returns the
|
|
287
|
+
* ids of the deleted children plus the updated épica (or null when no épica
|
|
288
|
+
* exists / no longer flagged).
|
|
289
|
+
*/
|
|
290
|
+
function applySmashUndo(filePath, epicId, smashedAt, nowIso) {
|
|
291
|
+
let epic = null;
|
|
292
|
+
const deletedChildren = [];
|
|
293
|
+
(0, ticket_store_2.mutateStore)(filePath, (s) => {
|
|
294
|
+
const target = s.tickets[String(epicId)];
|
|
295
|
+
if (!target)
|
|
296
|
+
return;
|
|
297
|
+
if (!target.is_epic)
|
|
298
|
+
return;
|
|
299
|
+
for (const idStr of Object.keys(s.tickets)) {
|
|
300
|
+
const t = s.tickets[idStr];
|
|
301
|
+
if (t.parent_epic_id === epicId && t.created_at >= smashedAt) {
|
|
302
|
+
deletedChildren.push(t.id);
|
|
303
|
+
delete s.tickets[idStr];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
target.is_epic = false;
|
|
307
|
+
// Restore the pre-SMASH status from metadata if available.
|
|
308
|
+
// B61: 'done' was missing from this whitelist, so undoing a SMASH on a ticket
|
|
309
|
+
// that was 'done' before silently demoted it to 'todo'. Include every valid
|
|
310
|
+
// non-epic status.
|
|
311
|
+
const md = (target.metadata ?? {});
|
|
312
|
+
if (md.pre_smash_status === 'todo' || md.pre_smash_status === 'in_progress'
|
|
313
|
+
|| md.pre_smash_status === 'cancelled' || md.pre_smash_status === 'draft'
|
|
314
|
+
|| md.pre_smash_status === 'done') {
|
|
315
|
+
target.status = md.pre_smash_status;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
target.status = 'todo';
|
|
319
|
+
}
|
|
320
|
+
delete md.pre_smash_status;
|
|
321
|
+
target.updated_at = nowIso;
|
|
322
|
+
epic = target;
|
|
323
|
+
});
|
|
324
|
+
return { epic, deletedChildren };
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Delete all children of an épica without undoing the épica flip itself.
|
|
328
|
+
* Used by the Re-SMASH flow.
|
|
329
|
+
*/
|
|
330
|
+
function applyDeleteEpicChildren(filePath, epicId) {
|
|
331
|
+
const deletedChildren = [];
|
|
332
|
+
const store = (0, ticket_store_2.mutateStore)(filePath, (s) => {
|
|
333
|
+
for (const idStr of Object.keys(s.tickets)) {
|
|
334
|
+
const t = s.tickets[idStr];
|
|
335
|
+
if (t.parent_epic_id === epicId) {
|
|
336
|
+
deletedChildren.push(t.id);
|
|
337
|
+
delete s.tickets[idStr];
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
return { deletedChildren, revision: store.revision };
|
|
342
|
+
}
|
|
343
|
+
// ─── recordInvocation wrapper ────────────────────────────────────────────────
|
|
344
|
+
function recordSafely(deps, ticketId, runId, startedAt, finishedAt, status, resultEvent, model) {
|
|
345
|
+
try {
|
|
346
|
+
const normalised = resultEvent ? (0, result_event_1.normaliseResultEvent)(resultEvent, 'claude') : {};
|
|
347
|
+
(0, ai_invocations_1.recordInvocation)(deps.db, {
|
|
348
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
349
|
+
project_id: deps.projectId,
|
|
350
|
+
provider: 'claude',
|
|
351
|
+
surface: 'smash',
|
|
352
|
+
surface_ref_id: `smash:${runId}`,
|
|
353
|
+
conversation_id: null,
|
|
354
|
+
ticket_id: ticketId,
|
|
355
|
+
status,
|
|
356
|
+
started_at: startedAt,
|
|
357
|
+
finished_at: finishedAt,
|
|
358
|
+
...normalised,
|
|
359
|
+
model: resultEvent?.model ?? model ?? undefined,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
catch (err) {
|
|
363
|
+
console.error('[smash-runner] recordInvocation failed:', err);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* On a successful SMASH, attribute the spawn cost/tokens proportionally to
|
|
368
|
+
* each Sub-Spec so the per-child spending line is populated immediately.
|
|
369
|
+
* Cost / tokens / duration are split evenly across N children; num_turns is
|
|
370
|
+
* floor(turns/N) per row (small loss of precision, acceptable).
|
|
371
|
+
*/
|
|
372
|
+
function recordChildrenInvocations(deps, childrenIds, runId, startedAt, finishedAt, resultEvent, model) {
|
|
373
|
+
if (childrenIds.length === 0 || !resultEvent)
|
|
374
|
+
return;
|
|
375
|
+
try {
|
|
376
|
+
const normalised = (0, result_event_1.normaliseResultEvent)(resultEvent, 'claude');
|
|
377
|
+
const n = childrenIds.length;
|
|
378
|
+
const split = (v) => {
|
|
379
|
+
if (v === null || v === undefined)
|
|
380
|
+
return undefined;
|
|
381
|
+
return v / n;
|
|
382
|
+
};
|
|
383
|
+
const splitInt = (v) => {
|
|
384
|
+
if (v === null || v === undefined)
|
|
385
|
+
return undefined;
|
|
386
|
+
return Math.floor(v / n);
|
|
387
|
+
};
|
|
388
|
+
for (const childId of childrenIds) {
|
|
389
|
+
(0, ai_invocations_1.recordInvocation)(deps.db, {
|
|
390
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
391
|
+
project_id: deps.projectId,
|
|
392
|
+
provider: 'claude',
|
|
393
|
+
surface: 'smash',
|
|
394
|
+
surface_ref_id: `smash:${runId}:child:${childId}`,
|
|
395
|
+
conversation_id: null,
|
|
396
|
+
ticket_id: childId,
|
|
397
|
+
status: 'success',
|
|
398
|
+
started_at: startedAt,
|
|
399
|
+
finished_at: finishedAt,
|
|
400
|
+
duration_ms: split(normalised.duration_ms),
|
|
401
|
+
duration_api_ms: split(normalised.duration_api_ms),
|
|
402
|
+
tokens_in: splitInt(normalised.tokens_in),
|
|
403
|
+
tokens_out: splitInt(normalised.tokens_out),
|
|
404
|
+
tokens_cache_read: splitInt(normalised.tokens_cache_read),
|
|
405
|
+
tokens_cache_create: splitInt(normalised.tokens_cache_create),
|
|
406
|
+
total_cost_usd: split(normalised.total_cost_usd),
|
|
407
|
+
num_turns: splitInt(normalised.num_turns),
|
|
408
|
+
session_id: normalised.session_id,
|
|
409
|
+
model: resultEvent.model ?? model ?? undefined,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
console.error('[smash-runner] recordChildrenInvocations failed:', err);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// ─── Public runner ───────────────────────────────────────────────────────────
|
|
418
|
+
/**
|
|
419
|
+
* Fire a SMASH attempt for a single ticket.
|
|
420
|
+
*/
|
|
421
|
+
// B60: in-process guard against concurrent SMASH of the same ticket. Eligibility
|
|
422
|
+
// is checked pre-spawn (not under the store lock), so two near-simultaneous
|
|
423
|
+
// requests would both pass the gate and each spawn a child set → duplicates.
|
|
424
|
+
// Keyed by `${projectId}:${ticketId}` so distinct projects don't collide.
|
|
425
|
+
const _smashInFlight = new Set();
|
|
426
|
+
async function runSmash(deps, ticketId) {
|
|
427
|
+
const now = deps.now ?? (() => new Date());
|
|
428
|
+
const mode = deps.mode === 'full' ? 'full' : 'simple';
|
|
429
|
+
const defaultTimeout = mode === 'full' ? SMASH_TIMEOUT_MS_FULL : SMASH_TIMEOUT_MS_SIMPLE;
|
|
430
|
+
const timeoutMs = deps.timeoutMs ?? defaultTimeout;
|
|
431
|
+
const spawn = deps.spawn ?? cli_prompt_1.spawnAiCli;
|
|
432
|
+
const runId = (0, node_crypto_1.randomUUID)();
|
|
433
|
+
console.log(`[smash-runner] entry ticket=${ticketId} run=${runId}`);
|
|
434
|
+
if ((0, explore_smash_1.isSpecsSmashKillSwitchActive)()) {
|
|
435
|
+
return { ok: false, reason: 'disabled', ticketId, runId };
|
|
436
|
+
}
|
|
437
|
+
// B60: reject a concurrent SMASH of the same ticket. The has-check + add are
|
|
438
|
+
// synchronous (no await between them), so they close the TOCTOU window the
|
|
439
|
+
// pre-flight eligibility check leaves open. Released in the finally below.
|
|
440
|
+
const inFlightKey = `${deps.projectId}:${ticketId}`;
|
|
441
|
+
if (_smashInFlight.has(inFlightKey)) {
|
|
442
|
+
return { ok: false, reason: 'in-progress', ticketId, runId };
|
|
443
|
+
}
|
|
444
|
+
_smashInFlight.add(inFlightKey);
|
|
445
|
+
try {
|
|
446
|
+
// Pre-flight: read store and check eligibility.
|
|
447
|
+
const filePath = (0, ticket_store_2.resolveTicketStoragePath)(deps.projectPath);
|
|
448
|
+
let ticket;
|
|
449
|
+
try {
|
|
450
|
+
const { readStore } = await Promise.resolve().then(() => __importStar(require('./ticket-store')));
|
|
451
|
+
const store = readStore(filePath);
|
|
452
|
+
const gate = checkSmashEligibility(store, ticketId);
|
|
453
|
+
if (!gate.ok) {
|
|
454
|
+
return { ok: false, reason: gate.reason, ticketId, runId };
|
|
455
|
+
}
|
|
456
|
+
ticket = gate.context.ticket;
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
console.error('[smash-runner] pre-flight read failed:', err);
|
|
460
|
+
return { ok: false, reason: 'crashed', ticketId, runId };
|
|
461
|
+
}
|
|
462
|
+
// Broadcast start.
|
|
463
|
+
const startedAt = now().toISOString();
|
|
464
|
+
deps.broadcast({
|
|
465
|
+
type: 'smash.started',
|
|
466
|
+
projectId: deps.projectId,
|
|
467
|
+
ticketId,
|
|
468
|
+
runId,
|
|
469
|
+
ticketTitle: ticket.title,
|
|
470
|
+
timestamp: startedAt,
|
|
471
|
+
});
|
|
472
|
+
const { args, cwd } = prepareSmashSpawn({
|
|
473
|
+
projectSlug: deps.projectSlug,
|
|
474
|
+
projectPath: deps.projectPath,
|
|
475
|
+
projectName: deps.projectName,
|
|
476
|
+
model: deps.model,
|
|
477
|
+
}, ticket);
|
|
478
|
+
let child;
|
|
479
|
+
try {
|
|
480
|
+
child = spawn('claude', args, {
|
|
481
|
+
env: process.env,
|
|
482
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
483
|
+
cwd,
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
catch (err) {
|
|
487
|
+
const finishedAt = now().toISOString();
|
|
488
|
+
recordSafely(deps, ticketId, runId, startedAt, finishedAt, 'failed', null, deps.model);
|
|
489
|
+
deps.broadcast({
|
|
490
|
+
type: 'smash.failed',
|
|
491
|
+
projectId: deps.projectId,
|
|
492
|
+
ticketId,
|
|
493
|
+
runId,
|
|
494
|
+
reason: 'crashed',
|
|
495
|
+
timestamp: finishedAt,
|
|
496
|
+
});
|
|
497
|
+
return { ok: false, reason: 'crashed', ticketId, runId };
|
|
498
|
+
}
|
|
499
|
+
// Synthesize progress pills as the spawn proceeds. The runner uses fixed
|
|
500
|
+
// offsets — cheap and deterministic vs trying to read tool-use events.
|
|
501
|
+
const progressTimers = [];
|
|
502
|
+
const stages = ['analyzing', 'identifying', 'ordering'];
|
|
503
|
+
stages.forEach((stage, i) => {
|
|
504
|
+
progressTimers.push(setTimeout(() => {
|
|
505
|
+
deps.broadcast({
|
|
506
|
+
type: 'smash.progress',
|
|
507
|
+
projectId: deps.projectId,
|
|
508
|
+
ticketId,
|
|
509
|
+
runId,
|
|
510
|
+
stage,
|
|
511
|
+
timestamp: now().toISOString(),
|
|
512
|
+
});
|
|
513
|
+
}, 200 + i * 1500));
|
|
514
|
+
});
|
|
515
|
+
const result = await readSmashChildOutput(child, timeoutMs);
|
|
516
|
+
progressTimers.forEach((t) => clearTimeout(t));
|
|
517
|
+
const finishedAt = now().toISOString();
|
|
518
|
+
console.log(`[smash-runner] spawn done code=${result.code} timedOut=${result.timedOut} hasResult=${!!result.resultEvent} textBytes=${result.fullText.length}`);
|
|
519
|
+
if (result.timedOut) {
|
|
520
|
+
recordSafely(deps, ticketId, runId, startedAt, finishedAt, 'aborted', result.resultEvent, deps.model);
|
|
521
|
+
deps.broadcast({
|
|
522
|
+
type: 'smash.failed',
|
|
523
|
+
projectId: deps.projectId,
|
|
524
|
+
ticketId,
|
|
525
|
+
runId,
|
|
526
|
+
reason: 'timeout',
|
|
527
|
+
timestamp: finishedAt,
|
|
528
|
+
});
|
|
529
|
+
return { ok: false, reason: 'timeout', ticketId, runId };
|
|
530
|
+
}
|
|
531
|
+
if (result.code !== 0 || !result.resultEvent) {
|
|
532
|
+
recordSafely(deps, ticketId, runId, startedAt, finishedAt, 'failed', result.resultEvent, deps.model);
|
|
533
|
+
const reason = result.resultEvent ? 'model_error' : 'crashed';
|
|
534
|
+
deps.broadcast({
|
|
535
|
+
type: 'smash.failed',
|
|
536
|
+
projectId: deps.projectId,
|
|
537
|
+
ticketId,
|
|
538
|
+
runId,
|
|
539
|
+
reason,
|
|
540
|
+
timestamp: finishedAt,
|
|
541
|
+
});
|
|
542
|
+
return { ok: false, reason, ticketId, runId };
|
|
543
|
+
}
|
|
544
|
+
const parse = (0, explore_smash_1.parseSmashOutput)(result.fullText);
|
|
545
|
+
if (!parse.ok) {
|
|
546
|
+
console.log(`[smash-runner] parse failed reason=${parse.reason} detail=${parse.detail ?? '-'}`);
|
|
547
|
+
recordSafely(deps, ticketId, runId, startedAt, finishedAt, 'failed', result.resultEvent, deps.model);
|
|
548
|
+
deps.broadcast({
|
|
549
|
+
type: 'smash.failed',
|
|
550
|
+
projectId: deps.projectId,
|
|
551
|
+
ticketId,
|
|
552
|
+
runId,
|
|
553
|
+
reason: 'invalid-output',
|
|
554
|
+
detail: parse.reason,
|
|
555
|
+
timestamp: finishedAt,
|
|
556
|
+
});
|
|
557
|
+
return { ok: false, reason: 'invalid-output', ticketId, runId };
|
|
558
|
+
}
|
|
559
|
+
// Atomic flip + insert.
|
|
560
|
+
let applied;
|
|
561
|
+
try {
|
|
562
|
+
applied = applySmashToStore(filePath, ticketId, parse.value.children, finishedAt, 'sr-specs-smash');
|
|
563
|
+
}
|
|
564
|
+
catch (err) {
|
|
565
|
+
console.error('[smash-runner] mutation failed:', err);
|
|
566
|
+
recordSafely(deps, ticketId, runId, startedAt, finishedAt, 'failed', result.resultEvent, deps.model);
|
|
567
|
+
deps.broadcast({
|
|
568
|
+
type: 'smash.failed',
|
|
569
|
+
projectId: deps.projectId,
|
|
570
|
+
ticketId,
|
|
571
|
+
runId,
|
|
572
|
+
reason: 'mutation-failed',
|
|
573
|
+
timestamp: finishedAt,
|
|
574
|
+
});
|
|
575
|
+
return { ok: false, reason: 'mutation-failed', ticketId, runId };
|
|
576
|
+
}
|
|
577
|
+
// On a successful SMASH, attribute the spawn cost ONLY to the children
|
|
578
|
+
// (cost split evenly). The Epic itself does not accrue cost here — the
|
|
579
|
+
// operation logically birthed the Sub-Specs and the cost belongs to them.
|
|
580
|
+
// Total project cost remains the original spawn cost (sum of N × X/N = X).
|
|
581
|
+
recordChildrenInvocations(deps, applied.children.map((c) => c.id), runId, startedAt, finishedAt, result.resultEvent, deps.model);
|
|
582
|
+
// Broadcast underlying state changes first, then the SMASH-specific event.
|
|
583
|
+
deps.broadcast({
|
|
584
|
+
type: 'ticket_updated',
|
|
585
|
+
ticket: applied.epic,
|
|
586
|
+
projectId: deps.projectId,
|
|
587
|
+
timestamp: finishedAt,
|
|
588
|
+
});
|
|
589
|
+
for (const child of applied.children) {
|
|
590
|
+
deps.broadcast({
|
|
591
|
+
type: 'ticket_created',
|
|
592
|
+
ticket: child,
|
|
593
|
+
projectId: deps.projectId,
|
|
594
|
+
timestamp: finishedAt,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
deps.broadcast({
|
|
598
|
+
type: 'smash.completed',
|
|
599
|
+
projectId: deps.projectId,
|
|
600
|
+
ticketId,
|
|
601
|
+
runId,
|
|
602
|
+
smashedAt: finishedAt,
|
|
603
|
+
childrenIds: applied.children.map((c) => c.id),
|
|
604
|
+
timestamp: finishedAt,
|
|
605
|
+
});
|
|
606
|
+
deps.broadcast({ type: 'spending.invalidated', projectId: deps.projectId });
|
|
607
|
+
return {
|
|
608
|
+
ok: true,
|
|
609
|
+
ticketId,
|
|
610
|
+
runId,
|
|
611
|
+
childrenIds: applied.children.map((c) => c.id),
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
finally {
|
|
615
|
+
// B60: always release the in-flight guard (success, failure, or throw).
|
|
616
|
+
_smashInFlight.delete(inFlightKey);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Undo a prior SMASH: deletes any child created since `smashedAt` and clears
|
|
621
|
+
* `is_epic` on the parent ticket.
|
|
622
|
+
*/
|
|
623
|
+
async function runSmashUndo(deps, ticketId, smashedAt) {
|
|
624
|
+
if ((0, explore_smash_1.isSpecsSmashKillSwitchActive)()) {
|
|
625
|
+
return { ok: false, deletedChildren: [], reason: 'disabled' };
|
|
626
|
+
}
|
|
627
|
+
const now = deps.now ?? (() => new Date());
|
|
628
|
+
const filePath = (0, ticket_store_2.resolveTicketStoragePath)(deps.projectPath);
|
|
629
|
+
const finishedAt = now().toISOString();
|
|
630
|
+
let undone;
|
|
631
|
+
try {
|
|
632
|
+
undone = applySmashUndo(filePath, ticketId, smashedAt, finishedAt);
|
|
633
|
+
}
|
|
634
|
+
catch (err) {
|
|
635
|
+
console.error('[smash-runner] undo failed:', err);
|
|
636
|
+
return { ok: false, deletedChildren: [], reason: 'mutation-failed' };
|
|
637
|
+
}
|
|
638
|
+
if (!undone.epic) {
|
|
639
|
+
return { ok: false, deletedChildren: [], reason: 'ticket-not-found' };
|
|
640
|
+
}
|
|
641
|
+
for (const id of undone.deletedChildren) {
|
|
642
|
+
deps.broadcast({
|
|
643
|
+
type: 'ticket_deleted',
|
|
644
|
+
ticketId: id,
|
|
645
|
+
projectId: deps.projectId,
|
|
646
|
+
timestamp: finishedAt,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
deps.broadcast({
|
|
650
|
+
type: 'ticket_updated',
|
|
651
|
+
ticket: undone.epic,
|
|
652
|
+
projectId: deps.projectId,
|
|
653
|
+
timestamp: finishedAt,
|
|
654
|
+
});
|
|
655
|
+
deps.broadcast({
|
|
656
|
+
type: 'smash.undone',
|
|
657
|
+
projectId: deps.projectId,
|
|
658
|
+
ticketId,
|
|
659
|
+
childrenIds: undone.deletedChildren,
|
|
660
|
+
timestamp: finishedAt,
|
|
661
|
+
});
|
|
662
|
+
return { ok: true, deletedChildren: undone.deletedChildren };
|
|
663
|
+
}
|