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,207 @@
|
|
|
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.createDocsRouter = createDocsRouter;
|
|
7
|
+
const express_1 = require("express");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
// ─── Docs directory resolution ────────────────────────────────────────────────
|
|
12
|
+
// Try ~/.specrails/docs/ first (user-editable), then fall back to bundled docs/
|
|
13
|
+
function resolveDocsDir() {
|
|
14
|
+
const userDocsDir = path_1.default.join(os_1.default.homedir(), '.specrails', 'docs');
|
|
15
|
+
if (fs_1.default.existsSync(userDocsDir)) {
|
|
16
|
+
return userDocsDir;
|
|
17
|
+
}
|
|
18
|
+
// Bundled docs: try relative to this file (works in dev and compiled)
|
|
19
|
+
const candidates = [
|
|
20
|
+
path_1.default.resolve(__dirname, '../docs'), // dev: server/ -> ../docs
|
|
21
|
+
path_1.default.resolve(__dirname, '../../docs'), // compiled: server/dist/ -> ../../docs
|
|
22
|
+
];
|
|
23
|
+
for (const candidate of candidates) {
|
|
24
|
+
if (fs_1.default.existsSync(candidate)) {
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Fall back to user dir (will be empty until populated)
|
|
29
|
+
return userDocsDir;
|
|
30
|
+
}
|
|
31
|
+
// ─── Category configuration ───────────────────────────────────────────────────
|
|
32
|
+
/**
|
|
33
|
+
* Top-level `.md` files are surfaced under a synthetic "guides" category.
|
|
34
|
+
* Any subdirectory becomes its own category, with the directory name as slug
|
|
35
|
+
* and a friendly label resolved from `CATEGORY_LABELS` (or title-cased).
|
|
36
|
+
*/
|
|
37
|
+
const TOP_LEVEL_CATEGORY_SLUG = 'guides';
|
|
38
|
+
const CATEGORY_LABELS = {
|
|
39
|
+
guides: 'Guides',
|
|
40
|
+
platforms: 'Platforms',
|
|
41
|
+
internals: 'Internals',
|
|
42
|
+
// Legacy folders (kept so user-customized ~/.specrails/docs trees still work)
|
|
43
|
+
general: 'General',
|
|
44
|
+
product: 'Product',
|
|
45
|
+
engineering: 'Engineering',
|
|
46
|
+
operations: 'Operations',
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Preferred display order. Categories not in this list come after, in the
|
|
50
|
+
* order `fs.readdirSync` returns them.
|
|
51
|
+
*/
|
|
52
|
+
const CATEGORY_ORDER = ['guides', 'platforms', 'internals', 'general', 'product', 'engineering', 'operations'];
|
|
53
|
+
/**
|
|
54
|
+
* Preferred document order within the "guides" category. Files not listed
|
|
55
|
+
* here are appended in alphabetical order so adding a new doc never breaks
|
|
56
|
+
* the build.
|
|
57
|
+
*/
|
|
58
|
+
const GUIDES_DOC_ORDER = [
|
|
59
|
+
'getting-started',
|
|
60
|
+
'creating-specs',
|
|
61
|
+
'running-pipelines',
|
|
62
|
+
'tracking-cost',
|
|
63
|
+
'customizing',
|
|
64
|
+
'terminal',
|
|
65
|
+
'cli',
|
|
66
|
+
'codex',
|
|
67
|
+
];
|
|
68
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
69
|
+
function slugToTitle(slug) {
|
|
70
|
+
return slug
|
|
71
|
+
.replace(/-/g, ' ')
|
|
72
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
73
|
+
}
|
|
74
|
+
function categoryLabel(slug) {
|
|
75
|
+
return CATEGORY_LABELS[slug] ?? slugToTitle(slug);
|
|
76
|
+
}
|
|
77
|
+
function extractTitle(content, slug) {
|
|
78
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
79
|
+
return match ? match[1].trim() : slugToTitle(slug);
|
|
80
|
+
}
|
|
81
|
+
function sortBy(items, preferredOrder, keyFn) {
|
|
82
|
+
const preferredIndex = (key) => {
|
|
83
|
+
const i = preferredOrder.indexOf(key);
|
|
84
|
+
return i === -1 ? Number.MAX_SAFE_INTEGER : i;
|
|
85
|
+
};
|
|
86
|
+
return [...items].sort((a, b) => {
|
|
87
|
+
const aKey = keyFn(a);
|
|
88
|
+
const bKey = keyFn(b);
|
|
89
|
+
const ai = preferredIndex(aKey);
|
|
90
|
+
const bi = preferredIndex(bKey);
|
|
91
|
+
if (ai !== bi)
|
|
92
|
+
return ai - bi;
|
|
93
|
+
return aKey.localeCompare(bKey);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function readMarkdownFiles(dir) {
|
|
97
|
+
try {
|
|
98
|
+
return fs_1.default.readdirSync(dir).filter((f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function readDocEntry(file, filePath) {
|
|
105
|
+
const slug = file.replace(/\.md$/, '');
|
|
106
|
+
try {
|
|
107
|
+
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
108
|
+
return { title: extractTitle(content, slug), slug };
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return { title: slugToTitle(slug), slug };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function buildCategories(docsDir) {
|
|
115
|
+
if (!fs_1.default.existsSync(docsDir))
|
|
116
|
+
return [];
|
|
117
|
+
const categories = [];
|
|
118
|
+
// 1. Top-level .md files → "guides" category
|
|
119
|
+
const topFiles = readMarkdownFiles(docsDir);
|
|
120
|
+
if (topFiles.length > 0) {
|
|
121
|
+
const docs = topFiles.map((f) => readDocEntry(f, path_1.default.join(docsDir, f)));
|
|
122
|
+
const ordered = sortBy(docs, GUIDES_DOC_ORDER, (d) => d.slug);
|
|
123
|
+
categories.push({ name: categoryLabel(TOP_LEVEL_CATEGORY_SLUG), slug: TOP_LEVEL_CATEGORY_SLUG, docs: ordered });
|
|
124
|
+
}
|
|
125
|
+
// 2. Each subdirectory → its own category
|
|
126
|
+
let entries;
|
|
127
|
+
try {
|
|
128
|
+
entries = fs_1.default.readdirSync(docsDir, { withFileTypes: true });
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
entries = [];
|
|
132
|
+
}
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
if (!entry.isDirectory())
|
|
135
|
+
continue;
|
|
136
|
+
// Avoid colliding with synthetic top-level slug
|
|
137
|
+
if (entry.name === TOP_LEVEL_CATEGORY_SLUG)
|
|
138
|
+
continue;
|
|
139
|
+
const catDir = path_1.default.join(docsDir, entry.name);
|
|
140
|
+
const files = readMarkdownFiles(catDir);
|
|
141
|
+
const docs = files.map((f) => readDocEntry(f, path_1.default.join(catDir, f)));
|
|
142
|
+
// Sort docs within unknown categories alphabetically.
|
|
143
|
+
docs.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
144
|
+
categories.push({ name: categoryLabel(entry.name), slug: entry.name, docs });
|
|
145
|
+
}
|
|
146
|
+
return sortBy(categories, CATEGORY_ORDER, (c) => c.slug);
|
|
147
|
+
}
|
|
148
|
+
function isValidCategorySlug(cat) {
|
|
149
|
+
// basename(cat) === cat guards against slashes, but `path.basename('..') === '..'`,
|
|
150
|
+
// so '.'/'..' slip through and `path.join(docsDir, '..', ...)` escapes one level
|
|
151
|
+
// up (B4). Reject the dot segments explicitly.
|
|
152
|
+
return (cat.length > 0 &&
|
|
153
|
+
cat !== '.' &&
|
|
154
|
+
cat !== '..' &&
|
|
155
|
+
cat === path_1.default.basename(cat) &&
|
|
156
|
+
!cat.includes('/') &&
|
|
157
|
+
!cat.includes('\\'));
|
|
158
|
+
}
|
|
159
|
+
// ─── Router ───────────────────────────────────────────────────────────────────
|
|
160
|
+
function createDocsRouter() {
|
|
161
|
+
const router = (0, express_1.Router)();
|
|
162
|
+
router.get('/', (_req, res) => {
|
|
163
|
+
const docsDir = resolveDocsDir();
|
|
164
|
+
const categories = buildCategories(docsDir);
|
|
165
|
+
res.json({ categories });
|
|
166
|
+
});
|
|
167
|
+
router.get('/:category/:slug', (req, res) => {
|
|
168
|
+
const { category, slug } = req.params;
|
|
169
|
+
if (!isValidCategorySlug(category)) {
|
|
170
|
+
res.status(404).json({ error: 'Category not found' });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// Prevent directory traversal in slug too
|
|
174
|
+
const safeSlug = path_1.default.basename(slug);
|
|
175
|
+
if (safeSlug !== slug || slug.includes('/') || slug.includes('\\')) {
|
|
176
|
+
res.status(400).json({ error: 'Invalid slug' });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const docsDir = resolveDocsDir();
|
|
180
|
+
// For the synthetic "guides" category, files live at the top level of docsDir.
|
|
181
|
+
const filePath = category === TOP_LEVEL_CATEGORY_SLUG
|
|
182
|
+
? path_1.default.join(docsDir, `${safeSlug}.md`)
|
|
183
|
+
: path_1.default.join(docsDir, category, `${safeSlug}.md`);
|
|
184
|
+
// B4: defence-in-depth — never serve a file resolved outside docsDir, even
|
|
185
|
+
// if a future change loosens the slug/category validation above.
|
|
186
|
+
const rel = path_1.default.relative(path_1.default.resolve(docsDir), path_1.default.resolve(filePath));
|
|
187
|
+
if (rel.startsWith('..') || path_1.default.isAbsolute(rel)) {
|
|
188
|
+
res.status(404).json({ error: 'Document not found' });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
192
|
+
res.status(404).json({ error: 'Document not found' });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
let content;
|
|
196
|
+
try {
|
|
197
|
+
content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
res.status(500).json({ error: 'Failed to read document' });
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const title = extractTitle(content, safeSlug);
|
|
204
|
+
res.json({ title, content, category, slug: safeSlug });
|
|
205
|
+
});
|
|
206
|
+
return router;
|
|
207
|
+
}
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Explore Spec — Contract Refine
|
|
4
|
+
*
|
|
5
|
+
* Post-commit refinement turn that appends a structured "Contract Layer"
|
|
6
|
+
* section to a committed Explore Spec ticket. The refine spawn is a single
|
|
7
|
+
* structural Claude turn with a byte-stable system prompt (cache-friendly),
|
|
8
|
+
* read-only (no tools), that emits one ```contract-layer fenced JSON block.
|
|
9
|
+
*
|
|
10
|
+
* This module contains the pure pieces (prompt builder, parser, renderer,
|
|
11
|
+
* kill switch). The actual spawn lifecycle lives in ChatManager.
|
|
12
|
+
*
|
|
13
|
+
* See openspec/changes/explore-spec-contract-refine.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.CONTRACT_LAYER_SEPARATOR = exports.CONTRACT_MARKER_USER_MESSAGE = exports.CONTRACT_PROMPT_VERSION = void 0;
|
|
17
|
+
exports.isExploreContractRefineKillSwitchActive = isExploreContractRefineKillSwitchActive;
|
|
18
|
+
exports.buildContractRefineSystemPrompt = buildContractRefineSystemPrompt;
|
|
19
|
+
exports.parseContractLayerBlock = parseContractLayerBlock;
|
|
20
|
+
exports.stripContractLayerBlock = stripContractLayerBlock;
|
|
21
|
+
exports.renderContractLayerMarkdown = renderContractLayerMarkdown;
|
|
22
|
+
exports.appendContractLayerToDescription = appendContractLayerToDescription;
|
|
23
|
+
exports.hasContractLayer = hasContractLayer;
|
|
24
|
+
exports.splitDescriptionAtContractLayer = splitDescriptionAtContractLayer;
|
|
25
|
+
exports.CONTRACT_PROMPT_VERSION = 1;
|
|
26
|
+
exports.CONTRACT_MARKER_USER_MESSAGE = [
|
|
27
|
+
'CONTRACT REFINE — structural pass.',
|
|
28
|
+
'',
|
|
29
|
+
'The user has already committed the spec. Do NOT continue exploring, do NOT',
|
|
30
|
+
'ask questions, do NOT call tools. Emit EXACTLY ONE fenced code block tagged',
|
|
31
|
+
'`contract-layer` containing the JSON shape described in your system prompt.',
|
|
32
|
+
'No prose before or after the block.',
|
|
33
|
+
].join('\n');
|
|
34
|
+
// ─── Kill switch ─────────────────────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* Returns `true` when the app-wide kill switch is active and Contract Refine
|
|
37
|
+
* spawns MUST be skipped regardless of per-project toggles.
|
|
38
|
+
*
|
|
39
|
+
* The env var `SPECRAILS_EXPLORE_CONTRACT_REFINE` controls the feature:
|
|
40
|
+
* - unset / any other value → feature ENABLED (kill switch inactive)
|
|
41
|
+
* - `0` / `false` / `off` (CI) → feature DISABLED (kill switch active)
|
|
42
|
+
*/
|
|
43
|
+
function isExploreContractRefineKillSwitchActive(envValue = process.env.SPECRAILS_EXPLORE_CONTRACT_REFINE) {
|
|
44
|
+
if (envValue == null)
|
|
45
|
+
return false;
|
|
46
|
+
const v = envValue.trim().toLowerCase();
|
|
47
|
+
return v === '0' || v === 'false' || v === 'off';
|
|
48
|
+
}
|
|
49
|
+
// ─── System prompt ───────────────────────────────────────────────────────────
|
|
50
|
+
const EXAMPLE_BLOCK = `\`\`\`contract-layer
|
|
51
|
+
{
|
|
52
|
+
"contractVersion": 1,
|
|
53
|
+
"namingContract": {
|
|
54
|
+
"enums": [
|
|
55
|
+
{ "name": "RoundState", "values": ["INTRO","FIGHTING","KO_FREEZE","ROUND_END","MATCH_END"], "file": "engine/game_loop.py" }
|
|
56
|
+
],
|
|
57
|
+
"fields": [
|
|
58
|
+
{ "name": "p1_rounds_won", "type": "int", "where": "Match" }
|
|
59
|
+
],
|
|
60
|
+
"functions": [
|
|
61
|
+
{ "signature": "Match.advance_round_state(input_state) -> RoundState", "file": "engine/game_loop.py" }
|
|
62
|
+
],
|
|
63
|
+
"files": [
|
|
64
|
+
{ "path": "engine/game_loop.py", "purpose": "extend update() with RoundState machine" }
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
"dataShapes": [
|
|
68
|
+
{ "name": "Match", "ts": "{ p1RoundsWon: number; p2RoundsWon: number; roundTimerFrames: number; state: RoundState }" }
|
|
69
|
+
],
|
|
70
|
+
"stateMachine": "INTRO -> FIGHTING -> (KO_FREEZE | TIMEOUT) -> ROUND_END -> (INTRO | MATCH_END)",
|
|
71
|
+
"invariants": [
|
|
72
|
+
"p1_rounds_won + p2_rounds_won + double_kos <= 3",
|
|
73
|
+
"round_timer_frames only decrements while state == FIGHTING"
|
|
74
|
+
],
|
|
75
|
+
"fileTouchList": [
|
|
76
|
+
{ "path": "engine/game_loop.py", "action": "extend", "reason": "round state machine + transitions" },
|
|
77
|
+
{ "path": "engine/hud.py", "action": "extend", "reason": "round counter + KO banner" }
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
\`\`\``;
|
|
81
|
+
/**
|
|
82
|
+
* Build the structural-only system prompt for a Contract Refine turn.
|
|
83
|
+
*
|
|
84
|
+
* MUST be byte-stable across two consecutive calls — the only inputs that
|
|
85
|
+
* vary the output are intentional version bumps (CONTRACT_PROMPT_VERSION).
|
|
86
|
+
*/
|
|
87
|
+
function buildContractRefineSystemPrompt() {
|
|
88
|
+
return [
|
|
89
|
+
'# Contract Refine — structural pass',
|
|
90
|
+
'',
|
|
91
|
+
'You are running a STRUCTURAL post-commit refinement on a spec that has already',
|
|
92
|
+
'been committed by the user. Your sole job is to emit a single fenced code block',
|
|
93
|
+
'tagged `contract-layer` containing a JSON object that anchors the spec with',
|
|
94
|
+
'prescriptive, anti-reinvention details (exact names, types, file paths,',
|
|
95
|
+
'invariants). Downstream agents (Architect, Developer, Reviewer) will read these',
|
|
96
|
+
'anchors to avoid inventing divergent identifiers.',
|
|
97
|
+
'',
|
|
98
|
+
'## Hard rules',
|
|
99
|
+
'',
|
|
100
|
+
'- DO NOT modify, restate, paraphrase, or critique the user-authored title,',
|
|
101
|
+
' description, labels, priority, or acceptance criteria of the committed spec.',
|
|
102
|
+
'- DO NOT call any tool. Do not Read, Grep, Glob, or Bash anything. Work only',
|
|
103
|
+
' from the conversation transcript and the committed spec body already in',
|
|
104
|
+
' context.',
|
|
105
|
+
'- DO NOT ask clarifying questions. If a section has no concrete content,',
|
|
106
|
+
' emit an empty array (or `null` for `stateMachine`).',
|
|
107
|
+
'- Output EXACTLY one fenced code block tagged `contract-layer`. No prose',
|
|
108
|
+
' before or after the block. No second block.',
|
|
109
|
+
'',
|
|
110
|
+
'## Required JSON shape',
|
|
111
|
+
'',
|
|
112
|
+
'```',
|
|
113
|
+
'{',
|
|
114
|
+
' "contractVersion": 1,',
|
|
115
|
+
' "namingContract": {',
|
|
116
|
+
' "enums": [{ "name": string, "values": string[], "file": string }],',
|
|
117
|
+
' "fields": [{ "name": string, "type": string, "where": string }],',
|
|
118
|
+
' "functions": [{ "signature": string, "file": string }],',
|
|
119
|
+
' "files": [{ "path": string, "purpose": string }]',
|
|
120
|
+
' },',
|
|
121
|
+
' "dataShapes": [{ "name": string, "ts": string }],',
|
|
122
|
+
' "stateMachine": string | null,',
|
|
123
|
+
' "invariants": string[],',
|
|
124
|
+
' "fileTouchList": [{ "path": string, "action": "create" | "extend" | "delete", "reason": string }]',
|
|
125
|
+
'}',
|
|
126
|
+
'```',
|
|
127
|
+
'',
|
|
128
|
+
'## Example output',
|
|
129
|
+
'',
|
|
130
|
+
EXAMPLE_BLOCK,
|
|
131
|
+
'',
|
|
132
|
+
`## Prompt version: ${exports.CONTRACT_PROMPT_VERSION}`,
|
|
133
|
+
'',
|
|
134
|
+
].join('\n');
|
|
135
|
+
}
|
|
136
|
+
// ─── Parser ──────────────────────────────────────────────────────────────────
|
|
137
|
+
const CONTRACT_FENCE_RE = /```contract-layer\s*\n([\s\S]*?)\n```/;
|
|
138
|
+
function asString(v) {
|
|
139
|
+
return typeof v === 'string' ? v : undefined;
|
|
140
|
+
}
|
|
141
|
+
function asStringArray(v) {
|
|
142
|
+
if (!Array.isArray(v))
|
|
143
|
+
return [];
|
|
144
|
+
return v.filter((s) => typeof s === 'string');
|
|
145
|
+
}
|
|
146
|
+
function normaliseNamingContract(raw) {
|
|
147
|
+
const r = (raw ?? {});
|
|
148
|
+
const enums = Array.isArray(r.enums) ? r.enums : [];
|
|
149
|
+
const fields = Array.isArray(r.fields) ? r.fields : [];
|
|
150
|
+
const functions = Array.isArray(r.functions) ? r.functions : [];
|
|
151
|
+
const files = Array.isArray(r.files) ? r.files : [];
|
|
152
|
+
return {
|
|
153
|
+
enums: enums
|
|
154
|
+
.map((e) => {
|
|
155
|
+
const o = (e ?? {});
|
|
156
|
+
const name = asString(o.name);
|
|
157
|
+
const file = asString(o.file);
|
|
158
|
+
if (!name || !file)
|
|
159
|
+
return null;
|
|
160
|
+
return { name, values: asStringArray(o.values), file };
|
|
161
|
+
})
|
|
162
|
+
.filter((x) => x !== null),
|
|
163
|
+
fields: fields
|
|
164
|
+
.map((e) => {
|
|
165
|
+
const o = (e ?? {});
|
|
166
|
+
const name = asString(o.name);
|
|
167
|
+
const type = asString(o.type);
|
|
168
|
+
const where = asString(o.where);
|
|
169
|
+
if (!name || !type || !where)
|
|
170
|
+
return null;
|
|
171
|
+
return { name, type, where };
|
|
172
|
+
})
|
|
173
|
+
.filter((x) => x !== null),
|
|
174
|
+
functions: functions
|
|
175
|
+
.map((e) => {
|
|
176
|
+
const o = (e ?? {});
|
|
177
|
+
const signature = asString(o.signature);
|
|
178
|
+
const file = asString(o.file);
|
|
179
|
+
if (!signature || !file)
|
|
180
|
+
return null;
|
|
181
|
+
return { signature, file };
|
|
182
|
+
})
|
|
183
|
+
.filter((x) => x !== null),
|
|
184
|
+
files: files
|
|
185
|
+
.map((e) => {
|
|
186
|
+
const o = (e ?? {});
|
|
187
|
+
const path = asString(o.path);
|
|
188
|
+
const purpose = asString(o.purpose);
|
|
189
|
+
if (!path || !purpose)
|
|
190
|
+
return null;
|
|
191
|
+
return { path, purpose };
|
|
192
|
+
})
|
|
193
|
+
.filter((x) => x !== null),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function normaliseDataShapes(raw) {
|
|
197
|
+
if (!Array.isArray(raw))
|
|
198
|
+
return [];
|
|
199
|
+
return raw
|
|
200
|
+
.map((e) => {
|
|
201
|
+
const o = (e ?? {});
|
|
202
|
+
const name = asString(o.name);
|
|
203
|
+
const ts = asString(o.ts);
|
|
204
|
+
if (!name || !ts)
|
|
205
|
+
return null;
|
|
206
|
+
return { name, ts };
|
|
207
|
+
})
|
|
208
|
+
.filter((x) => x !== null);
|
|
209
|
+
}
|
|
210
|
+
function normaliseFileTouch(raw) {
|
|
211
|
+
if (!Array.isArray(raw))
|
|
212
|
+
return [];
|
|
213
|
+
return raw
|
|
214
|
+
.map((e) => {
|
|
215
|
+
const o = (e ?? {});
|
|
216
|
+
const path = asString(o.path);
|
|
217
|
+
const action = asString(o.action);
|
|
218
|
+
const reason = asString(o.reason);
|
|
219
|
+
if (!path || !reason)
|
|
220
|
+
return null;
|
|
221
|
+
if (action !== 'create' && action !== 'extend' && action !== 'delete')
|
|
222
|
+
return null;
|
|
223
|
+
return { path, action, reason };
|
|
224
|
+
})
|
|
225
|
+
.filter((x) => x !== null);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Parse a `contract-layer` fenced block from a raw assistant text.
|
|
229
|
+
*
|
|
230
|
+
* - Returns `{ ok: false, reason: 'not-found' }` when no block is present.
|
|
231
|
+
* - Returns `{ ok: false, reason: 'malformed' }` when JSON parse fails.
|
|
232
|
+
* - Returns `{ ok: false, reason: 'missing-version' }` when `contractVersion`
|
|
233
|
+
* is missing or not equal to 1.
|
|
234
|
+
* - Unknown top-level keys are dropped silently.
|
|
235
|
+
* - Missing optional arrays default to `[]`.
|
|
236
|
+
* - `stateMachine` defaults to `null` if missing or non-string.
|
|
237
|
+
*/
|
|
238
|
+
function parseContractLayerBlock(raw) {
|
|
239
|
+
try {
|
|
240
|
+
const match = raw.match(CONTRACT_FENCE_RE);
|
|
241
|
+
if (!match)
|
|
242
|
+
return { ok: false, reason: 'not-found' };
|
|
243
|
+
let payload;
|
|
244
|
+
try {
|
|
245
|
+
payload = JSON.parse(match[1]);
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
return { ok: false, reason: 'malformed' };
|
|
249
|
+
}
|
|
250
|
+
const obj = (payload ?? {});
|
|
251
|
+
if (obj.contractVersion !== 1) {
|
|
252
|
+
return { ok: false, reason: 'missing-version' };
|
|
253
|
+
}
|
|
254
|
+
const stateMachineRaw = obj.stateMachine;
|
|
255
|
+
const value = {
|
|
256
|
+
contractVersion: 1,
|
|
257
|
+
namingContract: normaliseNamingContract(obj.namingContract),
|
|
258
|
+
dataShapes: normaliseDataShapes(obj.dataShapes),
|
|
259
|
+
stateMachine: typeof stateMachineRaw === 'string' ? stateMachineRaw : null,
|
|
260
|
+
invariants: asStringArray(obj.invariants),
|
|
261
|
+
fileTouchList: normaliseFileTouch(obj.fileTouchList),
|
|
262
|
+
};
|
|
263
|
+
return { ok: true, value };
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return { ok: false, reason: 'parser-error' };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Strip the `contract-layer` fenced block from raw text (for chat content
|
|
271
|
+
* broadcast to the client). Returns the input unchanged when no block is
|
|
272
|
+
* present.
|
|
273
|
+
*/
|
|
274
|
+
function stripContractLayerBlock(raw) {
|
|
275
|
+
return raw.replace(CONTRACT_FENCE_RE, '').replace(/\n{3,}/g, '\n\n').trim();
|
|
276
|
+
}
|
|
277
|
+
// ─── Renderer ────────────────────────────────────────────────────────────────
|
|
278
|
+
const NA_LINE = '_N/A — model did not produce items for this subsection._';
|
|
279
|
+
exports.CONTRACT_LAYER_SEPARATOR = '\n\n---\n\n## Contract Layer\n\n';
|
|
280
|
+
function renderNamingContract(nc) {
|
|
281
|
+
const lines = [];
|
|
282
|
+
const hasAny = nc.enums.length || nc.fields.length || nc.functions.length || nc.files.length;
|
|
283
|
+
if (!hasAny)
|
|
284
|
+
return NA_LINE;
|
|
285
|
+
if (nc.enums.length) {
|
|
286
|
+
lines.push('**Enums**');
|
|
287
|
+
for (const e of nc.enums) {
|
|
288
|
+
lines.push(`- \`${e.name}\` in \`${e.file}\` — values: ${e.values.map((v) => `\`${v}\``).join(', ') || '_(none)_'}`);
|
|
289
|
+
}
|
|
290
|
+
lines.push('');
|
|
291
|
+
}
|
|
292
|
+
if (nc.fields.length) {
|
|
293
|
+
lines.push('**Fields**');
|
|
294
|
+
for (const f of nc.fields) {
|
|
295
|
+
lines.push(`- \`${f.name}: ${f.type}\` on \`${f.where}\``);
|
|
296
|
+
}
|
|
297
|
+
lines.push('');
|
|
298
|
+
}
|
|
299
|
+
if (nc.functions.length) {
|
|
300
|
+
lines.push('**Functions**');
|
|
301
|
+
for (const f of nc.functions) {
|
|
302
|
+
lines.push(`- \`${f.signature}\` — \`${f.file}\``);
|
|
303
|
+
}
|
|
304
|
+
lines.push('');
|
|
305
|
+
}
|
|
306
|
+
if (nc.files.length) {
|
|
307
|
+
lines.push('**Files**');
|
|
308
|
+
for (const f of nc.files) {
|
|
309
|
+
lines.push(`- \`${f.path}\` — ${f.purpose}`);
|
|
310
|
+
}
|
|
311
|
+
lines.push('');
|
|
312
|
+
}
|
|
313
|
+
return lines.join('\n').trimEnd();
|
|
314
|
+
}
|
|
315
|
+
function renderDataShapes(shapes) {
|
|
316
|
+
if (!shapes.length)
|
|
317
|
+
return NA_LINE;
|
|
318
|
+
const lines = [];
|
|
319
|
+
for (const s of shapes) {
|
|
320
|
+
lines.push(`**${s.name}**`);
|
|
321
|
+
lines.push('```ts');
|
|
322
|
+
lines.push(s.ts);
|
|
323
|
+
lines.push('```');
|
|
324
|
+
lines.push('');
|
|
325
|
+
}
|
|
326
|
+
return lines.join('\n').trimEnd();
|
|
327
|
+
}
|
|
328
|
+
function renderStateMachine(sm) {
|
|
329
|
+
if (!sm)
|
|
330
|
+
return NA_LINE;
|
|
331
|
+
return ['```', sm, '```'].join('\n');
|
|
332
|
+
}
|
|
333
|
+
function renderInvariants(items) {
|
|
334
|
+
if (!items.length)
|
|
335
|
+
return NA_LINE;
|
|
336
|
+
return items.map((i) => `- ${i}`).join('\n');
|
|
337
|
+
}
|
|
338
|
+
function renderFileTouchList(entries) {
|
|
339
|
+
if (!entries.length)
|
|
340
|
+
return NA_LINE;
|
|
341
|
+
const rows = ['| Path | Action | Reason |', '|------|--------|--------|'];
|
|
342
|
+
for (const e of entries) {
|
|
343
|
+
const safeReason = e.reason.replace(/\|/g, '\\|');
|
|
344
|
+
rows.push(`| \`${e.path}\` | ${e.action} | ${safeReason} |`);
|
|
345
|
+
}
|
|
346
|
+
return rows.join('\n');
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Render a parsed Contract Layer to a deterministic markdown subsection.
|
|
350
|
+
* The output begins after the `## Contract Layer` heading and contains the
|
|
351
|
+
* five labelled subsections in fixed order.
|
|
352
|
+
*/
|
|
353
|
+
function renderContractLayerMarkdown(layer) {
|
|
354
|
+
return [
|
|
355
|
+
'### Naming Contract',
|
|
356
|
+
'',
|
|
357
|
+
renderNamingContract(layer.namingContract),
|
|
358
|
+
'',
|
|
359
|
+
'### Data Shapes',
|
|
360
|
+
'',
|
|
361
|
+
renderDataShapes(layer.dataShapes),
|
|
362
|
+
'',
|
|
363
|
+
'### State Machine',
|
|
364
|
+
'',
|
|
365
|
+
renderStateMachine(layer.stateMachine),
|
|
366
|
+
'',
|
|
367
|
+
'### Invariants',
|
|
368
|
+
'',
|
|
369
|
+
renderInvariants(layer.invariants),
|
|
370
|
+
'',
|
|
371
|
+
'### File Touch List',
|
|
372
|
+
'',
|
|
373
|
+
renderFileTouchList(layer.fileTouchList),
|
|
374
|
+
'',
|
|
375
|
+
].join('\n');
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Append the Contract Layer to a ticket description, producing the canonical
|
|
379
|
+
* `userBody\n\n---\n\n## Contract Layer\n\n<section>` shape.
|
|
380
|
+
*/
|
|
381
|
+
function appendContractLayerToDescription(userBody, layer) {
|
|
382
|
+
// B59: a Contract Refine retry must REPLACE an existing Contract Layer, not
|
|
383
|
+
// append a second one. If the body already carries a layer, strip it down to
|
|
384
|
+
// the user-authored part first so re-running is idempotent.
|
|
385
|
+
const base = hasContractLayer(userBody)
|
|
386
|
+
? splitDescriptionAtContractLayer(userBody).user
|
|
387
|
+
: userBody;
|
|
388
|
+
const trimmed = base.replace(/\s+$/, '');
|
|
389
|
+
return `${trimmed}${exports.CONTRACT_LAYER_SEPARATOR}${renderContractLayerMarkdown(layer)}`;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Detects whether a ticket description already contains a Contract Layer.
|
|
393
|
+
*/
|
|
394
|
+
function hasContractLayer(description) {
|
|
395
|
+
if (!description)
|
|
396
|
+
return false;
|
|
397
|
+
return description.includes(exports.CONTRACT_LAYER_SEPARATOR.trim());
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Splits a ticket description into the user-authored body and the contract
|
|
401
|
+
* layer markdown (when present). When no Contract Layer is detected, returns
|
|
402
|
+
* `{ user: description, contract: null }`.
|
|
403
|
+
*/
|
|
404
|
+
function splitDescriptionAtContractLayer(description) {
|
|
405
|
+
const idx = description.indexOf(exports.CONTRACT_LAYER_SEPARATOR);
|
|
406
|
+
if (idx < 0) {
|
|
407
|
+
// also check the trimmed variant in case of stray whitespace
|
|
408
|
+
const altSep = '\n---\n\n## Contract Layer\n';
|
|
409
|
+
const altIdx = description.indexOf(altSep);
|
|
410
|
+
if (altIdx < 0)
|
|
411
|
+
return { user: description, contract: null };
|
|
412
|
+
return {
|
|
413
|
+
user: description.slice(0, altIdx),
|
|
414
|
+
contract: description.slice(altIdx + altSep.length),
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
user: description.slice(0, idx),
|
|
419
|
+
contract: description.slice(idx + exports.CONTRACT_LAYER_SEPARATOR.length),
|
|
420
|
+
};
|
|
421
|
+
}
|